mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
refactor(editor): rewrite resize and rotate (#12054)
### Changed
This pr split the old `edgeless-selected-rect` into four focused modules:
- `edgeless-selected-rect`: Provide an entry point for user operation on view layer only, no further logic here.
- `GfxViewInteractionExtension`: Allow you to plug in custom resize/rotate behaviors for block or canvas element. If you don’t register an extension, it falls back to the default behaviours.
- `InteractivityManager`: Provide the API that accepts resize/rotate requests, invokes any custom behaviors you’ve registered, tracks the lifecycle and intermediate state, then hands off to the math engine.
- `ResizeController`: A pure math engine that listens for pointer moves and pointer ups and calculates new sizes, positions, and angles. It doesn’t call any business APIs.
### Customizing an element’s resize/rotate behavior
Call `GfxViewInteractionExtension` with the element’s flavour or type plus a config object. In the config you can define:
- `resizeConstraint` (min/max width & height, lock ratio)
- `handleResize(context)` method that returns an object containing `beforeResize`、`onResizeStart`、`onResizeMove`、`onResizeEnd`
- `handleRotate(context)` method that returns an object containing `beforeRotate`、`onRotateStart`、`onRotateMove`、`onRotateEnd`
```typescript
import { GfxViewInteractionExtension } from '@blocksuite/std/gfx';
GfxViewInteractionExtension(
flavourOrElementType,
{
resizeConstraint: {
minWidth,
maxWidth,
lockRatio,
minHeight,
maxHeight
},
handleResize(context) {
return {
beforeResize(context) {},
onResizeStart(context) {},
onResizeMove(context) {},
onResizeEnd(context) {}
};
},
handleRotate(context) {
return {
beforeRotate(context) {},
onRotateStart(context) {},
onRotateMove(context) {},
onRotateEnd(context) {}
};
}
}
);
```
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit
- **New Features**
- Added interaction extensions for edgeless variants of attachment, bookmark, edgeless text, embedded docs, images, notes, frames, AI chat blocks, and various embed blocks (Figma, GitHub, HTML, iframe, Loom, YouTube).
- Introduced interaction extensions for graphical elements including connectors, groups, mind maps, shapes, and text, supporting constrained resizing and rotation disabling where applicable.
- Implemented a unified interaction extension framework enabling configurable resize and rotate lifecycle handlers.
- Enhanced autocomplete overlay behavior based on selection context.
- **Refactor**
- Removed legacy resize manager and element-specific resize/rotate logic, replacing with a centralized, extensible interaction system.
- Simplified resize handle rendering to a data-driven approach with improved cursor management.
- Replaced complex cursor rotation calculations with fixed-angle mappings for resize handles.
- Streamlined selection rectangle component to use interactivity services for resize and rotate handling.
- **Bug Fixes**
- Fixed connector update triggers to reduce unnecessary updates.
- Improved resize constraints enforcement and interaction state tracking.
- **Tests**
- Refined end-to-end tests to use higher-level resize utilities and added finer-grained assertions on element dimensions.
- Enhanced mouse movement granularity in drag tests for better simulation fidelity.
- **Chores**
- Added new workspace dependencies and project references for the interaction framework modules.
- Extended public API exports to include new interaction types and extensions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,18 +1,33 @@
|
||||
import { type ServiceIdentifier } from '@blocksuite/global/di';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import { Bound, Point } from '@blocksuite/global/gfx';
|
||||
import { Bound, clamp, Point } from '@blocksuite/global/gfx';
|
||||
import { signal } from '@preact/signals-core';
|
||||
|
||||
import type { PointerEventState } from '../../event/state/pointer.js';
|
||||
import { getTopElements } from '../../utils/tree.js';
|
||||
import type { GfxBlockComponent } from '../../view/index.js';
|
||||
import { GfxExtension, GfxExtensionIdentifier } from '../extension.js';
|
||||
import { GfxBlockElementModel } from '../model/gfx-block-model.js';
|
||||
import type { GfxModel } from '../model/model.js';
|
||||
import type { GfxElementModelView } from '../view/view.js';
|
||||
import { createInteractionContext, type SupportedEvents } from './event.js';
|
||||
import {
|
||||
type InteractivityActionAPI,
|
||||
type InteractivityEventAPI,
|
||||
InteractivityExtensionIdentifier,
|
||||
} from './extension/base.js';
|
||||
import {
|
||||
type GfxViewInteractionConfig,
|
||||
GfxViewInteractionIdentifier,
|
||||
} from './extension/view.js';
|
||||
import { GfxViewEventManager } from './gfx-view-event-handler.js';
|
||||
import {
|
||||
DEFAULT_HANDLES,
|
||||
type OptionResize,
|
||||
ResizeController,
|
||||
type ResizeHandle,
|
||||
type RotateOption,
|
||||
} from './resize/manager.js';
|
||||
import type { RequestElementsCloneContext } from './types/clone.js';
|
||||
import type {
|
||||
DragExtensionInitializeContext,
|
||||
@@ -21,7 +36,11 @@ import type {
|
||||
ExtensionDragMoveContext,
|
||||
ExtensionDragStartContext,
|
||||
} from './types/drag.js';
|
||||
import type { BoxSelectionContext } from './types/view.js';
|
||||
import type {
|
||||
BoxSelectionContext,
|
||||
ResizeConstraint,
|
||||
RotateConstraint,
|
||||
} from './types/view.js';
|
||||
|
||||
type ExtensionPointerHandler = Exclude<
|
||||
SupportedEvents,
|
||||
@@ -46,6 +65,11 @@ export class InteractivityManager extends GfxExtension {
|
||||
});
|
||||
}
|
||||
|
||||
activeInteraction$ = signal<null | {
|
||||
type: 'move' | 'resize' | 'rotate';
|
||||
elements: GfxModel[];
|
||||
} | null>(null);
|
||||
|
||||
override unmounted(): void {
|
||||
this._disposable.dispose();
|
||||
this.interactExtensions.forEach(ext => {
|
||||
@@ -76,6 +100,10 @@ export class InteractivityManager extends GfxExtension {
|
||||
* @returns
|
||||
*/
|
||||
dispatchEvent(eventName: ExtensionPointerHandler, evt: PointerEventState) {
|
||||
if (this.activeInteraction$.peek()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { context, preventDefaultState } = createInteractionContext(evt);
|
||||
const extensions = this.interactExtensions;
|
||||
|
||||
@@ -247,6 +275,8 @@ export class InteractivityManager extends GfxExtension {
|
||||
});
|
||||
};
|
||||
const onDragEnd = (event: PointerEvent) => {
|
||||
this.activeInteraction$.value = null;
|
||||
|
||||
host.removeEventListener('pointermove', onDragMove, false);
|
||||
host.removeEventListener('pointerup', onDragEnd, false);
|
||||
viewportWatcher.unsubscribe();
|
||||
@@ -292,6 +322,11 @@ export class InteractivityManager extends GfxExtension {
|
||||
host.addEventListener('pointerup', onDragEnd, false);
|
||||
};
|
||||
const dragStart = () => {
|
||||
this.activeInteraction$.value = {
|
||||
type: 'move',
|
||||
elements: context.elements,
|
||||
};
|
||||
|
||||
internal.elements.forEach(({ view, originalBound }) => {
|
||||
view.onDragStart({
|
||||
currentBound: originalBound,
|
||||
@@ -316,6 +351,519 @@ export class InteractivityManager extends GfxExtension {
|
||||
dragStart();
|
||||
}
|
||||
|
||||
handleElementRotate(
|
||||
options: Omit<
|
||||
RotateOption,
|
||||
'onRotateStart' | 'onRotateEnd' | 'onRotateUpdate'
|
||||
> & {
|
||||
onRotateUpdate?: (payload: {
|
||||
currentAngle: number;
|
||||
delta: number;
|
||||
}) => void;
|
||||
onRotateStart?: () => void;
|
||||
onRotateEnd?: () => void;
|
||||
}
|
||||
) {
|
||||
const { rotatable, viewConfigMap, initialRotate } =
|
||||
this._getViewRotateConfig(options.elements);
|
||||
|
||||
if (!rotatable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = new ResizeController({ gfx: this.gfx });
|
||||
const elements = Array.from(viewConfigMap.values()).map(
|
||||
config => config.view.model
|
||||
) as GfxModel[];
|
||||
|
||||
handler.startRotate({
|
||||
...options,
|
||||
elements,
|
||||
onRotateStart: payload => {
|
||||
this.activeInteraction$.value = {
|
||||
type: 'rotate',
|
||||
elements,
|
||||
};
|
||||
options.onRotateStart?.();
|
||||
payload.data.forEach(({ model }) => {
|
||||
if (!viewConfigMap.has(model.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { handlers, defaultHandlers, view, constraint } =
|
||||
viewConfigMap.get(model.id)!;
|
||||
|
||||
handlers.onRotateStart({
|
||||
default: defaultHandlers.onRotateStart as () => void,
|
||||
constraint,
|
||||
model,
|
||||
view,
|
||||
});
|
||||
});
|
||||
},
|
||||
onRotateUpdate: payload => {
|
||||
options.onRotateUpdate?.({
|
||||
currentAngle: initialRotate + payload.delta,
|
||||
delta: payload.delta,
|
||||
});
|
||||
payload.data.forEach(
|
||||
({
|
||||
model,
|
||||
newBound,
|
||||
originalBound,
|
||||
newRotate,
|
||||
originalRotate,
|
||||
matrix,
|
||||
}) => {
|
||||
if (!viewConfigMap.has(model.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { handlers, defaultHandlers, view, constraint } =
|
||||
viewConfigMap.get(model.id)!;
|
||||
|
||||
handlers.onRotateMove({
|
||||
model,
|
||||
newBound,
|
||||
originalBound,
|
||||
newRotate,
|
||||
originalRotate,
|
||||
default: defaultHandlers.onRotateMove as () => void,
|
||||
constraint,
|
||||
view,
|
||||
matrix,
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
onRotateEnd: payload => {
|
||||
this.activeInteraction$.value = null;
|
||||
options.onRotateEnd?.();
|
||||
this.std.store.transact(() => {
|
||||
payload.data.forEach(({ model }) => {
|
||||
if (!viewConfigMap.has(model.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { handlers, defaultHandlers, view, constraint } =
|
||||
viewConfigMap.get(model.id)!;
|
||||
|
||||
handlers.onRotateEnd({
|
||||
default: defaultHandlers.onRotateEnd as () => void,
|
||||
view,
|
||||
model,
|
||||
constraint,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _getViewRotateConfig(elements: GfxModel[]) {
|
||||
const deleted = new Set<GfxModel>();
|
||||
const added = new Set<GfxModel>();
|
||||
const del = (model: GfxModel) => {
|
||||
deleted.add(model);
|
||||
};
|
||||
const add = (model: GfxModel) => {
|
||||
added.add(model);
|
||||
};
|
||||
|
||||
type ViewRotateHandlers = Required<
|
||||
ReturnType<Required<GfxViewInteractionConfig>['handleRotate']>
|
||||
>;
|
||||
|
||||
const viewConfigMap = new Map<
|
||||
string,
|
||||
{
|
||||
model: GfxModel;
|
||||
view: GfxElementModelView | GfxBlockComponent;
|
||||
handlers: ViewRotateHandlers;
|
||||
defaultHandlers: ViewRotateHandlers;
|
||||
constraint: Required<RotateConstraint>;
|
||||
}
|
||||
>();
|
||||
|
||||
const addToConfigMap = (model: GfxModel) => {
|
||||
const flavourOrType = 'type' in model ? model.type : model.flavour;
|
||||
const interactionConfig = this.std.getOptional(
|
||||
GfxViewInteractionIdentifier(flavourOrType)
|
||||
);
|
||||
const view = this.gfx.view.get(model);
|
||||
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultHandlers: ViewRotateHandlers = {
|
||||
beforeRotate: () => {},
|
||||
onRotateStart: context => {
|
||||
if (!context.constraint.rotatable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (model instanceof GfxBlockElementModel) {
|
||||
if (Object.hasOwn(model.props, 'rotate')) {
|
||||
// @ts-expect-error prop existence has been checked
|
||||
model.stash('rotate');
|
||||
model.stash('xywh');
|
||||
}
|
||||
} else {
|
||||
model.stash('rotate');
|
||||
model.stash('xywh');
|
||||
}
|
||||
},
|
||||
onRotateEnd: context => {
|
||||
if (!context.constraint.rotatable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (model instanceof GfxBlockElementModel) {
|
||||
if (Object.hasOwn(model.props, 'rotate')) {
|
||||
// @ts-expect-error prop existence has been checked
|
||||
model.pop('rotate');
|
||||
model.pop('xywh');
|
||||
}
|
||||
} else {
|
||||
model.pop('rotate');
|
||||
model.pop('xywh');
|
||||
}
|
||||
},
|
||||
onRotateMove: context => {
|
||||
if (!context.constraint.rotatable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { newBound, newRotate } = context;
|
||||
model.rotate = newRotate;
|
||||
model.xywh = newBound.serialize();
|
||||
},
|
||||
};
|
||||
const handlers = interactionConfig?.handleRotate?.({
|
||||
std: this.std,
|
||||
gfx: this.gfx,
|
||||
view,
|
||||
model,
|
||||
delete: del,
|
||||
add,
|
||||
});
|
||||
|
||||
viewConfigMap.set(model.id, {
|
||||
model,
|
||||
view,
|
||||
defaultHandlers,
|
||||
handlers: Object.assign({}, defaultHandlers, handlers ?? {}),
|
||||
constraint: {
|
||||
rotatable: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
elements.forEach(addToConfigMap);
|
||||
|
||||
deleted.forEach(model => {
|
||||
if (viewConfigMap.has(model.id)) {
|
||||
viewConfigMap.delete(model.id);
|
||||
}
|
||||
});
|
||||
|
||||
added.forEach(model => {
|
||||
if (viewConfigMap.has(model.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
addToConfigMap(model);
|
||||
});
|
||||
|
||||
const views = Array.from(viewConfigMap.values().map(item => item.view));
|
||||
|
||||
let rotatable = true;
|
||||
viewConfigMap.forEach(config => {
|
||||
const handlers = config.handlers;
|
||||
|
||||
handlers.beforeRotate({
|
||||
set: (newConstraint: RotateConstraint) => {
|
||||
Object.assign(config.constraint, newConstraint);
|
||||
rotatable = rotatable && config.constraint.rotatable;
|
||||
},
|
||||
elements: views,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
initialRotate: views.length > 1 ? 0 : (views[0]?.model.rotate ?? 0),
|
||||
rotatable,
|
||||
viewConfigMap,
|
||||
};
|
||||
}
|
||||
|
||||
private _getViewResizeConfig(elements: GfxModel[]) {
|
||||
const deleted = new Set<GfxModel>();
|
||||
const added = new Set<GfxModel>();
|
||||
const del = (model: GfxModel) => {
|
||||
deleted.add(model);
|
||||
};
|
||||
const add = (model: GfxModel) => {
|
||||
added.add(model);
|
||||
};
|
||||
|
||||
type ViewResizeHandlers = Required<
|
||||
ReturnType<Required<GfxViewInteractionConfig>['handleResize']>
|
||||
>;
|
||||
|
||||
const viewConfigMap = new Map<
|
||||
string,
|
||||
{
|
||||
model: GfxModel;
|
||||
view: GfxElementModelView | GfxBlockComponent;
|
||||
constraint: Required<ResizeConstraint>;
|
||||
handlers: ViewResizeHandlers;
|
||||
defaultHandlers: ViewResizeHandlers;
|
||||
}
|
||||
>();
|
||||
const addToConfigMap = (model: GfxModel) => {
|
||||
const flavourOrType = 'type' in model ? model.type : model.flavour;
|
||||
const interactionConfig = this.std.getOptional(
|
||||
GfxViewInteractionIdentifier(flavourOrType)
|
||||
);
|
||||
const view = this.gfx.view.get(model);
|
||||
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultHandlers: ViewResizeHandlers = {
|
||||
beforeResize: () => {},
|
||||
onResizeStart: () => {
|
||||
model.stash('xywh');
|
||||
},
|
||||
onResizeEnd: () => {
|
||||
model.pop('xywh');
|
||||
},
|
||||
onResizeMove: context => {
|
||||
const { newBound, constraint } = context;
|
||||
const { minWidth, minHeight, maxWidth, maxHeight } = constraint;
|
||||
|
||||
newBound.w = clamp(newBound.w, minWidth, maxWidth);
|
||||
newBound.h = clamp(newBound.h, minHeight, maxHeight);
|
||||
|
||||
model.xywh = newBound.serialize();
|
||||
},
|
||||
};
|
||||
const handlers = interactionConfig?.handleResize?.({
|
||||
std: this.std,
|
||||
gfx: this.gfx,
|
||||
view,
|
||||
model,
|
||||
delete: del,
|
||||
add,
|
||||
});
|
||||
|
||||
viewConfigMap.set(model.id, {
|
||||
model,
|
||||
view,
|
||||
constraint: {
|
||||
lockRatio: false,
|
||||
allowedHandlers: DEFAULT_HANDLES,
|
||||
minHeight: 2,
|
||||
minWidth: 2,
|
||||
maxHeight: 5000000,
|
||||
maxWidth: 5000000,
|
||||
...interactionConfig?.resizeConstraint,
|
||||
},
|
||||
defaultHandlers,
|
||||
handlers: Object.assign({}, defaultHandlers, handlers ?? {}),
|
||||
});
|
||||
};
|
||||
|
||||
elements.forEach(addToConfigMap);
|
||||
|
||||
deleted.forEach(model => {
|
||||
if (viewConfigMap.has(model.id)) {
|
||||
viewConfigMap.delete(model.id);
|
||||
}
|
||||
});
|
||||
|
||||
added.forEach(model => {
|
||||
if (viewConfigMap.has(model.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
addToConfigMap(model);
|
||||
});
|
||||
|
||||
const views = Array.from(viewConfigMap.values().map(item => item.view));
|
||||
let allowedHandlers = new Set(DEFAULT_HANDLES);
|
||||
|
||||
viewConfigMap.forEach(config => {
|
||||
const currConstraint: Required<ResizeConstraint> = config.constraint;
|
||||
|
||||
config.handlers.beforeResize({
|
||||
set: (newConstraint: ResizeConstraint) => {
|
||||
Object.assign(currConstraint, newConstraint);
|
||||
},
|
||||
elements: views,
|
||||
});
|
||||
|
||||
config.constraint = currConstraint;
|
||||
|
||||
const currentAllowedHandlers = new Set(currConstraint.allowedHandlers);
|
||||
allowedHandlers.forEach(h => {
|
||||
if (!currentAllowedHandlers.has(h)) {
|
||||
allowedHandlers.delete(h);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
allowedHandlers: Array.from(allowedHandlers) as ResizeHandle[],
|
||||
viewConfigMap,
|
||||
};
|
||||
}
|
||||
|
||||
getRotateConfig(options: { elements: GfxModel[] }) {
|
||||
return this._getViewRotateConfig(options.elements);
|
||||
}
|
||||
|
||||
getResizeHandlers(options: { elements: GfxModel[] }) {
|
||||
return this._getViewResizeConfig(options.elements).allowedHandlers;
|
||||
}
|
||||
|
||||
handleElementResize(
|
||||
options: Omit<
|
||||
OptionResize,
|
||||
'lockRatio' | 'onResizeStart' | 'onResizeEnd' | 'onResizeUpdate'
|
||||
> & {
|
||||
onResizeStart?: () => void;
|
||||
onResizeEnd?: () => void;
|
||||
onResizeUpdate?: (payload: {
|
||||
lockRatio: boolean;
|
||||
scaleX: number;
|
||||
scaleY: number;
|
||||
exceed: {
|
||||
w: boolean;
|
||||
h: boolean;
|
||||
};
|
||||
}) => void;
|
||||
}
|
||||
) {
|
||||
const { viewConfigMap, allowedHandlers } = this._getViewResizeConfig(
|
||||
options.elements
|
||||
);
|
||||
|
||||
if (!allowedHandlers.includes(options.handle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { handle } = options;
|
||||
const controller = new ResizeController({ gfx: this.gfx });
|
||||
const elements = Array.from(viewConfigMap.values()).map(
|
||||
config => config.view.model
|
||||
) as GfxModel[];
|
||||
let lockRatio = false;
|
||||
|
||||
viewConfigMap.forEach(config => {
|
||||
const { lockRatio: lockRatioConfig } = config.constraint;
|
||||
|
||||
lockRatio =
|
||||
lockRatio ||
|
||||
lockRatioConfig === true ||
|
||||
(Array.isArray(lockRatioConfig) && lockRatioConfig.includes(handle));
|
||||
});
|
||||
|
||||
controller.startResize({
|
||||
...options,
|
||||
lockRatio,
|
||||
elements,
|
||||
onResizeStart: ({ data }) => {
|
||||
this.activeInteraction$.value = {
|
||||
type: 'resize',
|
||||
elements,
|
||||
};
|
||||
options.onResizeStart?.();
|
||||
data.forEach(({ model }) => {
|
||||
if (!viewConfigMap.has(model.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { handlers, defaultHandlers, view, constraint } =
|
||||
viewConfigMap.get(model.id)!;
|
||||
|
||||
handlers.onResizeStart({
|
||||
handle,
|
||||
default: defaultHandlers.onResizeStart as () => void,
|
||||
constraint,
|
||||
model,
|
||||
view,
|
||||
});
|
||||
});
|
||||
},
|
||||
onResizeUpdate: ({ data, scaleX, scaleY, lockRatio }) => {
|
||||
const exceed = {
|
||||
w: false,
|
||||
h: false,
|
||||
};
|
||||
|
||||
data.forEach(
|
||||
({ model, newBound, originalBound, lockRatio, matrix }) => {
|
||||
if (!viewConfigMap.has(model.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { handlers, defaultHandlers, view, constraint } =
|
||||
viewConfigMap.get(model.id)!;
|
||||
|
||||
handlers.onResizeMove({
|
||||
model,
|
||||
newBound,
|
||||
originalBound,
|
||||
handle,
|
||||
default: defaultHandlers.onResizeMove as () => void,
|
||||
constraint,
|
||||
view,
|
||||
lockRatio,
|
||||
matrix,
|
||||
});
|
||||
|
||||
exceed.w =
|
||||
exceed.w ||
|
||||
model.w === constraint.minWidth ||
|
||||
model.w === constraint.maxWidth;
|
||||
exceed.h =
|
||||
exceed.h ||
|
||||
model.h === constraint.minHeight ||
|
||||
model.h === constraint.maxHeight;
|
||||
}
|
||||
);
|
||||
|
||||
options.onResizeUpdate?.({ scaleX, scaleY, lockRatio, exceed });
|
||||
},
|
||||
onResizeEnd: ({ data }) => {
|
||||
this.activeInteraction$.value = null;
|
||||
options.onResizeEnd?.();
|
||||
this.std.store.transact(() => {
|
||||
data.forEach(({ model }) => {
|
||||
if (!viewConfigMap.has(model.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { handlers, defaultHandlers, view, constraint } =
|
||||
viewConfigMap.get(model.id)!;
|
||||
|
||||
handlers.onResizeEnd({
|
||||
default: defaultHandlers.onResizeEnd as () => void,
|
||||
view,
|
||||
model,
|
||||
constraint,
|
||||
handle,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
requestElementClone(options: RequestElementsCloneContext) {
|
||||
const extensions = this.interactExtensions;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user