Skip to content

Commit 47f300c

Browse files
committed
BUGFIX: Prevent unfocus events during inline editing
Keep the Neos UI and CKEditor from unfocusing our currently edited node or editable area when we click parts of the toolbar.
1 parent 2dbe108 commit 47f300c

File tree

7 files changed

+65
-13
lines changed

7 files changed

+65
-13
lines changed

packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,30 @@ export const createEditor = store => async options => {
132132
.then(editor => {
133133
const debouncedOnChange = debounce(() => onChange(cleanupContentBeforeCommit(editor.getData())), 1500, {maxWait: 5000});
134134
editor.model.document.on('change:data', debouncedOnChange);
135-
editor.ui.focusTracker.on('change:isFocused', event => {
136-
if (!event.source.isFocused) {
137-
// when another editor is focused commit all possible pending changes
138-
debouncedOnChange.flush();
139-
editor.ui.view.toolbar.element.classList.remove('neos-ck-anchored-toolbar--visible');
140-
return
135+
editor.ui.focusTracker.on('change:isFocused', (event, data, isFocused) => {
136+
if (!isFocused) {
137+
// Use setTimeout to check activeElement after the browser has updated focus
138+
// This prevents premature blur detection when clicking from toolbar to editable area
139+
setTimeout(() => {
140+
// Double-check that the editor is still not focused
141+
if (editor.ui.focusTracker.isFocused) {
142+
return;
143+
}
144+
145+
// Check if focus moved to a CKEditor UI element (like the toolbar)
146+
// If so, we should not treat this as leaving the editor
147+
// TODO: Verify whether this is precise enough, or we should instead check all editor components instead
148+
const activeElement = getGuestFrameDocument().activeElement;
149+
const isWithinEditor = activeElement === editor.sourceElement;
150+
if (isWithinEditor) {
151+
return;
152+
}
153+
154+
// when another editor is focused commit all possible pending changes
155+
debouncedOnChange.flush();
156+
editor.ui.view.toolbar.element.classList.remove('neos-ck-anchored-toolbar--visible');
157+
}, 0);
158+
return;
141159
}
142160

143161
currentEditor = editor;
@@ -159,7 +177,6 @@ export const createEditor = store => async options => {
159177
backendContainer.appendChild(editor.ui.view.toolbar.element);
160178

161179
// Anchor the toolbar to the dom-node representing the edited property
162-
// TODO: Move to CSS class and set the class on the element instead of setting styles directly
163180
editor.ui.view.toolbar.element.style.positionAnchor = propertyDomNode.dataset.neosInlineEditorAnchorName;
164181
editor.ui.view.toolbar.element.classList.add('neos-ck-anchored-toolbar');
165182

packages/neos-ui-ckeditor5-bindings/src/cke-overwrites.vanilla-css

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,11 @@
2525
max-width: 400px;
2626
display: none;
2727
position-try-fallbacks: --neos-ck-toolbar-align-right;
28+
z-index: calc(2147480000 + 1); /* one above the NodeToolBar z-index */
2829
}
2930
.ck.ck-toolbar.neos-ck-anchored-toolbar--visible {
3031
display: flex;
3132
}
32-
.ck.ck-toolbar.neos-ck-anchored-toolbar--visible:hover {
33-
z-index: 2147483646;
34-
}
3533

3634
/**
3735
* Reduce size of ck toolbar to match Neos UI design which has not spacing around buttons
@@ -95,7 +93,7 @@
9593
--ck-color-shadow-inner: rgba(0, 0, 0, .1);
9694

9795
--ck-color-widget-blurred-border: #dedede;
98-
--ck-color-widget-hover-border: #ffc83d;
96+
--ck-color-widget-hover-border: var(--ck-color-base-active);
9997
--ck-color-widget-editable-focus-background: transparent;
10098
--ck-color-widget-drag-handle-icon-color: var(--ck-color-base-background);
10199

packages/neos-ui-guest-frame/src/InlineUI/NodeToolbar/ContextToolbar/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ type ContextToolbarProps = {
4848
fusionPath: string;
4949
}
5050

51+
/**
52+
* The ContextToolbar contains buttons for context specific operations on nodes,
53+
* like copying, hiding, moving deleting the focused node.
54+
*/
5155
const ContextToolbar: React.FC<ContextToolbarProps> = ({
5256
buttonProps,
5357
nodeTypesRegistry,

packages/neos-ui-guest-frame/src/InlineUI/NodeToolbar/ContextToolbar/style.module.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@
2929
bottom: calc(anchor(top) + 10px);
3030
/*position-try-fallbacks: --toolbar-sticky;*/
3131
}
32+
/**
33+
* Increase z-index when hovering over the context toolbar or any of its siblings,
34+
* so it appears above the ckeditor toolbar when both are visible.
35+
*/
36+
.contextToolBar:has(~ :hover),
37+
.contextToolBar:hover,
38+
.contextToolBar:hover ~ * {
39+
z-index: calc(var(--zIndex-NodeToolBar) + 2);
40+
}
3241

3342
.contextToolBar--isSticky {
3443
position: fixed;
@@ -41,7 +50,7 @@
4150
display: flex;
4251
align-items: center;
4352
gap: 1ch;
44-
padding-left: 10px;
53+
padding: 0 5px;
4554
font-size: var(--fontSize-Small);
4655
}
4756

packages/neos-ui-guest-frame/src/InlineUI/NodeToolbar/StructuralToolbar/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ type StructuralToolbarProps = {
1717
buttonProps?: {[key: string]: any};
1818
}
1919

20+
/**
21+
* The StructuralToolbar contains buttons for structural operations on nodes,
22+
* like adding or inserting nodes from clipboard.
23+
*/
2024
const StructuralToolbar: React.FC<StructuralToolbarProps> = ({insertPosition, buttonProps, guestFrameRegistry}) => {
2125
const buttons = guestFrameRegistry.getChildren('NodeToolbar/Buttons');
2226

packages/neos-ui-guest-frame/src/InlineUI/NodeToolbar/StructuralToolbar/style.module.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,24 @@
99
background: transparent;
1010
}
1111

12+
/**
13+
* Fallback if the structural toolbar is on top of the node and
14+
* does not fit on the left side of the context toolbar
15+
*/
16+
@position-try --structural-toolbar-align-right {
17+
left: anchor(left);
18+
bottom: anchor(top);
19+
position-area: none;
20+
}
1221
.structuralToolBar__popover {
1322
z-index: var(--zIndex-NodeToolBar);
1423
position: absolute;
1524
position-anchor: --inline-ui-context-toolbar-anchor;
1625
position-area: center left;
26+
position-try-fallbacks: --structural-toolbar-align-right;
27+
}
28+
.structuralToolBar__popover:hover {
29+
z-index: calc(var(--zIndex-NodeToolBar) + 2);
1730
}
1831

1932
.structuralToolBar__popover--isBelow {
@@ -23,11 +36,13 @@
2336
border-left: 2px solid white;
2437
border-bottom: 2px solid white;
2538
border-right: 2px solid white;
39+
position-try-fallbacks: none;
2640
}
2741

2842
.structuralToolBar__popover--isInside {
2943
position-anchor: --inline-ui-node-toolbar-anchor;
3044
position-area: center center;
3145
top: 10px;
3246
border: 2px solid white;
47+
position-try-fallbacks: none;
3348
}

packages/neos-ui-guest-frame/src/initializeGuestFrame.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ export default ({globalRegistry, store}) => function * initializeGuestFrame() {
119119
domNode.getAttribute &&
120120
domNode.getAttribute('data-__neos__inline-ui')
121121
);
122+
const isInsideCKToolbar = clickPath.some(domNode =>
123+
domNode &&
124+
domNode.classList &&
125+
domNode.classList.contains('ck-toolbar')
126+
);
122127
const isInsideEditableProperty = clickPath.some(domNode =>
123128
domNode &&
124129
domNode.getAttribute &&
@@ -130,7 +135,7 @@ export default ({globalRegistry, store}) => function * initializeGuestFrame() {
130135
domNode.getAttribute('data-__neos-node-contextpath')
131136
);
132137

133-
if (isInsideInlineUi) {
138+
if (isInsideInlineUi || isInsideCKToolbar) {
134139
// Do nothing, everything OK!
135140
} else if (selectedDomNode) {
136141
const contextPath = selectedDomNode.getAttribute('data-__neos-node-contextpath');

0 commit comments

Comments
 (0)