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 4e9c314ebd..f89f184fe5 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
@@ -9,6 +9,14 @@ function applyShapeSpecificStyles(
element: HTMLElement,
zoom: number
) {
+ // Reset properties that might be set by different shape types
+ element.style.clipPath = '';
+ element.style.borderRadius = '';
+ // Clear innerHTML for shapes that don't use SVG, or if type changes from SVG-based to non-SVG-based
+ if (model.shapeType !== 'diamond' && model.shapeType !== 'triangle') {
+ element.innerHTML = '';
+ }
+
if (model.shapeType === 'rect') {
const w = model.w * zoom;
const h = model.h * zoom;
@@ -18,9 +26,12 @@ function applyShapeSpecificStyles(
element.style.borderRadius = borderRadius;
} else if (model.shapeType === 'ellipse') {
element.style.borderRadius = '50%';
- } else {
- element.style.borderRadius = '';
+ } else if (model.shapeType === 'diamond') {
+ element.style.clipPath = 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)';
+ } else if (model.shapeType === 'triangle') {
+ element.style.clipPath = 'polygon(50% 0%, 100% 100%, 0% 100%)';
}
+ // No 'else' needed to clear styles, as they are reset at the beginning of the function.
}
function applyBorderStyles(
@@ -78,6 +89,9 @@ export const shapeDomRenderer = (
renderer: DomRenderer
): void => {
const { zoom } = renderer.viewport;
+ const unscaledWidth = model.w;
+ const unscaledHeight = model.h;
+
const fillColor = renderer.getColorValue(
model.fillColor,
DefaultTheme.shapeFillColor,
@@ -89,17 +103,69 @@ export const shapeDomRenderer = (
true
);
- element.style.width = `${model.w * zoom}px`;
- element.style.height = `${model.h * zoom}px`;
+ element.style.width = `${unscaledWidth * zoom}px`;
+ element.style.height = `${unscaledHeight * zoom}px`;
+ element.style.boxSizing = 'border-box';
+ // Apply shape-specific clipping, border-radius, and potentially clear innerHTML
applyShapeSpecificStyles(model, element, zoom);
- element.style.backgroundColor = model.filled ? fillColor : 'transparent';
+ if (model.shapeType === 'diamond' || model.shapeType === 'triangle') {
+ // For diamond and triangle, fill and border are handled by inline SVG
+ element.style.border = 'none'; // Ensure no standard CSS border interferes
+ element.style.backgroundColor = 'transparent'; // Host element is transparent
+
+ const strokeW = model.strokeWidth;
+ const halfStroke = strokeW / 2; // Calculate half stroke width for point adjustment
+
+ let svgPoints = '';
+ if (model.shapeType === 'diamond') {
+ // Adjusted points for diamond
+ svgPoints = `\
+${unscaledWidth / 2},${halfStroke} \
+${unscaledWidth - halfStroke},${unscaledHeight / 2} \
+${unscaledWidth / 2},${unscaledHeight - halfStroke} \
+${halfStroke},${unscaledHeight / 2}`;
+ } else {
+ // triangle
+ // Adjusted points for triangle
+ svgPoints = `\
+${unscaledWidth / 2},${halfStroke} \
+${unscaledWidth - halfStroke},${unscaledHeight - halfStroke} \
+${halfStroke},${unscaledHeight - halfStroke}`;
+ }
+
+ // Determine if stroke should be visible and its color
+ const finalStrokeColor =
+ model.strokeStyle !== 'none' && strokeW > 0 ? strokeColor : 'transparent';
+ // Determine dash array, only if stroke is visible and style is 'dash'
+ const finalStrokeDasharray =
+ model.strokeStyle === 'dash' && finalStrokeColor !== 'transparent'
+ ? '12, 12'
+ : 'none';
+ // Determine fill color
+ const finalFillColor = model.filled ? fillColor : 'transparent';
+
+ element.innerHTML = `
+
+ `;
+ } else {
+ // Standard rendering for other shapes (e.g., rect, ellipse)
+ // innerHTML was already cleared by applyShapeSpecificStyles if necessary
+ element.style.backgroundColor = model.filled ? fillColor : 'transparent';
+ applyBorderStyles(model, element, strokeColor, zoom); // Uses standard CSS border
+ }
- applyBorderStyles(model, element, strokeColor, zoom);
applyTransformStyles(model, element);
- element.style.boxSizing = 'border-box';
element.style.zIndex = renderer.layerManager.getZIndex(model).toString();
manageClassNames(model, element);
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 b0abb469df..eee3719f45 100644
--- a/blocksuite/integration-test/src/__tests__/edgeless/shape-dom.spec.ts
+++ b/blocksuite/integration-test/src/__tests__/edgeless/shape-dom.spec.ts
@@ -91,4 +91,48 @@ describe('Shape rendering with DOM renderer', () => {
);
expect(shapeElement).toBeNull();
});
+
+ test('should correctly apply clip-path for diamond shape', async () => {
+ const surfaceView = getSurface(window.doc, window.editor);
+ const surfaceModel = surfaceView.model;
+ const shapeProps = {
+ type: 'shape',
+ subType: 'diamond',
+ xywh: '[150, 150, 80, 60]',
+ fill: '#ff0000',
+ stroke: '#000000',
+ };
+ const shapeId = surfaceModel.addElement(shapeProps as any);
+ await wait(100);
+ const shapeElement = surfaceView?.renderRoot.querySelector(
+ `[data-element-id="${shapeId}"]`
+ );
+
+ expect(shapeElement).not.toBeNull();
+ expect(shapeElement?.style.clipPath).toBe(
+ 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)'
+ );
+ });
+
+ test('should correctly apply clip-path for triangle shape', async () => {
+ const surfaceView = getSurface(window.doc, window.editor);
+ const surfaceModel = surfaceView.model;
+ const shapeProps = {
+ type: 'shape',
+ subType: 'triangle',
+ xywh: '[150, 150, 80, 60]',
+ fill: '#ff0000',
+ stroke: '#000000',
+ };
+ const shapeId = surfaceModel.addElement(shapeProps as any);
+ await wait(100);
+ const shapeElement = surfaceView?.renderRoot.querySelector(
+ `[data-element-id="${shapeId}"]`
+ );
+
+ expect(shapeElement).not.toBeNull();
+ expect(shapeElement?.style.clipPath).toBe(
+ 'polygon(50% 0%, 100% 100%, 0% 100%)'
+ );
+ });
});