fix(editor): wrong position of remote selection and at menu in edgeless (#10137)
Close [BS-2552](https://linear.app/affine-design/issue/BS-2552/menu-loading-时滚动,定位错误), [BS-2490](https://linear.app/affine-design/issue/BS-2490/note-block-的menu的输入阴影错位), [BS-2300](https://linear.app/affine-design/issue/BS-2300/at-menu的输入阴影在暗黑模式看不见) ### What Changes - fix the position of remote selection mask and @ menu input mask in edgeless - fix the position of @ menu is no updated during edgeless viewport change - update @ menu mask color in dark mode ### Before https://github.com/user-attachments/assets/f44f618e-a791-497a-9f53-74824fe48dea ### After https://github.com/user-attachments/assets/5d87b999-deae-4435-9b8b-4cdf55393395
This commit is contained in:
@@ -1,6 +1,4 @@
|
|||||||
import type { BlockComponent, EditorHost } from '@blocksuite/block-std';
|
import type { EditorHost } from '@blocksuite/block-std';
|
||||||
|
|
||||||
import { isInsidePageEditor } from './checker.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get editor viewport element.
|
* Get editor viewport element.
|
||||||
@@ -13,22 +11,9 @@ import { isInsidePageEditor } from './checker.js';
|
|||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function getViewportElement(editorHost: EditorHost): HTMLElement | null {
|
export function getViewportElement(editorHost: EditorHost) {
|
||||||
if (!isInsidePageEditor(editorHost)) return null;
|
return (
|
||||||
const doc = editorHost.doc;
|
editorHost.closest<HTMLElement>('.affine-page-viewport') ??
|
||||||
if (!doc.root) {
|
editorHost.closest<HTMLElement>('.affine-edgeless-viewport')
|
||||||
console.error('Failed to get root doc');
|
);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const rootComponent = editorHost.view.getBlock(doc.root.id);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!rootComponent ||
|
|
||||||
rootComponent.closest('affine-page-root') !== rootComponent
|
|
||||||
) {
|
|
||||||
console.error('Failed to get viewport element!');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (rootComponent as BlockComponent & { viewportElement: HTMLElement })
|
|
||||||
.viewportElement;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
TextSelection,
|
TextSelection,
|
||||||
WidgetComponent,
|
WidgetComponent,
|
||||||
} from '@blocksuite/block-std';
|
} from '@blocksuite/block-std';
|
||||||
|
import { GfxController } from '@blocksuite/block-std/gfx';
|
||||||
import { throttle } from '@blocksuite/global/utils';
|
import { throttle } from '@blocksuite/global/utils';
|
||||||
import type { BaseSelection, UserInfo } from '@blocksuite/store';
|
import type { BaseSelection, UserInfo } from '@blocksuite/store';
|
||||||
import { computed, effect } from '@preact/signals-core';
|
import { computed, effect } from '@preact/signals-core';
|
||||||
@@ -268,24 +269,28 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent {
|
|||||||
this._abortController.abort();
|
this._abortController.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly _updateSelections = throttle(
|
private readonly _updateSelections = (
|
||||||
(selections: typeof this._remoteSelections.value) => {
|
selections: typeof this._remoteSelections.value
|
||||||
const remoteUsers = new Set<number>();
|
) => {
|
||||||
this._selections = selections.flatMap(({ selections, id, user }) => {
|
const remoteUsers = new Set<number>();
|
||||||
if (remoteUsers.has(id)) {
|
this._selections = selections.flatMap(({ selections, id, user }) => {
|
||||||
return [];
|
if (remoteUsers.has(id)) {
|
||||||
} else {
|
return [];
|
||||||
remoteUsers.add(id);
|
} else {
|
||||||
}
|
remoteUsers.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
selections,
|
selections,
|
||||||
rects: this._getSelectionRect(selections),
|
rects: this._getSelectionRect(selections),
|
||||||
user,
|
user,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
|
|
||||||
|
private readonly _updateSelectionsThrottled = throttle(
|
||||||
|
this._updateSelections,
|
||||||
60
|
60
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -293,13 +298,22 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent {
|
|||||||
this.disposables.add(
|
this.disposables.add(
|
||||||
effect(() => {
|
effect(() => {
|
||||||
const selections = this._remoteSelections.value;
|
const selections = this._remoteSelections.value;
|
||||||
this._updateSelections(selections);
|
this._updateSelectionsThrottled(selections);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this.disposables.add(
|
this.disposables.add(
|
||||||
this.std.store.slots.blockUpdated.on(() => {
|
this.std.store.slots.blockUpdated.on(() => {
|
||||||
this._updateSelections(this._remoteSelections.peek());
|
this._updateSelectionsThrottled(this._remoteSelections.peek());
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const gfx = this.std.getOptional(GfxController);
|
||||||
|
if (!gfx) return;
|
||||||
|
this.disposables.add(
|
||||||
|
gfx.viewport.viewportUpdated.on(() => {
|
||||||
|
const selections = this._remoteSelections.peek();
|
||||||
|
this._updateSelections(selections);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export function compareTopAndBottomSpace(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the position of the popper element with flip.
|
* Get the position of the popper element with flip.
|
||||||
|
* return null if the reference rect is all zero.
|
||||||
*/
|
*/
|
||||||
export function getPopperPosition(
|
export function getPopperPosition(
|
||||||
popper: {
|
popper: {
|
||||||
@@ -93,6 +94,14 @@ export function getPopperPosition(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const referenceRect = reference.getBoundingClientRect();
|
const referenceRect = reference.getBoundingClientRect();
|
||||||
|
if (
|
||||||
|
referenceRect.x === 0 &&
|
||||||
|
referenceRect.y === 0 &&
|
||||||
|
referenceRect.width === 0 &&
|
||||||
|
referenceRect.height === 0
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
|
||||||
const positioningPoint = {
|
const positioningPoint = {
|
||||||
x: referenceRect.x,
|
x: referenceRect.x,
|
||||||
y: referenceRect.y + (placement === 'bottom' ? referenceRect.height : 0),
|
y: referenceRect.y + (placement === 'bottom' ? referenceRect.height : 0),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
|||||||
import { getViewportElement } from '@blocksuite/affine-shared/utils';
|
import { getViewportElement } from '@blocksuite/affine-shared/utils';
|
||||||
import type { BlockComponent } from '@blocksuite/block-std';
|
import type { BlockComponent } from '@blocksuite/block-std';
|
||||||
import { BLOCK_ID_ATTR, WidgetComponent } from '@blocksuite/block-std';
|
import { BLOCK_ID_ATTR, WidgetComponent } from '@blocksuite/block-std';
|
||||||
|
import { GfxController } from '@blocksuite/block-std/gfx';
|
||||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||||
import {
|
import {
|
||||||
INLINE_ROOT_ATTR,
|
INLINE_ROOT_ATTR,
|
||||||
@@ -15,7 +16,6 @@ import {
|
|||||||
} from '@blocksuite/inline';
|
} from '@blocksuite/inline';
|
||||||
import { signal } from '@preact/signals-core';
|
import { signal } from '@preact/signals-core';
|
||||||
import { html, nothing } from 'lit';
|
import { html, nothing } from 'lit';
|
||||||
import { state } from 'lit/decorators.js';
|
|
||||||
import { choose } from 'lit/directives/choose.js';
|
import { choose } from 'lit/directives/choose.js';
|
||||||
import { repeat } from 'lit/directives/repeat.js';
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
import { styleMap } from 'lit/directives/style-map.js';
|
import { styleMap } from 'lit/directives/style-map.js';
|
||||||
@@ -37,6 +37,40 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
|||||||
> {
|
> {
|
||||||
static override styles = linkedDocWidgetStyles;
|
static override styles = linkedDocWidgetStyles;
|
||||||
|
|
||||||
|
private _context: LinkedDocContext | null = null;
|
||||||
|
|
||||||
|
private readonly _inputRects$ = signal<SelectionRect[]>([]);
|
||||||
|
|
||||||
|
private readonly _mode$ = signal<'desktop' | 'mobile' | 'none'>('none');
|
||||||
|
|
||||||
|
private _updateInputRects() {
|
||||||
|
if (!this._context) return;
|
||||||
|
const { inlineEditor, startRange, triggerKey } = this._context;
|
||||||
|
|
||||||
|
const currentInlineRange = inlineEditor.getInlineRange();
|
||||||
|
if (!currentInlineRange) return;
|
||||||
|
|
||||||
|
const startIndex = startRange.index - triggerKey.length;
|
||||||
|
const range = inlineEditor.toDomRange({
|
||||||
|
index: startIndex,
|
||||||
|
length: currentInlineRange.index - startIndex,
|
||||||
|
});
|
||||||
|
if (!range) return;
|
||||||
|
|
||||||
|
this._inputRects$.value = getRangeRects(
|
||||||
|
range,
|
||||||
|
getViewportElement(this.host)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _isCursorAtEnd() {
|
||||||
|
if (!this._context) return false;
|
||||||
|
const { inlineEditor } = this._context;
|
||||||
|
const currentInlineRange = inlineEditor.getInlineRange();
|
||||||
|
if (!currentInlineRange) return false;
|
||||||
|
return currentInlineRange.index === inlineEditor.yTextLength;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly _renderLinkedDocMenu = () => {
|
private readonly _renderLinkedDocMenu = () => {
|
||||||
if (!this.block.rootComponent) return nothing;
|
if (!this.block.rootComponent) return nothing;
|
||||||
|
|
||||||
@@ -52,13 +86,13 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
|||||||
></affine-linked-doc-popover>`;
|
></affine-linked-doc-popover>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@state()
|
|
||||||
private accessor _inputRects: SelectionRect[] = [];
|
|
||||||
private _renderInputMask() {
|
private _renderInputMask() {
|
||||||
return html`${repeat(
|
return html`${repeat(
|
||||||
this._inputRects,
|
this._inputRects$.value,
|
||||||
({ top, left, width, height }, index) => {
|
({ top, left, width, height }, index) => {
|
||||||
const last = index === this._inputRects.length - 1;
|
const last =
|
||||||
|
index === this._inputRects$.value.length - 1 && this._isCursorAtEnd;
|
||||||
|
|
||||||
const padding = 2;
|
const padding = 2;
|
||||||
return html`<div
|
return html`<div
|
||||||
class="input-mask"
|
class="input-mask"
|
||||||
@@ -73,29 +107,7 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
|||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly _mode$ = signal<'desktop' | 'mobile' | 'none'>('none');
|
private _watchInput() {
|
||||||
|
|
||||||
get config(): LinkedWidgetConfig {
|
|
||||||
return {
|
|
||||||
triggerKeys: ['@', '[[', '【【'],
|
|
||||||
ignoreBlockTypes: ['affine:code'],
|
|
||||||
ignoreSelector:
|
|
||||||
'edgeless-text-editor, edgeless-shape-text-editor, edgeless-group-title-editor, edgeless-frame-title-editor, edgeless-connector-label-editor',
|
|
||||||
convertTriggerKey: true,
|
|
||||||
getMenus,
|
|
||||||
mobile: {
|
|
||||||
useScreenHeight: false,
|
|
||||||
scrollContainer: getViewportElement(this.std.host) ?? window,
|
|
||||||
scrollTopOffset: 46,
|
|
||||||
},
|
|
||||||
...this.std.getConfig('affine:page')?.linkedWidget,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private _context: LinkedDocContext | null = null;
|
|
||||||
override connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
|
|
||||||
this.handleEvent('beforeInput', ctx => {
|
this.handleEvent('beforeInput', ctx => {
|
||||||
if (this._mode$.peek() !== 'none') return;
|
if (this._mode$.peek() !== 'none') return;
|
||||||
|
|
||||||
@@ -184,6 +196,40 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _watchViewportChange() {
|
||||||
|
const gfx = this.std.getOptional(GfxController);
|
||||||
|
if (!gfx) return;
|
||||||
|
this.disposables.add(
|
||||||
|
gfx.viewport.viewportUpdated.on(() => {
|
||||||
|
this._updateInputRects();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get config(): LinkedWidgetConfig {
|
||||||
|
return {
|
||||||
|
triggerKeys: ['@', '[[', '【【'],
|
||||||
|
ignoreBlockTypes: ['affine:code'],
|
||||||
|
ignoreSelector:
|
||||||
|
'edgeless-text-editor, edgeless-shape-text-editor, edgeless-group-title-editor, edgeless-frame-title-editor, edgeless-connector-label-editor',
|
||||||
|
convertTriggerKey: true,
|
||||||
|
getMenus,
|
||||||
|
mobile: {
|
||||||
|
useScreenHeight: false,
|
||||||
|
scrollContainer: getViewportElement(this.std.host) ?? window,
|
||||||
|
scrollTopOffset: 46,
|
||||||
|
},
|
||||||
|
...this.std.getConfig('affine:page')?.linkedWidget,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
override connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
|
||||||
|
this._watchInput();
|
||||||
|
this._watchViewportChange();
|
||||||
|
}
|
||||||
|
|
||||||
show(props?: {
|
show(props?: {
|
||||||
inlineEditor?: InlineEditor;
|
inlineEditor?: InlineEditor;
|
||||||
primaryTriggerKey?: string;
|
primaryTriggerKey?: string;
|
||||||
@@ -229,14 +275,7 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const disposable = inlineEditor.slots.renderComplete.on(() => {
|
const disposable = inlineEditor.slots.renderComplete.on(() => {
|
||||||
const currentInlineRange = inlineEditor.getInlineRange();
|
this._updateInputRects();
|
||||||
if (!currentInlineRange) return;
|
|
||||||
const range = inlineEditor.toDomRange({
|
|
||||||
index: inlineRange.index,
|
|
||||||
length: currentInlineRange.index - inlineRange.index,
|
|
||||||
});
|
|
||||||
if (!range) return;
|
|
||||||
this._inputRects = getRangeRects(range, getViewportElement(host));
|
|
||||||
});
|
});
|
||||||
this._context = {
|
this._context = {
|
||||||
std: this.std,
|
std: this.std,
|
||||||
@@ -246,12 +285,14 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
|||||||
config: this.config,
|
config: this.config,
|
||||||
close: () => {
|
close: () => {
|
||||||
disposable.dispose();
|
disposable.dispose();
|
||||||
this._inputRects = [];
|
this._inputRects$.value = [];
|
||||||
this._mode$.value = 'none';
|
this._mode$.value = 'none';
|
||||||
this._context = null;
|
this._context = null;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._updateInputRects();
|
||||||
|
|
||||||
const enableMobile = this.doc
|
const enableMobile = this.doc
|
||||||
.get(FeatureFlagService)
|
.get(FeatureFlagService)
|
||||||
.getFlag('enable_mobile_linked_doc_menu');
|
.getFlag('enable_mobile_linked_doc_menu');
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
getViewportElement,
|
getViewportElement,
|
||||||
} from '@blocksuite/affine-shared/utils';
|
} from '@blocksuite/affine-shared/utils';
|
||||||
import { PropTypes, requiredProperties } from '@blocksuite/block-std';
|
import { PropTypes, requiredProperties } from '@blocksuite/block-std';
|
||||||
|
import { GfxController } from '@blocksuite/block-std/gfx';
|
||||||
import {
|
import {
|
||||||
SignalWatcher,
|
SignalWatcher,
|
||||||
throttle,
|
throttle,
|
||||||
@@ -317,18 +318,13 @@ export class LinkedDocPopover extends SignalWatcher(
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition(position: { height: number; x: string; y: string }) {
|
|
||||||
this._position = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
override willUpdate() {
|
override willUpdate() {
|
||||||
if (!this.hasUpdated) {
|
if (!this.hasUpdated) {
|
||||||
const curRange = getCurrentNativeRange();
|
const curRange = getCurrentNativeRange();
|
||||||
if (!curRange) return;
|
if (!curRange) return;
|
||||||
|
|
||||||
const updatePosition = throttle(() => {
|
const updatePosition = throttle(() => {
|
||||||
const position = getPopperPosition(this, curRange);
|
this._position = getPopperPosition(this, curRange);
|
||||||
this.updatePosition(position);
|
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
this.disposables.addFromEvent(window, 'resize', updatePosition);
|
this.disposables.addFromEvent(window, 'resize', updatePosition);
|
||||||
@@ -344,6 +340,12 @@ export class LinkedDocPopover extends SignalWatcher(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const gfx = this.context.std.getOptional(GfxController);
|
||||||
|
if (gfx) {
|
||||||
|
this.disposables.add(gfx.viewport.viewportUpdated.on(updatePosition));
|
||||||
|
}
|
||||||
|
|
||||||
updatePosition();
|
updatePosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,16 @@ import {
|
|||||||
type AffineInlineEditor,
|
type AffineInlineEditor,
|
||||||
getInlineEditorByModel,
|
getInlineEditorByModel,
|
||||||
} from '@blocksuite/affine-components/rich-text';
|
} from '@blocksuite/affine-components/rich-text';
|
||||||
import { getCurrentNativeRange } from '@blocksuite/affine-shared/utils';
|
|
||||||
import type { UIEventStateContext } from '@blocksuite/block-std';
|
import type { UIEventStateContext } from '@blocksuite/block-std';
|
||||||
import { TextSelection, WidgetComponent } from '@blocksuite/block-std';
|
import { TextSelection, WidgetComponent } from '@blocksuite/block-std';
|
||||||
import {
|
import {
|
||||||
assertExists,
|
|
||||||
assertType,
|
assertType,
|
||||||
debounce,
|
debounce,
|
||||||
DisposableGroup,
|
DisposableGroup,
|
||||||
throttle,
|
|
||||||
} from '@blocksuite/global/utils';
|
} from '@blocksuite/global/utils';
|
||||||
import { InlineEditor } from '@blocksuite/inline';
|
import { InlineEditor } from '@blocksuite/inline';
|
||||||
|
|
||||||
import type { RootBlockComponent } from '../../types.js';
|
import type { RootBlockComponent } from '../../types.js';
|
||||||
import { getPopperPosition } from '../../utils/position.js';
|
|
||||||
import {
|
import {
|
||||||
defaultSlashMenuConfig,
|
defaultSlashMenuConfig,
|
||||||
type SlashMenuActionItem,
|
type SlashMenuActionItem,
|
||||||
@@ -56,9 +52,6 @@ const showSlashMenu = debounce(
|
|||||||
config: SlashMenuStaticConfig;
|
config: SlashMenuStaticConfig;
|
||||||
triggerKey: string;
|
triggerKey: string;
|
||||||
}) => {
|
}) => {
|
||||||
const curRange = getCurrentNativeRange();
|
|
||||||
if (!curRange) return;
|
|
||||||
|
|
||||||
globalAbortController = abortController;
|
globalAbortController = abortController;
|
||||||
const disposables = new DisposableGroup();
|
const disposables = new DisposableGroup();
|
||||||
abortController.signal.addEventListener('abort', () =>
|
abortController.signal.addEventListener('abort', () =>
|
||||||
@@ -76,25 +69,10 @@ const showSlashMenu = debounce(
|
|||||||
slashMenu.config = config;
|
slashMenu.config = config;
|
||||||
slashMenu.triggerKey = triggerKey;
|
slashMenu.triggerKey = triggerKey;
|
||||||
|
|
||||||
// Handle position
|
|
||||||
const updatePosition = throttle(() => {
|
|
||||||
const slashMenuElement = slashMenu.slashMenuElement;
|
|
||||||
assertExists(
|
|
||||||
slashMenuElement,
|
|
||||||
'You should render the slash menu node even if no position'
|
|
||||||
);
|
|
||||||
const position = getPopperPosition(slashMenuElement, curRange);
|
|
||||||
slashMenu.updatePosition(position);
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
disposables.addFromEvent(window, 'resize', updatePosition);
|
|
||||||
|
|
||||||
// FIXME(Flrande): It is not a best practice,
|
// FIXME(Flrande): It is not a best practice,
|
||||||
// but merely a temporary measure for reusing previous components.
|
// but merely a temporary measure for reusing previous components.
|
||||||
// Mount
|
// Mount
|
||||||
container.append(slashMenu);
|
container.append(slashMenu);
|
||||||
// Wait for the Node to be mounted
|
|
||||||
setTimeout(updatePosition);
|
|
||||||
return slashMenu;
|
return slashMenu;
|
||||||
},
|
},
|
||||||
100
|
100
|
||||||
|
|||||||
@@ -8,17 +8,23 @@ import {
|
|||||||
} from '@blocksuite/affine-components/rich-text';
|
} from '@blocksuite/affine-components/rich-text';
|
||||||
import {
|
import {
|
||||||
createKeydownObserver,
|
createKeydownObserver,
|
||||||
|
getCurrentNativeRange,
|
||||||
isControlledKeyboardEvent,
|
isControlledKeyboardEvent,
|
||||||
isFuzzyMatch,
|
isFuzzyMatch,
|
||||||
substringMatchScore,
|
substringMatchScore,
|
||||||
} from '@blocksuite/affine-shared/utils';
|
} from '@blocksuite/affine-shared/utils';
|
||||||
import { assertExists, WithDisposable } from '@blocksuite/global/utils';
|
import {
|
||||||
|
assertExists,
|
||||||
|
throttle,
|
||||||
|
WithDisposable,
|
||||||
|
} from '@blocksuite/global/utils';
|
||||||
import { autoPlacement, offset } from '@floating-ui/dom';
|
import { autoPlacement, offset } from '@floating-ui/dom';
|
||||||
import { html, LitElement, nothing, type PropertyValues } from 'lit';
|
import { html, LitElement, nothing, type PropertyValues } from 'lit';
|
||||||
import { property, query, state } from 'lit/decorators.js';
|
import { property, state } from 'lit/decorators.js';
|
||||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||||
import { styleMap } from 'lit/directives/style-map.js';
|
import { styleMap } from 'lit/directives/style-map.js';
|
||||||
|
|
||||||
|
import { getPopperPosition } from '../../utils/position.js';
|
||||||
import type {
|
import type {
|
||||||
SlashMenuActionItem,
|
SlashMenuActionItem,
|
||||||
SlashMenuContext,
|
SlashMenuContext,
|
||||||
@@ -139,10 +145,6 @@ export class SlashMenu extends WithDisposable(LitElement) {
|
|||||||
this._queryState = this._filteredItems.length === 0 ? 'no_result' : 'on';
|
this._queryState = this._filteredItems.length === 0 ? 'no_result' : 'on';
|
||||||
};
|
};
|
||||||
|
|
||||||
updatePosition = (position: { x: string; y: string; height: number }) => {
|
|
||||||
this._position = position;
|
|
||||||
};
|
|
||||||
|
|
||||||
private get _query() {
|
private get _query() {
|
||||||
return getTextContentFromInlineRange(this.inlineEditor, this._startRange);
|
return getTextContentFromInlineRange(this.inlineEditor, this._startRange);
|
||||||
}
|
}
|
||||||
@@ -247,6 +249,24 @@ export class SlashMenu extends WithDisposable(LitElement) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override willUpdate() {
|
||||||
|
if (!this.hasUpdated) {
|
||||||
|
const currRage = getCurrentNativeRange();
|
||||||
|
if (!currRage) {
|
||||||
|
this.abortController.abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle position
|
||||||
|
const updatePosition = throttle(() => {
|
||||||
|
this._position = getPopperPosition(this, currRage);
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
this.disposables.addFromEvent(window, 'resize', updatePosition);
|
||||||
|
updatePosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
const slashMenuStyles = this._position
|
const slashMenuStyles = this._position
|
||||||
? {
|
? {
|
||||||
@@ -291,9 +311,6 @@ export class SlashMenu extends WithDisposable(LitElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor context!: SlashMenuContext;
|
accessor context!: SlashMenuContext;
|
||||||
|
|
||||||
@query('inner-slash-menu')
|
|
||||||
accessor slashMenuElement!: HTMLElement;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor triggerKey!: string;
|
accessor triggerKey!: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user