fix: avoid unnecessary rerendering of selected-rect (#12583)
### Changed - Note scale issue - Overlay should call refresh when `clear` is called - Optimize edgeless-selected-rect to avoid unecessary rerendering <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Edgeless note blocks now respect both minimum and maximum size limits when resizing. - **Improvements** - Enhanced performance and responsiveness of resize and rotate handles in selection overlays by caching allowed handles and optimizing cursor management. - Cursor styles for resize and rotate handles are now set more reliably and efficiently through declarative styling. - **Bug Fixes** - Ensured overlay clearing now properly refreshes the renderer for more consistent visual updates. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -400,7 +400,7 @@ export const EdgelessNoteInteraction =
|
|||||||
|
|
||||||
onResizeMove(context): void {
|
onResizeMove(context): void {
|
||||||
const { originalBound, newBound, lockRatio, constraint } = context;
|
const { originalBound, newBound, lockRatio, constraint } = context;
|
||||||
const { minWidth, minHeight } = constraint;
|
const { minWidth, minHeight, maxHeight, maxWidth } = constraint;
|
||||||
|
|
||||||
let scale = initialScale;
|
let scale = initialScale;
|
||||||
let edgelessProp = { ...model.props.edgeless };
|
let edgelessProp = { ...model.props.edgeless };
|
||||||
@@ -411,8 +411,8 @@ export const EdgelessNoteInteraction =
|
|||||||
edgelessProp.scale = scale;
|
edgelessProp.scale = scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
newBound.w = clamp(newBound.w, minWidth, Number.MAX_SAFE_INTEGER);
|
newBound.w = clamp(newBound.w, minWidth * scale, maxWidth);
|
||||||
newBound.h = clamp(newBound.h, minHeight, Number.MAX_SAFE_INTEGER);
|
newBound.h = clamp(newBound.h, minHeight * scale, maxHeight);
|
||||||
|
|
||||||
if (newBound.h > minHeight * scale) {
|
if (newBound.h > minHeight * scale) {
|
||||||
edgelessProp.collapse = true;
|
edgelessProp.collapse = true;
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ export abstract class Overlay extends Extension {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {}
|
clear() {
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
dispose() {}
|
dispose() {}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import { state } from 'lit/decorators.js';
|
|||||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
import { ifDefined } from 'lit/directives/if-defined.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';
|
||||||
import { type Subscription } from 'rxjs';
|
|
||||||
|
|
||||||
import { RenderResizeHandles } from './resize-handles.js';
|
import { RenderResizeHandles } from './resize-handles.js';
|
||||||
import { generateCursorUrl, getRotatedResizeCursor } from './utils.js';
|
import { generateCursorUrl, getRotatedResizeCursor } from './utils.js';
|
||||||
@@ -359,21 +358,6 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
private readonly _initSelectedSlot = () => {
|
|
||||||
this._propDisposables.forEach(disposable => disposable.unsubscribe());
|
|
||||||
this._propDisposables = [];
|
|
||||||
|
|
||||||
this.selection.selectedElements.forEach(element => {
|
|
||||||
if ('flavour' in element) {
|
|
||||||
this._propDisposables.push(
|
|
||||||
element.propsUpdated.subscribe(() => {
|
|
||||||
this._updateOnElementChange(element.id);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly _dragEndCleanup = () => {
|
private readonly _dragEndCleanup = () => {
|
||||||
this._isWidthLimit = false;
|
this._isWidthLimit = false;
|
||||||
this._isHeightLimit = false;
|
this._isHeightLimit = false;
|
||||||
@@ -386,16 +370,15 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
|||||||
this.frameOverlay.clear();
|
this.frameOverlay.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
private _propDisposables: Subscription[] = [];
|
|
||||||
|
|
||||||
private readonly _updateCursor = (options?: {
|
private readonly _updateCursor = (options?: {
|
||||||
type: 'resize' | 'rotate';
|
type: 'resize' | 'rotate';
|
||||||
angle: number;
|
angle: number;
|
||||||
handle: ResizeHandle;
|
handle: ResizeHandle;
|
||||||
|
pure?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
if (!options) {
|
if (!options) {
|
||||||
!this._isResizing && (this.gfx.cursor$.value = 'default');
|
!this._isResizing && (this.gfx.cursor$.value = 'default');
|
||||||
return;
|
return 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
const { type, angle, handle } = options;
|
const { type, angle, handle } = options;
|
||||||
@@ -410,7 +393,11 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.pure !== true) {
|
||||||
this.gfx.cursor$.value = cursor;
|
this.gfx.cursor$.value = cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursor;
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _updateOnElementChange = (
|
private readonly _updateOnElementChange = (
|
||||||
@@ -423,8 +410,30 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly _updateHandles = () => {
|
||||||
|
const interaction = this._interaction;
|
||||||
|
const { store, selection } = this;
|
||||||
|
const elements = selection.selectedElements;
|
||||||
|
|
||||||
|
if (interaction && !selection.editing && !store.readonly) {
|
||||||
|
const resizeHandles = interaction.getResizeHandlers({
|
||||||
|
elements,
|
||||||
|
});
|
||||||
|
const { rotatable } = interaction.getRotateConfig({
|
||||||
|
elements,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._allowedHandles = {
|
||||||
|
rotatable,
|
||||||
|
resizeHandles,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this._allowedHandles = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private readonly _updateOnSelectionChange = () => {
|
private readonly _updateOnSelectionChange = () => {
|
||||||
this._initSelectedSlot();
|
this._updateHandles();
|
||||||
this._updateSelectedRect();
|
this._updateSelectedRect();
|
||||||
// Reset the cursor
|
// Reset the cursor
|
||||||
this._updateCursor();
|
this._updateCursor();
|
||||||
@@ -554,10 +563,6 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_disposables.add(() => {
|
|
||||||
this._propDisposables.forEach(disposable => disposable.unsubscribe());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _interaction() {
|
private get _interaction() {
|
||||||
@@ -569,7 +574,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _renderHandles() {
|
private _renderHandles() {
|
||||||
const { selection, gfx, block, store } = this;
|
const { selection, gfx, block } = this;
|
||||||
const elements = selection.selectedElements;
|
const elements = selection.selectedElements;
|
||||||
|
|
||||||
if (selection.inoperable) {
|
if (selection.inoperable) {
|
||||||
@@ -579,23 +584,16 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
|||||||
const handles = [];
|
const handles = [];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
this._allowedHandles &&
|
||||||
this._interaction &&
|
this._interaction &&
|
||||||
!selection.editing &&
|
|
||||||
!store.readonly &&
|
|
||||||
!elements.some(element => element.isLocked())
|
!elements.some(element => element.isLocked())
|
||||||
) {
|
) {
|
||||||
const interaction = this._interaction;
|
const interaction = this._interaction;
|
||||||
const resizeHandlers = interaction.getResizeHandlers({
|
|
||||||
elements,
|
|
||||||
});
|
|
||||||
const { rotatable } = interaction.getRotateConfig({
|
|
||||||
elements,
|
|
||||||
});
|
|
||||||
|
|
||||||
handles.push(
|
handles.push(
|
||||||
RenderResizeHandles(
|
RenderResizeHandles(
|
||||||
resizeHandlers,
|
this._allowedHandles.resizeHandles,
|
||||||
rotatable,
|
this._allowedHandles.rotatable,
|
||||||
(e: PointerEvent, handle: ResizeHandle) => {
|
(e: PointerEvent, handle: ResizeHandle) => {
|
||||||
const isRotate = (e.target as HTMLElement).classList.contains(
|
const isRotate = (e.target as HTMLElement).classList.contains(
|
||||||
'rotate'
|
'rotate'
|
||||||
@@ -603,7 +601,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
|||||||
|
|
||||||
if (isRotate) {
|
if (isRotate) {
|
||||||
interaction.handleElementRotate({
|
interaction.handleElementRotate({
|
||||||
elements: this.selection.selectedElements,
|
elements,
|
||||||
event: e,
|
event: e,
|
||||||
onRotateStart: () => {
|
onRotateStart: () => {
|
||||||
this._mode = 'rotate';
|
this._mode = 'rotate';
|
||||||
@@ -622,7 +620,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
interaction.handleElementResize({
|
interaction.handleElementResize({
|
||||||
elements: this.selection.selectedElements,
|
elements,
|
||||||
handle,
|
handle,
|
||||||
event: e,
|
event: e,
|
||||||
onResizeStart: () => {
|
onResizeStart: () => {
|
||||||
@@ -632,12 +630,19 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
|||||||
if (lockRatio) {
|
if (lockRatio) {
|
||||||
this._scaleDirection = handle;
|
this._scaleDirection = handle;
|
||||||
this._scalePercent = `${Math.round(scaleX * 100)}%`;
|
this._scalePercent = `${Math.round(scaleX * 100)}%`;
|
||||||
|
this._mode = 'scale';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exceed) {
|
if (exceed) {
|
||||||
this._isWidthLimit = exceed.w;
|
this._isWidthLimit = exceed.w;
|
||||||
this._isHeightLimit = exceed.h;
|
this._isHeightLimit = exceed.h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._updateCursor({
|
||||||
|
type: 'resize',
|
||||||
|
angle: elements.length > 1 ? 0 : (elements[0]?.rotate ?? 0),
|
||||||
|
handle,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onResizeEnd: () => {
|
onResizeEnd: () => {
|
||||||
this._mode = 'resize';
|
this._mode = 'resize';
|
||||||
@@ -647,14 +652,11 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
option => {
|
option => {
|
||||||
if (option) {
|
return this._updateCursor({
|
||||||
this._updateCursor({
|
|
||||||
...option,
|
...option,
|
||||||
angle: elements.length > 1 ? 0 : (elements[0]?.rotate ?? 0),
|
angle: elements.length > 1 ? 0 : (elements[0]?.rotate ?? 0),
|
||||||
|
pure: true,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this._updateCursor();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -784,6 +786,12 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private accessor _allowedHandles: {
|
||||||
|
rotatable: boolean;
|
||||||
|
resizeHandles: ResizeHandle[];
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private accessor _isHeightLimit = false;
|
private accessor _isHeightLimit = false;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { ResizeHandle } from '@blocksuite/std/gfx';
|
import type { ResizeHandle } from '@blocksuite/std/gfx';
|
||||||
import { html, nothing } from 'lit';
|
import { html, nothing } from 'lit';
|
||||||
import { repeat } from 'lit/directives/repeat.js';
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
|
import { styleMap } from 'lit/directives/style-map.js';
|
||||||
|
|
||||||
export enum HandleDirection {
|
export enum HandleDirection {
|
||||||
Bottom = 'bottom',
|
Bottom = 'bottom',
|
||||||
@@ -17,36 +18,28 @@ function ResizeHandleRenderer(
|
|||||||
handle: ResizeHandle,
|
handle: ResizeHandle,
|
||||||
rotatable: boolean,
|
rotatable: boolean,
|
||||||
onPointerDown?: (e: PointerEvent, direction: ResizeHandle) => void,
|
onPointerDown?: (e: PointerEvent, direction: ResizeHandle) => void,
|
||||||
updateCursor?: (options?: {
|
getCursor?: (options: {
|
||||||
type: 'resize' | 'rotate';
|
type: 'resize' | 'rotate';
|
||||||
handle: ResizeHandle;
|
handle: ResizeHandle;
|
||||||
}) => void
|
}) => string
|
||||||
) {
|
) {
|
||||||
const handlerPointerDown = (e: PointerEvent) => {
|
const handlerPointerDown = (e: PointerEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onPointerDown && onPointerDown(e, handle);
|
onPointerDown && onPointerDown(e, handle);
|
||||||
};
|
};
|
||||||
|
|
||||||
const pointerEnter = (type: 'resize' | 'rotate') => (e: PointerEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (e.buttons === 1 || !updateCursor) return;
|
|
||||||
|
|
||||||
updateCursor({ type, handle });
|
|
||||||
};
|
|
||||||
|
|
||||||
const pointerLeave = (e: PointerEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (e.buttons === 1 || !updateCursor) return;
|
|
||||||
|
|
||||||
updateCursor();
|
|
||||||
};
|
|
||||||
|
|
||||||
const rotationTpl =
|
const rotationTpl =
|
||||||
handle.length > 6 && rotatable
|
handle.length > 6 && rotatable
|
||||||
? html`<div
|
? html`<div
|
||||||
class="rotate"
|
class="rotate"
|
||||||
@pointerover=${pointerEnter('rotate')}
|
style=${styleMap({
|
||||||
@pointerout=${pointerLeave}
|
cursor: getCursor
|
||||||
|
? getCursor({
|
||||||
|
type: 'rotate',
|
||||||
|
handle,
|
||||||
|
})
|
||||||
|
: 'default',
|
||||||
|
})}
|
||||||
></div>`
|
></div>`
|
||||||
: nothing;
|
: nothing;
|
||||||
|
|
||||||
@@ -58,8 +51,14 @@ function ResizeHandleRenderer(
|
|||||||
${rotationTpl}
|
${rotationTpl}
|
||||||
<div
|
<div
|
||||||
class="resize transparent-handle"
|
class="resize transparent-handle"
|
||||||
@pointerover=${pointerEnter('resize')}
|
style=${styleMap({
|
||||||
@pointerout=${pointerLeave}
|
cursor: getCursor
|
||||||
|
? getCursor({
|
||||||
|
type: 'resize',
|
||||||
|
handle,
|
||||||
|
})
|
||||||
|
: 'default',
|
||||||
|
})}
|
||||||
></div>
|
></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -79,17 +78,17 @@ export function RenderResizeHandles(
|
|||||||
resizeHandles: ResizeHandle[],
|
resizeHandles: ResizeHandle[],
|
||||||
rotatable: boolean,
|
rotatable: boolean,
|
||||||
onPointerDown: (e: PointerEvent, direction: ResizeHandle) => void,
|
onPointerDown: (e: PointerEvent, direction: ResizeHandle) => void,
|
||||||
updateCursor?: (options?: {
|
getCursor?: (options: {
|
||||||
type: 'resize' | 'rotate';
|
type: 'resize' | 'rotate';
|
||||||
handle: ResizeHandle;
|
handle: ResizeHandle;
|
||||||
}) => void
|
}) => string
|
||||||
) {
|
) {
|
||||||
return html`
|
return html`
|
||||||
${repeat(
|
${repeat(
|
||||||
resizeHandles,
|
resizeHandles,
|
||||||
handle => handle,
|
handle => handle,
|
||||||
handle =>
|
handle =>
|
||||||
ResizeHandleRenderer(handle, rotatable, onPointerDown, updateCursor)
|
ResizeHandleRenderer(handle, rotatable, onPointerDown, getCursor)
|
||||||
)}
|
)}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user