Skip to content

Commit 9d518a8

Browse files
committed
fix: expand border layer by border width to prevent overlap with text
With box-sizing: border-box, CSS borders are drawn inside the element. The border layer must expand by space + borderWidth (not just space) so the border's inner edge sits at the correct distance from text. The shading layer only needs space expansion since it has no CSS borders.
1 parent 23aa391 commit 9d518a8

1 file changed

Lines changed: 41 additions & 10 deletions

File tree

  • packages/layout-engine/painters/dom/src/features/paragraph-borders

β€Žpackages/layout-engine/painters/dom/src/features/paragraph-borders/border-layer.tsβ€Ž

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,18 @@ export const createParagraphDecorationLayers = (
9898
// Both real between (showBetweenBorder) and nil/none between (suppressBottomBorder)
9999
// need gap extension to keep left/right borders continuous through the spacing gap.
100100
const gapExtension = betweenInfo?.showBetweenBorder || betweenInfo?.suppressBottomBorder ? betweenInfo!.gapBelow : 0;
101-
const totalBottomExpansion = gapExtension + space.bottom;
102-
const bottomValue = totalBottomExpansion > 0 ? `-${totalBottomExpansion}px` : '0px';
103-
const topValue = space.top > 0 ? `-${space.top}px` : '0px';
104101

105-
const baseStyles = {
102+
// Border widths for each rendered side. With box-sizing: border-box, CSS borders are
103+
// drawn INSIDE the element. To position the border's inner edge at `space` distance
104+
// from the text, the border layer must be expanded by space + borderWidth.
105+
// The shading layer only needs space (it has no CSS borders).
106+
const bw = computeRenderedBorderWidths(attrs.borders, betweenInfo);
107+
108+
const shadingBottom = gapExtension + space.bottom;
109+
const borderBottom = gapExtension + space.bottom + bw.bottom;
110+
111+
const commonStyles = {
106112
position: 'absolute',
107-
top: topValue,
108-
bottom: bottomValue,
109-
left: `${borderBox.leftInset - space.left}px`,
110-
width: `${borderBox.width + space.left + space.right}px`,
111113
pointerEvents: 'none',
112114
boxSizing: 'border-box',
113115
} as const;
@@ -116,22 +118,51 @@ export const createParagraphDecorationLayers = (
116118
if (attrs.shading) {
117119
shadingLayer = doc.createElement('div');
118120
shadingLayer.classList.add('superdoc-paragraph-shading');
119-
Object.assign(shadingLayer.style, baseStyles);
121+
Object.assign(shadingLayer.style, commonStyles);
122+
shadingLayer.style.top = space.top > 0 ? `-${space.top}px` : '0px';
123+
shadingLayer.style.bottom = shadingBottom > 0 ? `-${shadingBottom}px` : '0px';
124+
shadingLayer.style.left = `${borderBox.leftInset - space.left}px`;
125+
shadingLayer.style.width = `${borderBox.width + space.left + space.right}px`;
120126
applyParagraphShadingStyles(shadingLayer, attrs.shading);
121127
}
122128

123129
let borderLayer: HTMLElement | undefined;
124130
if (attrs.borders) {
125131
borderLayer = doc.createElement('div');
126132
borderLayer.classList.add('superdoc-paragraph-border');
127-
Object.assign(borderLayer.style, baseStyles);
133+
Object.assign(borderLayer.style, commonStyles);
134+
borderLayer.style.top = space.top + bw.top > 0 ? `-${space.top + bw.top}px` : '0px';
135+
borderLayer.style.bottom = borderBottom > 0 ? `-${borderBottom}px` : '0px';
136+
borderLayer.style.left = `${borderBox.leftInset - space.left - bw.left}px`;
137+
borderLayer.style.width = `${borderBox.width + space.left + bw.left + space.right + bw.right}px`;
128138
borderLayer.style.zIndex = '1';
129139
applyParagraphBorderStyles(borderLayer, attrs.borders, betweenInfo);
130140
}
131141

132142
return { shadingLayer, borderLayer };
133143
};
134144

145+
/**
146+
* Computes the rendered CSS border widths per side, accounting for suppressed sides.
147+
* Used to expand the border layer so the inner edge is at the correct `space` offset.
148+
*/
149+
const computeRenderedBorderWidths = (
150+
borders?: ParagraphBorders,
151+
betweenInfo?: BetweenBorderInfo,
152+
): { top: number; bottom: number; left: number; right: number } => {
153+
if (!borders) return { top: 0, bottom: 0, left: 0, right: 0 };
154+
const suppressTop = betweenInfo?.suppressTopBorder ?? false;
155+
const suppressBottom = betweenInfo?.suppressBottomBorder ?? false;
156+
const showBetween = betweenInfo?.showBetweenBorder ?? false;
157+
158+
return {
159+
top: !suppressTop ? (borders.top?.width ?? 0) : 0,
160+
bottom: showBetween ? (borders.between?.width ?? 0) : !suppressBottom ? (borders.bottom?.width ?? 0) : 0,
161+
left: borders.left?.width ?? 0,
162+
right: borders.right?.width ?? 0,
163+
};
164+
};
165+
135166
// ─── Border CSS application ────────────────────────────────────────
136167

137168
type CssBorderSide = 'top' | 'right' | 'bottom' | 'left';

0 commit comments

Comments
Β (0)