feat(editor): support border radius for shape dom renderer (#12326)

Comparison:

![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/lEGcysB4lFTEbCwZ8jMv/f2a266ba-c3d5-46ea-9aa5-38e5d0de6d5a.png)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
	- Border radius and border thickness of shapes now scale dynamically with zoom level for improved visual consistency.

- **Tests**
	- Added a test to ensure percentage-based border radius values are correctly rendered in the DOM.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
doodlewind
2025-05-16 09:02:45 +00:00
parent 101062aa25
commit 8ed4f14380
2 changed files with 35 additions and 6 deletions
@@ -6,10 +6,16 @@ import { manageClassNames, setStyles } from './utils';
function applyShapeSpecificStyles(
model: ShapeElementModel,
element: HTMLElement
element: HTMLElement,
zoom: number
) {
if (model.shapeType === 'rect') {
element.style.borderRadius = `${model.radius ?? 0}px`;
const w = model.w * zoom;
const h = model.h * zoom;
const r = model.radius ?? 0;
const borderRadius =
r < 1 ? `${Math.min(w * r, h * r)}px` : `${r * zoom}px`;
element.style.borderRadius = borderRadius;
} else if (model.shapeType === 'ellipse') {
element.style.borderRadius = '50%';
} else {
@@ -20,11 +26,12 @@ function applyShapeSpecificStyles(
function applyBorderStyles(
model: ShapeElementModel,
element: HTMLElement,
strokeColor: string
strokeColor: string,
zoom: number
) {
element.style.border =
model.strokeStyle !== 'none'
? `${model.strokeWidth}px ${model.strokeStyle === 'dash' ? 'dashed' : 'solid'} ${strokeColor}`
? `${model.strokeWidth * zoom}px ${model.strokeStyle === 'dash' ? 'dashed' : 'solid'} ${strokeColor}`
: 'none';
}
@@ -85,11 +92,11 @@ export const shapeDomRenderer = (
element.style.width = `${model.w * zoom}px`;
element.style.height = `${model.h * zoom}px`;
applyShapeSpecificStyles(model, element);
applyShapeSpecificStyles(model, element, zoom);
element.style.backgroundColor = model.filled ? fillColor : 'transparent';
applyBorderStyles(model, element, strokeColor);
applyBorderStyles(model, element, strokeColor, zoom);
applyTransformStyles(model, element);
element.style.boxSizing = 'border-box';
@@ -1,6 +1,7 @@
import { DomRenderer } from '@blocksuite/affine-block-surface';
import { beforeEach, describe, expect, test } from 'vitest';
import { wait } from '../utils/common.js';
import { getSurface } from '../utils/edgeless.js';
import { setupEditor } from '../utils/setup.js';
@@ -40,6 +41,27 @@ describe('Shape rendering with DOM renderer', () => {
expect(shapeElement).toBeInstanceOf(HTMLElement);
});
test('should correctly apply percentage-based border radius', async () => {
const surfaceView = getSurface(window.doc, window.editor);
const surfaceModel = surfaceView.model;
const shapeProps = {
type: 'shape',
subType: 'rectangle',
xywh: '[150, 150, 80, 60]', // width: 80, height: 60
radius: 0.1, // 10% of min(width, height) = 10% of 60 = 6
fill: '#ff0000',
stroke: '#000000',
};
const shapeId = surfaceModel.addElement(shapeProps);
await wait(100);
const shapeElement = surfaceView?.renderRoot.querySelector<HTMLElement>(
`[data-element-id="${shapeId}"]`
);
expect(shapeElement).not.toBeNull();
expect(shapeElement?.style.borderRadius).toBe('6px');
});
test('should remove shape DOM node when element is deleted', async () => {
const surfaceView = getSurface(window.doc, window.editor);
const surfaceModel = surfaceView.model;