feat(core): add keyboard event listener to ai popover (#10952)
Close [BS-2827](https://linear.app/affine-design/issue/BS-2827).
This commit is contained in:
@@ -94,6 +94,11 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
font-size: var(--affine-font-sm);
|
font-size: var(--affine-font-sm);
|
||||||
color: var(--affine-text-secondary-color);
|
color: var(--affine-text-secondary-color);
|
||||||
}
|
}
|
||||||
|
.item-suffix {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: var(--affine-font-xs);
|
||||||
|
color: var(--affine-text-secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
${scrollbarStyle('.add-popover')}
|
${scrollbarStyle('.add-popover')}
|
||||||
`;
|
`;
|
||||||
@@ -155,8 +160,28 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private get _menuGroup() {
|
||||||
|
switch (this._mode) {
|
||||||
|
case AddPopoverMode.Tags:
|
||||||
|
return [];
|
||||||
|
case AddPopoverMode.Collections:
|
||||||
|
return [];
|
||||||
|
default:
|
||||||
|
if (this._query) {
|
||||||
|
return [this._docGroup, this.uploadGroup];
|
||||||
|
}
|
||||||
|
return [this._docGroup, this.tcGroup, this.uploadGroup];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _flattenMenuGroup() {
|
||||||
|
return this._menuGroup.flatMap(group => {
|
||||||
|
return Array.isArray(group.items) ? group.items : group.items.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private accessor _activatedItemIndex = 0;
|
private accessor _activatedIndex = 0;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private accessor _mode: AddPopoverMode = AddPopoverMode.Default;
|
private accessor _mode: AddPopoverMode = AddPopoverMode.Default;
|
||||||
@@ -176,6 +201,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
override connectedCallback() {
|
override connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this._updateDocGroup();
|
this._updateDocGroup();
|
||||||
|
this.addEventListener('keydown', this._handleKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
override firstUpdated() {
|
override firstUpdated() {
|
||||||
@@ -184,11 +210,15 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this.removeEventListener('keydown', this._handleKeyDown);
|
||||||
|
}
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
const groups = this._getMenuGroup();
|
|
||||||
return html`<div class="add-popover">
|
return html`<div class="add-popover">
|
||||||
${this._renderSearchInput()} ${this._renderDivider()}
|
${this._renderSearchInput()} ${this._renderDivider()}
|
||||||
${this._renderMenuGroup(groups)}
|
${this._renderMenuGroup(this._menuGroup)}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,40 +268,29 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
private _renderMenuItems(items: MenuItem[], startIndex: number) {
|
private _renderMenuItems(items: MenuItem[], startIndex: number) {
|
||||||
return html`<div>
|
return html`<div>
|
||||||
${items.length > 0
|
${items.length > 0
|
||||||
? items.map(({ key, name, icon, action }, idx) => {
|
? items.map(({ key, name, icon, action, suffix }, idx) => {
|
||||||
const curIdx = startIndex + idx;
|
const curIdx = startIndex + idx;
|
||||||
return html`<icon-button
|
return html`<icon-button
|
||||||
width="280px"
|
width="280px"
|
||||||
height="30px"
|
height="30px"
|
||||||
data-id=${key}
|
data-id=${key}
|
||||||
|
data-index=${curIdx}
|
||||||
.text=${name}
|
.text=${name}
|
||||||
hover=${this._activatedItemIndex === curIdx}
|
hover=${this._activatedIndex === curIdx}
|
||||||
@click=${() => action()?.catch(console.error)}
|
@click=${() => action()?.catch(console.error)}
|
||||||
@mousemove=${() => (this._activatedItemIndex = curIdx)}
|
@mousemove=${() => (this._activatedIndex = curIdx)}
|
||||||
>
|
>
|
||||||
${icon}
|
${icon}
|
||||||
|
${suffix ? html`<div class="item-suffix">${suffix}</div>` : ''}
|
||||||
</icon-button>`;
|
</icon-button>`;
|
||||||
})
|
})
|
||||||
: html`<div class="no-result">No Result</div>`}
|
: html`<div class="no-result">No Result</div>`}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getMenuGroup() {
|
|
||||||
switch (this._mode) {
|
|
||||||
case AddPopoverMode.Tags:
|
|
||||||
return [];
|
|
||||||
case AddPopoverMode.Collections:
|
|
||||||
return [];
|
|
||||||
default:
|
|
||||||
if (this._query) {
|
|
||||||
return [this._docGroup, this.uploadGroup];
|
|
||||||
}
|
|
||||||
return [this._docGroup, this.tcGroup, this.uploadGroup];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onInput(event: Event) {
|
private _onInput(event: Event) {
|
||||||
this._query = (event.target as HTMLInputElement).value;
|
this._query = (event.target as HTMLInputElement).value;
|
||||||
|
this._activatedIndex = 0;
|
||||||
this._updateDocGroup();
|
this._updateDocGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,4 +309,46 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
|||||||
});
|
});
|
||||||
this.abortController.abort();
|
this.abortController.abort();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly _handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.isComposing) return;
|
||||||
|
|
||||||
|
const { key } = event;
|
||||||
|
|
||||||
|
if (key === 'ArrowDown' || key === 'ArrowUp') {
|
||||||
|
event.preventDefault();
|
||||||
|
const totalItems = this._flattenMenuGroup.length;
|
||||||
|
if (totalItems === 0) return;
|
||||||
|
|
||||||
|
if (key === 'ArrowDown') {
|
||||||
|
this._activatedIndex = (this._activatedIndex + 1) % totalItems;
|
||||||
|
} else if (key === 'ArrowUp') {
|
||||||
|
this._activatedIndex =
|
||||||
|
(this._activatedIndex - 1 + totalItems) % totalItems;
|
||||||
|
}
|
||||||
|
this._scrollItemIntoView();
|
||||||
|
} else if (key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
this._flattenMenuGroup[this._activatedIndex]
|
||||||
|
.action()
|
||||||
|
?.catch(console.error);
|
||||||
|
} else if (key === 'Escape') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.abortController.abort();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private _scrollItemIntoView() {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const element = this.renderRoot.querySelector(
|
||||||
|
`[data-index="${this._activatedIndex}"]`
|
||||||
|
);
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'nearest',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user