fix(editor): textarea not focusing in AI panel (#11429)
Closes: [BS-3006](https://linear.app/affine-design/issue/BS-3006/insider上面,圈选yuan素后,ad-hoc的ai对话框焦点控制有大问题(无法对焦,总是会跳走))
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import { RANGE_SYNC_EXCLUDE_ATTR } from './consts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the active element is in the editor host.
|
* Check if the active element is in the editor host.
|
||||||
* TODO(@mirone): this is a trade-off, we need to use separate awareness store for every store to make sure the selection is isolated.
|
* TODO(@mirone): this is a trade-off, we need to use separate awareness store for every store to make sure the selection is isolated.
|
||||||
@@ -8,6 +10,9 @@
|
|||||||
export function isActiveInEditor(editorHost: HTMLElement) {
|
export function isActiveInEditor(editorHost: HTMLElement) {
|
||||||
const currentActiveElement = document.activeElement;
|
const currentActiveElement = document.activeElement;
|
||||||
if (!currentActiveElement) return false;
|
if (!currentActiveElement) return false;
|
||||||
|
// The input or textarea in the widget should be ignored.
|
||||||
|
if (currentActiveElement.closest(`[${RANGE_SYNC_EXCLUDE_ATTR}="true"]`))
|
||||||
|
return false;
|
||||||
const currentEditorHost = currentActiveElement?.closest('editor-host');
|
const currentEditorHost = currentActiveElement?.closest('editor-host');
|
||||||
if (!currentEditorHost) return false;
|
if (!currentEditorHost) return false;
|
||||||
return currentEditorHost === editorHost;
|
return currentEditorHost === editorHost;
|
||||||
|
|||||||
@@ -210,12 +210,11 @@ export class RangeBinding {
|
|||||||
const el = getElement(range.commonAncestorContainer);
|
const el = getElement(range.commonAncestorContainer);
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
const closestExclude = el.closest(`[${RANGE_SYNC_EXCLUDE_ATTR}]`);
|
const closestExclude = el.closest(`[${RANGE_SYNC_EXCLUDE_ATTR}="true"]`);
|
||||||
if (closestExclude?.getAttribute(RANGE_SYNC_EXCLUDE_ATTR) === 'true')
|
if (closestExclude) return;
|
||||||
return;
|
|
||||||
|
|
||||||
const closestEditable = el.closest('[contenteditable]');
|
const closestEditable = el.closest('[contenteditable="false"]');
|
||||||
if (closestEditable?.getAttribute('contenteditable') === 'false') return;
|
if (closestEditable) return;
|
||||||
|
|
||||||
const startElement = getElement(range.startContainer);
|
const startElement = getElement(range.startContainer);
|
||||||
const endElement = getElement(range.endContainer);
|
const endElement = getElement(range.endContainer);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
} from '@blocksuite/affine/shared/utils';
|
} from '@blocksuite/affine/shared/utils';
|
||||||
import { WidgetComponent, WidgetViewExtension } from '@blocksuite/affine/std';
|
import { WidgetComponent, WidgetViewExtension } from '@blocksuite/affine/std';
|
||||||
import { GfxControllerIdentifier } from '@blocksuite/affine/std/gfx';
|
import { GfxControllerIdentifier } from '@blocksuite/affine/std/gfx';
|
||||||
|
import { RANGE_SYNC_EXCLUDE_ATTR } from '@blocksuite/affine/std/inline';
|
||||||
import type { BaseSelection } from '@blocksuite/affine/store';
|
import type { BaseSelection } from '@blocksuite/affine/store';
|
||||||
import {
|
import {
|
||||||
autoPlacement,
|
autoPlacement,
|
||||||
@@ -415,6 +416,8 @@ export class AffineAIPanelWidget extends WidgetComponent {
|
|||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
this.tabIndex = -1;
|
this.tabIndex = -1;
|
||||||
|
// No need to synchronize the contents of the input into the editor.
|
||||||
|
this.setAttribute(RANGE_SYNC_EXCLUDE_ATTR, 'true');
|
||||||
this.disposables.addFromEvent(
|
this.disposables.addFromEvent(
|
||||||
document,
|
document,
|
||||||
'pointerdown',
|
'pointerdown',
|
||||||
@@ -437,6 +440,9 @@ export class AffineAIPanelWidget extends WidgetComponent {
|
|||||||
this.disposables.addFromEvent(this, 'wheel', stopPropagation);
|
this.disposables.addFromEvent(this, 'wheel', stopPropagation);
|
||||||
this.disposables.addFromEvent(this, 'pointerdown', stopPropagation);
|
this.disposables.addFromEvent(this, 'pointerdown', stopPropagation);
|
||||||
this.disposables.addFromEvent(this, 'pointerup', stopPropagation);
|
this.disposables.addFromEvent(this, 'pointerup', stopPropagation);
|
||||||
|
this.disposables.addFromEvent(this, 'cut', stopPropagation);
|
||||||
|
this.disposables.addFromEvent(this, 'copy', stopPropagation);
|
||||||
|
this.disposables.addFromEvent(this, 'paste', stopPropagation);
|
||||||
this.disposables.addFromEvent(this, 'keydown', this._onKeyDown);
|
this.disposables.addFromEvent(this, 'keydown', this._onKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,4 +127,15 @@ test.describe('AIChatWith/Text', () => {
|
|||||||
const content = await utils.editor.getEditorContent(page);
|
const content = await utils.editor.getEditorContent(page);
|
||||||
expect(content).toBe('Apple');
|
expect(content).toBe('Apple');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should focus on textarea', async ({ page, utils }) => {
|
||||||
|
await utils.editor.askAIWithText(page, 'Apple');
|
||||||
|
|
||||||
|
const textarea = await utils.editor.whatAreYourThoughts(page, 'Coffee');
|
||||||
|
|
||||||
|
await expect(textarea).toBeFocused();
|
||||||
|
|
||||||
|
const value = await textarea.inputValue();
|
||||||
|
expect(value).toBe('Coffee');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -582,4 +582,12 @@ export class EditorUtils {
|
|||||||
),
|
),
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async whatAreYourThoughts(page: Page, text: string) {
|
||||||
|
const textarea = page.locator(
|
||||||
|
'affine-ai-panel-widget .ai-panel-container textarea'
|
||||||
|
);
|
||||||
|
await textarea.fill(text);
|
||||||
|
return textarea;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user