feat(editor): impl shape text with dom renderer (#13471)
#### PR Dependency Tree
* **PR #13464**
* **PR #13465**
* **PR #13471** 👈
* **PR #13472**
* **PR #13473**
This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* DOM rendering added for groups, mind maps and connectors so group
titles/outlines and mindmap connectors are visible on canvas.
* Shapes now support right-to-left text with proper vertical alignment.
* **Improvements**
* Connector labels scale with viewport zoom for crisper display.
* Group-related selections (including nested groups) now update visuals
consistently.
* **Performance**
* Reduced DOM churn and fewer redraws during rendering and selection
changes.
* **Refactor**
* Renderer import/export surfaces consolidated with no user-facing
behavior changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
export * from './adapter';
|
||||
export * from './command';
|
||||
export * from './element-renderer';
|
||||
export * from './element-view';
|
||||
export * from './renderer';
|
||||
export * from './text/text';
|
||||
export * from './toolbar/config';
|
||||
|
||||
62
blocksuite/affine/gfx/group/src/renderer/dom-renderer.ts
Normal file
62
blocksuite/affine/gfx/group/src/renderer/dom-renderer.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { DomElementRendererExtension } from '@blocksuite/affine-block-surface';
|
||||
import { FontWeight, type GroupElementModel } from '@blocksuite/affine-model';
|
||||
|
||||
import {
|
||||
GROUP_TITLE_FONT,
|
||||
GROUP_TITLE_FONT_SIZE,
|
||||
GROUP_TITLE_PADDING,
|
||||
} from './consts';
|
||||
import { titleRenderParams } from './utils';
|
||||
|
||||
export const GroupDomRendererExtension = DomElementRendererExtension(
|
||||
'group',
|
||||
(model: GroupElementModel, domElement, renderer) => {
|
||||
const { zoom } = renderer.viewport;
|
||||
const [, , w, h] = model.deserializedXYWH;
|
||||
|
||||
const renderParams = titleRenderParams(model, zoom);
|
||||
model.externalXYWH = renderParams.titleBound.serialize();
|
||||
|
||||
domElement.innerHTML = '';
|
||||
domElement.style.outlineColor = '';
|
||||
domElement.style.outlineWidth = '';
|
||||
domElement.style.outlineStyle = '';
|
||||
|
||||
const elements = renderer.provider.selectedElements?.() || [];
|
||||
|
||||
const renderTitle = () => {
|
||||
const { text } = renderParams;
|
||||
const titleElement = document.createElement('div');
|
||||
titleElement.style.transform = `translate(0, -100%)`;
|
||||
titleElement.style.fontFamily = GROUP_TITLE_FONT;
|
||||
titleElement.style.fontWeight = `${FontWeight.Regular}`;
|
||||
titleElement.style.fontStyle = 'normal';
|
||||
titleElement.style.fontSize = `${GROUP_TITLE_FONT_SIZE}px`;
|
||||
titleElement.style.color = renderer.getPropertyValue('--affine-blue');
|
||||
titleElement.style.textAlign = 'left';
|
||||
titleElement.style.padding = `${GROUP_TITLE_PADDING[0]}px ${GROUP_TITLE_PADDING[1]}px`;
|
||||
titleElement.textContent = text;
|
||||
domElement.replaceChildren(titleElement);
|
||||
};
|
||||
|
||||
if (elements.includes(model.id)) {
|
||||
if (model.showTitle) {
|
||||
renderTitle();
|
||||
} else {
|
||||
domElement.style.outlineColor =
|
||||
renderer.getPropertyValue('--affine-blue');
|
||||
domElement.style.outlineWidth = '2px';
|
||||
domElement.style.outlineStyle = 'solid';
|
||||
}
|
||||
} else if (model.childElements.some(child => elements.includes(child.id))) {
|
||||
domElement.style.outlineColor = '#8FD1FF';
|
||||
domElement.style.outlineWidth = '2px';
|
||||
domElement.style.outlineStyle = 'solid';
|
||||
}
|
||||
|
||||
domElement.style.width = `${w * zoom}px`;
|
||||
domElement.style.height = `${h * zoom}px`;
|
||||
domElement.style.overflow = 'visible';
|
||||
domElement.style.pointerEvents = 'none';
|
||||
}
|
||||
);
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import type { GroupElementModel } from '@blocksuite/affine-model';
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
|
||||
import { titleRenderParams } from './utils.js';
|
||||
import { titleRenderParams } from './utils';
|
||||
|
||||
export const group: ElementRenderer<GroupElementModel> = (
|
||||
model,
|
||||
2
blocksuite/affine/gfx/group/src/renderer/index.ts
Normal file
2
blocksuite/affine/gfx/group/src/renderer/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './dom-renderer';
|
||||
export * from './element-renderer';
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
GROUP_TITLE_FONT_SIZE,
|
||||
GROUP_TITLE_OFFSET,
|
||||
GROUP_TITLE_PADDING,
|
||||
} from './consts.js';
|
||||
} from './consts';
|
||||
|
||||
export function titleRenderParams(group: GroupElementModel, zoom: number) {
|
||||
let text = group.title.toString().trim();
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
GROUP_TITLE_FONT_SIZE,
|
||||
GROUP_TITLE_OFFSET,
|
||||
GROUP_TITLE_PADDING,
|
||||
} from '../element-renderer/consts';
|
||||
} from '../renderer/consts';
|
||||
|
||||
export function mountGroupTitleEditor(
|
||||
group: GroupElementModel,
|
||||
|
||||
@@ -4,9 +4,12 @@ import {
|
||||
} from '@blocksuite/affine-ext-loader';
|
||||
|
||||
import { effects } from './effects';
|
||||
import { GroupElementRendererExtension } from './element-renderer';
|
||||
import { GroupElementView, GroupInteraction } from './element-view';
|
||||
import { GroupInteractionExtension } from './interaction-ext';
|
||||
import {
|
||||
GroupDomRendererExtension,
|
||||
GroupElementRendererExtension,
|
||||
} from './renderer';
|
||||
import { groupToolbarExtension } from './toolbar/config';
|
||||
|
||||
export class GroupViewExtension extends ViewExtensionProvider {
|
||||
@@ -20,6 +23,7 @@ export class GroupViewExtension extends ViewExtensionProvider {
|
||||
override setup(context: ViewExtensionContext) {
|
||||
super.setup(context);
|
||||
context.register(GroupElementRendererExtension);
|
||||
context.register(GroupDomRendererExtension);
|
||||
context.register(GroupElementView);
|
||||
if (this.isEdgeless(context.scope)) {
|
||||
context.register(groupToolbarExtension);
|
||||
|
||||
Reference in New Issue
Block a user