From 8ed4f14380998ea75bba8794536fd1d8c38f38d3 Mon Sep 17 00:00:00 2001 From: doodlewind <7312949+doodlewind@users.noreply.github.com> Date: Fri, 16 May 2025 09:02:45 +0000 Subject: [PATCH] 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) ## 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. --- .../src/element-renderer/shape-dom/index.ts | 19 +++++++++++----- .../src/__tests__/edgeless/shape-dom.spec.ts | 22 +++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/blocksuite/affine/gfx/shape/src/element-renderer/shape-dom/index.ts b/blocksuite/affine/gfx/shape/src/element-renderer/shape-dom/index.ts index 5a9078d28..4e9c314eb 100644 --- a/blocksuite/affine/gfx/shape/src/element-renderer/shape-dom/index.ts +++ b/blocksuite/affine/gfx/shape/src/element-renderer/shape-dom/index.ts @@ -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'; diff --git a/blocksuite/integration-test/src/__tests__/edgeless/shape-dom.spec.ts b/blocksuite/integration-test/src/__tests__/edgeless/shape-dom.spec.ts index ce6580afd..b0abb469d 100644 --- a/blocksuite/integration-test/src/__tests__/edgeless/shape-dom.spec.ts +++ b/blocksuite/integration-test/src/__tests__/edgeless/shape-dom.spec.ts @@ -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( + `[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;