From c5d282d203cdaad8ab7db1c882aefdd6256175d2 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Thu, 20 Nov 2025 00:09:10 +0100 Subject: [PATCH 01/29] Mathlive --- packages/ckeditor5-math/package.json | 3 +- packages/ckeditor5-math/src/index.ts | 2 + packages/ckeditor5-math/src/mathui.ts | 19 +- .../ckeditor5-math/src/ui/mainformview.ts | 151 +++++++---- .../src/ui/mathliveinputview.ts | 105 ++++++++ .../src/ui/rawlatexinputview.ts | 61 +++++ packages/ckeditor5-math/tests/mathui.ts | 6 +- .../ckeditor5-math/theme/icons/cancel.svg | 4 + packages/ckeditor5-math/theme/icons/check.svg | 3 + packages/ckeditor5-math/theme/mathform.css | 238 ++++++++++++++++-- pnpm-lock.yaml | 196 ++++++++------- 11 files changed, 610 insertions(+), 178 deletions(-) create mode 100644 packages/ckeditor5-math/src/ui/mathliveinputview.ts create mode 100644 packages/ckeditor5-math/src/ui/rawlatexinputview.ts create mode 100644 packages/ckeditor5-math/theme/icons/cancel.svg create mode 100644 packages/ckeditor5-math/theme/icons/check.svg diff --git a/packages/ckeditor5-math/package.json b/packages/ckeditor5-math/package.json index 5a18b797d..583a448cc 100644 --- a/packages/ckeditor5-math/package.json +++ b/packages/ckeditor5-math/package.json @@ -71,6 +71,7 @@ ] }, "dependencies": { - "@ckeditor/ckeditor5-icons": "47.2.0" + "@ckeditor/ckeditor5-icons": "47.2.0", + "mathlive": "0.108.2" } } diff --git a/packages/ckeditor5-math/src/index.ts b/packages/ckeditor5-math/src/index.ts index 6d6982ae2..b3475309f 100644 --- a/packages/ckeditor5-math/src/index.ts +++ b/packages/ckeditor5-math/src/index.ts @@ -1,6 +1,8 @@ import ckeditor from './../theme/icons/math.svg?raw'; import './augmentation.js'; import "../theme/mathform.css"; +import 'mathlive/fonts.css'; +import 'mathlive/static.css'; export { default as Math } from './math.js'; export { default as MathUI } from './mathui.js'; diff --git a/packages/ckeditor5-math/src/mathui.ts b/packages/ckeditor5-math/src/mathui.ts index 4c4a2794c..2ac4c79cd 100644 --- a/packages/ckeditor5-math/src/mathui.ts +++ b/packages/ckeditor5-math/src/mathui.ts @@ -4,6 +4,7 @@ import mathIcon from '../theme/icons/math.svg?raw'; import { Plugin, ClickObserver, ButtonView, ContextualBalloon, clickOutsideHandler, CKEditorError, uid } from 'ckeditor5'; import { getBalloonPositionData } from './utils.js'; import MathCommand from './mathcommand.js'; +import 'mathlive'; const mathKeystroke = 'Ctrl+M'; @@ -56,7 +57,7 @@ export default class MathUI extends Plugin { this._balloon.showStack( 'main' ); requestAnimationFrame(() => { - this.formView?.mathInputView.fieldView.element?.focus(); + this.formView?.mathInputView.focus(); }); } @@ -89,7 +90,7 @@ export default class MathUI extends Plugin { mathConfig.katexRenderOptions! ); - formView.mathInputView.bind( 'value' ).to( mathCommand, 'value' ); + formView.mathInputView.bind( 'value' ).to( mathCommand, value => value ?? '' ); formView.displayButtonView.bind( 'isOn' ).to( mathCommand, 'display' ); // Form elements should be read-only when corresponding commands are disabled. @@ -122,18 +123,6 @@ export default class MathUI extends Plugin { } }); - // Allow the textarea to be resizable - formView.mathInputView.fieldView.once('render', () => { - const textarea = formView.mathInputView.fieldView.element; - if (!textarea) return; - Object.assign(textarea.style, { - resize: 'both', - height: '100px', - width: '400px', - minWidth: '100%', - }); - }); - return formView; } @@ -162,7 +151,7 @@ export default class MathUI extends Plugin { } ); if ( this._balloon.visibleView === this.formView ) { - this.formView.mathInputView.fieldView.element?.select(); + this.formView.mathInputView.focus(); } // Show preview element diff --git a/packages/ckeditor5-math/src/ui/mainformview.ts b/packages/ckeditor5-math/src/ui/mainformview.ts index 2d1c59793..ef818e203 100644 --- a/packages/ckeditor5-math/src/ui/mainformview.ts +++ b/packages/ckeditor5-math/src/ui/mainformview.ts @@ -1,23 +1,18 @@ -import { ButtonView, createLabeledTextarea, FocusCycler, LabelView, LabeledFieldView, submitHandler, SwitchButtonView, View, ViewCollection, type TextareaView, type FocusableView, Locale, FocusTracker, KeystrokeHandler } from 'ckeditor5'; -import IconCheck from "@ckeditor/ckeditor5-icons/theme/icons/check.svg?raw"; -import IconCancel from "@ckeditor/ckeditor5-icons/theme/icons/cancel.svg?raw"; +import { ButtonView, FocusCycler, LabelView, submitHandler, SwitchButtonView, View, ViewCollection, type FocusableView, Locale, FocusTracker, KeystrokeHandler } from 'ckeditor5'; +import IconCheck from '../../theme/icons/check.svg?raw'; +import IconCancel from '../../theme/icons/cancel.svg?raw'; import { extractDelimiters, hasDelimiters } from '../utils.js'; import MathView from './mathview.js'; +import MathLiveInputView from './mathliveinputview.js'; +import RawLatexInputView from './rawlatexinputview.js'; import '../../theme/mathform.css'; import type { KatexOptions } from '../typings-external.js'; -class MathInputView extends LabeledFieldView { - public value: null | string = null; - public isReadOnly = false; - - constructor( locale: Locale ) { - super( locale, createLabeledTextarea ); - } -} - export default class MainFormView extends View { public saveButtonView: ButtonView; - public mathInputView: MathInputView; + public mathInputView: MathLiveInputView; + public rawLatexInputView: RawLatexInputView; + public rawLatexLabel: LabelView; public displayButtonView: SwitchButtonView; public cancelButtonView: ButtonView; public previewEnabled: boolean; @@ -54,6 +49,13 @@ export default class MainFormView extends View { // Equation input this.mathInputView = this._createMathInput(); + // Raw LaTeX input + this.rawLatexInputView = this._createRawLatexInput(); + + // Raw LaTeX label + this.rawLatexLabel = new LabelView( locale ); + this.rawLatexLabel.text = t( 'LaTeX' ); + // Display button this.displayButtonView = this._createDisplayButton(); @@ -74,6 +76,8 @@ export default class MainFormView extends View { children = [ this.mathInputView, + this.rawLatexLabel, + this.rawLatexInputView, this.displayButtonView, this.previewLabel, this.mathView @@ -81,6 +85,8 @@ export default class MainFormView extends View { } else { children = [ this.mathInputView, + this.rawLatexLabel, + this.rawLatexInputView, this.displayButtonView ]; } @@ -101,14 +107,28 @@ export default class MainFormView extends View { { tag: 'div', attributes: { - class: [ - 'ck-math-view' - ] + class: [ 'ck-math-scroll' ] }, - children + children: [ + { + tag: 'div', + attributes: { + class: [ 'ck-math-view' ] + }, + children + } + ] }, - this.saveButtonView, - this.cancelButtonView + { + tag: 'div', + attributes: { + class: [ 'ck-math-button-row' ] + }, + children: [ + this.saveButtonView, + this.cancelButtonView + ] + } ] } ); } @@ -124,6 +144,7 @@ export default class MainFormView extends View { // Register form elements to focusable elements const childViews = [ this.mathInputView, + this.rawLatexInputView, this.displayButtonView, this.saveButtonView, this.cancelButtonView @@ -147,13 +168,12 @@ export default class MainFormView extends View { } public get equation(): string { - return this.mathInputView.fieldView.element?.value ?? ''; + return this.mathInputView.value ?? ''; } public set equation( equation: string ) { - if ( this.mathInputView.fieldView.element ) { - this.mathInputView.fieldView.element.value = equation; - } + this.mathInputView.value = equation; + this.rawLatexInputView.value = equation; if ( this.previewEnabled && this.mathView ) { this.mathView.value = equation; } @@ -172,46 +192,79 @@ export default class MainFormView extends View { } } ); + /** + * Creates the MathLive visual equation editor. + * + * Handles bidirectional synchronization with the raw LaTeX input and preview. + */ private _createMathInput() { - const t = this.locale.t; - - // Create equation input - const mathInput = new MathInputView( this.locale ); - const fieldView = mathInput.fieldView; - mathInput.infoText = t( 'Insert equation in TeX format.' ); + const mathInput = new MathLiveInputView( this.locale ); const onInput = () => { - if ( fieldView.element != null ) { - let equationInput = fieldView.element.value.trim(); + const rawValue = mathInput.value ?? ''; + let equationInput = rawValue.trim(); - // If input has delimiters - if ( hasDelimiters( equationInput ) ) { - // Get equation without delimiters - const params = extractDelimiters( equationInput ); + // If input has delimiters + if ( hasDelimiters( equationInput ) ) { + // Get equation without delimiters + const params = extractDelimiters( equationInput ); - // Remove delimiters from input field - fieldView.element.value = params.equation; + // Remove delimiters from input field + mathInput.value = params.equation; - equationInput = params.equation; + equationInput = params.equation; - // update display button and preview - this.displayButtonView.isOn = params.display; - } - if ( this.previewEnabled && this.mathView ) { - // Update preview view - this.mathView.value = equationInput; - } - - this.saveButtonView.isEnabled = !!equationInput; + // update display button and preview + this.displayButtonView.isOn = params.display; } + + // Sync to raw LaTeX input + this.rawLatexInputView.value = equationInput; + + if ( this.previewEnabled && this.mathView ) { + // Update preview view + this.mathView.value = equationInput; + } + + this.saveButtonView.isEnabled = !!equationInput; }; - fieldView.on( 'render', onInput ); - fieldView.on( 'input', onInput ); + mathInput.on( 'change:value', onInput ); return mathInput; } + /** + * Creates the raw LaTeX code textarea editor. + * + * Provides direct LaTeX editing and synchronizes changes with the MathLive visual editor. + */ + private _createRawLatexInput() { + const t = this.locale.t; + const rawLatexInput = new RawLatexInputView( this.locale ); + rawLatexInput.label = t( 'LaTeX' ); + + // Sync raw LaTeX changes to MathLive visual editor + rawLatexInput.on( 'change:value', () => { + const rawValue = rawLatexInput.value ?? ''; + const equationInput = rawValue.trim(); + + // Update MathLive field + if ( this.mathInputView.value !== equationInput ) { + this.mathInputView.value = equationInput; + } + + // Update preview if enabled + if ( this.previewEnabled && this.mathView ) { + this.mathView.value = equationInput; + } + + this.saveButtonView.isEnabled = !!equationInput; + } ); + + return rawLatexInput; + } + private _createButton( label: string, icon: string, diff --git a/packages/ckeditor5-math/src/ui/mathliveinputview.ts b/packages/ckeditor5-math/src/ui/mathliveinputview.ts new file mode 100644 index 000000000..a09ec641d --- /dev/null +++ b/packages/ckeditor5-math/src/ui/mathliveinputview.ts @@ -0,0 +1,105 @@ +import { View, type Locale } from 'ckeditor5'; + +/** + * A view that wraps the MathLive `` web component for interactive LaTeX equation editing. + * + * MathLive provides a rich math input experience with live rendering, virtual keyboard support, + * and various accessibility features. + * + * @see https://cortexjs.io/mathlive/ + */ +export default class MathLiveInputView extends View { + /** + * The current LaTeX value of the math field. + * + * @observable + */ + public declare value: string; + + /** + * Whether the input is in read-only mode. + * + * @observable + */ + public declare isReadOnly: boolean; + + /** + * Reference to the `` DOM element. + */ + public mathfield: HTMLElement | null = null; + + constructor( locale: Locale ) { + super( locale ); + + this.set( 'value', '' ); + this.set( 'isReadOnly', false ); + + this.setTemplate( { + tag: 'div', + attributes: { + class: [ 'ck', 'ck-mathlive-input' ] + } + } ); + }git config --local credential.helper "" + + /** + * @inheritDoc + */ + public override render(): void { + super.render(); + + // Create the MathLive math-field custom element + const mathfield = document.createElement( 'math-field' ) as any; + this.mathfield = mathfield; + + // Configure the virtual keyboard to be manually controlled (shown by user interaction) + mathfield.setAttribute( 'virtual-keyboard-mode', 'manual' ); + + // Set initial value + if ( this.value ) { + ( mathfield as any ).value = this.value; + } + + // Bind readonly state + if ( this.isReadOnly ) { + ( mathfield as any ).readOnly = true; + } + + // Sync math-field changes to observable value + mathfield.addEventListener( 'input', () => { + this.value = ( mathfield as any ).value; + } ); + + // Sync observable value changes back to math-field + this.on( 'change:value', () => { + if ( ( mathfield as any ).value !== this.value ) { + ( mathfield as any ).value = this.value; + } + } ); + + // Sync readonly state to math-field + this.on( 'change:isReadOnly', () => { + ( mathfield as any ).readOnly = this.isReadOnly; + } ); + + this.element?.appendChild( mathfield ); + } + + /** + * Focuses the math-field element. + */ + public focus(): void { + this.mathfield?.focus(); + } + + /** + * @inheritDoc + */ + public override destroy(): void { + if ( this.mathfield ) { + this.mathfield.remove(); + this.mathfield = null; + } + super.destroy(); + } +} diff --git a/packages/ckeditor5-math/src/ui/rawlatexinputview.ts b/packages/ckeditor5-math/src/ui/rawlatexinputview.ts new file mode 100644 index 000000000..871f23e72 --- /dev/null +++ b/packages/ckeditor5-math/src/ui/rawlatexinputview.ts @@ -0,0 +1,61 @@ +import { LabeledFieldView, createLabeledTextarea, type Locale, type TextareaView } from 'ckeditor5'; + +/** + * A labeled textarea view for direct LaTeX code editing. + * + * This provides a plain text input for users who prefer to write LaTeX syntax directly + * or need to paste/edit raw LaTeX code. + */ +export default class RawLatexInputView extends LabeledFieldView { + /** + * The current LaTeX value. + * + * @observable + */ + public declare value: string; + + /** + * Whether the input is in read-only mode. + * + * @observable + */ + public declare isReadOnly: boolean; + + constructor( locale: Locale ) { + super( locale, createLabeledTextarea ); + + this.set( 'value', '' ); + this.set( 'isReadOnly', false ); + + const fieldView = this.fieldView; + + // Sync textarea input to observable value + fieldView.on( 'input', () => { + if ( fieldView.element ) { + this.value = fieldView.element.value; + } + } ); + + // Sync observable value changes back to textarea + this.on( 'change:value', () => { + if ( fieldView.element && fieldView.element.value !== this.value ) { + fieldView.element.value = this.value; + } + } ); + + // Sync readonly state (manual binding to avoid CKEditor observable rebind error) + this.on( 'change:isReadOnly', () => { + if ( fieldView.element ) { + fieldView.element.readOnly = this.isReadOnly; + } + } ); + } + + /** + * @inheritDoc + */ + public override render(): void { + super.render(); + // All styling is handled via CSS in mathform.css + } +} diff --git a/packages/ckeditor5-math/tests/mathui.ts b/packages/ckeditor5-math/tests/mathui.ts index 5a392c0db..a55fda523 100644 --- a/packages/ckeditor5-math/tests/mathui.ts +++ b/packages/ckeditor5-math/tests/mathui.ts @@ -410,7 +410,7 @@ describe( 'MathUI', () => { it( 'should bind mainFormView.mathInputView#value to math command value', () => { const command = editor.commands.get( 'math' ); - expect( formView!.mathInputView.value ).to.null; + expect( formView!.mathInputView.value ).to.be.null; command!.value = 'x^2'; expect( formView!.mathInputView.value ).to.equal( 'x^2' ); @@ -419,10 +419,10 @@ describe( 'MathUI', () => { it( 'should execute math command on mainFormView#submit event', () => { const executeSpy = vi.spyOn( editor, 'execute' ); - formView!.mathInputView.fieldView.element!.value = 'x^2'; + formView!.mathInputView.value = 'x^2'; formView!.fire( 'submit' ); - expect(executeSpy.mock.lastCall?.slice(0, 2)).toMatchObject(['math', 'x^2']); + expect( executeSpy.mock.lastCall?.slice( 0, 2 ) ).toMatchObject( [ 'math', 'x^2' ] ); } ); it( 'should hide the balloon on mainFormView#cancel if math command does not have a value', () => { diff --git a/packages/ckeditor5-math/theme/icons/cancel.svg b/packages/ckeditor5-math/theme/icons/cancel.svg new file mode 100644 index 000000000..6f755ce79 --- /dev/null +++ b/packages/ckeditor5-math/theme/icons/cancel.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ckeditor5-math/theme/icons/check.svg b/packages/ckeditor5-math/theme/icons/check.svg new file mode 100644 index 000000000..d62f08d2e --- /dev/null +++ b/packages/ckeditor5-math/theme/icons/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ckeditor5-math/theme/mathform.css b/packages/ckeditor5-math/theme/mathform.css index 3b7b4047f..45446d8f7 100644 --- a/packages/ckeditor5-math/theme/mathform.css +++ b/packages/ckeditor5-math/theme/mathform.css @@ -1,35 +1,227 @@ +/** + * Math equation editor dialog styles + * + * This stylesheet provides the layout and styling for the math equation editor, + * which includes a MathLive visual editor, a raw LaTeX textarea, and control buttons. + * The dialog is resizable and uses a flexible layout to accommodate different content sizes. + */ + +/* ============================================================================ + Form Layout + ========================================================================= */ + .ck.ck-math-form { display: flex; - align-items: flex-start; - flex-direction: row; - flex-wrap: nowrap; + flex-direction: column; padding: var(--ck-spacing-standard); + width: 100%; + height: 100%; + box-sizing: border-box; @media screen and (max-width: 600px) { flex-wrap: wrap; - - & .ck-math-view { - flex-basis: 100%; - - & .ck-labeled-view { - flex-basis: 100%; - } - - & .ck-label { - flex-basis: 100%; - } - } - - & .ck-button { - flex-basis: 50%; - } } } -.ck-math-tex.ck-placeholder::before { + +/* ============================================================================ + Button Row + ========================================================================= */ + +/* Button row */ +.ck-math-button-row { + display: flex; + gap: var(--ck-spacing-standard); + flex-shrink: 0; + margin-top: var(--ck-spacing-standard); + width: fit-content; +} + +/* Scrollable content area */ +.ck-math-scroll { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + min-width: 0; + overflow: hidden; +} + +/* ============================================================================ + Math Panel Layout + ========================================================================= */ + +/* Math panel layout */ +.ck-math-view { + display: flex; + flex-direction: column; + gap: var(--ck-spacing-standard); + flex: 1 1 auto; + min-height: fit-content; +} + +/* ============================================================================ + MathLive Integration + ========================================================================= */ + +/* MathLive input container */ +.ck.ck-mathlive-input { + display: inline-block; + flex: 0 0 auto; + width: auto; + min-width: 100%; + min-height: 140px; + max-height: 70vh; + resize: both; + overflow: auto; + padding-bottom: var(--ck-spacing-small); + box-sizing: border-box; +} + +/* MathLive field styling */ +.ck.ck-mathlive-input math-field { + display: block !important; + width: 100%; + height: 100%; + min-height: 140px; + box-sizing: border-box; + resize: none !important; + overflow: hidden !important; +} + +/* Style MathLive shadow DOM parts so the whole area is interactive */ +.ck.ck-math-form math-field::part(container), +.ck.ck-math-form math-field::part(content), +.ck.ck-math-form math-field::part(field) { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 100%; + height: 100%; + align-items: flex-start; + justify-content: flex-start; +} + + +/* Position MathLive virtual keyboard toggle button */ +.ck.ck-math-form math-field::part(virtual-keyboard-toggle) { + position: absolute; + right: 8px; + top: 8px; +} + +/* Position MathLive menu toggle button and ensure it's always visible */ +.ck.ck-math-form math-field::part(menu-toggle) { + position: absolute; + right: 8px; + top: 48px; + /* Force visibility even when field is empty */ + display: flex !important; + visibility: visible !important; +} + + +/* ============================================================================ + Raw LaTeX Integration + ========================================================================= */ + +/* Mirror MathLive container behavior for the labeled textarea wrapper */ + + +.ck-math-view .ck-labeled-field-view { + display: flex; + flex-direction: column; + flex: 0 0 auto; + width: 100%; + min-width: 100%; + min-height: 140px; + max-height: 70vh; + resize: vertical; + overflow: auto; + padding-bottom: 0; + box-sizing: border-box; + background: transparent; + border: none; + border-radius: 0; + outline: none; +} + +/* Let the internal wrapper stretch so the textarea can fill the space */ +.ck-math-view .ck-labeled-field-view .ck-labeled-field-view__input-wrapper { + display: flex; + flex-direction: column; + flex: 1 1 auto; + width: 100%; + height: auto; + min-height: 100px; + margin: 0; + padding: 0; + background: transparent; + border: none; + box-shadow: none; +} + +/* Ensure the raw textarea fills its wrapper like MathLive's math-field */ +.ck-math-view .ck-labeled-field-view textarea { + display: block; + width: 100% !important; + flex: 1 1 auto; + height: 100%; + min-height: 140px; + box-sizing: border-box; + resize: none !important; + overflow: hidden !important; +} + + +/* ============================================================================ + Shared Input Styling (MathLive & Raw LaTeX) + ========================================================================= */ + +/* Base styling for both MathLive field and raw LaTeX textarea */ +.ck.ck-math-form math-field, +.ck.ck-math-form textarea { + padding: var(--ck-spacing-small); + border: none !important; + border-radius: var(--ck-border-radius, 6px); + font-size: var(--ck-font-size-base); + box-sizing: border-box; + background: var(--input-background-color); + color: var(--input-text-color); + outline: 3px solid transparent; + outline-offset: 6px; +} + +/* Hover state */ +.ck.ck-math-form math-field:hover, +.ck.ck-math-form textarea:hover { + background: var(--input-hover-background); + color: var(--input-hover-color); +} + +/* Make the raw LaTeX textarea flat (no rounded corners or hover animation) */ +.ck-math-view .ck-labeled-field-view textarea { + border-radius: 0 !important; + outline: none !important; + box-shadow: none !important; + transition: none !important; +} + +.ck-math-view .ck-labeled-field-view textarea:hover, +.ck-math-view .ck-labeled-field-view textarea:focus { + background: var(--input-background-color); + color: var(--input-text-color); + outline: none !important; + box-shadow: none !important; + transition: none !important; +} + +/* Hide the internal label (we use a separate label element for better layout control) */ +.ck-math-view .ck-labeled-field-view .ck-label { display: none !important; } -.ck.ck-toolbar-container { - z-index: calc(var(--ck-z-panel) + 2); -} + + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad305250f..c23634f4c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -641,7 +641,7 @@ importers: version: 3.0.0 debug: specifier: 4.4.3 - version: 4.4.3(supports-color@6.0.0) + version: 4.4.3(supports-color@8.1.1) ejs: specifier: 3.1.10 version: 3.1.10 @@ -1058,6 +1058,9 @@ importers: '@ckeditor/ckeditor5-icons': specifier: 47.2.0 version: 47.2.0 + mathlive: + specifier: 0.108.2 + version: 0.108.2 devDependencies: '@ckeditor/ckeditor5-dev-build-tools': specifier: 43.1.0 @@ -2101,6 +2104,10 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@cortex-js/compute-engine@0.30.2': + resolution: {integrity: sha512-Zx+iisk9WWdbxjm8EYsneIBszvjfUs7BHNwf1jBtSINIgfWGpHrTTq9vW0J59iGCFt6bOFxbmWyxNMRSmksHMA==} + engines: {node: '>=21.7.3', npm: '>=10.5.0'} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -6823,6 +6830,10 @@ packages: compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + complex-esm@2.1.1-esm1: + resolution: {integrity: sha512-IShBEWHILB9s7MnfyevqNGxV0A1cfcSnewL/4uPFiSxkcQL4Mm3FxJ0pXMtCXuWLjYz3lRRyk6OfkeDZcjD6nw==} + engines: {node: '>=16.14.2', npm: '>=8.5.0'} + component-emitter@1.3.1: resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} @@ -10138,6 +10149,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mathlive@0.108.2: + resolution: {integrity: sha512-GIZkfprGTxrbHckOvwo92ZmOOxdD018BHDzlrEwYUU+pzR5KabhqI1s43lxe/vqXdF5RLiQKgDcuk5jxEjhkYg==} + mathml-tag-names@2.1.3: resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} @@ -15329,7 +15343,7 @@ snapshots: '@babel/traverse': 7.28.4 '@babel/types': 7.28.4 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -15446,7 +15460,7 @@ snapshots: '@babel/parser': 7.28.4 '@babel/template': 7.27.2 '@babel/types': 7.28.4 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -15459,7 +15473,7 @@ snapshots: '@babel/parser': 7.28.4 '@babel/template': 7.27.2 '@babel/types': 7.28.4 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -15471,7 +15485,7 @@ snapshots: '@babel/parser': 7.28.4 '@babel/template': 7.27.2 '@babel/types': 7.28.4 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -15692,6 +15706,8 @@ snapshots: '@ckeditor/ckeditor5-core': 47.2.0 '@ckeditor/ckeditor5-utils': 47.2.0 ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-code-block@47.2.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -15756,8 +15772,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.2.0 '@ckeditor/ckeditor5-watchdog': 47.2.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-dev-build-tools@43.1.0(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)': dependencies: @@ -15952,8 +15966,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.2.0 ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-multi-root@47.2.0': dependencies: @@ -16866,6 +16878,11 @@ snapshots: '@colors/colors@1.5.0': {} + '@cortex-js/compute-engine@0.30.2': + dependencies: + complex-esm: 2.1.1-esm1 + decimal.js: 10.6.0 + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -16935,7 +16952,7 @@ snapshots: '@listr2/prompt-adapter-inquirer': 2.0.22(@inquirer/prompts@6.0.1) chalk: 4.1.2 commander: 11.1.0 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fs-extra: 10.1.0 listr2: 7.0.2 log-symbols: 4.1.0 @@ -16955,7 +16972,7 @@ snapshots: '@electron/rebuild': 3.7.2 '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) find-up: 5.0.0 fs-extra: 10.1.0 log-symbols: 4.1.0 @@ -16984,7 +17001,7 @@ snapshots: '@malept/cross-spawn-promise': 2.0.0 '@vscode/sudo-prompt': 9.3.1 chalk: 4.1.2 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fast-glob: 3.3.3 filenamify: 4.3.0 find-up: 5.0.0 @@ -17120,7 +17137,7 @@ snapshots: '@electron-forge/core-utils': 7.10.2 '@electron-forge/shared-types': 7.10.2 '@malept/cross-spawn-promise': 2.0.0 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fs-extra: 10.1.0 semver: 7.7.3 username: 5.1.0 @@ -17182,7 +17199,7 @@ snapshots: '@electron/get@2.0.3': dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) env-paths: 2.2.1 fs-extra: 8.1.0 got: 11.8.6 @@ -17196,7 +17213,7 @@ snapshots: '@electron/get@3.1.0': dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) env-paths: 2.2.1 fs-extra: 8.1.0 got: 11.8.6 @@ -17226,7 +17243,7 @@ snapshots: '@electron/notarize@2.5.0': dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fs-extra: 9.1.0 promise-retry: 2.0.1 transitivePeerDependencies: @@ -17235,7 +17252,7 @@ snapshots: '@electron/osx-sign@1.3.3': dependencies: compare-version: 0.1.2 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fs-extra: 10.1.0 isbinaryfile: 4.0.10 minimist: 1.2.8 @@ -17251,7 +17268,7 @@ snapshots: '@electron/osx-sign': 1.3.3 '@electron/universal': 2.0.2 '@electron/windows-sign': 1.2.1 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) extract-zip: 2.0.1 filenamify: 4.3.0 fs-extra: 11.3.2 @@ -17272,7 +17289,7 @@ snapshots: '@electron/node-gyp': https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2 '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) detect-libc: 2.1.1 fs-extra: 10.1.0 got: 11.8.6 @@ -17291,7 +17308,7 @@ snapshots: dependencies: '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) detect-libc: 2.0.4 got: 11.8.6 graceful-fs: 4.2.11 @@ -17314,7 +17331,7 @@ snapshots: dependencies: '@electron/asar': 3.4.1 '@malept/cross-spawn-promise': 2.0.0 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) dir-compare: 4.2.0 fs-extra: 11.3.2 minimatch: 9.0.5 @@ -17325,7 +17342,7 @@ snapshots: '@electron/windows-sign@1.2.1': dependencies: cross-dirname: 0.1.0 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fs-extra: 11.3.2 minimist: 1.2.8 postject: 1.0.0-alpha.6 @@ -17612,7 +17629,7 @@ snapshots: '@eslint/config-array@0.21.1': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -17636,7 +17653,7 @@ snapshots: '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -17996,7 +18013,7 @@ snapshots: '@antfu/install-pkg': 1.1.0 '@antfu/utils': 9.2.0 '@iconify/types': 2.0.0 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) globals: 15.15.0 kolorist: 1.8.0 local-pkg: 1.1.1 @@ -18414,7 +18431,7 @@ snapshots: '@kwsites/file-exists@1.1.1': dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -18498,7 +18515,7 @@ snapshots: '@malept/electron-installer-flatpak@0.11.4': dependencies: '@malept/flatpak-bundler': 0.4.0 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) electron-installer-common: 0.10.4 lodash: 4.17.21 semver: 7.7.3 @@ -18509,7 +18526,7 @@ snapshots: '@malept/flatpak-bundler@0.4.0': dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fs-extra: 9.1.0 lodash: 4.17.21 tmp-promise: 3.0.3 @@ -18968,7 +18985,7 @@ snapshots: '@puppeteer/browsers@2.10.10': dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) extract-zip: 2.0.1 progress: 2.0.3 proxy-agent: 6.5.0 @@ -20249,7 +20266,7 @@ snapshots: '@tokenizer/inflate@0.2.7': dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fflate: 0.8.2 token-types: 6.0.0 transitivePeerDependencies: @@ -20866,7 +20883,7 @@ snapshots: '@typescript-eslint/types': 8.46.4 '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.46.4 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -20876,7 +20893,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) '@typescript-eslint/types': 8.46.4 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -20895,7 +20912,7 @@ snapshots: '@typescript-eslint/types': 8.46.4 '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) '@typescript-eslint/utils': 8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) eslint: 9.39.1(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 @@ -20910,7 +20927,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) '@typescript-eslint/types': 8.46.4 '@typescript-eslint/visitor-keys': 8.46.4 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -20992,7 +21009,7 @@ snapshots: '@vitest/coverage-istanbul@3.2.4(vitest@3.2.4)': dependencies: '@istanbuljs/schema': 0.1.3 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 6.0.3 istanbul-lib-report: 3.0.1 @@ -21010,7 +21027,7 @@ snapshots: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 ast-v8-to-istanbul: 0.3.3 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -21321,7 +21338,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -21765,7 +21782,7 @@ snapshots: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) http-errors: 2.0.0 iconv-lite: 0.6.3 on-finished: 2.4.1 @@ -22442,6 +22459,8 @@ snapshots: compare-versions@6.1.1: {} + complex-esm@2.1.1-esm1: {} + component-emitter@1.3.1: {} compress-commons@6.0.2: @@ -23225,8 +23244,7 @@ snapshots: decimal.js@10.5.0: {} - decimal.js@10.6.0: - optional: true + decimal.js@10.6.0: {} decko@1.2.0: {} @@ -23524,7 +23542,7 @@ snapshots: dependencies: '@electron/asar': 3.4.1 '@malept/cross-spawn-promise': 1.1.1 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fs-extra: 9.1.0 glob: 7.2.3 lodash: 4.17.21 @@ -23540,7 +23558,7 @@ snapshots: electron-installer-debian@3.2.0: dependencies: '@malept/cross-spawn-promise': 1.1.1 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) electron-installer-common: 0.10.4 fs-extra: 9.1.0 get-folder-size: 2.0.1 @@ -23554,7 +23572,7 @@ snapshots: electron-installer-dmg@5.0.1: dependencies: '@types/appdmg': 0.5.5 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) minimist: 1.2.8 optionalDependencies: appdmg: 0.6.6 @@ -23565,7 +23583,7 @@ snapshots: electron-installer-redhat@3.4.0: dependencies: '@malept/cross-spawn-promise': 1.1.1 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) electron-installer-common: 0.10.4 fs-extra: 9.1.0 lodash: 4.17.21 @@ -23581,7 +23599,7 @@ snapshots: electron-localshortcut@3.2.1: dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) electron-is-accelerator: 0.1.2 keyboardevent-from-electron-accelerator: 2.0.0 keyboardevents-areequal: 0.2.2 @@ -23606,7 +23624,7 @@ snapshots: electron-winstaller@5.4.0: dependencies: '@electron/asar': 3.4.1 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fs-extra: 7.0.1 lodash: 4.17.21 temp: 0.9.4 @@ -24154,7 +24172,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -24264,7 +24282,7 @@ snapshots: express-http-proxy@2.1.2: dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) es6-promise: 4.2.8 raw-body: 2.5.2 transitivePeerDependencies: @@ -24275,7 +24293,7 @@ snapshots: base64url: 3.0.1 clone: 2.1.2 cookie: 0.7.2 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) express: 5.1.0 futoin-hkdf: 1.5.3 http-errors: 1.8.1 @@ -24349,7 +24367,7 @@ snapshots: content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -24398,7 +24416,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -24540,7 +24558,7 @@ snapshots: finalhandler@2.1.0: dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -24596,7 +24614,7 @@ snapshots: flora-colossus@2.0.0: dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fs-extra: 10.1.0 transitivePeerDependencies: - supports-color @@ -24608,7 +24626,7 @@ snapshots: follow-redirects@1.15.9(debug@4.4.3): optionalDependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) for-each@0.3.5: dependencies: @@ -24749,7 +24767,7 @@ snapshots: galactus@1.0.0: dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) flora-colossus: 2.0.0 fs-extra: 10.1.0 transitivePeerDependencies: @@ -24865,7 +24883,7 @@ snapshots: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -25342,7 +25360,7 @@ snapshots: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -25350,14 +25368,14 @@ snapshots: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -25410,14 +25428,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -25812,7 +25830,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: '@jridgewell/trace-mapping': 0.3.31 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -26426,7 +26444,7 @@ snapshots: log4js@6.9.1: dependencies: date-format: 4.0.14 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) flatted: 3.3.3 rfdc: 1.4.1 streamroller: 3.1.5 @@ -26642,6 +26660,10 @@ snapshots: math-intrinsics@1.1.0: {} + mathlive@0.108.2: + dependencies: + '@cortex-js/compute-engine': 0.30.2 + mathml-tag-names@2.1.3: {} mdast-util-find-and-replace@3.0.2: @@ -27019,7 +27041,7 @@ snapshots: micromark@4.0.2: dependencies: '@types/debug': 4.1.12 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) decode-named-character-reference: 1.2.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 @@ -27857,7 +27879,7 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.4 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) get-uri: 6.0.5 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 @@ -28133,7 +28155,7 @@ snapshots: portfinder@1.0.36: dependencies: async: 3.2.6 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -28978,7 +29000,7 @@ snapshots: proxy-agent@6.5.0: dependencies: agent-base: 7.1.4 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -29196,7 +29218,7 @@ snapshots: read-binary-file-arch@1.0.6: dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -29639,7 +29661,7 @@ snapshots: router@2.2.0: dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -29900,7 +29922,7 @@ snapshots: send@1.2.0: dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -30117,13 +30139,13 @@ snapshots: dependencies: '@kwsites/file-exists': 1.1.1 '@kwsites/promise-deferred': 1.1.1 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color simple-websocket@9.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5): dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) queue-microtask: 1.2.3 randombytes: 2.1.0 readable-stream: 3.6.2 @@ -30217,7 +30239,7 @@ snapshots: socks-proxy-agent@6.2.1: dependencies: agent-base: 6.0.2 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) socks: 2.8.4 transitivePeerDependencies: - supports-color @@ -30226,7 +30248,7 @@ snapshots: socks-proxy-agent@7.0.0: dependencies: agent-base: 6.0.2 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) socks: 2.8.7 transitivePeerDependencies: - supports-color @@ -30234,7 +30256,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.4 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) socks: 2.8.7 transitivePeerDependencies: - supports-color @@ -30295,7 +30317,7 @@ snapshots: spdy-transport@3.0.0: dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) detect-node: 2.1.0 hpack.js: 2.1.6 obuf: 1.1.2 @@ -30306,7 +30328,7 @@ snapshots: spdy@4.0.2: dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) handle-thing: 2.0.1 http-deceiver: 1.2.7 select-hose: 2.0.0 @@ -30390,7 +30412,7 @@ snapshots: streamroller@3.1.5: dependencies: date-format: 4.0.14 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fs-extra: 8.1.0 transitivePeerDependencies: - supports-color @@ -30641,7 +30663,7 @@ snapshots: cosmiconfig: 9.0.0(typescript@5.0.4) css-functions-list: 3.2.3 css-tree: 3.1.0 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fast-glob: 3.3.3 fastest-levenshtein: 1.0.16 file-entry-cache: 10.1.4 @@ -30685,7 +30707,7 @@ snapshots: cosmiconfig: 9.0.0(typescript@5.9.3) css-functions-list: 3.2.3 css-tree: 3.1.0 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fast-glob: 3.3.3 fastest-levenshtein: 1.0.16 file-entry-cache: 10.1.4 @@ -30731,7 +30753,7 @@ snapshots: sumchecker@3.0.1: dependencies: - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -30739,7 +30761,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) fast-safe-stringify: 2.1.1 form-data: 4.0.4 formidable: 3.5.4 @@ -31196,7 +31218,7 @@ snapshots: tuf-js@4.0.0: dependencies: '@tufjs/models': 4.0.0 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) make-fetch-happen: 15.0.3 transitivePeerDependencies: - supports-color @@ -31566,7 +31588,7 @@ snapshots: vite-node@3.2.4(@types/node@24.10.1)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1): dependencies: cac: 6.7.14 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) es-module-lexer: 1.7.0 pathe: 2.0.3 vite: 7.2.2(@types/node@24.10.1)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) @@ -31591,7 +31613,7 @@ snapshots: '@volar/typescript': 2.4.13 '@vue/language-core': 2.2.0(typescript@5.9.3) compare-versions: 6.1.1 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) kolorist: 1.8.0 local-pkg: 1.1.1 magic-string: 0.30.17 @@ -31658,7 +31680,7 @@ snapshots: '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 chai: 5.2.0 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) expect-type: 1.2.1 magic-string: 0.30.18 pathe: 2.0.3 @@ -31741,7 +31763,7 @@ snapshots: dependencies: chalk: 4.1.2 commander: 9.5.0 - debug: 4.4.3(supports-color@6.0.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color From e777b06fb8ae303e588eea793cade07d9c1884cd Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Thu, 20 Nov 2025 18:53:39 +0100 Subject: [PATCH 02/29] Math --- packages/ckeditor5-math/src/ui/mathliveinputview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-math/src/ui/mathliveinputview.ts b/packages/ckeditor5-math/src/ui/mathliveinputview.ts index a09ec641d..340314dc3 100644 --- a/packages/ckeditor5-math/src/ui/mathliveinputview.ts +++ b/packages/ckeditor5-math/src/ui/mathliveinputview.ts @@ -40,7 +40,7 @@ export default class MathLiveInputView extends View { class: [ 'ck', 'ck-mathlive-input' ] } } ); - }git config --local credential.helper "" + } /** * @inheritDoc From 49e90c08a9c2d6cbc3b8323a17aed2908f5fb722 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Thu, 20 Nov 2025 22:45:21 +0100 Subject: [PATCH 03/29] Better Names for Math UI Components --- packages/ckeditor5-math/package.json | 1 - packages/ckeditor5-math/src/mathui.ts | 21 +- .../ckeditor5-math/src/ui/mainformview.ts | 65 +++--- .../src/ui/mathliveinputview.ts | 17 +- ...nputView-value-to-math-command-value-1.png | Bin 0 -> 23437 bytes ...lement-when-math-command-is-disabled-1.png | Bin 0 -> 8642 bytes pnpm-lock.yaml | 203 ++++++++---------- 7 files changed, 149 insertions(+), 158 deletions(-) create mode 100644 packages/ckeditor5-math/tests/__screenshots__/mathui.ts/MathUI--showUI---math-form-view-binding-should-bind-mainFormView-mathInputView-value-to-math-command-value-1.png create mode 100644 packages/ckeditor5-math/tests/__screenshots__/mathui.ts/MathUI--showUI---should-disable--mainFormView-element-when-math-command-is-disabled-1.png diff --git a/packages/ckeditor5-math/package.json b/packages/ckeditor5-math/package.json index 34f845f91..0f3e8a37a 100644 --- a/packages/ckeditor5-math/package.json +++ b/packages/ckeditor5-math/package.json @@ -71,7 +71,6 @@ ] }, "dependencies": { - "@ckeditor/ckeditor5-icons": "47.2.0", "mathlive": "0.108.2" } } diff --git a/packages/ckeditor5-math/src/mathui.ts b/packages/ckeditor5-math/src/mathui.ts index 2ac4c79cd..76290626f 100644 --- a/packages/ckeditor5-math/src/mathui.ts +++ b/packages/ckeditor5-math/src/mathui.ts @@ -57,7 +57,7 @@ export default class MathUI extends Plugin { this._balloon.showStack( 'main' ); requestAnimationFrame(() => { - this.formView?.mathInputView.focus(); + this.formView?.mathLiveInputView.focus(); }); } @@ -90,13 +90,22 @@ export default class MathUI extends Plugin { mathConfig.katexRenderOptions! ); - formView.mathInputView.bind( 'value' ).to( mathCommand, value => value ?? '' ); + formView.mathLiveInputView.bind( 'value' ).to( mathCommand, 'value' ); formView.displayButtonView.bind( 'isOn' ).to( mathCommand, 'display' ); // Form elements should be read-only when corresponding commands are disabled. - formView.mathInputView.bind( 'isReadOnly' ).to( mathCommand, 'isEnabled', value => !value ); - formView.saveButtonView.bind( 'isEnabled' ).to( mathCommand ); - formView.displayButtonView.bind( 'isEnabled' ).to( mathCommand ); + formView.mathLiveInputView.bind( 'isReadOnly' ).to( mathCommand, 'isEnabled', value => !value ); + formView.saveButtonView.bind( 'isEnabled' ).to( + mathCommand, + 'isEnabled', + formView.mathLiveInputView, + 'value', + ( commandEnabled, equation ) => { + const normalizedEquation = ( equation ?? '' ).trim(); + return commandEnabled && normalizedEquation.length > 0; + } + ); + formView.displayButtonView.bind( 'isEnabled' ).to( mathCommand, 'isEnabled' ); // Listen to submit button click this.listenTo( formView, 'submit', () => { @@ -151,7 +160,7 @@ export default class MathUI extends Plugin { } ); if ( this._balloon.visibleView === this.formView ) { - this.formView.mathInputView.focus(); + this.formView.mathLiveInputView.focus(); } // Show preview element diff --git a/packages/ckeditor5-math/src/ui/mainformview.ts b/packages/ckeditor5-math/src/ui/mainformview.ts index ef818e203..7c57fce8f 100644 --- a/packages/ckeditor5-math/src/ui/mainformview.ts +++ b/packages/ckeditor5-math/src/ui/mainformview.ts @@ -10,7 +10,7 @@ import type { KatexOptions } from '../typings-external.js'; export default class MainFormView extends View { public saveButtonView: ButtonView; - public mathInputView: MathLiveInputView; + public mathLiveInputView: MathLiveInputView; public rawLatexInputView: RawLatexInputView; public rawLatexLabel: LabelView; public displayButtonView: SwitchButtonView; @@ -46,8 +46,8 @@ export default class MainFormView extends View { this.saveButtonView = this._createButton( t( 'Save' ), IconCheck, 'ck-button-save', null ); this.saveButtonView.type = 'submit'; - // Equation input - this.mathInputView = this._createMathInput(); + // MathLive visual equation editor + this.mathLiveInputView = this._createMathLiveInput(); // Raw LaTeX input this.rawLatexInputView = this._createRawLatexInput(); @@ -75,7 +75,7 @@ export default class MainFormView extends View { this.mathView.bind( 'display' ).to( this.displayButtonView, 'isOn' ); children = [ - this.mathInputView, + this.mathLiveInputView, this.rawLatexLabel, this.rawLatexInputView, this.displayButtonView, @@ -84,7 +84,7 @@ export default class MainFormView extends View { ]; } else { children = [ - this.mathInputView, + this.mathLiveInputView, this.rawLatexLabel, this.rawLatexInputView, this.displayButtonView @@ -143,7 +143,7 @@ export default class MainFormView extends View { // Register form elements to focusable elements const childViews = [ - this.mathInputView, + this.mathLiveInputView, this.rawLatexInputView, this.displayButtonView, this.saveButtonView, @@ -168,14 +168,15 @@ export default class MainFormView extends View { } public get equation(): string { - return this.mathInputView.value ?? ''; + return this.mathLiveInputView.value ?? ''; } public set equation( equation: string ) { - this.mathInputView.value = equation; - this.rawLatexInputView.value = equation; + const normalizedEquation = equation.trim(); + this.mathLiveInputView.value = normalizedEquation.length ? normalizedEquation : null; + this.rawLatexInputView.value = normalizedEquation; if ( this.previewEnabled && this.mathView ) { - this.mathView.value = equation; + this.mathView.value = normalizedEquation; } } @@ -195,13 +196,13 @@ export default class MainFormView extends View { /** * Creates the MathLive visual equation editor. * - * Handles bidirectional synchronization with the raw LaTeX input and preview. + * Handles bidirectional synchronization with the raw LaTeX textarea and preview. */ - private _createMathInput() { - const mathInput = new MathLiveInputView( this.locale ); + private _createMathLiveInput() { + const mathLiveInput = new MathLiveInputView( this.locale ); const onInput = () => { - const rawValue = mathInput.value ?? ''; + const rawValue = mathLiveInput.value ?? ''; let equationInput = rawValue.trim(); // If input has delimiters @@ -210,56 +211,58 @@ export default class MainFormView extends View { const params = extractDelimiters( equationInput ); // Remove delimiters from input field - mathInput.value = params.equation; + mathLiveInput.value = params.equation; equationInput = params.equation; - // update display button and preview + // Update display button and preview this.displayButtonView.isOn = params.display; } - // Sync to raw LaTeX input + const normalizedEquation = equationInput.length ? equationInput : null; + if ( mathLiveInput.value !== normalizedEquation ) { + mathLiveInput.value = normalizedEquation; + } + + // Sync to raw LaTeX textarea this.rawLatexInputView.value = equationInput; if ( this.previewEnabled && this.mathView ) { - // Update preview view + // Update preview this.mathView.value = equationInput; } - - this.saveButtonView.isEnabled = !!equationInput; }; - mathInput.on( 'change:value', onInput ); + mathLiveInput.on( 'change:value', onInput ); - return mathInput; + return mathLiveInput; } /** - * Creates the raw LaTeX code textarea editor. + * Creates the raw LaTeX textarea editor. * - * Provides direct LaTeX editing and synchronizes changes with the MathLive visual editor. + * Provides direct LaTeX code editing and synchronizes changes with the MathLive visual editor. */ private _createRawLatexInput() { const t = this.locale.t; const rawLatexInput = new RawLatexInputView( this.locale ); rawLatexInput.label = t( 'LaTeX' ); - // Sync raw LaTeX changes to MathLive visual editor + // Sync raw LaTeX textarea changes to MathLive visual editor rawLatexInput.on( 'change:value', () => { const rawValue = rawLatexInput.value ?? ''; const equationInput = rawValue.trim(); - // Update MathLive field - if ( this.mathInputView.value !== equationInput ) { - this.mathInputView.value = equationInput; + // Update MathLive visual editor + const normalizedEquation = equationInput.length ? equationInput : null; + if ( this.mathLiveInputView.value !== normalizedEquation ) { + this.mathLiveInputView.value = normalizedEquation; } - // Update preview if enabled + // Update preview if ( this.previewEnabled && this.mathView ) { this.mathView.value = equationInput; } - - this.saveButtonView.isEnabled = !!equationInput; } ); return rawLatexInput; diff --git a/packages/ckeditor5-math/src/ui/mathliveinputview.ts b/packages/ckeditor5-math/src/ui/mathliveinputview.ts index 340314dc3..148ae7f14 100644 --- a/packages/ckeditor5-math/src/ui/mathliveinputview.ts +++ b/packages/ckeditor5-math/src/ui/mathliveinputview.ts @@ -14,7 +14,7 @@ export default class MathLiveInputView extends View { * * @observable */ - public declare value: string; + public declare value: string | null; /** * Whether the input is in read-only mode. @@ -31,7 +31,7 @@ export default class MathLiveInputView extends View { constructor( locale: Locale ) { super( locale ); - this.set( 'value', '' ); + this.set( 'value', null ); this.set( 'isReadOnly', false ); this.setTemplate( { @@ -56,8 +56,9 @@ export default class MathLiveInputView extends View { mathfield.setAttribute( 'virtual-keyboard-mode', 'manual' ); // Set initial value - if ( this.value ) { - ( mathfield as any ).value = this.value; + const initialValue = this.value ?? ''; + if ( initialValue ) { + ( mathfield as any ).value = initialValue; } // Bind readonly state @@ -67,13 +68,15 @@ export default class MathLiveInputView extends View { // Sync math-field changes to observable value mathfield.addEventListener( 'input', () => { - this.value = ( mathfield as any ).value; + const nextValue: string = ( mathfield as any ).value; + this.value = nextValue.length ? nextValue : null; } ); // Sync observable value changes back to math-field this.on( 'change:value', () => { - if ( ( mathfield as any ).value !== this.value ) { - ( mathfield as any ).value = this.value; + const nextValue = this.value ?? ''; + if ( ( mathfield as any ).value !== nextValue ) { + ( mathfield as any ).value = nextValue; } } ); diff --git a/packages/ckeditor5-math/tests/__screenshots__/mathui.ts/MathUI--showUI---math-form-view-binding-should-bind-mainFormView-mathInputView-value-to-math-command-value-1.png b/packages/ckeditor5-math/tests/__screenshots__/mathui.ts/MathUI--showUI---math-form-view-binding-should-bind-mainFormView-mathInputView-value-to-math-command-value-1.png new file mode 100644 index 0000000000000000000000000000000000000000..30a255f4a953e913a0e192c9873ece104bc7f4bd GIT binary patch literal 23437 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 UOQ2L9t{`rxYF*90a_ixL0i4=(egFUf literal 0 HcmV?d00001 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aacb56ee4..903192f4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -641,7 +641,7 @@ importers: version: 3.0.0 debug: specifier: 4.4.3 - version: 4.4.3(supports-color@8.1.1) + version: 4.4.3(supports-color@6.0.0) ejs: specifier: 3.1.10 version: 3.1.10 @@ -15356,7 +15356,7 @@ snapshots: '@babel/traverse': 7.28.4 '@babel/types': 7.28.4 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -15479,7 +15479,7 @@ snapshots: '@babel/parser': 7.28.4 '@babel/template': 7.27.2 '@babel/types': 7.28.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -15492,7 +15492,7 @@ snapshots: '@babel/parser': 7.28.4 '@babel/template': 7.27.2 '@babel/types': 7.28.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color @@ -15504,7 +15504,7 @@ snapshots: '@babel/parser': 7.28.4 '@babel/template': 7.27.2 '@babel/types': 7.28.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color @@ -16498,8 +16498,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.2.0 '@ckeditor/ckeditor5-utils': 47.2.0 ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-restricted-editing@47.2.0': dependencies: @@ -16978,7 +16976,7 @@ snapshots: '@listr2/prompt-adapter-inquirer': 2.0.22(@inquirer/prompts@6.0.1) chalk: 4.1.2 commander: 11.1.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fs-extra: 10.1.0 listr2: 7.0.2 log-symbols: 4.1.0 @@ -16998,7 +16996,7 @@ snapshots: '@electron/rebuild': 3.7.2 '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) find-up: 5.0.0 fs-extra: 10.1.0 log-symbols: 4.1.0 @@ -17027,7 +17025,7 @@ snapshots: '@malept/cross-spawn-promise': 2.0.0 '@vscode/sudo-prompt': 9.3.1 chalk: 4.1.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fast-glob: 3.3.3 filenamify: 4.3.0 find-up: 5.0.0 @@ -17163,7 +17161,7 @@ snapshots: '@electron-forge/core-utils': 7.10.2 '@electron-forge/shared-types': 7.10.2 '@malept/cross-spawn-promise': 2.0.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fs-extra: 10.1.0 semver: 7.7.3 username: 5.1.0 @@ -17225,7 +17223,7 @@ snapshots: '@electron/get@2.0.3': dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) env-paths: 2.2.1 fs-extra: 8.1.0 got: 11.8.6 @@ -17239,7 +17237,7 @@ snapshots: '@electron/get@3.1.0': dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) env-paths: 2.2.1 fs-extra: 8.1.0 got: 11.8.6 @@ -17269,7 +17267,7 @@ snapshots: '@electron/notarize@2.5.0': dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fs-extra: 9.1.0 promise-retry: 2.0.1 transitivePeerDependencies: @@ -17278,7 +17276,7 @@ snapshots: '@electron/osx-sign@1.3.3': dependencies: compare-version: 0.1.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fs-extra: 10.1.0 isbinaryfile: 4.0.10 minimist: 1.2.8 @@ -17294,7 +17292,7 @@ snapshots: '@electron/osx-sign': 1.3.3 '@electron/universal': 2.0.2 '@electron/windows-sign': 1.2.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) extract-zip: 2.0.1 filenamify: 4.3.0 fs-extra: 11.3.2 @@ -17315,7 +17313,7 @@ snapshots: '@electron/node-gyp': https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2 '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) detect-libc: 2.1.1 fs-extra: 10.1.0 got: 11.8.6 @@ -17334,7 +17332,7 @@ snapshots: dependencies: '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) detect-libc: 2.0.4 got: 11.8.6 graceful-fs: 4.2.11 @@ -17357,7 +17355,7 @@ snapshots: dependencies: '@electron/asar': 3.4.1 '@malept/cross-spawn-promise': 2.0.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) dir-compare: 4.2.0 fs-extra: 11.3.2 minimatch: 9.0.5 @@ -17368,7 +17366,7 @@ snapshots: '@electron/windows-sign@1.2.1': dependencies: cross-dirname: 0.1.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fs-extra: 11.3.2 minimist: 1.2.8 postject: 1.0.0-alpha.6 @@ -17655,7 +17653,7 @@ snapshots: '@eslint/config-array@0.21.1': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -17679,7 +17677,7 @@ snapshots: '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -18039,7 +18037,7 @@ snapshots: '@antfu/install-pkg': 1.1.0 '@antfu/utils': 9.2.0 '@iconify/types': 2.0.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) globals: 15.15.0 kolorist: 1.8.0 local-pkg: 1.1.1 @@ -18457,7 +18455,7 @@ snapshots: '@kwsites/file-exists@1.1.1': dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color @@ -18541,7 +18539,7 @@ snapshots: '@malept/electron-installer-flatpak@0.11.4': dependencies: '@malept/flatpak-bundler': 0.4.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) electron-installer-common: 0.10.4 lodash: 4.17.21 semver: 7.7.3 @@ -18552,7 +18550,7 @@ snapshots: '@malept/flatpak-bundler@0.4.0': dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fs-extra: 9.1.0 lodash: 4.17.21 tmp-promise: 3.0.3 @@ -19011,7 +19009,7 @@ snapshots: '@puppeteer/browsers@2.10.10': dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) extract-zip: 2.0.1 progress: 2.0.3 proxy-agent: 6.5.0 @@ -20279,7 +20277,7 @@ snapshots: '@tokenizer/inflate@0.2.7': dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fflate: 0.8.2 token-types: 6.0.0 transitivePeerDependencies: @@ -20905,7 +20903,7 @@ snapshots: '@typescript-eslint/types': 8.46.4 '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.46.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -20927,7 +20925,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) '@typescript-eslint/types': 8.46.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -20964,7 +20962,7 @@ snapshots: '@typescript-eslint/types': 8.46.4 '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) '@typescript-eslint/utils': 8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) eslint: 9.39.1(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 @@ -20993,7 +20991,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) '@typescript-eslint/types': 8.46.4 '@typescript-eslint/visitor-keys': 8.46.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -21115,7 +21113,7 @@ snapshots: '@vitest/coverage-istanbul@4.0.10(vitest@4.0.10)': dependencies: '@istanbuljs/schema': 0.1.3 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 6.0.3 istanbul-lib-report: 3.0.1 @@ -21130,7 +21128,8 @@ snapshots: '@vitest/coverage-v8@4.0.10(@vitest/browser@4.0.10(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.1)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vitest@4.0.10))(vitest@4.0.10)': dependencies: '@bcoe/v8-coverage': 1.0.2 - ast-v8-to-istanbul: 0.3.3 + '@vitest/utils': 4.0.10 + ast-v8-to-istanbul: 0.3.8 debug: 4.4.3(supports-color@6.0.0) istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 @@ -21436,7 +21435,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color @@ -21872,7 +21871,7 @@ snapshots: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) http-errors: 2.0.0 iconv-lite: 0.6.3 on-finished: 2.4.1 @@ -23618,7 +23617,7 @@ snapshots: dependencies: '@electron/asar': 3.4.1 '@malept/cross-spawn-promise': 1.1.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fs-extra: 9.1.0 glob: 7.2.3 lodash: 4.17.21 @@ -23634,7 +23633,7 @@ snapshots: electron-installer-debian@3.2.0: dependencies: '@malept/cross-spawn-promise': 1.1.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) electron-installer-common: 0.10.4 fs-extra: 9.1.0 get-folder-size: 2.0.1 @@ -23648,7 +23647,7 @@ snapshots: electron-installer-dmg@5.0.1: dependencies: '@types/appdmg': 0.5.5 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) minimist: 1.2.8 optionalDependencies: appdmg: 0.6.6 @@ -23659,7 +23658,7 @@ snapshots: electron-installer-redhat@3.4.0: dependencies: '@malept/cross-spawn-promise': 1.1.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) electron-installer-common: 0.10.4 fs-extra: 9.1.0 lodash: 4.17.21 @@ -23675,7 +23674,7 @@ snapshots: electron-localshortcut@3.2.1: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) electron-is-accelerator: 0.1.2 keyboardevent-from-electron-accelerator: 2.0.0 keyboardevents-areequal: 0.2.2 @@ -23700,7 +23699,7 @@ snapshots: electron-winstaller@5.4.0: dependencies: '@electron/asar': 3.4.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fs-extra: 7.0.1 lodash: 4.17.21 temp: 0.9.4 @@ -24248,7 +24247,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -24358,7 +24357,7 @@ snapshots: express-http-proxy@2.1.2: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) es6-promise: 4.2.8 raw-body: 2.5.2 transitivePeerDependencies: @@ -24369,7 +24368,7 @@ snapshots: base64url: 3.0.1 clone: 2.1.2 cookie: 0.7.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) express: 5.1.0 futoin-hkdf: 1.5.3 http-errors: 1.8.1 @@ -24443,7 +24442,7 @@ snapshots: content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -24492,7 +24491,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -24634,7 +24633,7 @@ snapshots: finalhandler@2.1.0: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -24690,7 +24689,7 @@ snapshots: flora-colossus@2.0.0: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fs-extra: 10.1.0 transitivePeerDependencies: - supports-color @@ -24702,7 +24701,7 @@ snapshots: follow-redirects@1.15.9(debug@4.4.3): optionalDependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) for-each@0.3.5: dependencies: @@ -24843,7 +24842,7 @@ snapshots: galactus@1.0.0: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) flora-colossus: 2.0.0 fs-extra: 10.1.0 transitivePeerDependencies: @@ -24959,7 +24958,7 @@ snapshots: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color @@ -25436,7 +25435,7 @@ snapshots: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color @@ -25444,14 +25443,14 @@ snapshots: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color @@ -25504,14 +25503,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color @@ -25906,7 +25905,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: '@jridgewell/trace-mapping': 0.3.31 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -26520,7 +26519,7 @@ snapshots: log4js@6.9.1: dependencies: date-format: 4.0.14 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) flatted: 3.3.3 rfdc: 1.4.1 streamroller: 3.1.5 @@ -27119,7 +27118,7 @@ snapshots: micromark@4.0.2: dependencies: '@types/debug': 4.1.12 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) decode-named-character-reference: 1.2.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 @@ -27957,7 +27956,7 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) get-uri: 6.0.5 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 @@ -28235,7 +28234,7 @@ snapshots: portfinder@1.0.36: dependencies: async: 3.2.6 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color @@ -29074,7 +29073,7 @@ snapshots: proxy-agent@6.5.0: dependencies: agent-base: 7.1.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -29290,7 +29289,7 @@ snapshots: read-binary-file-arch@1.0.6: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color @@ -29733,7 +29732,7 @@ snapshots: router@2.2.0: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -29994,7 +29993,7 @@ snapshots: send@1.2.0: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -30211,13 +30210,13 @@ snapshots: dependencies: '@kwsites/file-exists': 1.1.1 '@kwsites/promise-deferred': 1.1.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color simple-websocket@9.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5): dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) queue-microtask: 1.2.3 randombytes: 2.1.0 readable-stream: 3.6.2 @@ -30311,7 +30310,7 @@ snapshots: socks-proxy-agent@6.2.1: dependencies: agent-base: 6.0.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) socks: 2.8.4 transitivePeerDependencies: - supports-color @@ -30320,7 +30319,7 @@ snapshots: socks-proxy-agent@7.0.0: dependencies: agent-base: 6.0.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) socks: 2.8.7 transitivePeerDependencies: - supports-color @@ -30328,7 +30327,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) socks: 2.8.7 transitivePeerDependencies: - supports-color @@ -30389,7 +30388,7 @@ snapshots: spdy-transport@3.0.0: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) detect-node: 2.1.0 hpack.js: 2.1.6 obuf: 1.1.2 @@ -30400,7 +30399,7 @@ snapshots: spdy@4.0.2: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) handle-thing: 2.0.1 http-deceiver: 1.2.7 select-hose: 2.0.0 @@ -30484,7 +30483,7 @@ snapshots: streamroller@3.1.5: dependencies: date-format: 4.0.14 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fs-extra: 8.1.0 transitivePeerDependencies: - supports-color @@ -30731,7 +30730,7 @@ snapshots: cosmiconfig: 9.0.0(typescript@5.0.4) css-functions-list: 3.2.3 css-tree: 3.1.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fast-glob: 3.3.3 fastest-levenshtein: 1.0.16 file-entry-cache: 10.1.4 @@ -30775,7 +30774,7 @@ snapshots: cosmiconfig: 9.0.0(typescript@5.9.3) css-functions-list: 3.2.3 css-tree: 3.1.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fast-glob: 3.3.3 fastest-levenshtein: 1.0.16 file-entry-cache: 10.1.4 @@ -30821,7 +30820,7 @@ snapshots: sumchecker@3.0.1: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color @@ -30829,7 +30828,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) fast-safe-stringify: 2.1.1 form-data: 4.0.4 formidable: 3.5.4 @@ -31276,7 +31275,7 @@ snapshots: tuf-js@4.0.0: dependencies: '@tufjs/models': 4.0.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) make-fetch-happen: 15.0.3 transitivePeerDependencies: - supports-color @@ -31654,27 +31653,6 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-node@3.2.4(@types/node@24.10.1)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1): - dependencies: - cac: 6.7.14 - debug: 4.4.3(supports-color@6.0.0) - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.2.2(@types/node@24.10.1)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vite-plugin-dts@4.5.4(@types/node@24.10.1)(rollup@4.52.0)(typescript@5.9.3)(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)): dependencies: '@microsoft/api-extractor': 7.52.8(@types/node@24.10.1) @@ -31682,7 +31660,7 @@ snapshots: '@volar/typescript': 2.4.13 '@vue/language-core': 2.2.0(typescript@5.9.3) compare-versions: 6.1.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) kolorist: 1.8.0 local-pkg: 1.1.1 magic-string: 0.30.21 @@ -31740,18 +31718,17 @@ snapshots: vitest@4.0.10(@types/debug@4.1.12)(@types/node@24.10.1)(@vitest/browser-webdriverio@4.0.10)(@vitest/ui@4.0.10)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@24.10.1)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1): dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.7.5(@types/node@24.10.1)(typescript@5.9.3))(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.2.0 + '@vitest/expect': 4.0.10 + '@vitest/mocker': 4.0.10(msw@2.7.5(@types/node@24.10.1)(typescript@5.9.3))(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.10 + '@vitest/runner': 4.0.10 + '@vitest/snapshot': 4.0.10 + '@vitest/spy': 4.0.10 + '@vitest/utils': 4.0.10 debug: 4.4.3(supports-color@6.0.0) - expect-type: 1.2.1 - magic-string: 0.30.18 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.21 pathe: 2.0.3 picomatch: 4.0.3 std-env: 3.10.0 @@ -31830,7 +31807,7 @@ snapshots: dependencies: chalk: 4.1.2 commander: 9.5.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@6.0.0) transitivePeerDependencies: - supports-color From 39838c25c29b113a17e5293dd09ab7ab9aa3aa0d Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Fri, 21 Nov 2025 23:50:49 +0100 Subject: [PATCH 04/29] Fixed chaching problems --- apps/client/package.json | 1 + apps/client/vite.config.mts | 2 +- packages/ckeditor5-math/src/index.ts | 1 + packages/ckeditor5-math/src/mathui.ts | 1 - packages/ckeditor5-math/src/ui/mathliveinputview.ts | 1 + pnpm-lock.yaml | 10 +++++++--- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index ba0fbb97b..d8db8801d 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -54,6 +54,7 @@ "leaflet-gpx": "2.2.0", "mark.js": "8.11.1", "marked": "17.0.0", + "mathlive": "0.108.2", "mermaid": "11.12.1", "mind-elixir": "5.3.6", "normalize.css": "8.0.1", diff --git a/apps/client/vite.config.mts b/apps/client/vite.config.mts index 8d3b0c583..12eac25ce 100644 --- a/apps/client/vite.config.mts +++ b/apps/client/vite.config.mts @@ -83,7 +83,7 @@ export default defineConfig(() => ({ chunkFileNames: "src/[name].js", assetFileNames: "src/[name].[ext]", manualChunks: { - "ckeditor5": [ "@triliumnext/ckeditor5" ], + "ckeditor5": [ "@triliumnext/ckeditor5", "mathlive" ], "boxicons": [ "../../node_modules/boxicons/css/boxicons.min.css" ] }, }, diff --git a/packages/ckeditor5-math/src/index.ts b/packages/ckeditor5-math/src/index.ts index b3475309f..eac4d7b22 100644 --- a/packages/ckeditor5-math/src/index.ts +++ b/packages/ckeditor5-math/src/index.ts @@ -1,6 +1,7 @@ import ckeditor from './../theme/icons/math.svg?raw'; import './augmentation.js'; import "../theme/mathform.css"; +import 'mathlive'; import 'mathlive/fonts.css'; import 'mathlive/static.css'; diff --git a/packages/ckeditor5-math/src/mathui.ts b/packages/ckeditor5-math/src/mathui.ts index 76290626f..504adf77a 100644 --- a/packages/ckeditor5-math/src/mathui.ts +++ b/packages/ckeditor5-math/src/mathui.ts @@ -4,7 +4,6 @@ import mathIcon from '../theme/icons/math.svg?raw'; import { Plugin, ClickObserver, ButtonView, ContextualBalloon, clickOutsideHandler, CKEditorError, uid } from 'ckeditor5'; import { getBalloonPositionData } from './utils.js'; import MathCommand from './mathcommand.js'; -import 'mathlive'; const mathKeystroke = 'Ctrl+M'; diff --git a/packages/ckeditor5-math/src/ui/mathliveinputview.ts b/packages/ckeditor5-math/src/ui/mathliveinputview.ts index 148ae7f14..689ec891a 100644 --- a/packages/ckeditor5-math/src/ui/mathliveinputview.ts +++ b/packages/ckeditor5-math/src/ui/mathliveinputview.ts @@ -1,4 +1,5 @@ import { View, type Locale } from 'ckeditor5'; +import 'mathlive'; /** * A view that wraps the MathLive `` web component for interactive LaTeX equation editing. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 903192f4f..1e470a489 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -274,6 +274,9 @@ importers: marked: specifier: 17.0.0 version: 17.0.0 + mathlive: + specifier: 0.108.2 + version: 0.108.2 mermaid: specifier: 11.12.1 version: 11.12.1 @@ -1055,9 +1058,6 @@ importers: packages/ckeditor5-math: dependencies: - '@ckeditor/ckeditor5-icons': - specifier: 47.2.0 - version: 47.2.0 mathlive: specifier: 0.108.2 version: 0.108.2 @@ -15584,6 +15584,8 @@ snapshots: '@ckeditor/ckeditor5-core': 47.2.0 '@ckeditor/ckeditor5-upload': 47.2.0 ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-ai@47.2.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)': dependencies: @@ -15990,6 +15992,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.2.0 ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-multi-root@47.2.0': dependencies: From 569b09609da96bba97eac7d47e39b488e95e8395 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Sat, 22 Nov 2025 00:01:14 +0100 Subject: [PATCH 05/29] Remove mathlive dependency and chunking --- apps/client/package.json | 1 - apps/client/vite.config.mts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index d8db8801d..ba0fbb97b 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -54,7 +54,6 @@ "leaflet-gpx": "2.2.0", "mark.js": "8.11.1", "marked": "17.0.0", - "mathlive": "0.108.2", "mermaid": "11.12.1", "mind-elixir": "5.3.6", "normalize.css": "8.0.1", diff --git a/apps/client/vite.config.mts b/apps/client/vite.config.mts index 12eac25ce..8d3b0c583 100644 --- a/apps/client/vite.config.mts +++ b/apps/client/vite.config.mts @@ -83,7 +83,7 @@ export default defineConfig(() => ({ chunkFileNames: "src/[name].js", assetFileNames: "src/[name].[ext]", manualChunks: { - "ckeditor5": [ "@triliumnext/ckeditor5", "mathlive" ], + "ckeditor5": [ "@triliumnext/ckeditor5" ], "boxicons": [ "../../node_modules/boxicons/css/boxicons.min.css" ] }, }, From 4eef30f8b5a093f5011f946b7da7baaa311415ed Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Sat, 22 Nov 2025 00:20:20 +0100 Subject: [PATCH 06/29] Fix names --- ...command-on-mainFormView-submit-event-1.png | Bin 0 -> 23437 bytes packages/ckeditor5-math/tests/mathui.ts | 20 ++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 packages/ckeditor5-math/tests/__screenshots__/mathui.ts/MathUI--showUI---math-form-view-binding-should-execute-math-command-on-mainFormView-submit-event-1.png diff --git a/packages/ckeditor5-math/tests/__screenshots__/mathui.ts/MathUI--showUI---math-form-view-binding-should-execute-math-command-on-mainFormView-submit-event-1.png b/packages/ckeditor5-math/tests/__screenshots__/mathui.ts/MathUI--showUI---math-form-view-binding-should-execute-math-command-on-mainFormView-submit-event-1.png new file mode 100644 index 0000000000000000000000000000000000000000..30a255f4a953e913a0e192c9873ece104bc7f4bd GIT binary patch literal 23437 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 { command.isEnabled = true; - expect( formView!.mathInputView.isReadOnly ).to.be.false; + expect( formView!.mathLiveInputView.isReadOnly ).to.be.false; expect( formView!.saveButtonView.isEnabled ).to.be.false; expect( formView!.cancelButtonView.isEnabled ).to.be.true; command.isEnabled = false; - expect( formView!.mathInputView.isReadOnly ).to.be.true; + expect( formView!.mathLiveInputView.isReadOnly ).to.be.true; expect( formView!.saveButtonView.isEnabled ).to.be.false; expect( formView!.cancelButtonView.isEnabled ).to.be.true; } ); @@ -407,24 +407,32 @@ describe( 'MathUI', () => { setModelData( editor.model, 'f[o]o' ); } ); - it( 'should bind mainFormView.mathInputView#value to math command value', () => { + it( 'should bind mainFormView.mathLiveInputView#value to math command value', () => { const command = editor.commands.get( 'math' ); - expect( formView!.mathInputView.value ).to.be.null; + expect( formView!.mathLiveInputView.value ).to.be.null; command!.value = 'x^2'; - expect( formView!.mathInputView.value ).to.equal( 'x^2' ); + expect( formView!.mathLiveInputView.value ).to.equal( 'x^2' ); } ); it( 'should execute math command on mainFormView#submit event', () => { const executeSpy = vi.spyOn( editor, 'execute' ); - formView!.mathInputView.value = 'x^2'; + formView!.mathLiveInputView.value = 'x^2'; formView!.fire( 'submit' ); expect( executeSpy.mock.lastCall?.slice( 0, 2 ) ).toMatchObject( [ 'math', 'x^2' ] ); } ); + it( 'should sync mathLiveInputView and rawLatexInputView', () => { + formView!.mathLiveInputView.value = 'x^2'; + expect( formView!.rawLatexInputView.value ).to.equal( 'x^2' ); + + formView!.rawLatexInputView.value = '\\frac{1}{2}'; + expect( formView!.mathLiveInputView.value ).to.equal( '\\frac{1}{2}' ); + } ); + it( 'should hide the balloon on mainFormView#cancel if math command does not have a value', () => { mathUIFeature._showUI(); formView!.fire( 'cancel' ); From e225794f72da5c8912d82ea6614c077daee531d8 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Sat, 22 Nov 2025 21:35:37 +0100 Subject: [PATCH 07/29] Better window focus handling in MathFormView --- .../ckeditor5-math/src/ui/mainformview.ts | 80 ++++++++++++++++++- packages/ckeditor5-math/theme/mathform.css | 2 +- 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-math/src/ui/mainformview.ts b/packages/ckeditor5-math/src/ui/mainformview.ts index 7c57fce8f..f5f630dff 100644 --- a/packages/ckeditor5-math/src/ui/mainformview.ts +++ b/packages/ckeditor5-math/src/ui/mainformview.ts @@ -19,7 +19,6 @@ export default class MainFormView extends View { public previewLabel?: LabelView; public mathView?: MathView; public override locale: Locale = new Locale(); - public lazyLoad: undefined | ( () => Promise ); constructor( locale: Locale, @@ -161,6 +160,16 @@ export default class MainFormView extends View { if ( this.element ) { this.keystrokes.listenTo( this.element ); } + + this._initResizeSync(); + } + + public override destroy(): void { + super.destroy(); + this._resizeObserver?.disconnect(); + document.removeEventListener( 'mouseup', this._onMouseUp ); + this.mathLiveInputView.element?.removeEventListener( 'mousedown', this._onMouseDown ); + this.rawLatexInputView.element?.removeEventListener( 'mousedown', this._onMouseDown ); } public focus(): void { @@ -193,6 +202,75 @@ export default class MainFormView extends View { } } ); + private _resizeObserver: ResizeObserver | null = null; + private _activeResizeTarget: HTMLElement | null = null; + + private _onMouseUp = () => { + this._activeResizeTarget = null; + // Re-observe everything to ensure state is reset + if ( this.mathLiveInputView.element ) this._resizeObserver?.observe( this.mathLiveInputView.element ); + if ( this.rawLatexInputView.element ) this._resizeObserver?.observe( this.rawLatexInputView.element ); + }; + + private _onMouseDown = ( evt: Event ) => { + const target = evt.currentTarget as HTMLElement; + this._activeResizeTarget = target; + + // Stop observing the OTHER element to prevent loops and errors while resizing + if ( target === this.mathLiveInputView.element ) { + if ( this.rawLatexInputView.element ) { + this._resizeObserver?.unobserve( this.rawLatexInputView.element ); + } + } else if ( target === this.rawLatexInputView.element ) { + if ( this.mathLiveInputView.element ) { + this._resizeObserver?.unobserve( this.mathLiveInputView.element ); + } + } + }; + + private _initResizeSync() { + if ( this.mathLiveInputView.element ) { + this.mathLiveInputView.element.addEventListener( 'mousedown', this._onMouseDown ); + } + if ( this.rawLatexInputView.element ) { + this.rawLatexInputView.element.addEventListener( 'mousedown', this._onMouseDown ); + } + document.addEventListener( 'mouseup', this._onMouseUp ); + + // Synchronize width between MathLive and Raw LaTeX inputs + this._resizeObserver = new ResizeObserver( entries => { + if ( !this._activeResizeTarget ) { + return; + } + + for ( const entry of entries ) { + if ( entry.target === this._activeResizeTarget ) { + // Use style.width directly to avoid box-sizing issues causing infinite growth + const width = ( entry.target as HTMLElement ).style.width; + + if ( !width ) continue; + + const other = entry.target === this.mathLiveInputView.element + ? this.rawLatexInputView.element + : this.mathLiveInputView.element; + + if ( other && other.style.width !== width ) { + window.requestAnimationFrame( () => { + other.style.width = width; + } ); + } + } + } + } ); + + if ( this.mathLiveInputView.element ) { + this._resizeObserver.observe( this.mathLiveInputView.element ); + } + if ( this.rawLatexInputView.element ) { + this._resizeObserver.observe( this.rawLatexInputView.element ); + } + } + /** * Creates the MathLive visual equation editor. * diff --git a/packages/ckeditor5-math/theme/mathform.css b/packages/ckeditor5-math/theme/mathform.css index 45446d8f7..5821c7fd2 100644 --- a/packages/ckeditor5-math/theme/mathform.css +++ b/packages/ckeditor5-math/theme/mathform.css @@ -136,7 +136,7 @@ min-width: 100%; min-height: 140px; max-height: 70vh; - resize: vertical; + resize: both; overflow: auto; padding-bottom: 0; box-sizing: border-box; From 48a4b81fbef61e131ab739b90d55797b249111c7 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Sat, 22 Nov 2025 21:40:55 +0100 Subject: [PATCH 08/29] remove automated screenshot files --- .gitignore | 2 +- ...hInputView-value-to-math-command-value-1.png | Bin 23437 -> 0 bytes ...h-command-on-mainFormView-submit-event-1.png | Bin 23437 -> 0 bytes ...-element-when-math-command-is-disabled-1.png | Bin 8642 -> 0 bytes 4 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 packages/ckeditor5-math/tests/__screenshots__/mathui.ts/MathUI--showUI---math-form-view-binding-should-bind-mainFormView-mathInputView-value-to-math-command-value-1.png delete mode 100644 packages/ckeditor5-math/tests/__screenshots__/mathui.ts/MathUI--showUI---math-form-view-binding-should-execute-math-command-on-mainFormView-submit-event-1.png delete mode 100644 packages/ckeditor5-math/tests/__screenshots__/mathui.ts/MathUI--showUI---should-disable--mainFormView-element-when-math-command-is-disabled-1.png diff --git a/.gitignore b/.gitignore index b2c4e3c46..9321d866f 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,4 @@ upload .svelte-kit # docs -site/ +**/__screenshots__/ diff --git a/packages/ckeditor5-math/tests/__screenshots__/mathui.ts/MathUI--showUI---math-form-view-binding-should-bind-mainFormView-mathInputView-value-to-math-command-value-1.png b/packages/ckeditor5-math/tests/__screenshots__/mathui.ts/MathUI--showUI---math-form-view-binding-should-bind-mainFormView-mathInputView-value-to-math-command-value-1.png deleted file mode 100644 index 30a255f4a953e913a0e192c9873ece104bc7f4bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23437 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 UOQ2L9t{`rxYF*90a_ixL0i4=(egFUf From de80eb4806e17f6e04b843c21fdd6cdc5672475a Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Sat, 22 Nov 2025 22:42:34 +0100 Subject: [PATCH 09/29] Improve mathform.css styling for better visual integration --- packages/ckeditor5-math/theme/mathform.css | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/ckeditor5-math/theme/mathform.css b/packages/ckeditor5-math/theme/mathform.css index 5821c7fd2..6a15c852a 100644 --- a/packages/ckeditor5-math/theme/mathform.css +++ b/packages/ckeditor5-math/theme/mathform.css @@ -186,19 +186,12 @@ border-radius: var(--ck-border-radius, 6px); font-size: var(--ck-font-size-base); box-sizing: border-box; - background: var(--input-background-color); - color: var(--input-text-color); + background: var(--ck-color-input-background,) !important; + color: var(--ck-color-input-text, inherit); outline: 3px solid transparent; outline-offset: 6px; } -/* Hover state */ -.ck.ck-math-form math-field:hover, -.ck.ck-math-form textarea:hover { - background: var(--input-hover-background); - color: var(--input-hover-color); -} - /* Make the raw LaTeX textarea flat (no rounded corners or hover animation) */ .ck-math-view .ck-labeled-field-view textarea { border-radius: 0 !important; @@ -209,8 +202,8 @@ .ck-math-view .ck-labeled-field-view textarea:hover, .ck-math-view .ck-labeled-field-view textarea:focus { - background: var(--input-background-color); - color: var(--input-text-color); + background: var(--ck-color-input-background, ) !important; + color: var(--ck-color-input-text, inherit) !important; outline: none !important; box-shadow: none !important; transition: none !important; From a0f16f918428edbbee4bcd5830ac09af04db4cdd Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Sun, 23 Nov 2025 13:09:56 +0100 Subject: [PATCH 10/29] Fix typos in mathform.css --- packages/ckeditor5-math/theme/mathform.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ckeditor5-math/theme/mathform.css b/packages/ckeditor5-math/theme/mathform.css index 6a15c852a..9537cdbb0 100644 --- a/packages/ckeditor5-math/theme/mathform.css +++ b/packages/ckeditor5-math/theme/mathform.css @@ -186,7 +186,7 @@ border-radius: var(--ck-border-radius, 6px); font-size: var(--ck-font-size-base); box-sizing: border-box; - background: var(--ck-color-input-background,) !important; + background: var(--ck-color-input-background) !important; color: var(--ck-color-input-text, inherit); outline: 3px solid transparent; outline-offset: 6px; @@ -202,7 +202,7 @@ .ck-math-view .ck-labeled-field-view textarea:hover, .ck-math-view .ck-labeled-field-view textarea:focus { - background: var(--ck-color-input-background, ) !important; + background: var(--ck-color-input-background) !important; color: var(--ck-color-input-text, inherit) !important; outline: none !important; box-shadow: none !important; From 56834cb88a44c2d4d55188423141fecd8fe8b672 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Sun, 23 Nov 2025 13:29:26 +0100 Subject: [PATCH 11/29] Improve MathLive and Raw LaTeX input views to propagate mousedown events --- .../ckeditor5-math/src/ui/mainformview.ts | 25 +++++++++++-------- .../src/ui/mathliveinputview.ts | 5 ++++ .../src/ui/rawlatexinputview.ts | 13 +++++++--- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/ckeditor5-math/src/ui/mainformview.ts b/packages/ckeditor5-math/src/ui/mainformview.ts index f5f630dff..3a1d913d8 100644 --- a/packages/ckeditor5-math/src/ui/mainformview.ts +++ b/packages/ckeditor5-math/src/ui/mainformview.ts @@ -168,8 +168,6 @@ export default class MainFormView extends View { super.destroy(); this._resizeObserver?.disconnect(); document.removeEventListener( 'mouseup', this._onMouseUp ); - this.mathLiveInputView.element?.removeEventListener( 'mousedown', this._onMouseDown ); - this.rawLatexInputView.element?.removeEventListener( 'mousedown', this._onMouseDown ); } public focus(): void { @@ -212,8 +210,7 @@ export default class MainFormView extends View { if ( this.rawLatexInputView.element ) this._resizeObserver?.observe( this.rawLatexInputView.element ); }; - private _onMouseDown = ( evt: Event ) => { - const target = evt.currentTarget as HTMLElement; + private _onMouseDown( target: HTMLElement ) { this._activeResizeTarget = target; // Stop observing the OTHER element to prevent loops and errors while resizing @@ -226,15 +223,21 @@ export default class MainFormView extends View { this._resizeObserver?.unobserve( this.mathLiveInputView.element ); } } - }; + } private _initResizeSync() { - if ( this.mathLiveInputView.element ) { - this.mathLiveInputView.element.addEventListener( 'mousedown', this._onMouseDown ); - } - if ( this.rawLatexInputView.element ) { - this.rawLatexInputView.element.addEventListener( 'mousedown', this._onMouseDown ); - } + this.listenTo( this.mathLiveInputView, 'mousedown', () => { + if ( this.mathLiveInputView.element ) { + this._onMouseDown( this.mathLiveInputView.element ); + } + } ); + + this.listenTo( this.rawLatexInputView, 'mousedown', () => { + if ( this.rawLatexInputView.element ) { + this._onMouseDown( this.rawLatexInputView.element ); + } + } ); + document.addEventListener( 'mouseup', this._onMouseUp ); // Synchronize width between MathLive and Raw LaTeX inputs diff --git a/packages/ckeditor5-math/src/ui/mathliveinputview.ts b/packages/ckeditor5-math/src/ui/mathliveinputview.ts index 689ec891a..9dbce2903 100644 --- a/packages/ckeditor5-math/src/ui/mathliveinputview.ts +++ b/packages/ckeditor5-math/src/ui/mathliveinputview.ts @@ -49,6 +49,11 @@ export default class MathLiveInputView extends View { public override render(): void { super.render(); + // Propagate mousedown event to the view + this.element!.addEventListener( 'mousedown', ( evt ) => { + this.fire( 'mousedown', evt ); + } ); + // Create the MathLive math-field custom element const mathfield = document.createElement( 'math-field' ) as any; this.mathfield = mathfield; diff --git a/packages/ckeditor5-math/src/ui/rawlatexinputview.ts b/packages/ckeditor5-math/src/ui/rawlatexinputview.ts index 871f23e72..4c994778d 100644 --- a/packages/ckeditor5-math/src/ui/rawlatexinputview.ts +++ b/packages/ckeditor5-math/src/ui/rawlatexinputview.ts @@ -2,21 +2,21 @@ import { LabeledFieldView, createLabeledTextarea, type Locale, type TextareaView /** * A labeled textarea view for direct LaTeX code editing. - * + * * This provides a plain text input for users who prefer to write LaTeX syntax directly * or need to paste/edit raw LaTeX code. */ export default class RawLatexInputView extends LabeledFieldView { /** * The current LaTeX value. - * + * * @observable */ public declare value: string; - + /** * Whether the input is in read-only mode. - * + * * @observable */ public declare isReadOnly: boolean; @@ -57,5 +57,10 @@ export default class RawLatexInputView extends LabeledFieldView { public override render(): void { super.render(); // All styling is handled via CSS in mathform.css + + // Propagate mousedown event to the view + this.element!.addEventListener( 'mousedown', ( evt ) => { + this.fire( 'mousedown', evt ); + } ); } } From 1471a726337ac5ac1b017726f5a6db2d46b1c89a Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Sun, 23 Nov 2025 13:34:22 +0100 Subject: [PATCH 12/29] refactor: avoid recursive updates in mathLiveInput by normalizing value before updateing --- .../ckeditor5-math/src/ui/mainformview.ts | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/ckeditor5-math/src/ui/mainformview.ts b/packages/ckeditor5-math/src/ui/mainformview.ts index 3a1d913d8..46f6e1aaf 100644 --- a/packages/ckeditor5-math/src/ui/mainformview.ts +++ b/packages/ckeditor5-math/src/ui/mainformview.ts @@ -283,33 +283,29 @@ export default class MainFormView extends View { const mathLiveInput = new MathLiveInputView( this.locale ); const onInput = () => { - const rawValue = mathLiveInput.value ?? ''; - let equationInput = rawValue.trim(); + let equationInput = ( mathLiveInput.value ?? '' ).trim(); - // If input has delimiters + // If input has delimiters, strip them and update the display mode. if ( hasDelimiters( equationInput ) ) { - // Get equation without delimiters const params = extractDelimiters( equationInput ); - - // Remove delimiters from input field - mathLiveInput.value = params.equation; - equationInput = params.equation; - - // Update display button and preview this.displayButtonView.isOn = params.display; } const normalizedEquation = equationInput.length ? equationInput : null; + + // Update self if needed. if ( mathLiveInput.value !== normalizedEquation ) { mathLiveInput.value = normalizedEquation; } - // Sync to raw LaTeX textarea - this.rawLatexInputView.value = equationInput; + // Sync to raw LaTeX textarea if its value is different. + if ( this.rawLatexInputView.value !== equationInput ) { + this.rawLatexInputView.value = equationInput; + } - if ( this.previewEnabled && this.mathView ) { - // Update preview + // Update preview if enabled and its value is different. + if ( this.previewEnabled && this.mathView && this.mathView.value !== equationInput ) { this.mathView.value = equationInput; } }; From edba8188fef402b5e1cf3f38c9fd5231d266b923 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Sun, 23 Nov 2025 13:44:28 +0100 Subject: [PATCH 13/29] Fix dark selection colors in MathLive math-field --- packages/ckeditor5-math/theme/mathform.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/ckeditor5-math/theme/mathform.css b/packages/ckeditor5-math/theme/mathform.css index 9537cdbb0..300e759d3 100644 --- a/packages/ckeditor5-math/theme/mathform.css +++ b/packages/ckeditor5-math/theme/mathform.css @@ -192,6 +192,13 @@ outline-offset: 6px; } +/* Override MathLive selection colors to prevent dark blocks in light mode */ +.ck.ck-math-form math-field { + --selection-background-color: rgba(33, 150, 243, 0.2); + --selection-color: inherit; + --contains-highlight-background-color: rgba(0, 0, 0, 0.05); +} + /* Make the raw LaTeX textarea flat (no rounded corners or hover animation) */ .ck-math-view .ck-labeled-field-view textarea { border-radius: 0 !important; From 5821c350e18e5d68da39b92ad1d9f78c0cde5911 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Sun, 23 Nov 2025 17:58:51 +0100 Subject: [PATCH 14/29] Fixing class property initialization order --- .../ckeditor5-math/src/ui/mainformview.ts | 21 ++++++++++--------- pnpm-lock.yaml | 9 ++------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/packages/ckeditor5-math/src/ui/mainformview.ts b/packages/ckeditor5-math/src/ui/mainformview.ts index 46f6e1aaf..ac25bc5f0 100644 --- a/packages/ckeditor5-math/src/ui/mainformview.ts +++ b/packages/ckeditor5-math/src/ui/mainformview.ts @@ -9,16 +9,16 @@ import '../../theme/mathform.css'; import type { KatexOptions } from '../typings-external.js'; export default class MainFormView extends View { - public saveButtonView: ButtonView; - public mathLiveInputView: MathLiveInputView; - public rawLatexInputView: RawLatexInputView; - public rawLatexLabel: LabelView; - public displayButtonView: SwitchButtonView; - public cancelButtonView: ButtonView; - public previewEnabled: boolean; - public previewLabel?: LabelView; - public mathView?: MathView; - public override locale: Locale = new Locale(); + public declare saveButtonView: ButtonView; + public declare mathLiveInputView: MathLiveInputView; + public declare rawLatexInputView: RawLatexInputView; + public declare rawLatexLabel: LabelView; + public declare displayButtonView: SwitchButtonView; + public declare cancelButtonView: ButtonView; + public declare previewEnabled: boolean; + public declare previewLabel?: LabelView; + public declare mathView?: MathView; + public override locale: Locale; constructor( locale: Locale, @@ -38,6 +38,7 @@ export default class MainFormView extends View { katexRenderOptions: KatexOptions ) { super( locale ); + this.locale = locale; const t = locale.t; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e470a489..65308fda2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -274,9 +274,6 @@ importers: marked: specifier: 17.0.0 version: 17.0.0 - mathlive: - specifier: 0.108.2 - version: 0.108.2 mermaid: specifier: 11.12.1 version: 11.12.1 @@ -15584,8 +15581,6 @@ snapshots: '@ckeditor/ckeditor5-core': 47.2.0 '@ckeditor/ckeditor5-upload': 47.2.0 ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-ai@47.2.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)': dependencies: @@ -16016,8 +16011,6 @@ snapshots: '@ckeditor/ckeditor5-table': 47.2.0 '@ckeditor/ckeditor5-utils': 47.2.0 ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-emoji@47.2.0': dependencies: @@ -16502,6 +16495,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.2.0 '@ckeditor/ckeditor5-utils': 47.2.0 ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-restricted-editing@47.2.0': dependencies: From 4f044c4a57f2b9b7b5d6d4b5724120774e7b5b42 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Sun, 23 Nov 2025 22:43:07 +0100 Subject: [PATCH 15/29] Use icons form CKEditor5 icons, instead of testing icons. --- packages/ckeditor5-math/package.json | 1 + packages/ckeditor5-math/src/ui/mainformview.ts | 4 ++-- pnpm-lock.yaml | 9 +++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/ckeditor5-math/package.json b/packages/ckeditor5-math/package.json index 0f3e8a37a..34f845f91 100644 --- a/packages/ckeditor5-math/package.json +++ b/packages/ckeditor5-math/package.json @@ -71,6 +71,7 @@ ] }, "dependencies": { + "@ckeditor/ckeditor5-icons": "47.2.0", "mathlive": "0.108.2" } } diff --git a/packages/ckeditor5-math/src/ui/mainformview.ts b/packages/ckeditor5-math/src/ui/mainformview.ts index ac25bc5f0..ab904c86e 100644 --- a/packages/ckeditor5-math/src/ui/mainformview.ts +++ b/packages/ckeditor5-math/src/ui/mainformview.ts @@ -1,6 +1,6 @@ import { ButtonView, FocusCycler, LabelView, submitHandler, SwitchButtonView, View, ViewCollection, type FocusableView, Locale, FocusTracker, KeystrokeHandler } from 'ckeditor5'; -import IconCheck from '../../theme/icons/check.svg?raw'; -import IconCancel from '../../theme/icons/cancel.svg?raw'; +import IconCheck from '@ckeditor/ckeditor5-icons/theme/icons/check.svg?raw'; +import IconCancel from '@ckeditor/ckeditor5-icons/theme/icons/cancel.svg?raw'; import { extractDelimiters, hasDelimiters } from '../utils.js'; import MathView from './mathview.js'; import MathLiveInputView from './mathliveinputview.js'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65308fda2..54486943a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1055,6 +1055,9 @@ importers: packages/ckeditor5-math: dependencies: + '@ckeditor/ckeditor5-icons': + specifier: 47.2.0 + version: 47.2.0 mathlive: specifier: 0.108.2 version: 0.108.2 @@ -15581,6 +15584,8 @@ snapshots: '@ckeditor/ckeditor5-core': 47.2.0 '@ckeditor/ckeditor5-upload': 47.2.0 ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-ai@47.2.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)': dependencies: @@ -16011,6 +16016,8 @@ snapshots: '@ckeditor/ckeditor5-table': 47.2.0 '@ckeditor/ckeditor5-utils': 47.2.0 ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-emoji@47.2.0': dependencies: @@ -16495,8 +16502,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.2.0 '@ckeditor/ckeditor5-utils': 47.2.0 ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-restricted-editing@47.2.0': dependencies: From d5e601eae9325f9914d1ac71fd5409aa4c44915a Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Mon, 24 Nov 2025 17:56:18 +0100 Subject: [PATCH 16/29] Simpliyfied resize logic for math input form and improved css --- .../ckeditor5-math/src/ui/mainformview.ts | 78 ----- .../src/ui/mathliveinputview.ts | 5 +- .../src/ui/rawlatexinputview.ts | 6 +- packages/ckeditor5-math/theme/mathform.css | 325 ++++++++---------- 4 files changed, 142 insertions(+), 272 deletions(-) diff --git a/packages/ckeditor5-math/src/ui/mainformview.ts b/packages/ckeditor5-math/src/ui/mainformview.ts index ab904c86e..cb665347c 100644 --- a/packages/ckeditor5-math/src/ui/mainformview.ts +++ b/packages/ckeditor5-math/src/ui/mainformview.ts @@ -161,14 +161,10 @@ export default class MainFormView extends View { if ( this.element ) { this.keystrokes.listenTo( this.element ); } - - this._initResizeSync(); } public override destroy(): void { super.destroy(); - this._resizeObserver?.disconnect(); - document.removeEventListener( 'mouseup', this._onMouseUp ); } public focus(): void { @@ -201,80 +197,6 @@ export default class MainFormView extends View { } } ); - private _resizeObserver: ResizeObserver | null = null; - private _activeResizeTarget: HTMLElement | null = null; - - private _onMouseUp = () => { - this._activeResizeTarget = null; - // Re-observe everything to ensure state is reset - if ( this.mathLiveInputView.element ) this._resizeObserver?.observe( this.mathLiveInputView.element ); - if ( this.rawLatexInputView.element ) this._resizeObserver?.observe( this.rawLatexInputView.element ); - }; - - private _onMouseDown( target: HTMLElement ) { - this._activeResizeTarget = target; - - // Stop observing the OTHER element to prevent loops and errors while resizing - if ( target === this.mathLiveInputView.element ) { - if ( this.rawLatexInputView.element ) { - this._resizeObserver?.unobserve( this.rawLatexInputView.element ); - } - } else if ( target === this.rawLatexInputView.element ) { - if ( this.mathLiveInputView.element ) { - this._resizeObserver?.unobserve( this.mathLiveInputView.element ); - } - } - } - - private _initResizeSync() { - this.listenTo( this.mathLiveInputView, 'mousedown', () => { - if ( this.mathLiveInputView.element ) { - this._onMouseDown( this.mathLiveInputView.element ); - } - } ); - - this.listenTo( this.rawLatexInputView, 'mousedown', () => { - if ( this.rawLatexInputView.element ) { - this._onMouseDown( this.rawLatexInputView.element ); - } - } ); - - document.addEventListener( 'mouseup', this._onMouseUp ); - - // Synchronize width between MathLive and Raw LaTeX inputs - this._resizeObserver = new ResizeObserver( entries => { - if ( !this._activeResizeTarget ) { - return; - } - - for ( const entry of entries ) { - if ( entry.target === this._activeResizeTarget ) { - // Use style.width directly to avoid box-sizing issues causing infinite growth - const width = ( entry.target as HTMLElement ).style.width; - - if ( !width ) continue; - - const other = entry.target === this.mathLiveInputView.element - ? this.rawLatexInputView.element - : this.mathLiveInputView.element; - - if ( other && other.style.width !== width ) { - window.requestAnimationFrame( () => { - other.style.width = width; - } ); - } - } - } - } ); - - if ( this.mathLiveInputView.element ) { - this._resizeObserver.observe( this.mathLiveInputView.element ); - } - if ( this.rawLatexInputView.element ) { - this._resizeObserver.observe( this.rawLatexInputView.element ); - } - } - /** * Creates the MathLive visual equation editor. * diff --git a/packages/ckeditor5-math/src/ui/mathliveinputview.ts b/packages/ckeditor5-math/src/ui/mathliveinputview.ts index 9dbce2903..d337a9bdb 100644 --- a/packages/ckeditor5-math/src/ui/mathliveinputview.ts +++ b/packages/ckeditor5-math/src/ui/mathliveinputview.ts @@ -49,10 +49,7 @@ export default class MathLiveInputView extends View { public override render(): void { super.render(); - // Propagate mousedown event to the view - this.element!.addEventListener( 'mousedown', ( evt ) => { - this.fire( 'mousedown', evt ); - } ); + // (Removed global area click-to-focus logic; focusing now relies on direct field interaction.) // Create the MathLive math-field custom element const mathfield = document.createElement( 'math-field' ) as any; diff --git a/packages/ckeditor5-math/src/ui/rawlatexinputview.ts b/packages/ckeditor5-math/src/ui/rawlatexinputview.ts index 4c994778d..5831fad08 100644 --- a/packages/ckeditor5-math/src/ui/rawlatexinputview.ts +++ b/packages/ckeditor5-math/src/ui/rawlatexinputview.ts @@ -57,10 +57,6 @@ export default class RawLatexInputView extends LabeledFieldView { public override render(): void { super.render(); // All styling is handled via CSS in mathform.css - - // Propagate mousedown event to the view - this.element!.addEventListener( 'mousedown', ( evt ) => { - this.fire( 'mousedown', evt ); - } ); + // (Removed obsolete mousedown propagation; no longer needed after resize & gray-area click removal.) } } diff --git a/packages/ckeditor5-math/theme/mathform.css b/packages/ckeditor5-math/theme/mathform.css index 300e759d3..3f2546c24 100644 --- a/packages/ckeditor5-math/theme/mathform.css +++ b/packages/ckeditor5-math/theme/mathform.css @@ -1,227 +1,182 @@ /** * Math equation editor dialog styles - * - * This stylesheet provides the layout and styling for the math equation editor, - * which includes a MathLive visual editor, a raw LaTeX textarea, and control buttons. - * The dialog is resizable and uses a flexible layout to accommodate different content sizes. + * Complete version with scrolling fixes for preview and input */ /* ============================================================================ - Form Layout + 1. Main Layout Containers (The Skeleton) ========================================================================= */ .ck.ck-math-form { - display: flex; - flex-direction: column; - padding: var(--ck-spacing-standard); - width: 100%; - height: 100%; - box-sizing: border-box; - - @media screen and (max-width: 600px) { - flex-wrap: wrap; - } + display: flex; + flex-direction: column; + padding: var(--ck-spacing-standard); + box-sizing: border-box; + max-width: 80vw; + height: 100%; /* Never wider than screen */ + overflow-x: hidden; /* Prevent the main window from scrolling horizontally */ } - -/* ============================================================================ - Button Row - ========================================================================= */ - -/* Button row */ -.ck-math-button-row { - display: flex; - gap: var(--ck-spacing-standard); - flex-shrink: 0; - margin-top: var(--ck-spacing-standard); - width: fit-content; +/* Mobile responsiveness */ +@media screen and (max-width: 600px) { + .ck.ck-math-form { + flex-wrap: wrap; + } } -/* Scrollable content area */ -.ck-math-scroll { - display: flex; - flex-direction: column; - flex: 1 1 auto; - min-height: 0; - min-width: 0; - overflow: hidden; -} - -/* ============================================================================ - Math Panel Layout - ========================================================================= */ - -/* Math panel layout */ .ck-math-view { - display: flex; - flex-direction: column; - gap: var(--ck-spacing-standard); - flex: 1 1 auto; - min-height: fit-content; + display: flex; + flex-direction: column; + flex: 1 1 auto; + gap: var(--ck-spacing-standard); + min-height: fit-content; + min-width: 0; + width: 100%; +} + +.ck-math-button-row { + display: flex; + flex-shrink: 0; + gap: var(--ck-spacing-standard); + margin-top: var(--ck-spacing-standard); + width: fit-content; + max-width: 100%; + flex-wrap: wrap; } /* ============================================================================ - MathLive Integration + 2. Shared Styling (Applies to Input AND Preview) ========================================================================= */ -/* MathLive input container */ +/* This targets both the top input AND the bottom preview */ +.ck.ck-math-form math-field, +.ck.ck-math-form textarea { + box-sizing: border-box; + padding: var(--ck-spacing-small); + background: var(--ck-color-input-background) !important; + color: var(--ck-color-input-text, inherit); + font-size: var(--ck-font-size-base); + border: none !important; + border-radius: var(--ck-border-radius, 6px); + outline: 3px solid transparent; + outline-offset: 6px; +} + +/* SPECIFIC FIX FOR PREVIEW SCROLLING */ +.ck.ck-math-form math-field { + display: block !important; + width: 100%; + + /* 3. Stop it from growing infinite */ + max-width: 100%; + + /* 4. Enable scrollbars for the red matrix area */ + overflow-x: auto !important; + + /* Theme overrides */ + --selection-background-color: rgba(33, 150, 243, 0.2); + --selection-color: inherit; + --contains-highlight-background-color: rgba(0, 0, 0, 0.05); +} + +/* ============================================================================ + 3. MathLive Input Specifics (The Top Box) + ========================================================================= */ + +/* Wrapper for the editable input at the top */ .ck.ck-mathlive-input { - display: inline-block; - flex: 0 0 auto; - width: auto; - min-width: 100%; - min-height: 140px; - max-height: 70vh; - resize: both; - overflow: auto; - padding-bottom: var(--ck-spacing-small); - box-sizing: border-box; + display: inline-block; + flex: 0 0 auto; + width: 100%; + max-width: 100%; /* Safety */ + min-height: fit-content; + max-height: 80vh; + overflow: auto; + padding-bottom: var(--ck-spacing-small); + resize: none; } -/* MathLive field styling */ -.ck.ck-mathlive-input math-field { - display: block !important; - width: 100%; - height: 100%; - min-height: 140px; - box-sizing: border-box; - resize: none !important; - overflow: hidden !important; -} - -/* Style MathLive shadow DOM parts so the whole area is interactive */ +/* Shadow DOM Layout adjustments (Keep your existing logic) */ .ck.ck-math-form math-field::part(container), .ck.ck-math-form math-field::part(content), .ck.ck-math-form math-field::part(field) { - display: flex; - flex-direction: column; - flex: 1 1 auto; - min-height: 100%; - height: 100%; - align-items: flex-start; - justify-content: flex-start; + display: flex; + flex-direction: column; + flex: 1 1 auto; + height: 100%; + align-items: flex-start; + justify-content: flex-start; } - -/* Position MathLive virtual keyboard toggle button */ -.ck.ck-math-form math-field::part(virtual-keyboard-toggle) { - position: absolute; - right: 8px; - top: 8px; -} - -/* Position MathLive menu toggle button and ensure it's always visible */ +/* UI Buttons positions */ +.ck.ck-math-form math-field::part(virtual-keyboard-toggle), .ck.ck-math-form math-field::part(menu-toggle) { - position: absolute; - right: 8px; - top: 48px; - /* Force visibility even when field is empty */ - display: flex !important; - visibility: visible !important; + position: absolute; + top: 8px; +} +.ck.ck-math-form math-field::part(virtual-keyboard-toggle) { right: 40px; } +.ck.ck-math-form math-field::part(menu-toggle) { + right: 8px; + display: flex !important; + visibility: visible !important; } - /* ============================================================================ - Raw LaTeX Integration - ========================================================================= */ - -/* Mirror MathLive container behavior for the labeled textarea wrapper */ - - -.ck-math-view .ck-labeled-field-view { - display: flex; - flex-direction: column; - flex: 0 0 auto; - width: 100%; - min-width: 100%; - min-height: 140px; - max-height: 70vh; - resize: both; - overflow: auto; - padding-bottom: 0; - box-sizing: border-box; - background: transparent; - border: none; - border-radius: 0; - outline: none; -} - -/* Let the internal wrapper stretch so the textarea can fill the space */ -.ck-math-view .ck-labeled-field-view .ck-labeled-field-view__input-wrapper { - display: flex; - flex-direction: column; - flex: 1 1 auto; - width: 100%; - height: auto; - min-height: 100px; - margin: 0; - padding: 0; - background: transparent; - border: none; - box-shadow: none; -} - -/* Ensure the raw textarea fills its wrapper like MathLive's math-field */ -.ck-math-view .ck-labeled-field-view textarea { - display: block; - width: 100% !important; - flex: 1 1 auto; - height: 100%; - min-height: 140px; - box-sizing: border-box; - resize: none !important; - overflow: hidden !important; -} - - -/* ============================================================================ - Shared Input Styling (MathLive & Raw LaTeX) + 4. Raw LaTeX Integration (The Middle Box) ========================================================================= */ -/* Base styling for both MathLive field and raw LaTeX textarea */ -.ck.ck-math-form math-field, -.ck.ck-math-form textarea { - padding: var(--ck-spacing-small); - border: none !important; - border-radius: var(--ck-border-radius, 6px); - font-size: var(--ck-font-size-base); - box-sizing: border-box; - background: var(--ck-color-input-background) !important; - color: var(--ck-color-input-text, inherit); - outline: 3px solid transparent; - outline-offset: 6px; +.ck-math-view .ck-labeled-field-view { + display: flex; + flex-direction: column; + flex: 0 0 auto; + min-width: 100%; + + /* Allow the middle box to shrink if needed */ + width: 100%; + max-width: 100%; + + min-height: 140px; + max-height: 70vh; + resize: both; + overflow: auto; + background: transparent; } -/* Override MathLive selection colors to prevent dark blocks in light mode */ -.ck.ck-math-form math-field { - --selection-background-color: rgba(33, 150, 243, 0.2); - --selection-color: inherit; - --contains-highlight-background-color: rgba(0, 0, 0, 0.05); +/* Hide label */ +.ck-math-view .ck-labeled-field-view .ck-label { + display: none !important; } -/* Make the raw LaTeX textarea flat (no rounded corners or hover animation) */ +/* Internal wrapper */ +.ck-math-view .ck-labeled-field-view .ck-labeled-field-view__input-wrapper { + display: flex; + flex-direction: column; + flex: 1 1 auto; + width: 100%; + min-height: 100px; + height: auto; + padding: 0; + border: none; + background: transparent; + box-shadow: none; +} + +/* The Textarea */ .ck-math-view .ck-labeled-field-view textarea { - border-radius: 0 !important; - outline: none !important; - box-shadow: none !important; - transition: none !important; + display: block; + flex: 1 1 auto; + width: 100% !important; + height: 100%; + min-height: 140px; + resize: none !important; + border-radius: 0 !important; + box-shadow: none !important; + transition: none !important; } .ck-math-view .ck-labeled-field-view textarea:hover, .ck-math-view .ck-labeled-field-view textarea:focus { - background: var(--ck-color-input-background) !important; - color: var(--ck-color-input-text, inherit) !important; - outline: none !important; - box-shadow: none !important; - transition: none !important; + background: var(--ck-color-input-background) !important; + outline: none !important; + box-shadow: none !important; } - -/* Hide the internal label (we use a separate label element for better layout control) */ -.ck-math-view .ck-labeled-field-view .ck-label { - display: none !important; -} - - - - - From 4110fec94f8458a1613d95c6eaeea679a2d05738 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Mon, 24 Nov 2025 18:28:59 +0100 Subject: [PATCH 17/29] Removed unnecessary declare keyboard --- packages/ckeditor5-math/src/ui/mainformview.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/ckeditor5-math/src/ui/mainformview.ts b/packages/ckeditor5-math/src/ui/mainformview.ts index cb665347c..f44b020ee 100644 --- a/packages/ckeditor5-math/src/ui/mainformview.ts +++ b/packages/ckeditor5-math/src/ui/mainformview.ts @@ -9,15 +9,15 @@ import '../../theme/mathform.css'; import type { KatexOptions } from '../typings-external.js'; export default class MainFormView extends View { - public declare saveButtonView: ButtonView; - public declare mathLiveInputView: MathLiveInputView; - public declare rawLatexInputView: RawLatexInputView; - public declare rawLatexLabel: LabelView; - public declare displayButtonView: SwitchButtonView; - public declare cancelButtonView: ButtonView; - public declare previewEnabled: boolean; - public declare previewLabel?: LabelView; - public declare mathView?: MathView; + public saveButtonView: ButtonView; + public mathLiveInputView: MathLiveInputView; + public rawLatexInputView: RawLatexInputView; + public rawLatexLabel: LabelView; + public displayButtonView: SwitchButtonView; + public cancelButtonView: ButtonView; + public previewEnabled: boolean; + public previewLabel?: LabelView; + public mathView?: MathView; public override locale: Locale; constructor( From e7355dc0e44cbb0d0b71e36e99bcb69d21696109 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Mon, 24 Nov 2025 18:43:52 +0100 Subject: [PATCH 18/29] remove gitignore unneccesary changes --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9321d866f..9ea55440e 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,4 @@ upload .svelte-kit # docs -**/__screenshots__/ +site/ \ No newline at end of file From 9c4301467fd86dc199e3ea01ad9002968b1a06cc Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Mon, 24 Nov 2025 19:46:04 +0100 Subject: [PATCH 19/29] Remove unused icons from ckeditor5-math package --- packages/ckeditor5-math/theme/icons/cancel.svg | 4 ---- packages/ckeditor5-math/theme/icons/check.svg | 3 --- 2 files changed, 7 deletions(-) delete mode 100644 packages/ckeditor5-math/theme/icons/cancel.svg delete mode 100644 packages/ckeditor5-math/theme/icons/check.svg diff --git a/packages/ckeditor5-math/theme/icons/cancel.svg b/packages/ckeditor5-math/theme/icons/cancel.svg deleted file mode 100644 index 6f755ce79..000000000 --- a/packages/ckeditor5-math/theme/icons/cancel.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/ckeditor5-math/theme/icons/check.svg b/packages/ckeditor5-math/theme/icons/check.svg deleted file mode 100644 index d62f08d2e..000000000 --- a/packages/ckeditor5-math/theme/icons/check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - From d2052ad236ef49328ebada652c9ef90a533d8802 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Mon, 24 Nov 2025 21:51:59 +0100 Subject: [PATCH 20/29] Disable mathlive sound effects --- packages/ckeditor5-math/src/ui/mathliveinputview.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-math/src/ui/mathliveinputview.ts b/packages/ckeditor5-math/src/ui/mathliveinputview.ts index d337a9bdb..6b4930805 100644 --- a/packages/ckeditor5-math/src/ui/mathliveinputview.ts +++ b/packages/ckeditor5-math/src/ui/mathliveinputview.ts @@ -1,5 +1,5 @@ import { View, type Locale } from 'ckeditor5'; -import 'mathlive'; +import { MathfieldElement } from 'mathlive'; /** * A view that wraps the MathLive `` web component for interactive LaTeX equation editing. @@ -48,6 +48,10 @@ export default class MathLiveInputView extends View { */ public override render(): void { super.render(); + // Disable sounds before creating mathfield + if (typeof MathfieldElement !== 'undefined') { + MathfieldElement.soundsDirectory = null; + } // (Removed global area click-to-focus logic; focusing now relies on direct field interaction.) From 51db729546adee649dfde69151e48498103c6b0c Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Tue, 25 Nov 2025 23:27:06 +0100 Subject: [PATCH 21/29] Improve and simplify Mathfield integration --- packages/ckeditor5-math/src/mathui.ts | 24 +- .../ckeditor5-math/src/ui/mainformview.ts | 359 ++++++------------ .../src/ui/mathliveinputview.ts | 81 ++-- packages/ckeditor5-math/src/ui/mathview.ts | 70 ++-- .../src/ui/rawlatexinputview.ts | 18 +- 5 files changed, 207 insertions(+), 345 deletions(-) diff --git a/packages/ckeditor5-math/src/mathui.ts b/packages/ckeditor5-math/src/mathui.ts index 504adf77a..a27ee87fd 100644 --- a/packages/ckeditor5-math/src/mathui.ts +++ b/packages/ckeditor5-math/src/mathui.ts @@ -71,22 +71,20 @@ export default class MathUI extends Plugin { throw new CKEditorError( 'math-command' ); } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const mathConfig = editor.config.get( 'math' )!; const formView = new MainFormView( editor.locale, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - mathConfig.engine!, - mathConfig.lazyLoad, + { + engine: mathConfig.engine!, + lazyLoad: mathConfig.lazyLoad, + previewUid: this._previewUid, + previewClassName: mathConfig.previewClassName!, + katexRenderOptions: mathConfig.katexRenderOptions! + }, mathConfig.enablePreview, - this._previewUid, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - mathConfig.previewClassName!, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - mathConfig.popupClassName!, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - mathConfig.katexRenderOptions! + mathConfig.popupClassName! ); formView.mathLiveInputView.bind( 'value' ).to( mathCommand, 'value' ); @@ -164,9 +162,9 @@ export default class MathUI extends Plugin { // Show preview element const previewEl = document.getElementById( this._previewUid ); - if ( previewEl && this.formView.previewEnabled ) { + if ( previewEl && this.formView.mathView ) { // Force refresh preview - this.formView.mathView?.updateMath(); + this.formView.mathView.updateMath(); } this.formView.equation = mathCommand.value ?? ''; diff --git a/packages/ckeditor5-math/src/ui/mainformview.ts b/packages/ckeditor5-math/src/ui/mainformview.ts index f44b020ee..15e3ed55d 100644 --- a/packages/ckeditor5-math/src/ui/mainformview.ts +++ b/packages/ckeditor5-math/src/ui/mainformview.ts @@ -1,174 +1,136 @@ -import { ButtonView, FocusCycler, LabelView, submitHandler, SwitchButtonView, View, ViewCollection, type FocusableView, Locale, FocusTracker, KeystrokeHandler } from 'ckeditor5'; +import { + ButtonView, + FocusCycler, + LabelView, + submitHandler, + SwitchButtonView, + View, + ViewCollection, + type FocusableView, + Locale, + FocusTracker, + KeystrokeHandler +} from 'ckeditor5'; import IconCheck from '@ckeditor/ckeditor5-icons/theme/icons/check.svg?raw'; import IconCancel from '@ckeditor/ckeditor5-icons/theme/icons/cancel.svg?raw'; import { extractDelimiters, hasDelimiters } from '../utils.js'; -import MathView from './mathview.js'; +import MathView, { type MathViewOptions } from './mathview.js'; import MathLiveInputView from './mathliveinputview.js'; import RawLatexInputView from './rawlatexinputview.js'; import '../../theme/mathform.css'; -import type { KatexOptions } from '../typings-external.js'; export default class MainFormView extends View { public saveButtonView: ButtonView; + public cancelButtonView: ButtonView; + public displayButtonView: SwitchButtonView; + public mathLiveInputView: MathLiveInputView; public rawLatexInputView: RawLatexInputView; - public rawLatexLabel: LabelView; - public displayButtonView: SwitchButtonView; - public cancelButtonView: ButtonView; - public previewEnabled: boolean; - public previewLabel?: LabelView; public mathView?: MathView; - public override locale: Locale; + + public focusTracker = new FocusTracker(); + public keystrokes = new KeystrokeHandler(); + private _focusables = new ViewCollection(); + private _focusCycler: FocusCycler; constructor( locale: Locale, - engine: - | 'mathjax' - | 'katex' - | ( ( - equation: string, - element: HTMLElement, - display: boolean, - ) => void ), - lazyLoad: undefined | ( () => Promise ), + mathViewOptions: MathViewOptions, previewEnabled = false, - previewUid: string, - previewClassName: Array, - popupClassName: Array, - katexRenderOptions: KatexOptions + popupClassName: string[] = [] ) { super( locale ); - this.locale = locale; - const t = locale.t; - // Submit button - this.saveButtonView = this._createButton( t( 'Save' ), IconCheck, 'ck-button-save', null ); + // --- 1. View Initialization --- + + this.mathLiveInputView = new MathLiveInputView( locale ); + this.rawLatexInputView = new RawLatexInputView( locale ); + this.rawLatexInputView.label = t( 'LaTeX' ); + + this.saveButtonView = this._createButton( t( 'Save' ), IconCheck, 'ck-button-save' ); this.saveButtonView.type = 'submit'; - // MathLive visual equation editor - this.mathLiveInputView = this._createMathLiveInput(); + this.cancelButtonView = this._createButton( t( 'Cancel' ), IconCancel, 'ck-button-cancel' ); + this.cancelButtonView.delegate( 'execute' ).to( this, 'cancel' ); - // Raw LaTeX input - this.rawLatexInputView = this._createRawLatexInput(); + this.displayButtonView = this._createDisplayButton( t ); - // Raw LaTeX label - this.rawLatexLabel = new LabelView( locale ); - this.rawLatexLabel.text = t( 'LaTeX' ); + // --- 2. Construct Children & Preview --- - // Display button - this.displayButtonView = this._createDisplayButton(); + const children: View[] = [ + this.mathLiveInputView, + this.rawLatexInputView, + this.displayButtonView + ]; - // Cancel button - this.cancelButtonView = this._createButton( t( 'Cancel' ), IconCancel, 'ck-button-cancel', 'cancel' ); + if ( previewEnabled ) { + const previewLabel = new LabelView( locale ); + previewLabel.text = t( 'Equation preview' ); - this.previewEnabled = previewEnabled; + // Clean instantiation using the options object + this.mathView = new MathView( locale, mathViewOptions ); - let children = []; - if ( this.previewEnabled ) { - // Preview label - this.previewLabel = new LabelView( locale ); - this.previewLabel.text = t( 'Equation preview' ); - - // Math element - this.mathView = new MathView( engine, lazyLoad, locale, previewUid, previewClassName, katexRenderOptions ); + // Bind display mode: When button flips, preview updates automatically this.mathView.bind( 'display' ).to( this.displayButtonView, 'isOn' ); - children = [ - this.mathLiveInputView, - this.rawLatexLabel, - this.rawLatexInputView, - this.displayButtonView, - this.previewLabel, - this.mathView - ]; - } else { - children = [ - this.mathLiveInputView, - this.rawLatexLabel, - this.rawLatexInputView, - this.displayButtonView - ]; + children.push( previewLabel, this.mathView ); } - // Add UI elements to template + // --- 3. Sync Logic --- + this._setupInputSync( previewEnabled ); + + // --- 4. Template Setup --- this.setTemplate( { tag: 'form', attributes: { - class: [ - 'ck', - 'ck-math-form', - ...popupClassName - ], + class: [ 'ck', 'ck-math-form', ...popupClassName ], tabindex: '-1', spellcheck: 'false' }, children: [ { tag: 'div', - attributes: { - class: [ 'ck-math-scroll' ] - }, - children: [ - { - tag: 'div', - attributes: { - class: [ 'ck-math-view' ] - }, - children - } - ] + attributes: { class: [ 'ck-math-scroll' ] }, + children: [ { tag: 'div', attributes: { class: [ 'ck-math-view' ] }, children } ] }, { tag: 'div', - attributes: { - class: [ 'ck-math-button-row' ] - }, - children: [ - this.saveButtonView, - this.cancelButtonView - ] + attributes: { class: [ 'ck-math-button-row' ] }, + children: [ this.saveButtonView, this.cancelButtonView ] } ] } ); + + // --- 5. Accessibility --- + this._focusCycler = new FocusCycler( { + focusables: this._focusables, + focusTracker: this.focusTracker, + keystrokeHandler: this.keystrokes, + actions: { focusPrevious: 'shift + tab', focusNext: 'tab' } + } ); } public override render(): void { super.render(); - // Prevent default form submit event & trigger custom 'submit' - submitHandler( { - view: this - } ); + submitHandler( { view: this } ); - // Register form elements to focusable elements - const childViews = [ + // Register focusables + [ this.mathLiveInputView, this.rawLatexInputView, this.displayButtonView, this.saveButtonView, this.cancelButtonView - ]; - - childViews.forEach( v => { + ].forEach( v => { if ( v.element ) { this._focusables.add( v ); this.focusTracker.add( v.element ); } } ); - // Listen to keypresses inside form element - if ( this.element ) { - this.keystrokes.listenTo( this.element ); - } - } - - public override destroy(): void { - super.destroy(); - } - - public focus(): void { - this._focusCycler.focusFirst(); + if ( this.element ) this.keystrokes.listenTo( this.element ); } public get equation(): string { @@ -176,151 +138,82 @@ export default class MainFormView extends View { } public set equation( equation: string ) { - const normalizedEquation = equation.trim(); - this.mathLiveInputView.value = normalizedEquation.length ? normalizedEquation : null; - this.rawLatexInputView.value = normalizedEquation; - if ( this.previewEnabled && this.mathView ) { - this.mathView.value = normalizedEquation; - } + const norm = equation.trim(); + // Direct updates to the "source of truth" + this.mathLiveInputView.value = norm.length ? norm : null; + this.rawLatexInputView.value = norm; + if ( this.mathView ) this.mathView.value = norm; } - public focusTracker: FocusTracker = new FocusTracker(); - public keystrokes: KeystrokeHandler = new KeystrokeHandler(); - private _focusables = new ViewCollection(); - private _focusCycler: FocusCycler = new FocusCycler( { - focusables: this._focusables, - focusTracker: this.focusTracker, - keystrokeHandler: this.keystrokes, - actions: { - focusPrevious: 'shift + tab', - focusNext: 'tab' - } - } ); + public focus(): void { + this._focusCycler.focusFirst(); + } /** - * Creates the MathLive visual equation editor. - * - * Handles bidirectional synchronization with the raw LaTeX textarea and preview. + * Sets up split handlers for synchronization. */ - private _createMathLiveInput() { - const mathLiveInput = new MathLiveInputView( this.locale ); + private _setupInputSync( previewEnabled: boolean ): void { + // Handler 1: MathLive -> Raw LaTeX + this.mathLiveInputView.on( 'change:value', () => { + let eq = ( this.mathLiveInputView.value ?? '' ).trim(); - const onInput = () => { - let equationInput = ( mathLiveInput.value ?? '' ).trim(); - - // If input has delimiters, strip them and update the display mode. - if ( hasDelimiters( equationInput ) ) { - const params = extractDelimiters( equationInput ); - equationInput = params.equation; + // Delimiter Normalization + if ( hasDelimiters( eq ) ) { + const params = extractDelimiters( eq ); + eq = params.equation; this.displayButtonView.isOn = params.display; + + // UX Fix: If we stripped delimiters, update the source + // so the visual editor doesn't show them. + if ( this.mathLiveInputView.value !== eq ) { + this.mathLiveInputView.value = eq; + } } - const normalizedEquation = equationInput.length ? equationInput : null; - - // Update self if needed. - if ( mathLiveInput.value !== normalizedEquation ) { - mathLiveInput.value = normalizedEquation; + // Sync to Raw LaTeX + if ( this.rawLatexInputView.value !== eq ) { + this.rawLatexInputView.value = eq; } - // Sync to raw LaTeX textarea if its value is different. - if ( this.rawLatexInputView.value !== equationInput ) { - this.rawLatexInputView.value = equationInput; + // Sync to Preview + if ( previewEnabled && this.mathView && this.mathView.value !== eq ) { + this.mathView.value = eq; + } + } ); + + // Handler 2: Raw LaTeX -> MathLive + this.rawLatexInputView.on( 'change:value', () => { + const eq = ( this.rawLatexInputView.value ?? '' ).trim(); + const normalized = eq.length ? eq : null; + + // Sync to MathLive + if ( this.mathLiveInputView.value !== normalized ) { + this.mathLiveInputView.value = normalized; } - // Update preview if enabled and its value is different. - if ( this.previewEnabled && this.mathView && this.mathView.value !== equationInput ) { - this.mathView.value = equationInput; + // Sync to Preview + if ( previewEnabled && this.mathView && this.mathView.value !== eq ) { + this.mathView.value = eq; } - }; - - mathLiveInput.on( 'change:value', onInput ); - - return mathLiveInput; + } ); } - /** - * Creates the raw LaTeX textarea editor. - * - * Provides direct LaTeX code editing and synchronizes changes with the MathLive visual editor. - */ - private _createRawLatexInput() { - const t = this.locale.t; - const rawLatexInput = new RawLatexInputView( this.locale ); - rawLatexInput.label = t( 'LaTeX' ); - - // Sync raw LaTeX textarea changes to MathLive visual editor - rawLatexInput.on( 'change:value', () => { - const rawValue = rawLatexInput.value ?? ''; - const equationInput = rawValue.trim(); - - // Update MathLive visual editor - const normalizedEquation = equationInput.length ? equationInput : null; - if ( this.mathLiveInputView.value !== normalizedEquation ) { - this.mathLiveInputView.value = normalizedEquation; - } - - // Update preview - if ( this.previewEnabled && this.mathView ) { - this.mathView.value = equationInput; - } - } ); - - return rawLatexInput; + private _createButton( label: string, icon: string, className: string ): ButtonView { + const btn = new ButtonView( this.locale ); + btn.set( { label, icon, tooltip: true } ); + btn.extendTemplate( { attributes: { class: className } } ); + return btn; } - private _createButton( - label: string, - icon: string, - className: string, - eventName: string | null - ) { - const button = new ButtonView( this.locale ); + private _createDisplayButton( t: ( str: string ) => string ): SwitchButtonView { + const btn = new SwitchButtonView( this.locale ); + btn.set( { label: t( 'Display mode' ), withText: true } ); + btn.extendTemplate( { attributes: { class: 'ck-button-display-toggle' } } ); - button.set( { - label, - icon, - tooltip: true + btn.on( 'execute', () => { + btn.isOn = !btn.isOn; + // mathView updates automatically via bind() } ); - - button.extendTemplate( { - attributes: { - class: className - } - } ); - - if ( eventName ) { - button.delegate( 'execute' ).to( this, eventName ); - } - - return button; - } - - private _createDisplayButton() { - const t = this.locale.t; - - const switchButton = new SwitchButtonView( this.locale ); - - switchButton.set( { - label: t( 'Display mode' ), - withText: true - } ); - - switchButton.extendTemplate( { - attributes: { - class: 'ck-button-display-toggle' - } - } ); - - switchButton.on( 'execute', () => { - // Toggle state - switchButton.isOn = !switchButton.isOn; - - if ( this.previewEnabled && this.mathView ) { - // Update preview view - this.mathView.display = switchButton.isOn; - } - } ); - - return switchButton; + return btn; } } diff --git a/packages/ckeditor5-math/src/ui/mathliveinputview.ts b/packages/ckeditor5-math/src/ui/mathliveinputview.ts index 6b4930805..b5f36e94e 100644 --- a/packages/ckeditor5-math/src/ui/mathliveinputview.ts +++ b/packages/ckeditor5-math/src/ui/mathliveinputview.ts @@ -1,33 +1,27 @@ import { View, type Locale } from 'ckeditor5'; -import { MathfieldElement } from 'mathlive'; +import 'mathlive'; // Import side-effects only (registers the tag) /** - * A view that wraps the MathLive `` web component for interactive LaTeX equation editing. - * - * MathLive provides a rich math input experience with live rendering, virtual keyboard support, - * and various accessibility features. - * - * @see https://cortexjs.io/mathlive/ + * A wrapper for the MathLive component. + * Uses 'any' typing to avoid TypeScript module resolution errors. */ export default class MathLiveInputView extends View { /** - * The current LaTeX value of the math field. - * + * The current LaTeX value. * @observable */ public declare value: string | null; /** - * Whether the input is in read-only mode. - * + * Read-only state. * @observable */ public declare isReadOnly: boolean; /** - * Reference to the `` DOM element. + * Reference to the DOM element (typed as any to prevent TS errors). */ - public mathfield: HTMLElement | null = null; + public mathfield: any = null; constructor( locale: Locale ) { super( locale ); @@ -43,68 +37,53 @@ export default class MathLiveInputView extends View { } ); } - /** - * @inheritDoc - */ public override render(): void { super.render(); - // Disable sounds before creating mathfield - if (typeof MathfieldElement !== 'undefined') { - MathfieldElement.soundsDirectory = null; - } - // (Removed global area click-to-focus logic; focusing now relies on direct field interaction.) - - // Create the MathLive math-field custom element + // 1. Create element using DOM API instead of Class constructor + // This avoids "Module has no exported member" errors. const mathfield = document.createElement( 'math-field' ) as any; - this.mathfield = mathfield; - // Configure the virtual keyboard to be manually controlled (shown by user interaction) - mathfield.setAttribute( 'virtual-keyboard-mode', 'manual' ); + // 2. Configure Options + mathfield.mathVirtualKeyboardPolicy = 'manual'; - // Set initial value - const initialValue = this.value ?? ''; - if ( initialValue ) { - ( mathfield as any ).value = initialValue; + // Disable sounds + const MathfieldElement = customElements.get( 'math-field' ); + if ( MathfieldElement ) { + ( MathfieldElement as any ).soundsDirectory = null; + ( MathfieldElement as any ).plonkSound = null; } - // Bind readonly state - if ( this.isReadOnly ) { - ( mathfield as any ).readOnly = true; - } + // 3. Set Initial State + mathfield.value = this.value ?? ''; + mathfield.readOnly = this.isReadOnly; - // Sync math-field changes to observable value + // 4. Bind Events (DOM -> Observable) mathfield.addEventListener( 'input', () => { - const nextValue: string = ( mathfield as any ).value; - this.value = nextValue.length ? nextValue : null; + const val = mathfield.value; + this.value = val.length ? val : null; } ); - // Sync observable value changes back to math-field - this.on( 'change:value', () => { - const nextValue = this.value ?? ''; - if ( ( mathfield as any ).value !== nextValue ) { - ( mathfield as any ).value = nextValue; + // 5. Bind Events (Observable -> DOM) + this.on( 'change:value', ( _evt, _name, nextValue ) => { + if ( mathfield.value !== nextValue ) { + mathfield.value = nextValue ?? ''; } } ); - // Sync readonly state to math-field - this.on( 'change:isReadOnly', () => { - ( mathfield as any ).readOnly = this.isReadOnly; + this.on( 'change:isReadOnly', ( _evt, _name, nextValue ) => { + mathfield.readOnly = nextValue; } ); + // 6. Mount this.element?.appendChild( mathfield ); + this.mathfield = mathfield; } - /** - * Focuses the math-field element. - */ public focus(): void { this.mathfield?.focus(); } - /** - * @inheritDoc - */ public override destroy(): void { if ( this.mathfield ) { this.mathfield.remove(); diff --git a/packages/ckeditor5-math/src/ui/mathview.ts b/packages/ckeditor5-math/src/ui/mathview.ts index fab16262e..254c306ab 100644 --- a/packages/ckeditor5-math/src/ui/mathview.ts +++ b/packages/ckeditor5-math/src/ui/mathview.ts @@ -2,44 +2,44 @@ import { View, type Locale } from 'ckeditor5'; import type { KatexOptions } from '../typings-external.js'; import { renderEquation } from '../utils.js'; +/** + * Configuration options for the MathView. + */ +export interface MathViewOptions { + engine: 'mathjax' | 'katex' | ( ( equation: string, element: HTMLElement, display: boolean ) => void ); + lazyLoad: undefined | ( () => Promise ); + previewUid: string; + previewClassName: Array; + katexRenderOptions: KatexOptions; +} + export default class MathView extends View { + /** + * The LaTeX equation value to render. + * @observable + */ public declare value: string; + + /** + * Whether to render in display mode (centered) or inline. + * @observable + */ public declare display: boolean; - public previewUid: string; - public previewClassName: Array; - public katexRenderOptions: KatexOptions; - public engine: - | 'mathjax' - | 'katex' - | ( ( equation: string, element: HTMLElement, display: boolean ) => void ); - public lazyLoad: undefined | ( () => Promise ); - constructor( - engine: - | 'mathjax' - | 'katex' - | ( ( - equation: string, - element: HTMLElement, - display: boolean, - ) => void ), - lazyLoad: undefined | ( () => Promise ), - locale: Locale, - previewUid: string, - previewClassName: Array, - katexRenderOptions: KatexOptions - ) { + /** + * Configuration options passed during initialization. + */ + private options: MathViewOptions; + + constructor( locale: Locale, options: MathViewOptions ) { super( locale ); - - this.engine = engine; - this.lazyLoad = lazyLoad; - this.previewUid = previewUid; - this.katexRenderOptions = katexRenderOptions; - this.previewClassName = previewClassName; + this.options = options; this.set( 'value', '' ); this.set( 'display', false ); + // Update rendering when state changes. + // Checking isRendered prevents errors during initialization. this.on( 'change', () => { if ( this.isRendered ) { this.updateMath(); @@ -59,13 +59,13 @@ export default class MathView extends View { void renderEquation( this.value, this.element, - this.engine, - this.lazyLoad, + this.options.engine, + this.options.lazyLoad, this.display, - true, - this.previewUid, - this.previewClassName, - this.katexRenderOptions + true, // isPreview + this.options.previewUid, + this.options.previewClassName, + this.options.katexRenderOptions ); } } diff --git a/packages/ckeditor5-math/src/ui/rawlatexinputview.ts b/packages/ckeditor5-math/src/ui/rawlatexinputview.ts index 5831fad08..81593ec76 100644 --- a/packages/ckeditor5-math/src/ui/rawlatexinputview.ts +++ b/packages/ckeditor5-math/src/ui/rawlatexinputview.ts @@ -2,21 +2,16 @@ import { LabeledFieldView, createLabeledTextarea, type Locale, type TextareaView /** * A labeled textarea view for direct LaTeX code editing. - * - * This provides a plain text input for users who prefer to write LaTeX syntax directly - * or need to paste/edit raw LaTeX code. */ export default class RawLatexInputView extends LabeledFieldView { /** * The current LaTeX value. - * * @observable */ public declare value: string; /** * Whether the input is in read-only mode. - * * @observable */ public declare isReadOnly: boolean; @@ -29,21 +24,23 @@ export default class RawLatexInputView extends LabeledFieldView { const fieldView = this.fieldView; - // Sync textarea input to observable value + // 1. Sync: DOM (Textarea) -> Observable + // We listen to the native 'input' event on the child view fieldView.on( 'input', () => { if ( fieldView.element ) { this.value = fieldView.element.value; } } ); - // Sync observable value changes back to textarea + // 2. Sync: Observable -> DOM (Textarea) this.on( 'change:value', () => { + // Check for difference to avoid cursor jumping or unnecessary updates if ( fieldView.element && fieldView.element.value !== this.value ) { fieldView.element.value = this.value; } } ); - // Sync readonly state (manual binding to avoid CKEditor observable rebind error) + // 3. Sync: ReadOnly State this.on( 'change:isReadOnly', () => { if ( fieldView.element ) { fieldView.element.readOnly = this.isReadOnly; @@ -51,12 +48,7 @@ export default class RawLatexInputView extends LabeledFieldView { } ); } - /** - * @inheritDoc - */ public override render(): void { super.render(); - // All styling is handled via CSS in mathform.css - // (Removed obsolete mousedown propagation; no longer needed after resize & gray-area click removal.) } } From c8d34e65ea230d45a1a627345f899218f91a8d34 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Wed, 26 Nov 2025 21:49:09 +0100 Subject: [PATCH 22/29] Improve max window size --- packages/ckeditor5-math/theme/mathform.css | 86 +++++++++++++++------- 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/packages/ckeditor5-math/theme/mathform.css b/packages/ckeditor5-math/theme/mathform.css index 3f2546c24..270e86568 100644 --- a/packages/ckeditor5-math/theme/mathform.css +++ b/packages/ckeditor5-math/theme/mathform.css @@ -1,10 +1,10 @@ /** * Math equation editor dialog styles - * Complete version with scrolling fixes for preview and input + * Supports MathLive input, raw LaTeX textarea, and equation preview */ /* ============================================================================ - 1. Main Layout Containers (The Skeleton) + Main Dialog Container ========================================================================= */ .ck.ck-math-form { @@ -12,9 +12,10 @@ flex-direction: column; padding: var(--ck-spacing-standard); box-sizing: border-box; - max-width: 80vw; - height: 100%; /* Never wider than screen */ - overflow-x: hidden; /* Prevent the main window from scrolling horizontally */ + max-width: 80vw; + max-height: 80vh; + height: 100%; + overflow-x: hidden; } /* Mobile responsiveness */ @@ -24,6 +25,10 @@ } } +/* ============================================================================ + Content Layout + ========================================================================= */ + .ck-math-view { display: flex; flex-direction: column; @@ -34,6 +39,36 @@ width: 100%; } +/* LaTeX section heading */ +.ck-math-view > .ck-labeled-field-view::before { + content: "LaTeX"; + display: block; + font-size: 12px; + font-weight: 600; + color: var(--ck-color-text, #333); + margin-bottom: 4px; + padding-left: 2px; + opacity: 0.8; +} + +/* Equation preview section heading */ +.ck-math-view > math-field::before { + content: "Equation preview"; + display: block; + font-size: 12px; + font-weight: 600; + color: var(--ck-color-text, #333); + margin-bottom: 4px; + padding-left: 2px; + opacity: 0.8; +} + +/* Add spacing between preview and action buttons */ +.ck-math-view > math-field { + margin-bottom: var(--ck-spacing-large, 16px); +} + +/* Action buttons row (Save/Cancel) */ .ck-math-button-row { display: flex; flex-shrink: 0; @@ -45,10 +80,10 @@ } /* ============================================================================ - 2. Shared Styling (Applies to Input AND Preview) + Shared Styles for Input Fields ========================================================================= */ -/* This targets both the top input AND the bottom preview */ +/* Base styling for both MathLive fields and textareas */ .ck.ck-math-form math-field, .ck.ck-math-form textarea { box-sizing: border-box; @@ -62,33 +97,28 @@ outline-offset: 6px; } -/* SPECIFIC FIX FOR PREVIEW SCROLLING */ +/* MathLive-specific configuration */ .ck.ck-math-form math-field { display: block !important; width: 100%; - - /* 3. Stop it from growing infinite */ max-width: 100%; - - /* 4. Enable scrollbars for the red matrix area */ overflow-x: auto !important; - /* Theme overrides */ + /* MathLive theme customization */ --selection-background-color: rgba(33, 150, 243, 0.2); --selection-color: inherit; --contains-highlight-background-color: rgba(0, 0, 0, 0.05); } /* ============================================================================ - 3. MathLive Input Specifics (The Top Box) + MathLive Visual Editor (Top Input) ========================================================================= */ -/* Wrapper for the editable input at the top */ .ck.ck-mathlive-input { display: inline-block; flex: 0 0 auto; width: 100%; - max-width: 100%; /* Safety */ + max-width: 100%; min-height: fit-content; max-height: 80vh; overflow: auto; @@ -96,7 +126,7 @@ resize: none; } -/* Shadow DOM Layout adjustments (Keep your existing logic) */ +/* Configure MathLive shadow DOM layout */ .ck.ck-math-form math-field::part(container), .ck.ck-math-form math-field::part(content), .ck.ck-math-form math-field::part(field) { @@ -108,13 +138,17 @@ justify-content: flex-start; } -/* UI Buttons positions */ +/* Position MathLive UI controls */ .ck.ck-math-form math-field::part(virtual-keyboard-toggle), .ck.ck-math-form math-field::part(menu-toggle) { position: absolute; top: 8px; } -.ck.ck-math-form math-field::part(virtual-keyboard-toggle) { right: 40px; } + +.ck.ck-math-form math-field::part(virtual-keyboard-toggle) { + right: 40px; +} + .ck.ck-math-form math-field::part(menu-toggle) { right: 8px; display: flex !important; @@ -122,7 +156,7 @@ } /* ============================================================================ - 4. Raw LaTeX Integration (The Middle Box) + Raw LaTeX Textarea (Middle Input) ========================================================================= */ .ck-math-view .ck-labeled-field-view { @@ -130,24 +164,21 @@ flex-direction: column; flex: 0 0 auto; min-width: 100%; - - /* Allow the middle box to shrink if needed */ width: 100%; max-width: 100%; - min-height: 140px; - max-height: 70vh; + max-height: 65vh; resize: both; overflow: auto; background: transparent; } -/* Hide label */ +/* Hide the default label (we use ::before for custom heading) */ .ck-math-view .ck-labeled-field-view .ck-label { display: none !important; } -/* Internal wrapper */ +/* Textarea wrapper */ .ck-math-view .ck-labeled-field-view .ck-labeled-field-view__input-wrapper { display: flex; flex-direction: column; @@ -161,7 +192,7 @@ box-shadow: none; } -/* The Textarea */ +/* Raw LaTeX textarea styling */ .ck-math-view .ck-labeled-field-view textarea { display: block; flex: 1 1 auto; @@ -174,6 +205,7 @@ transition: none !important; } +/* Textarea hover and focus states */ .ck-math-view .ck-labeled-field-view textarea:hover, .ck-math-view .ck-labeled-field-view textarea:focus { background: var(--ck-color-input-background) !important; From a6de1041c7abb10c358259ece2098e0abc003c18 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Wed, 26 Nov 2025 21:59:33 +0100 Subject: [PATCH 23/29] Fix bug in math rendering where old content was not cleared --- packages/ckeditor5-math/src/ui/mathview.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ckeditor5-math/src/ui/mathview.ts b/packages/ckeditor5-math/src/ui/mathview.ts index 254c306ab..42a4c0df1 100644 --- a/packages/ckeditor5-math/src/ui/mathview.ts +++ b/packages/ckeditor5-math/src/ui/mathview.ts @@ -56,6 +56,10 @@ export default class MathView extends View { public updateMath(): void { if ( this.element ) { + + // This prevents the new render from appending to the old one. + this.element.textContent = ''; + void renderEquation( this.value, this.element, From 64ab1c41162cc3043889a706e38fc789ccc0431a Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Wed, 26 Nov 2025 22:29:29 +0100 Subject: [PATCH 24/29] Imrovement for Latex --- packages/ckeditor5-math/theme/mathform.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ckeditor5-math/theme/mathform.css b/packages/ckeditor5-math/theme/mathform.css index 270e86568..c2a1476bb 100644 --- a/packages/ckeditor5-math/theme/mathform.css +++ b/packages/ckeditor5-math/theme/mathform.css @@ -166,7 +166,7 @@ min-width: 100%; width: 100%; max-width: 100%; - min-height: 140px; + min-height: 60px; max-height: 65vh; resize: both; overflow: auto; From c46cf418426bf7cf6d04c8b73a2bd6449061015b Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Wed, 26 Nov 2025 22:48:57 +0100 Subject: [PATCH 25/29] Small improvements --- .../src/ui/mathliveinputview.ts | 149 ++++++++++-------- .../src/ui/rawlatexinputview.ts | 78 ++++----- 2 files changed, 118 insertions(+), 109 deletions(-) diff --git a/packages/ckeditor5-math/src/ui/mathliveinputview.ts b/packages/ckeditor5-math/src/ui/mathliveinputview.ts index b5f36e94e..a3863077c 100644 --- a/packages/ckeditor5-math/src/ui/mathliveinputview.ts +++ b/packages/ckeditor5-math/src/ui/mathliveinputview.ts @@ -1,94 +1,103 @@ import { View, type Locale } from 'ckeditor5'; import 'mathlive'; // Import side-effects only (registers the tag) +/** + * Interface describing the custom element. + */ +interface MathFieldElement extends HTMLElement { + value: string; + readOnly: boolean; + mathVirtualKeyboardPolicy: string; + +} + /** * A wrapper for the MathLive component. - * Uses 'any' typing to avoid TypeScript module resolution errors. */ export default class MathLiveInputView extends View { - /** - * The current LaTeX value. - * @observable - */ - public declare value: string | null; + /** + * The current LaTeX value. + * @observable + */ + public declare value: string | null; - /** - * Read-only state. - * @observable - */ - public declare isReadOnly: boolean; + /** + * Read-only state. + * @observable + */ + public declare isReadOnly: boolean; - /** - * Reference to the DOM element (typed as any to prevent TS errors). - */ - public mathfield: any = null; + /** + * Reference to the DOM element. + * Typed as MathFieldElement | null for proper TS support. + */ + public mathfield: MathFieldElement | null = null; - constructor( locale: Locale ) { - super( locale ); + constructor( locale: Locale ) { + super( locale ); - this.set( 'value', null ); - this.set( 'isReadOnly', false ); + this.set( 'value', null ); + this.set( 'isReadOnly', false ); - this.setTemplate( { - tag: 'div', - attributes: { - class: [ 'ck', 'ck-mathlive-input' ] - } - } ); - } + this.setTemplate( { + tag: 'div', + attributes: { + class: [ 'ck', 'ck-mathlive-input' ] + } + } ); + } - public override render(): void { - super.render(); + public override render(): void { + super.render(); - // 1. Create element using DOM API instead of Class constructor - // This avoids "Module has no exported member" errors. - const mathfield = document.createElement( 'math-field' ) as any; + // 1. Create element with the specific type + const mathfield = document.createElement( 'math-field' ) as MathFieldElement; - // 2. Configure Options - mathfield.mathVirtualKeyboardPolicy = 'manual'; + // 2. Configure Options + mathfield.mathVirtualKeyboardPolicy = 'manual'; - // Disable sounds - const MathfieldElement = customElements.get( 'math-field' ); - if ( MathfieldElement ) { - ( MathfieldElement as any ).soundsDirectory = null; - ( MathfieldElement as any ).plonkSound = null; - } + // Disable sounds + const MathfieldConstructor = customElements.get( 'math-field' ); + if ( MathfieldConstructor ) { + ( MathfieldConstructor as any ).soundsDirectory = null; + ( MathfieldConstructor as any ).plonkSound = null; + } - // 3. Set Initial State - mathfield.value = this.value ?? ''; - mathfield.readOnly = this.isReadOnly; + // 3. Set Initial State + mathfield.value = this.value ?? ''; + mathfield.readOnly = this.isReadOnly; - // 4. Bind Events (DOM -> Observable) - mathfield.addEventListener( 'input', () => { - const val = mathfield.value; - this.value = val.length ? val : null; - } ); + // 4. Bind Events (DOM -> Observable) + mathfield.addEventListener( 'input', () => { + const val = mathfield.value; + this.value = val.length ? val : null; + } ); - // 5. Bind Events (Observable -> DOM) - this.on( 'change:value', ( _evt, _name, nextValue ) => { - if ( mathfield.value !== nextValue ) { - mathfield.value = nextValue ?? ''; - } - } ); + // 5. Bind Events (Observable -> DOM) + this.on( 'change:value', ( _evt, _name, nextValue ) => { + if ( mathfield.value !== nextValue ) { + mathfield.value = nextValue ?? ''; + } + } ); - this.on( 'change:isReadOnly', ( _evt, _name, nextValue ) => { - mathfield.readOnly = nextValue; - } ); + this.on( 'change:isReadOnly', ( _evt, _name, nextValue ) => { + mathfield.readOnly = nextValue; + } ); - // 6. Mount - this.element?.appendChild( mathfield ); - this.mathfield = mathfield; - } + // 6. Mount + this.element?.appendChild( mathfield ); + this.mathfield = mathfield; + } - public focus(): void { - this.mathfield?.focus(); - } + public focus(): void { + this.mathfield?.focus(); + } - public override destroy(): void { - if ( this.mathfield ) { - this.mathfield.remove(); - this.mathfield = null; - } - super.destroy(); - } + public override destroy(): void { + if ( this.mathfield ) { + this.mathfield.remove(); + this.mathfield = null; + } + super.destroy(); + } } diff --git a/packages/ckeditor5-math/src/ui/rawlatexinputview.ts b/packages/ckeditor5-math/src/ui/rawlatexinputview.ts index 81593ec76..cc468b434 100644 --- a/packages/ckeditor5-math/src/ui/rawlatexinputview.ts +++ b/packages/ckeditor5-math/src/ui/rawlatexinputview.ts @@ -4,51 +4,51 @@ import { LabeledFieldView, createLabeledTextarea, type Locale, type TextareaView * A labeled textarea view for direct LaTeX code editing. */ export default class RawLatexInputView extends LabeledFieldView { - /** - * The current LaTeX value. - * @observable - */ - public declare value: string; + /** + * The current LaTeX value. + * @observable + */ + public declare value: string; - /** - * Whether the input is in read-only mode. - * @observable - */ - public declare isReadOnly: boolean; + /** + * Whether the input is in read-only mode. + * @observable + */ + public declare isReadOnly: boolean; - constructor( locale: Locale ) { - super( locale, createLabeledTextarea ); + constructor( locale: Locale ) { + super( locale, createLabeledTextarea ); - this.set( 'value', '' ); - this.set( 'isReadOnly', false ); + this.set( 'value', '' ); + this.set( 'isReadOnly', false ); - const fieldView = this.fieldView; + const fieldView = this.fieldView; - // 1. Sync: DOM (Textarea) -> Observable - // We listen to the native 'input' event on the child view - fieldView.on( 'input', () => { - if ( fieldView.element ) { - this.value = fieldView.element.value; - } - } ); + // 1. Sync: DOM (Textarea) -> Observable + fieldView.on( 'input', () => { + // We cast strictly to HTMLTextAreaElement to access '.value' safely + const textarea = fieldView.element as HTMLTextAreaElement; + if ( textarea ) { + this.value = textarea.value; + } + } ); - // 2. Sync: Observable -> DOM (Textarea) - this.on( 'change:value', () => { - // Check for difference to avoid cursor jumping or unnecessary updates - if ( fieldView.element && fieldView.element.value !== this.value ) { - fieldView.element.value = this.value; - } - } ); + // 2. Sync: Observable -> DOM (Textarea) + this.on( 'change:value', () => { + const textarea = fieldView.element as HTMLTextAreaElement; + // Check for difference to avoid cursor jumping + if ( textarea && textarea.value !== this.value ) { + textarea.value = this.value; + } + } ); - // 3. Sync: ReadOnly State - this.on( 'change:isReadOnly', () => { - if ( fieldView.element ) { - fieldView.element.readOnly = this.isReadOnly; - } - } ); - } + // 3. Sync: ReadOnly State + this.on( 'change:isReadOnly', ( _evt, _name, nextValue ) => { + fieldView.isReadOnly = nextValue; + } ); + } - public override render(): void { - super.render(); - } + public override render(): void { + super.render(); + } } From f8d84814e079bdca892c1c2cac75c53baa96f20b Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Wed, 26 Nov 2025 23:02:34 +0100 Subject: [PATCH 26/29] Fix differential d problems --- .../src/ui/mathliveinputview.ts | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/ckeditor5-math/src/ui/mathliveinputview.ts b/packages/ckeditor5-math/src/ui/mathliveinputview.ts index a3863077c..f761be787 100644 --- a/packages/ckeditor5-math/src/ui/mathliveinputview.ts +++ b/packages/ckeditor5-math/src/ui/mathliveinputview.ts @@ -8,7 +8,8 @@ interface MathFieldElement extends HTMLElement { value: string; readOnly: boolean; mathVirtualKeyboardPolicy: string; - + // Interface includes the shortcuts property + inlineShortcuts: Record; } /** @@ -56,11 +57,23 @@ export default class MathLiveInputView extends View { // 2. Configure Options mathfield.mathVirtualKeyboardPolicy = 'manual'; - // Disable sounds + //Disable differential D + mathfield.addEventListener( 'mount', () => { + mathfield.inlineShortcuts = { + ...mathfield.inlineShortcuts, // Safe to read now + dx: 'dx', + dy: 'dy', + dt: 'dt' + }; + } ); + + + // Disable sounds safely const MathfieldConstructor = customElements.get( 'math-field' ); if ( MathfieldConstructor ) { - ( MathfieldConstructor as any ).soundsDirectory = null; - ( MathfieldConstructor as any ).plonkSound = null; + const proto = MathfieldConstructor as any; + if ( proto.soundsDirectory !== null ) proto.soundsDirectory = null; + if ( proto.plonkSound !== null ) proto.plonkSound = null; } // 3. Set Initial State @@ -84,7 +97,7 @@ export default class MathLiveInputView extends View { mathfield.readOnly = nextValue; } ); - // 6. Mount + // 6. Mount to the wrapper view this.element?.appendChild( mathfield ); this.mathfield = mathfield; } From acca22f3a1594bfeb17a28a8b9aec40eb20473a2 Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Tue, 2 Dec 2025 22:28:16 +0100 Subject: [PATCH 27/29] Improve Synchronization Between Mathlive and rawlatex input --- .../ckeditor5-math/src/ui/mainformview.ts | 70 +++++++++++-------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/ckeditor5-math/src/ui/mainformview.ts b/packages/ckeditor5-math/src/ui/mainformview.ts index 15e3ed55d..d7ceb2f20 100644 --- a/packages/ckeditor5-math/src/ui/mainformview.ts +++ b/packages/ckeditor5-math/src/ui/mainformview.ts @@ -48,8 +48,7 @@ export default class MainFormView extends View { this.rawLatexInputView = new RawLatexInputView( locale ); this.rawLatexInputView.label = t( 'LaTeX' ); - this.saveButtonView = this._createButton( t( 'Save' ), IconCheck, 'ck-button-save' ); - this.saveButtonView.type = 'submit'; + this.saveButtonView = this._createButton( t( 'Save' ), IconCheck, 'ck-button-save', 'submit' ); this.cancelButtonView = this._createButton( t( 'Cancel' ), IconCancel, 'ck-button-cancel' ); this.cancelButtonView.delegate( 'execute' ).to( this, 'cancel' ); @@ -150,58 +149,67 @@ export default class MainFormView extends View { } /** - * Sets up split handlers for synchronization. + * Checks if a view currently has focus. */ - private _setupInputSync( previewEnabled: boolean ): void { - // Handler 1: MathLive -> Raw LaTeX - this.mathLiveInputView.on( 'change:value', () => { - let eq = ( this.mathLiveInputView.value ?? '' ).trim(); + private _isViewFocused(view: View): boolean { + const el = view.element; + const active = document.activeElement; + return !!(el && active && el.contains(active)); + } - // Delimiter Normalization - if ( hasDelimiters( eq ) ) { - const params = extractDelimiters( eq ); + /** + * Sets up synchronization with Focus Gating. + */ + private _setupInputSync(previewEnabled: boolean): void { + const updatePreview = (eq: string) => { + if (previewEnabled && this.mathView && this.mathView.value !== eq) { + this.mathView.value = eq; + } + }; + + // Handler 1: MathLive -> Raw LaTeX + Preview + this.mathLiveInputView.on('change:value', () => { + let eq = (this.mathLiveInputView.value ?? '').trim(); + + // Strip delimiters if present (e.g. pasted content) + if (hasDelimiters(eq)) { + const params = extractDelimiters(eq); eq = params.equation; this.displayButtonView.isOn = params.display; - // UX Fix: If we stripped delimiters, update the source - // so the visual editor doesn't show them. - if ( this.mathLiveInputView.value !== eq ) { + // Only strip delimiters if not actively editing + if (!this._isViewFocused(this.mathLiveInputView) && this.mathLiveInputView.value !== eq) { this.mathLiveInputView.value = eq; } } - // Sync to Raw LaTeX - if ( this.rawLatexInputView.value !== eq ) { + // Sync to Raw LaTeX only if user isn't typing there + if (!this._isViewFocused(this.rawLatexInputView) && this.rawLatexInputView.value !== eq) { this.rawLatexInputView.value = eq; } - // Sync to Preview - if ( previewEnabled && this.mathView && this.mathView.value !== eq ) { - this.mathView.value = eq; - } - } ); + updatePreview(eq); + }); - // Handler 2: Raw LaTeX -> MathLive - this.rawLatexInputView.on( 'change:value', () => { - const eq = ( this.rawLatexInputView.value ?? '' ).trim(); + // Handler 2: Raw LaTeX -> MathLive + Preview + this.rawLatexInputView.on('change:value', () => { + const eq = (this.rawLatexInputView.value ?? '').trim(); const normalized = eq.length ? eq : null; - // Sync to MathLive - if ( this.mathLiveInputView.value !== normalized ) { + // Sync to MathLive only if user isn't interacting with it + if (!this._isViewFocused(this.mathLiveInputView) && this.mathLiveInputView.value !== normalized) { this.mathLiveInputView.value = normalized; } - // Sync to Preview - if ( previewEnabled && this.mathView && this.mathView.value !== eq ) { - this.mathView.value = eq; - } - } ); + updatePreview(eq); + }); } - private _createButton( label: string, icon: string, className: string ): ButtonView { + private _createButton( label: string, icon: string, className: string, type?: 'submit' | 'button' ): ButtonView { const btn = new ButtonView( this.locale ); btn.set( { label, icon, tooltip: true } ); btn.extendTemplate( { attributes: { class: className } } ); + if (type) btn.type = type; return btn; } From 9386465de7e549a7c57ea8486abc2c2162f8b9ae Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Tue, 2 Dec 2025 22:29:20 +0100 Subject: [PATCH 28/29] Added mathrender error class for better error handling in math rendering --- packages/ckeditor5-math/theme/mathform.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/ckeditor5-math/theme/mathform.css b/packages/ckeditor5-math/theme/mathform.css index c2a1476bb..1465b9e1b 100644 --- a/packages/ckeditor5-math/theme/mathform.css +++ b/packages/ckeditor5-math/theme/mathform.css @@ -212,3 +212,13 @@ outline: none !important; box-shadow: none !important; } + +.ck-math-render-error { + color: var(--ck-color-error-text, #db1d1d); + padding: var(--ck-spacing-small); + font-style: italic; + font-size: 0.9em; + border: 1px dashed var(--ck-color-error-text, #db1d1d); + border-radius: 2px; + background: var(--ck-color-base-background, #fff); +} From 162c076a145194b80f3275b4f85fbe790d0a332b Mon Sep 17 00:00:00 2001 From: meinzzzz Date: Tue, 2 Dec 2025 22:30:37 +0100 Subject: [PATCH 29/29] Improve MathLive integration and lazy loading --- .../src/ui/mathliveinputview.ts | 114 +++++++++--------- packages/ckeditor5-math/src/ui/mathview.ts | 48 +++++--- packages/ckeditor5-math/tests/lazyload.ts | 10 ++ 3 files changed, 96 insertions(+), 76 deletions(-) diff --git a/packages/ckeditor5-math/src/ui/mathliveinputview.ts b/packages/ckeditor5-math/src/ui/mathliveinputview.ts index f761be787..a2d540228 100644 --- a/packages/ckeditor5-math/src/ui/mathliveinputview.ts +++ b/packages/ckeditor5-math/src/ui/mathliveinputview.ts @@ -1,104 +1,98 @@ import { View, type Locale } from 'ckeditor5'; -import 'mathlive'; // Import side-effects only (registers the tag) -/** - * Interface describing the custom element. - */ interface MathFieldElement extends HTMLElement { value: string; readOnly: boolean; mathVirtualKeyboardPolicy: string; - // Interface includes the shortcuts property - inlineShortcuts: Record; + inlineShortcuts?: Record; } -/** - * A wrapper for the MathLive component. - */ export default class MathLiveInputView extends View { - /** - * The current LaTeX value. - * @observable - */ public declare value: string | null; - - /** - * Read-only state. - * @observable - */ public declare isReadOnly: boolean; - - /** - * Reference to the DOM element. - * Typed as MathFieldElement | null for proper TS support. - */ public mathfield: MathFieldElement | null = null; - constructor( locale: Locale ) { - super( locale ); + constructor(locale: Locale) { + super(locale); + this.set('value', null); + this.set('isReadOnly', false); - this.set( 'value', null ); - this.set( 'isReadOnly', false ); - - this.setTemplate( { + this.setTemplate({ tag: 'div', attributes: { - class: [ 'ck', 'ck-mathlive-input' ] + class: ['ck', 'ck-mathlive-input'] } - } ); + }); } public override render(): void { super.render(); + this._loadMathLive(); + } - // 1. Create element with the specific type - const mathfield = document.createElement( 'math-field' ) as MathFieldElement; + private async _loadMathLive(): Promise { + try { + await import('mathlive'); + await customElements.whenDefined('math-field'); - // 2. Configure Options + // Configure global MathLive settings + const MathfieldClass = customElements.get( 'math-field' ) as any; + if ( MathfieldClass ) { + MathfieldClass.soundsDirectory = null; + MathfieldClass.plonkSound = null; + } + + if (!this.element) return; + + this._createMathField(); + } catch (error) { + console.error('MathLive load failed:', error); + if (this.element) { + this.element.textContent = 'Math editor unavailable'; + } + } + } + + private _createMathField(): void { + if (!this.element) return; + + const mathfield = document.createElement('math-field') as MathFieldElement; + + // Instance-level config (no prototype pollution) mathfield.mathVirtualKeyboardPolicy = 'manual'; - //Disable differential D - mathfield.addEventListener( 'mount', () => { + // Configure shortcuts after mount + mathfield.addEventListener('mount', () => { mathfield.inlineShortcuts = { - ...mathfield.inlineShortcuts, // Safe to read now + ...mathfield.inlineShortcuts, dx: 'dx', dy: 'dy', dt: 'dt' }; - } ); + }, { once: true }); - - // Disable sounds safely - const MathfieldConstructor = customElements.get( 'math-field' ); - if ( MathfieldConstructor ) { - const proto = MathfieldConstructor as any; - if ( proto.soundsDirectory !== null ) proto.soundsDirectory = null; - if ( proto.plonkSound !== null ) proto.plonkSound = null; - } - - // 3. Set Initial State + // Initial state mathfield.value = this.value ?? ''; mathfield.readOnly = this.isReadOnly; - // 4. Bind Events (DOM -> Observable) - mathfield.addEventListener( 'input', () => { + // DOM -> Observable + mathfield.addEventListener('input', () => { const val = mathfield.value; this.value = val.length ? val : null; - } ); + }); - // 5. Bind Events (Observable -> DOM) - this.on( 'change:value', ( _evt, _name, nextValue ) => { - if ( mathfield.value !== nextValue ) { + // Observable -> DOM + this.on('change:value', (_evt, _name, nextValue) => { + if (mathfield.value !== nextValue) { mathfield.value = nextValue ?? ''; } - } ); + }); - this.on( 'change:isReadOnly', ( _evt, _name, nextValue ) => { + this.on('change:isReadOnly', (_evt, _name, nextValue) => { mathfield.readOnly = nextValue; - } ); + }); - // 6. Mount to the wrapper view - this.element?.appendChild( mathfield ); + this.element.appendChild(mathfield); this.mathfield = mathfield; } @@ -107,7 +101,7 @@ export default class MathLiveInputView extends View { } public override destroy(): void { - if ( this.mathfield ) { + if (this.mathfield) { this.mathfield.remove(); this.mathfield = null; } diff --git a/packages/ckeditor5-math/src/ui/mathview.ts b/packages/ckeditor5-math/src/ui/mathview.ts index 42a4c0df1..87af15d08 100644 --- a/packages/ckeditor5-math/src/ui/mathview.ts +++ b/packages/ckeditor5-math/src/ui/mathview.ts @@ -55,23 +55,39 @@ export default class MathView extends View { } public updateMath(): void { - if ( this.element ) { - - // This prevents the new render from appending to the old one. - this.element.textContent = ''; - - void renderEquation( - this.value, - this.element, - this.options.engine, - this.options.lazyLoad, - this.display, - true, // isPreview - this.options.previewUid, - this.options.previewClassName, - this.options.katexRenderOptions - ); + if (!this.element) { + return; } + + // Handle empty equations + if (!this.value || !this.value.trim()) { + this.element.textContent = ''; + this.element.classList.remove('ck-math-render-error'); + return; + } + + // Clear previous render + this.element.textContent = ''; + this.element.classList.remove('ck-math-render-error'); + + renderEquation( + this.value, + this.element, + this.options.engine, + this.options.lazyLoad, + this.display, + true, // isPreview + this.options.previewUid, + this.options.previewClassName, + this.options.katexRenderOptions + ).catch(error => { + console.error('Math rendering failed:', error); + + if (this.element) { + this.element.textContent = 'Error rendering equation'; + this.element.classList.add('ck-math-render-error'); + } + }); } public override render(): void { diff --git a/packages/ckeditor5-math/tests/lazyload.ts b/packages/ckeditor5-math/tests/lazyload.ts index 126507850..573984368 100644 --- a/packages/ckeditor5-math/tests/lazyload.ts +++ b/packages/ckeditor5-math/tests/lazyload.ts @@ -37,6 +37,7 @@ describe( 'Lazy load', () => { await buildEditor( { math: { engine: 'katex', + enablePreview: true, lazyLoad: async () => { lazyLoadInvoked = true; } @@ -44,6 +45,15 @@ describe( 'Lazy load', () => { } ); mathUIFeature._showUI(); + + // Trigger render with a non-empty value to bypass empty check optimization + if ( mathUIFeature.formView ) { + mathUIFeature.formView.equation = 'x^2'; + } + + // Wait for async rendering and lazy loading + await new Promise( resolve => setTimeout( resolve, 100 ) ); + expect( lazyLoadInvoked ).to.be.true; } ); } );