diff --git a/package-lock.json b/package-lock.json index 56979073c..e97a15918 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3939,6 +3939,35 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@volar/language-core": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.23.tgz", + "integrity": "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.23" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.23.tgz", + "integrity": "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.23.tgz", + "integrity": "sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, "node_modules/@vue/compiler-core": { "version": "3.5.25", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.25.tgz", @@ -4019,6 +4048,43 @@ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", "license": "MIT" }, + "node_modules/@vue/language-core": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.1.5.tgz", + "integrity": "sha512-FMcqyzWN+sYBeqRMWPGT2QY0mUasZMVIuHvmb5NT3eeqPrbHBYtCP8JWEUCDCgM+Zr62uuWY/qoeBrPrzfa78w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.0.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@vue/reactivity": { "version": "3.5.25", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.25.tgz", @@ -4204,6 +4270,13 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/alien-signals": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.1.tgz", + "integrity": "sha512-ogkIWbVrLwKtHY6oOAXaYkAxP+cTH7V5FZ5+Tm4NZFd8VDZ6uNMDrfzqctTZ42eTMCSR3ne3otpcxmqSnFfPYA==", + "dev": true, + "license": "MIT" + }, "node_modules/ansi-escapes": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", @@ -12279,6 +12352,13 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -21351,6 +21431,13 @@ "vue": "^3.0.0" } }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, "node_modules/vue": { "version": "3.5.25", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.25.tgz", @@ -21405,6 +21492,23 @@ } } }, + "node_modules/vue-tsc": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.1.5.tgz", + "integrity": "sha512-L/G9IUjOWhBU0yun89rv8fKqmKC+T0HfhrFjlIml71WpfBv9eb4E9Bev8FMbyueBIU9vxQqbd+oOsVcDa5amGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.23", + "@vue/language-core": "3.1.5" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, "node_modules/vueuc": { "version": "0.4.65", "resolved": "https://registry.npmjs.org/vueuc/-/vueuc-0.4.65.tgz", @@ -24800,6 +24904,8 @@ }, "devDependencies": { "@floating-ui/dom": "^1.7.0", + "@typescript-eslint/eslint-plugin": "^8.48.0", + "@typescript-eslint/parser": "^8.48.0", "@vitejs/plugin-vue": "^5.0.4", "@vue/test-utils": "^2.4.6", "postcss-nested": "^6.0.1", @@ -24809,7 +24915,8 @@ "typescript": "^5.7.3", "vite": "^6.3.5", "vite-plugin-node-polyfills": "^0.24.0", - "vitest": "^3.2.4" + "vitest": "^3.2.4", + "vue-tsc": "^3.1.5" }, "peerDependencies": { "@floating-ui/dom": "^1.7.0", diff --git a/packages/ai/src/editor-adapter.ts b/packages/ai/src/editor-adapter.ts index 56c8be274..7a19ca9cc 100644 --- a/packages/ai/src/editor-adapter.ts +++ b/packages/ai/src/editor-adapter.ts @@ -88,7 +88,7 @@ export class EditorAdapter { * @param from - Start position to scroll to * @param to - End position to scroll to */ - scrollToPosition(from: number, to: number): void { + scrollToPosition(from: number, _to: number): void { const { view } = this.editor; if (!view?.domAtPos) { return; diff --git a/packages/layout-engine/layout-bridge/src/cacheInvalidation.ts b/packages/layout-engine/layout-bridge/src/cacheInvalidation.ts index 037838b15..24f71ce29 100644 --- a/packages/layout-engine/layout-bridge/src/cacheInvalidation.ts +++ b/packages/layout-engine/layout-bridge/src/cacheInvalidation.ts @@ -66,7 +66,14 @@ export function computeSectionMetadataHash(sections: SectionMetadata[]): string const parts: string[] = []; for (const section of sections) { - parts.push(`section:${section.sectionIndex}`); + const sectionId = (section as { id?: string | null }).id; + parts.push(`section:${sectionId ?? section.sectionIndex}`); + + // Include section break type if present + const sectionType = (section as { type?: string | null }).type; + if (sectionType) { + parts.push(`type:${sectionType}`); + } // Include numbering properties that affect display if (section.numbering) { diff --git a/packages/layout-engine/layout-engine/src/layout-paragraph.ts b/packages/layout-engine/layout-engine/src/layout-paragraph.ts index 15d5355b0..593915caa 100644 --- a/packages/layout-engine/layout-engine/src/layout-paragraph.ts +++ b/packages/layout-engine/layout-engine/src/layout-paragraph.ts @@ -147,83 +147,6 @@ const asSafeNumber = (value: unknown): number => { return value; }; -/** - * Calculates the first line indent for list markers when remeasuring paragraphs. - * - * In Word layout, there are two distinct list marker layout patterns: - * - * 1. **firstLineIndentMode** (marker inline with text): - * - The marker is positioned at `left + firstLine` and consumes horizontal space on the first line - * - Text begins after the marker (at `textStartPx`) - * - The first line's available width must account for the marker's width - * - This pattern is indicated by `firstLineIndentMode === true` - * - * 2. **Standard hanging indent** (marker in hanging area): - * - The marker is positioned absolutely in the hanging region at `left - hanging` - * - The marker does NOT consume horizontal space from the text flow - * - Text begins at `left` on ALL lines (first and subsequent) - * - The first line's available width is the same as subsequent lines - * - This is the default pattern when `firstLineIndentMode` is not set - * - * This function determines which pattern is in use and calculates the appropriate - * first line indent for the remeasurement operation. - * - * @param block - The paragraph block being remeasured - * @param measure - The current paragraph measurement (may contain marker measurements) - * @returns The first line indent in pixels. Returns 0 for standard hanging indent, - * or the marker width + gutter width for firstLineIndentMode. - * - * @example - * ```typescript - * // Standard hanging indent - marker doesn't consume first line space - * const block1 = { - * attrs: { - * wordLayout: { - * marker: { markerBoxWidthPx: 20 }, - * // firstLineIndentMode is NOT set - * } - * } - * }; - * const indent1 = calculateFirstLineIndent(block1, measure); - * // Returns: 0 (marker is in hanging area) - * - * // firstLineIndentMode - marker consumes first line space - * const block2 = { - * attrs: { - * wordLayout: { - * marker: { markerBoxWidthPx: 20 }, - * firstLineIndentMode: true - * } - * } - * }; - * const indent2 = calculateFirstLineIndent(block2, measure); - * // Returns: markerWidth + gutterWidth (marker is inline) - * ``` - */ -function calculateFirstLineIndent(block: ParagraphBlock, measure: ParagraphMeasure): number { - const wordLayout = block.attrs?.wordLayout as WordLayoutAttrs | undefined; - - // Only apply first line indent in firstLineIndentMode - if (!wordLayout?.firstLineIndentMode) { - return 0; - } - - // Ensure marker exists in both wordLayout and measure - if (!wordLayout.marker || !measure.marker) { - return 0; - } - - // Extract marker width with fallback chain and validation - const markerWidthRaw = measure.marker.markerWidth ?? wordLayout.marker.markerBoxWidthPx ?? 0; - const markerWidth = Number.isFinite(markerWidthRaw) && markerWidthRaw >= 0 ? markerWidthRaw : 0; - - // Extract gutter width with validation - const gutterWidthRaw = measure.marker.gutterWidth ?? 0; - const gutterWidth = Number.isFinite(gutterWidthRaw) && gutterWidthRaw >= 0 ? gutterWidthRaw : 0; - - return markerWidth + gutterWidth; -} - export type ParagraphLayoutContext = { block: ParagraphBlock; measure: ParagraphMeasure; @@ -408,7 +331,6 @@ export function layoutParagraphBlock(ctx: ParagraphLayoutContext, anchors?: Para const negativeRightIndent = indentRight < 0 ? indentRight : 0; // Paragraph content width should honor paragraph indents (including negative values). const remeasureWidth = Math.max(1, columnWidth - indentLeft - indentRight); - const hasNegativeIndent = indentLeft < 0 || indentRight < 0; let didRemeasureForColumnWidth = false; if ( typeof remeasureParagraph === 'function' && diff --git a/packages/layout-engine/painters/dom/src/renderer.ts b/packages/layout-engine/painters/dom/src/renderer.ts index 34b8cbc29..8c0a14914 100644 --- a/packages/layout-engine/painters/dom/src/renderer.ts +++ b/packages/layout-engine/painters/dom/src/renderer.ts @@ -20,7 +20,6 @@ import type { ImageDrawing, ParagraphAttrs, ParagraphBorder, - ParagraphBorders, ListItemFragment, ListBlock, ListMeasure, @@ -67,12 +66,7 @@ import { sanitizeHref, encodeTooltip } from '@superdoc/url-validation'; import { renderTableFragment as renderTableFragmentElement } from './table/renderTableFragment.js'; import { assertPmPositions, assertFragmentPmPositions } from './pm-position-validation.js'; import { applySdtContainerStyling } from './utils/sdt-helpers.js'; -import { - generateRulerDefinitionFromPx, - createRulerElement, - ensureRulerStyles, - RULER_CLASS_NAMES, -} from './ruler/index.js'; +import { generateRulerDefinitionFromPx, createRulerElement, ensureRulerStyles } from './ruler/index.js'; import { toCssFontFamily } from '../../../../../shared/font-utils/index.js'; import { hashParagraphBorders, @@ -1887,7 +1881,6 @@ export class DomPainter { // When using explicit segment positioning, segments are absolutely positioned and textIndent // has no effect, so we skip it to avoid confusion. // Also skip when left indent is negative - fragment positioning already handles that case. - const hasNegativeLeftIndent = paraIndentLeft != null && paraIndentLeft < 0; if (!fragment.continuesFromPrev && index === 0 && firstLineOffset && !isListFirstLine) { if (!hasExplicitSegmentPositioning) { lineEl.style.textIndent = `${firstLineOffset}px`; @@ -3767,7 +3760,7 @@ export class DomPainter { context: FragmentRenderContext, availableWidthOverride?: number, lineIndex?: number, - skipJustify?: boolean, + _skipJustify?: boolean, ): HTMLElement { if (!this.doc) { throw new Error('DomPainter: document is not available'); diff --git a/packages/layout-engine/pm-adapter/src/attributes/paragraph-styles.ts b/packages/layout-engine/pm-adapter/src/attributes/paragraph-styles.ts index 1e04b95f9..ff3d387e6 100644 --- a/packages/layout-engine/pm-adapter/src/attributes/paragraph-styles.ts +++ b/packages/layout-engine/pm-adapter/src/attributes/paragraph-styles.ts @@ -167,7 +167,7 @@ export const hydrateParagraphStyleAttrs = ( } const hydrated: ParagraphStyleHydration = { - resolved, + resolved: resolved as ResolvedParagraphProperties, spacing: resolvedSpacing, indent: resolvedIndent, borders: cloneIfObject(resolvedExtended.borders) as ParagraphAttrs['borders'], diff --git a/packages/super-editor/index.html b/packages/super-editor/index.html index 14096b05c..7e9c5b2b6 100644 --- a/packages/super-editor/index.html +++ b/packages/super-editor/index.html @@ -7,6 +7,6 @@
- +