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 +21 -0
- package/README.md +28 -2
- package/dist/augmentation.d.ts +15 -0
- package/dist/augmentation.d.ts.map +1 -0
- package/dist/augmentation.js +2 -0
- package/dist/augmentation.js.map +1 -0
- package/dist/contextMenuObserver.d.ts +19 -0
- package/dist/contextMenuObserver.d.ts.map +1 -0
- package/dist/contextMenuObserver.js +18 -0
- package/dist/contextMenuObserver.js.map +1 -0
- package/dist/elementContextMenu.d.ts +34 -0
- package/dist/elementContextMenu.d.ts.map +1 -0
- package/dist/elementContextMenu.js +98 -0
- package/dist/elementContextMenu.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -3
- package/src/augmentation.ts +16 -0
- package/src/contextMenuObserver.ts +27 -0
- package/src/elementContextMenu.ts +116 -0
- package/src/index.ts +14 -0
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
|
-
#
|
|
1
|
+
# ckeditor5-elements-context-menu
|
|
2
2
|
|
|
3
|
-
|
|
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 @@
|
|
|
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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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';
|