fix(editor): Open sticky note color picker from context menu (#31917)

This commit is contained in:
Csaba Tuncsik
2026-06-09 13:00:13 +00:00
committed by GitHub
parent 79d1695c42
commit 0685db8ff6
5 changed files with 67 additions and 1 deletions
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { useContextMenu } from '../composables/useContextMenu';
import { type ContextMenuAction } from '../composables/useContextMenuItems';
import { isFocusHandoffAction, type ContextMenuAction } from '../composables/useContextMenuItems';
import { useStyles } from '@/app/composables/useStyles';
import { nextTick, ref, watch } from 'vue';
import {
@@ -41,10 +41,23 @@ watch(
{ flush: 'post' },
);
// When a selected action hands off to another floating layer (e.g. the sticky
// color popover), we must stop Reka from restoring focus as the menu closes —
// otherwise the restore lands outside the freshly-opened layer and dismisses it.
let suppressRestoreFocus = false;
function onActionSelect(item: ContextMenuAction) {
suppressRestoreFocus = isFocusHandoffAction(item);
emit('action', item, contextMenu.targetNodeIds.value);
}
function onCloseAutoFocus(event: Event) {
if (suppressRestoreFocus) {
event.preventDefault();
}
suppressRestoreFocus = false;
}
function onOpenChange(open: boolean) {
if (!open) {
contextMenu.close();
@@ -71,6 +84,7 @@ function onOpenChange(open: boolean) {
:class="$style.content"
data-test-id="context-menu"
:style="{ zIndex: APP_Z_INDEXES.CONTEXT_MENU }"
@close-auto-focus="onCloseAutoFocus"
>
<template v-for="item in actions" :key="item.id">
<ContextMenuSeparator v-if="item.divided" :class="$style.separator" />
@@ -0,0 +1,16 @@
import { isFocusHandoffAction } from './useContextMenuItems';
describe('isFocusHandoffAction', () => {
it('returns true for actions that open another focus-taking layer', () => {
// `change_color` opens the sticky color popover; the context menu must not
// restore focus on close or it would dismiss the just-opened popover.
expect(isFocusHandoffAction('change_color')).toBe(true);
});
it('returns false for regular actions that do not hand off focus', () => {
expect(isFocusHandoffAction('delete')).toBe(false);
expect(isFocusHandoffAction('duplicate')).toBe(false);
expect(isFocusHandoffAction('rename')).toBe(false);
expect(isFocusHandoffAction('open')).toBe(false);
});
});
@@ -41,6 +41,18 @@ export type ContextMenuAction =
| 'extract_sub_workflow'
| 'focus_ai_on_selected';
/**
* Actions that, once selected, hand off to another floating layer (e.g. a
* popover) which then takes focus. For these the context menu must not restore
* focus on close — otherwise the restore lands outside the freshly-opened layer
* and immediately dismisses it.
*/
const FOCUS_HANDOFF_ACTIONS = new Set<ContextMenuAction>(['change_color']);
export function isFocusHandoffAction(action: ContextMenuAction): boolean {
return FOCUS_HANDOFF_ACTIONS.has(action);
}
type Item = ActionDropdownItem<ContextMenuAction>;
export function useContextMenuItems(targetNodeIds: ComputedRef<string[]>): ComputedRef<Item[]> {
@@ -43,4 +43,16 @@ export class StickyComponent extends BasePage {
getDefaultStickyGuideLink(): Locator {
return this.getStickies().first().getByRole('link', { name: 'Guide' });
}
/**
* Open the color picker for a sticky via its right-click context menu.
*/
async openColorPickerFromContextMenu(sticky: Locator): Promise<void> {
await sticky.click({ button: 'right' });
await this.page.getByTestId('context-menu-item-change_color').click();
}
getColorOptions(): Locator {
return this.page.getByTestId('color');
}
}
@@ -31,5 +31,17 @@ test.describe(
const guideLink = n8n.canvas.sticky.getDefaultStickyGuideLink();
await expect(guideLink).toHaveAttribute('href');
});
test('opens the color picker from the right-click context menu', async ({ n8n }) => {
await n8n.start.fromBlankCanvas();
await n8n.canvas.sticky.addSticky();
const sticky = n8n.canvas.sticky.getStickies().first();
await n8n.canvas.sticky.openColorPickerFromContextMenu(sticky);
// The popover is opened as the context menu closes; it must survive the
// menu's focus restoration rather than being dismissed immediately.
await expect(n8n.canvas.sticky.getColorOptions().first()).toBeVisible();
});
},
);