ckeditor5-elements-context-menu 0.0.0-stage → 49.0.0-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ivan Kildyushov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,29 @@
1
- # Temporary Holding Version
1
+ # ckeditor5-elements-context-menu
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The element context menu of CKEditor 5 Elements: `ElementContextMenu` shows a menu of commands, submenus and separators
4
+ for the right-clicked element, or the element at the cursor (the context menu key, Shift+F10).
5
+
6
+ ## Installation
7
+
8
+ This package is part of `ckeditor5-elements`. Install the whole package to use it:
9
+
10
+ ```sh
11
+ npm install ckeditor5 ckeditor5-elements
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```ts
17
+ import { ElementClipboard, ElementContextMenu } from 'ckeditor5-elements';
18
+ import 'ckeditor5-elements/index.css';
19
+
20
+ // In the editor config:
21
+ plugins: [..., ElementContextMenu, ElementClipboard],
22
+ elementContextMenu: (element, editor) => [editor.commands.get('copyElement'), editor.commands.get('cutElement')]
23
+ ```
24
+
25
+ See the [Context menu guide](https://ivankild.github.io/ckeditor5-elements/guide/context-menu) of the documentation.
26
+
27
+ ## License
28
+
29
+ MIT
@@ -0,0 +1,15 @@
1
+ import type { ElementMenuConfig } from 'ckeditor5-elements-core';
2
+ import type { ElementContextMenu } from './index.js';
3
+ declare module 'ckeditor5' {
4
+ interface PluginsMap {
5
+ [ElementContextMenu.pluginName]: ElementContextMenu;
6
+ }
7
+ interface EditorConfig {
8
+ /**
9
+ * Returns the items (commands, submenus and `'|'` separators) of the context menu of the right-clicked element.
10
+ * See {@link ElementContextMenu}.
11
+ */
12
+ elementContextMenu?: ElementMenuConfig;
13
+ }
14
+ }
15
+ //# sourceMappingURL=augmentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"augmentation.d.ts","sourceRoot":"","sources":["../src/augmentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,QAAQ,WAAW,CAAC;IAC1B,UAAU,UAAU;QACnB,CAAC,kBAAkB,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;KACpD;IAED,UAAU,YAAY;QACrB;;;WAGG;QACH,kBAAkB,CAAC,EAAE,iBAAiB,CAAC;KACvC;CACD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=augmentation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"augmentation.js","sourceRoot":"","sources":["../src/augmentation.ts"],"names":[],"mappings":""}
@@ -0,0 +1,19 @@
1
+ import { DomEventObserver, type ViewDocumentDomEventData } from 'ckeditor5';
2
+ /**
3
+ * Fires the `contextmenu` event on the view document when the user opens the native context menu in an editable.
4
+ */
5
+ export default class ContextMenuObserver extends DomEventObserver<'contextmenu'> {
6
+ get domEventType(): 'contextmenu';
7
+ /**
8
+ * Fires `contextmenu` on the view document for the DOM event.
9
+ */
10
+ onDomEvent(domEvent: MouseEvent): void;
11
+ }
12
+ /**
13
+ * Fired on the view document by the {@link ContextMenuObserver}.
14
+ */
15
+ export interface ViewDocumentContextMenuEvent {
16
+ name: 'contextmenu';
17
+ args: [data: ViewDocumentDomEventData<MouseEvent>];
18
+ }
19
+ //# sourceMappingURL=contextMenuObserver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextMenuObserver.d.ts","sourceRoot":"","sources":["../src/contextMenuObserver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,KAAK,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAE5E;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,mBAAoB,SAAQ,gBAAgB,CAAC,aAAa,CAAC;IAG/E,IAAW,YAAY,IAAI,aAAa,CAEvC;IAED;;OAEG;IACI,UAAU,CAAC,QAAQ,EAAE,UAAU,GAAG,IAAI;CAG7C;AAED;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,EAAE,CAAC,IAAI,EAAE,wBAAwB,CAAC,UAAU,CAAC,CAAC,CAAC;CACnD"}
@@ -0,0 +1,18 @@
1
+ import { DomEventObserver } from 'ckeditor5';
2
+ /**
3
+ * Fires the `contextmenu` event on the view document when the user opens the native context menu in an editable.
4
+ */
5
+ export default class ContextMenuObserver extends DomEventObserver {
6
+ // The base class declares it as an accessor, so it cannot be a field here.
7
+ // eslint-disable-next-line @typescript-eslint/class-literal-property-style
8
+ get domEventType() {
9
+ return 'contextmenu';
10
+ }
11
+ /**
12
+ * Fires `contextmenu` on the view document for the DOM event.
13
+ */
14
+ onDomEvent(domEvent) {
15
+ this.fire(domEvent.type, domEvent);
16
+ }
17
+ }
18
+ //# sourceMappingURL=contextMenuObserver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextMenuObserver.js","sourceRoot":"","sources":["../src/contextMenuObserver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAiC,MAAM,WAAW,CAAC;AAE5E;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,mBAAoB,SAAQ,gBAA+B;IAC/E,2EAA2E;IAC3E,2EAA2E;IAC3E,IAAW,YAAY;QACtB,OAAO,aAAa,CAAC;IACtB,CAAC;IAED;;OAEG;IACI,UAAU,CAAC,QAAoB;QACrC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;CACD"}
@@ -0,0 +1,34 @@
1
+ import { Plugin, type ModelElement, type RectSource, type ViewElement } from 'ckeditor5';
2
+ import { ElementMenu } from 'ckeditor5-elements-core';
3
+ /**
4
+ * Shows the menu of an element when the user right-clicks it, or opens the context menu with the keyboard (the menu
5
+ * key, or Shift+F10) while the cursor is in it.
6
+ *
7
+ * The items come from the `elementContextMenu` editor config option only (see `ElementMenuConfig`): commands, submenus
8
+ * and `'|'` separators. Without it, or when it returns no items for an
9
+ * element, the native context menu shows. The menu itself is shown by the `ElementMenu` plugin.
10
+ */
11
+ export default class ElementContextMenu extends Plugin {
12
+ static get requires(): readonly [typeof ElementMenu];
13
+ static get pluginName(): 'ElementContextMenu';
14
+ init(): void;
15
+ /**
16
+ * Opens the menu of the element next to the target (e.g. a button, or a `DOMRect` in viewport coordinates), with
17
+ * the items from the `elementContextMenu` config option.
18
+ *
19
+ * Returns `false` when there are no items, so the caller can let the native context menu show instead.
20
+ */
21
+ open(element: ModelElement, target: RectSource): boolean;
22
+ /**
23
+ * Opens the menu of the element at the selection (the selected element, or the element with the cursor), next to
24
+ * the selection.
25
+ *
26
+ * Returns `false` when there is no such element or no items.
27
+ */
28
+ openAtSelection(): boolean;
29
+ /**
30
+ * Returns the closest model element mapped to the given view element or its ancestors, skipping the root.
31
+ */
32
+ getModelElement(viewElement: ViewElement): ModelElement | null;
33
+ }
34
+ //# sourceMappingURL=elementContextMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elementContextMenu.d.ts","sourceRoot":"","sources":["../src/elementContextMenu.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AACzF,OAAO,EAAE,WAAW,EAAgB,MAAM,yBAAyB,CAAC;AAGpE;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,OAAO,kBAAmB,SAAQ,MAAM;IACrD,WAAkB,QAAQ,IAAI,SAAS,CAAC,OAAO,WAAW,CAAC,CAE1D;IAED,WAAkB,UAAU,IAAI,oBAAoB,CAEnD;IAEM,IAAI,IAAI,IAAI;IAmCnB;;;;;OAKG;IACI,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO;IAY/D;;;;;OAKG;IACI,eAAe,IAAI,OAAO;IAmBjC;;OAEG;IACI,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,YAAY,GAAG,IAAI;CAarE"}
@@ -0,0 +1,98 @@
1
+ import { Plugin } from 'ckeditor5';
2
+ import { ElementMenu, isInDocument } from 'ckeditor5-elements-core';
3
+ import ContextMenuObserver, {} from './contextMenuObserver.js';
4
+ /**
5
+ * Shows the menu of an element when the user right-clicks it, or opens the context menu with the keyboard (the menu
6
+ * key, or Shift+F10) while the cursor is in it.
7
+ *
8
+ * The items come from the `elementContextMenu` editor config option only (see `ElementMenuConfig`): commands, submenus
9
+ * and `'|'` separators. Without it, or when it returns no items for an
10
+ * element, the native context menu shows. The menu itself is shown by the `ElementMenu` plugin.
11
+ */
12
+ export default class ElementContextMenu extends Plugin {
13
+ static get requires() {
14
+ return [ElementMenu];
15
+ }
16
+ static get pluginName() {
17
+ return 'ElementContextMenu';
18
+ }
19
+ init() {
20
+ const editor = this.editor;
21
+ const editingView = editor.editing.view;
22
+ editingView.addObserver(ContextMenuObserver);
23
+ this.listenTo(editingView.document, 'contextmenu', (evt, data) => {
24
+ const { domEvent } = data;
25
+ const element = this.getModelElement(data.target);
26
+ // A context menu opened with the keyboard (the menu key, or Shift+F10 in some browsers) targets the editable
27
+ // itself, not the element at the cursor. A right-click outside any element keeps the native menu.
28
+ const isOpen = element
29
+ ? this.open(element, new DOMRect(domEvent.clientX, domEvent.clientY, 0, 0))
30
+ : domEvent.button !== 2 && this.openAtSelection();
31
+ if (isOpen) {
32
+ data.preventDefault();
33
+ }
34
+ });
35
+ // Not every browser opens the context menu on Shift+F10 (e.g. on macOS), so handle it in all of them.
36
+ editor.keystrokes.set('Shift+F10', (data, cancel) => {
37
+ if (this.openAtSelection()) {
38
+ cancel();
39
+ }
40
+ });
41
+ if (typeof editor.config.get('elementContextMenu') === 'function') {
42
+ editor.accessibility.addKeystrokeInfos({
43
+ keystrokes: [{ label: editor.t('Open the menu of the element at the cursor'), keystroke: 'Shift+F10' }]
44
+ });
45
+ }
46
+ }
47
+ /**
48
+ * Opens the menu of the element next to the target (e.g. a button, or a `DOMRect` in viewport coordinates), with
49
+ * the items from the `elementContextMenu` config option.
50
+ *
51
+ * Returns `false` when there are no items, so the caller can let the native context menu show instead.
52
+ */
53
+ open(element, target) {
54
+ const config = this.editor.config.get('elementContextMenu');
55
+ if (typeof config !== 'function') {
56
+ return false;
57
+ }
58
+ return this.editor.plugins
59
+ .get(ElementMenu)
60
+ .open(element, target, config, { label: this.editor.t('Element menu') });
61
+ }
62
+ /**
63
+ * Opens the menu of the element at the selection (the selected element, or the element with the cursor), next to
64
+ * the selection.
65
+ *
66
+ * Returns `false` when there is no such element or no items.
67
+ */
68
+ openAtSelection() {
69
+ const editing = this.editor.editing;
70
+ const selection = this.editor.model.document.selection;
71
+ const selectedElement = selection.getSelectedElement();
72
+ const parent = selection.getFirstPosition()?.parent;
73
+ const element = selectedElement ?? (parent?.is('element') && !parent.is('rootElement') ? parent : null);
74
+ const viewElement = selectedElement && editing.mapper.toViewElement(selectedElement);
75
+ const viewRange = editing.view.document.selection.getFirstRange();
76
+ const target = viewElement
77
+ ? editing.view.domConverter.mapViewToDom(viewElement)
78
+ : viewRange && editing.view.domConverter.viewRangeToDom(viewRange);
79
+ if (!element || !isInDocument(element) || !(target instanceof HTMLElement || target instanceof Range)) {
80
+ return false;
81
+ }
82
+ return this.open(element, target);
83
+ }
84
+ /**
85
+ * Returns the closest model element mapped to the given view element or its ancestors, skipping the root.
86
+ */
87
+ getModelElement(viewElement) {
88
+ const mapper = this.editor.editing.mapper;
89
+ for (let node = viewElement; node; node = node.parent?.is('element') ? node.parent : null) {
90
+ const element = mapper.toModelElement(node);
91
+ if (element) {
92
+ return element.is('rootElement') ? null : element;
93
+ }
94
+ }
95
+ return null;
96
+ }
97
+ }
98
+ //# sourceMappingURL=elementContextMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elementContextMenu.js","sourceRoot":"","sources":["../src/elementContextMenu.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The element context menu: right-clicking an element (or Shift+F10, or the menu key) opens the menu of the element,
3
+ * configured by the `elementContextMenu` option. The menus are shown by `ElementMenu` from `ckeditor5-elements-core`.
4
+ *
5
+ * @module ckeditor5-elements-context-menu
6
+ */
7
+ export { default as ElementContextMenu } from './elementContextMenu.js';
8
+ export { default as ContextMenuObserver, type ViewDocumentContextMenuEvent } from './contextMenuObserver.js';
9
+ import './augmentation.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAGxE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,KAAK,4BAA4B,EAAE,MAAM,0BAA0B,CAAC;AAE7G,OAAO,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The element context menu: right-clicking an element (or Shift+F10, or the menu key) opens the menu of the element,
3
+ * configured by the `elementContextMenu` option. The menus are shown by `ElementMenu` from `ckeditor5-elements-core`.
4
+ *
5
+ * @module ckeditor5-elements-context-menu
6
+ */
7
+ // Plugins.
8
+ export { default as ElementContextMenu } from './elementContextMenu.js';
9
+ // Observer.
10
+ export { default as ContextMenuObserver } from './contextMenuObserver.js';
11
+ import './augmentation.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,WAAW;AACX,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAExE,YAAY;AACZ,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAqC,MAAM,0BAA0B,CAAC;AAE7G,OAAO,mBAAmB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "ckeditor5-elements-context-menu",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "49.0.0-alpha.0",
4
+ "description": "A context menu for CKEditor 5 elements: right-click an element, or press Shift+F10, to open a menu of commands for it.",
5
+ "keywords": [
6
+ "ckeditor",
7
+ "ckeditor5",
8
+ "ckeditor 5",
9
+ "ckeditor5-feature",
10
+ "ckeditor5-plugin"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Ivan Kildyushov",
14
+ "homepage": "https://ivankild.github.io/ckeditor5-elements/guide/context-menu",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/ivankild/ckeditor5-elements.git",
18
+ "directory": "packages/ckeditor5-elements-context-menu"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/ivankild/ckeditor5-elements/issues"
22
+ },
23
+ "type": "module",
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
30
+ },
31
+ "./package.json": "./package.json"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "src",
36
+ "README.md"
37
+ ],
38
+ "sideEffects": false,
39
+ "dependencies": {
40
+ "ckeditor5-elements-core": "49.0.0-alpha.0"
41
+ },
42
+ "peerDependencies": {
43
+ "ckeditor5": ">=49.0.0"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc --build"
47
+ }
6
48
  }
@@ -0,0 +1,16 @@
1
+ import type { ElementMenuConfig } from 'ckeditor5-elements-core';
2
+ import type { ElementContextMenu } from './index.js';
3
+
4
+ declare module 'ckeditor5' {
5
+ interface PluginsMap {
6
+ [ElementContextMenu.pluginName]: ElementContextMenu;
7
+ }
8
+
9
+ interface EditorConfig {
10
+ /**
11
+ * Returns the items (commands, submenus and `'|'` separators) of the context menu of the right-clicked element.
12
+ * See {@link ElementContextMenu}.
13
+ */
14
+ elementContextMenu?: ElementMenuConfig;
15
+ }
16
+ }
@@ -0,0 +1,27 @@
1
+ import { DomEventObserver, type ViewDocumentDomEventData } from 'ckeditor5';
2
+
3
+ /**
4
+ * Fires the `contextmenu` event on the view document when the user opens the native context menu in an editable.
5
+ */
6
+ export default class ContextMenuObserver extends DomEventObserver<'contextmenu'> {
7
+ // The base class declares it as an accessor, so it cannot be a field here.
8
+ // eslint-disable-next-line @typescript-eslint/class-literal-property-style
9
+ public get domEventType(): 'contextmenu' {
10
+ return 'contextmenu';
11
+ }
12
+
13
+ /**
14
+ * Fires `contextmenu` on the view document for the DOM event.
15
+ */
16
+ public onDomEvent(domEvent: MouseEvent): void {
17
+ this.fire(domEvent.type, domEvent);
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Fired on the view document by the {@link ContextMenuObserver}.
23
+ */
24
+ export interface ViewDocumentContextMenuEvent {
25
+ name: 'contextmenu';
26
+ args: [data: ViewDocumentDomEventData<MouseEvent>];
27
+ }
@@ -0,0 +1,116 @@
1
+ import { Plugin, type ModelElement, type RectSource, type ViewElement } from 'ckeditor5';
2
+ import { ElementMenu, isInDocument } from 'ckeditor5-elements-core';
3
+ import ContextMenuObserver, { type ViewDocumentContextMenuEvent } from './contextMenuObserver.js';
4
+
5
+ /**
6
+ * Shows the menu of an element when the user right-clicks it, or opens the context menu with the keyboard (the menu
7
+ * key, or Shift+F10) while the cursor is in it.
8
+ *
9
+ * The items come from the `elementContextMenu` editor config option only (see `ElementMenuConfig`): commands, submenus
10
+ * and `'|'` separators. Without it, or when it returns no items for an
11
+ * element, the native context menu shows. The menu itself is shown by the `ElementMenu` plugin.
12
+ */
13
+ export default class ElementContextMenu extends Plugin {
14
+ public static get requires(): readonly [typeof ElementMenu] {
15
+ return [ElementMenu] as const;
16
+ }
17
+
18
+ public static get pluginName(): 'ElementContextMenu' {
19
+ return 'ElementContextMenu' as const;
20
+ }
21
+
22
+ public init(): void {
23
+ const editor = this.editor;
24
+ const editingView = editor.editing.view;
25
+
26
+ editingView.addObserver(ContextMenuObserver);
27
+
28
+ this.listenTo<ViewDocumentContextMenuEvent>(editingView.document, 'contextmenu', (evt, data) => {
29
+ const { domEvent } = data;
30
+ const element = this.getModelElement(data.target);
31
+
32
+ // A context menu opened with the keyboard (the menu key, or Shift+F10 in some browsers) targets the editable
33
+ // itself, not the element at the cursor. A right-click outside any element keeps the native menu.
34
+ const isOpen = element
35
+ ? this.open(element, new DOMRect(domEvent.clientX, domEvent.clientY, 0, 0))
36
+ : domEvent.button !== 2 && this.openAtSelection();
37
+
38
+ if (isOpen) {
39
+ data.preventDefault();
40
+ }
41
+ });
42
+
43
+ // Not every browser opens the context menu on Shift+F10 (e.g. on macOS), so handle it in all of them.
44
+ editor.keystrokes.set('Shift+F10', (data, cancel) => {
45
+ if (this.openAtSelection()) {
46
+ cancel();
47
+ }
48
+ });
49
+
50
+ if (typeof editor.config.get('elementContextMenu') === 'function') {
51
+ editor.accessibility.addKeystrokeInfos({
52
+ keystrokes: [{ label: editor.t('Open the menu of the element at the cursor'), keystroke: 'Shift+F10' }]
53
+ });
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Opens the menu of the element next to the target (e.g. a button, or a `DOMRect` in viewport coordinates), with
59
+ * the items from the `elementContextMenu` config option.
60
+ *
61
+ * Returns `false` when there are no items, so the caller can let the native context menu show instead.
62
+ */
63
+ public open(element: ModelElement, target: RectSource): boolean {
64
+ const config = this.editor.config.get('elementContextMenu');
65
+
66
+ if (typeof config !== 'function') {
67
+ return false;
68
+ }
69
+
70
+ return this.editor.plugins
71
+ .get(ElementMenu)
72
+ .open(element, target, config, { label: this.editor.t('Element menu') });
73
+ }
74
+
75
+ /**
76
+ * Opens the menu of the element at the selection (the selected element, or the element with the cursor), next to
77
+ * the selection.
78
+ *
79
+ * Returns `false` when there is no such element or no items.
80
+ */
81
+ public openAtSelection(): boolean {
82
+ const editing = this.editor.editing;
83
+ const selection = this.editor.model.document.selection;
84
+ const selectedElement = selection.getSelectedElement();
85
+ const parent = selection.getFirstPosition()?.parent;
86
+ const element = selectedElement ?? (parent?.is('element') && !parent.is('rootElement') ? parent : null);
87
+ const viewElement = selectedElement && editing.mapper.toViewElement(selectedElement);
88
+ const viewRange = editing.view.document.selection.getFirstRange();
89
+ const target = viewElement
90
+ ? editing.view.domConverter.mapViewToDom(viewElement)
91
+ : viewRange && editing.view.domConverter.viewRangeToDom(viewRange);
92
+
93
+ if (!element || !isInDocument(element) || !(target instanceof HTMLElement || target instanceof Range)) {
94
+ return false;
95
+ }
96
+
97
+ return this.open(element, target);
98
+ }
99
+
100
+ /**
101
+ * Returns the closest model element mapped to the given view element or its ancestors, skipping the root.
102
+ */
103
+ public getModelElement(viewElement: ViewElement): ModelElement | null {
104
+ const mapper = this.editor.editing.mapper;
105
+
106
+ for (let node: ViewElement | null = viewElement; node; node = node.parent?.is('element') ? node.parent : null) {
107
+ const element = mapper.toModelElement(node);
108
+
109
+ if (element) {
110
+ return element.is('rootElement') ? null : element;
111
+ }
112
+ }
113
+
114
+ return null;
115
+ }
116
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The element context menu: right-clicking an element (or Shift+F10, or the menu key) opens the menu of the element,
3
+ * configured by the `elementContextMenu` option. The menus are shown by `ElementMenu` from `ckeditor5-elements-core`.
4
+ *
5
+ * @module ckeditor5-elements-context-menu
6
+ */
7
+
8
+ // Plugins.
9
+ export { default as ElementContextMenu } from './elementContextMenu.js';
10
+
11
+ // Observer.
12
+ export { default as ContextMenuObserver, type ViewDocumentContextMenuEvent } from './contextMenuObserver.js';
13
+
14
+ import './augmentation.js';