Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"test:superdoc": "vitest run --root ./packages/superdoc",
"test:cov": "node scripts/test-cov.mjs",
"type-check": "tsc -b --force tsconfig.references.json",
"rebuild:types": "pnpm run --filter=@superdoc/contracts --filter=@superdoc/geometry-utils --filter=@superdoc/style-engine --filter=@superdoc/pm-adapter --filter=@superdoc/measuring-dom --filter=@superdoc/layout-engine --filter=@superdoc/painter-dom --filter=@superdoc/layout-bridge build",
"rebuild:types": "pnpm run --filter=@superdoc/common --filter=@superdoc/word-layout --filter=@superdoc/contracts --filter=@superdoc/geometry-utils --filter=@superdoc/style-engine --filter=@superdoc/pm-adapter --filter=@superdoc/measuring-dom --filter=@superdoc/layout-engine --filter=@superdoc/painter-dom --filter=@superdoc/layout-bridge build",
"validate:commands": "node scripts/validate-command-types.mjs",
"unzip": "bash packages/super-editor/src/tests/helpers/unzip.sh",
"dev": "pnpm --prefix packages/superdoc run dev",
Expand Down
4 changes: 2 additions & 2 deletions packages/layout-engine/measuring/dom/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ import {
DEFAULT_LIST_HANGING_PX as DEFAULT_LIST_HANGING,
SPACE_SUFFIX_GAP_PX,
} from '@superdoc/common/layout-constants';
import { resolveListTextStartPx } from '@superdoc/common/list-marker-utils';
import { resolveListTextStartPx, type MinimalMarker } from '@superdoc/common/list-marker-utils';
import { calculateRotatedBounds, normalizeRotation } from '@superdoc/geometry-utils';
import { toCssFontFamily } from '@superdoc/font-utils';
export { installNodeCanvasPolyfill } from './setup.js';
Expand Down Expand Up @@ -895,7 +895,7 @@ async function measureParagraphBlock(block: ParagraphBlock, maxWidth: number): P
indentLeft,
firstLine,
hanging,
(markerText, marker) => {
(markerText: string, marker: MinimalMarker) => {
const markerRun = {
fontFamily: toCssFontFamily(marker.run?.fontFamily) ?? marker.run?.fontFamily ?? 'Arial',
fontSize: marker.run?.fontSize ?? fallbackFontSize,
Expand Down
1 change: 1 addition & 0 deletions packages/super-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
"@vitejs/plugin-vue": "catalog:",
"@vue/test-utils": "catalog:",
"canvas": "catalog:",
"happy-dom": "catalog:",
"postcss-nested": "catalog:",
"postcss-nested-import": "catalog:",
"prosemirror-test-builder": "catalog:",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export const handleDocxPaste = (html, editor, view) => {
Object.keys(textStyles).forEach((key) => {
const styleValue = textStyles[key];
if (styleValue) {
item.style[key] = styleValue;
item.style.setProperty(key, styleValue);
}
});
item.setAttribute('data-text-styles', JSON.stringify(textStyles));
Expand All @@ -157,7 +157,7 @@ export const handleDocxPaste = (html, editor, view) => {
Object.keys(textStyles).forEach((key) => {
const styleValue = textStyles[key];
if (styleValue) {
child.style[key] = styleValue;
child.style.setProperty(key, styleValue);
}
});
}
Expand All @@ -166,10 +166,10 @@ export const handleDocxPaste = (html, editor, view) => {

// Marks
if (resolvedStyle['font-weight'] === 'bold') {
item.style.fontWeight = 'bold';
item.style.setProperty('font-weight', 'bold');
for (const child of item.children) {
if (child.style) {
child.style.fontWeight = 'bold';
child.style.setProperty('font-weight', 'bold');
}
}
}
Expand Down Expand Up @@ -288,10 +288,10 @@ const transformWordLists = (container, editor) => {
Object.keys(textStyles).forEach((key) => {
const styleValue = textStyles[key];
if (styleValue) {
pElement.style[key] = styleValue;
pElement.style.setProperty(key, styleValue);
for (const child of pElement.children) {
if (child.style) {
child.style[key] = styleValue;
child.style.setProperty(key, styleValue);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ describe('handleDocxPaste', () => {
</head>
<body>
<ol type="1" start="1">
<li class="MsoListParagraph" style="mso-list:l0 level1 lfo1">
<span>First item</span>
<li class="MsoListParagraph" style="mso-list:l0 level1 lfo1;font-size:13pt;font-family:Calibri">
<span style="font-size:13pt;font-family:Calibri">First item</span>
</li>
</ol>
<p class="MsoListParagraph" style="mso-list:l0 level1 lfo1">
<p class="MsoListParagraph" style="mso-list:l0 level1 lfo1;font-size:13pt;font-family:Calibri">
<!--[if !supportLists]--><span style="font-family:Arial;font-size:12pt">2.</span><!--[endif]-->
Second item
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,10 @@ function getNestedLists(nodes) {
function mergeSeparateLists(container) {
const tempCont = container.cloneNode(true);

const rootLevelLists = Array.from(tempCont.querySelectorAll('ol:not(ol ol):not(ul ol)') || []);
// Find root-level ordered lists (not nested inside other lists)
// Note: Using filter instead of complex :not() selectors for better browser compatibility
const allOls = Array.from(tempCont.querySelectorAll('ol') || []);
const rootLevelLists = allOls.filter((ol) => !ol.parentElement?.closest('ol, ul'));
const mainList = rootLevelLists.find((list) => !list.getAttribute('start')) || rootLevelLists[0];
const hasStartAttr = rootLevelLists.some((list) => list.getAttribute('start') !== null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const transformListsInCopiedContent = (html) => {

const li = child.cloneNode(true);
li.setAttribute('aria-level', level + 1);
li.style['list-style-type'] = getListStyleType(numFmt, lvlText);
li.style.setProperty('list-style-type', getListStyleType(numFmt, lvlText));

// if current level not open, create new list
if (!stack.length || stack[stack.length - 1].level < level) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,20 @@
export function createHiddenHost(doc: Document, widthPx: number): HTMLElement {
const host = doc.createElement('div');
host.className = 'presentation-editor__hidden-host';
host.style.position = 'fixed';
host.style.left = '-9999px';
host.style.top = '0';
host.style.width = `${widthPx}px`;
host.style.setProperty('position', 'fixed');
host.style.setProperty('left', '-9999px');
host.style.setProperty('top', '0');
// Only set valid (non-negative) width values
if (widthPx >= 0) {
host.style.setProperty('width', `${widthPx}px`);
}
host.style.setProperty('overflow-anchor', 'none');
host.style.pointerEvents = 'none';
host.style.setProperty('pointer-events', 'none');
// DO NOT use visibility:hidden - it prevents focusing!
// Instead use opacity:0 and z-index to hide while keeping focusable
host.style.opacity = '0';
host.style.zIndex = '-1';
host.style.userSelect = 'none';
host.style.setProperty('opacity', '0');
host.style.setProperty('z-index', '-1');
host.style.setProperty('user-select', 'none');
// DO NOT set aria-hidden="true" on this element.
// This hidden host contains the actual ProseMirror editor which must remain accessible
// to screen readers and keyboard navigation. The viewport (#viewportHost) is aria-hidden
Expand Down
15 changes: 10 additions & 5 deletions packages/super-editor/src/extensions/tab/helpers/tabDecorations.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,13 +265,18 @@ export function measureRangeWidth(view, from, to, coordCache = null, domPosCache
range.setEnd(toRef.node, toRef.offset);
const rect = range.getBoundingClientRect();
range.detach?.();
return rect.width || 0;
// If getBoundingClientRect returns 0 (e.g., HappyDOM), fall back to coordsAtPos
if (rect.width > 0) {
return rect.width;
}
} catch {
const startLeft = getLeftCoord(view, from, coordCache, domPosCache);
const endLeft = getLeftCoord(view, to, coordCache, domPosCache);
if (startLeft == null || endLeft == null) return 0;
return Math.max(0, endLeft - startLeft);
// Fall through to coordsAtPos fallback
}
// Fallback: use view.coordsAtPos difference (works in test mocks and when Range fails)
const startLeft = getLeftCoord(view, from, coordCache, domPosCache);
const endLeft = getLeftCoord(view, to, coordCache, domPosCache);
if (startLeft == null || endLeft == null) return 0;
return Math.max(0, endLeft - startLeft);
}

export function getIndentWidth(view, paragraphStartPos, indentAttrs = {}, coordCache = null, domPosCache = null) {
Expand Down
3 changes: 2 additions & 1 deletion packages/super-editor/tsconfig.types.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "./tsconfig.build.json",
"compilerOptions": {
"composite": true,
"outDir": "dist-types"
"outDir": "dist",
"tsBuildInfoFile": "./dist/tsconfig.types.tsbuildinfo"
},
"references": [
{ "path": "../layout-engine/contracts/tsconfig.json" },
Expand Down
3 changes: 2 additions & 1 deletion packages/super-editor/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export default defineConfig(({ mode }) => {
minWorkers,
maxWorkers,
globals: true,
environment: 'jsdom',
// Use happy-dom for faster tests (set VITEST_DOM=jsdom to use jsdom)
environment: process.env.VITEST_DOM || 'happy-dom',
retry: 2,
testTimeout: 20000,
hookTimeout: 10000,
Expand Down
3 changes: 2 additions & 1 deletion packages/word-layout/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"rootDir": "./src",
"composite": true,
"declaration": true,
"declarationMap": true
"declarationMap": true,
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
},
"include": ["src/**/*"]
}
Loading
Loading