Improve and simplify Mathfield integration

This commit is contained in:
meinzzzz 2025-11-25 23:27:06 +01:00
parent d2052ad236
commit 51db729546
5 changed files with 207 additions and 345 deletions

View File

@ -71,22 +71,20 @@ export default class MathUI extends Plugin {
throw new CKEditorError( 'math-command' ); throw new CKEditorError( 'math-command' );
} }
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const mathConfig = editor.config.get( 'math' )!; const mathConfig = editor.config.get( 'math' )!;
const formView = new MainFormView( const formView = new MainFormView(
editor.locale, editor.locale,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion {
mathConfig.engine!, engine: mathConfig.engine!,
mathConfig.lazyLoad, lazyLoad: mathConfig.lazyLoad,
previewUid: this._previewUid,
previewClassName: mathConfig.previewClassName!,
katexRenderOptions: mathConfig.katexRenderOptions!
},
mathConfig.enablePreview, mathConfig.enablePreview,
this._previewUid, mathConfig.popupClassName!
// 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!
); );
formView.mathLiveInputView.bind( 'value' ).to( mathCommand, 'value' ); formView.mathLiveInputView.bind( 'value' ).to( mathCommand, 'value' );
@ -164,9 +162,9 @@ export default class MathUI extends Plugin {
// Show preview element // Show preview element
const previewEl = document.getElementById( this._previewUid ); const previewEl = document.getElementById( this._previewUid );
if ( previewEl && this.formView.previewEnabled ) { if ( previewEl && this.formView.mathView ) {
// Force refresh preview // Force refresh preview
this.formView.mathView?.updateMath(); this.formView.mathView.updateMath();
} }
this.formView.equation = mathCommand.value ?? ''; this.formView.equation = mathCommand.value ?? '';

View File

@ -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 IconCheck from '@ckeditor/ckeditor5-icons/theme/icons/check.svg?raw';
import IconCancel from '@ckeditor/ckeditor5-icons/theme/icons/cancel.svg?raw'; import IconCancel from '@ckeditor/ckeditor5-icons/theme/icons/cancel.svg?raw';
import { extractDelimiters, hasDelimiters } from '../utils.js'; import { extractDelimiters, hasDelimiters } from '../utils.js';
import MathView from './mathview.js'; import MathView, { type MathViewOptions } from './mathview.js';
import MathLiveInputView from './mathliveinputview.js'; import MathLiveInputView from './mathliveinputview.js';
import RawLatexInputView from './rawlatexinputview.js'; import RawLatexInputView from './rawlatexinputview.js';
import '../../theme/mathform.css'; import '../../theme/mathform.css';
import type { KatexOptions } from '../typings-external.js';
export default class MainFormView extends View { export default class MainFormView extends View {
public saveButtonView: ButtonView; public saveButtonView: ButtonView;
public cancelButtonView: ButtonView;
public displayButtonView: SwitchButtonView;
public mathLiveInputView: MathLiveInputView; public mathLiveInputView: MathLiveInputView;
public rawLatexInputView: RawLatexInputView; public rawLatexInputView: RawLatexInputView;
public rawLatexLabel: LabelView;
public displayButtonView: SwitchButtonView;
public cancelButtonView: ButtonView;
public previewEnabled: boolean;
public previewLabel?: LabelView;
public mathView?: MathView; public mathView?: MathView;
public override locale: Locale;
public focusTracker = new FocusTracker();
public keystrokes = new KeystrokeHandler();
private _focusables = new ViewCollection<FocusableView>();
private _focusCycler: FocusCycler;
constructor( constructor(
locale: Locale, locale: Locale,
engine: mathViewOptions: MathViewOptions,
| 'mathjax'
| 'katex'
| ( (
equation: string,
element: HTMLElement,
display: boolean,
) => void ),
lazyLoad: undefined | ( () => Promise<void> ),
previewEnabled = false, previewEnabled = false,
previewUid: string, popupClassName: string[] = []
previewClassName: Array<string>,
popupClassName: Array<string>,
katexRenderOptions: KatexOptions
) { ) {
super( locale ); super( locale );
this.locale = locale;
const t = locale.t; const t = locale.t;
// Submit button // --- 1. View Initialization ---
this.saveButtonView = this._createButton( t( 'Save' ), IconCheck, 'ck-button-save', null );
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'; this.saveButtonView.type = 'submit';
// MathLive visual equation editor this.cancelButtonView = this._createButton( t( 'Cancel' ), IconCancel, 'ck-button-cancel' );
this.mathLiveInputView = this._createMathLiveInput(); this.cancelButtonView.delegate( 'execute' ).to( this, 'cancel' );
// Raw LaTeX input this.displayButtonView = this._createDisplayButton( t );
this.rawLatexInputView = this._createRawLatexInput();
// Raw LaTeX label // --- 2. Construct Children & Preview ---
this.rawLatexLabel = new LabelView( locale );
this.rawLatexLabel.text = t( 'LaTeX' );
// Display button const children: View[] = [
this.displayButtonView = this._createDisplayButton(); this.mathLiveInputView,
this.rawLatexInputView,
this.displayButtonView
];
// Cancel button if ( previewEnabled ) {
this.cancelButtonView = this._createButton( t( 'Cancel' ), IconCancel, 'ck-button-cancel', 'cancel' ); 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 = []; // Bind display mode: When button flips, preview updates automatically
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 );
this.mathView.bind( 'display' ).to( this.displayButtonView, 'isOn' ); this.mathView.bind( 'display' ).to( this.displayButtonView, 'isOn' );
children = [ children.push( previewLabel, this.mathView );
this.mathLiveInputView,
this.rawLatexLabel,
this.rawLatexInputView,
this.displayButtonView,
this.previewLabel,
this.mathView
];
} else {
children = [
this.mathLiveInputView,
this.rawLatexLabel,
this.rawLatexInputView,
this.displayButtonView
];
} }
// Add UI elements to template // --- 3. Sync Logic ---
this._setupInputSync( previewEnabled );
// --- 4. Template Setup ---
this.setTemplate( { this.setTemplate( {
tag: 'form', tag: 'form',
attributes: { attributes: {
class: [ class: [ 'ck', 'ck-math-form', ...popupClassName ],
'ck',
'ck-math-form',
...popupClassName
],
tabindex: '-1', tabindex: '-1',
spellcheck: 'false' spellcheck: 'false'
}, },
children: [ children: [
{ {
tag: 'div', tag: 'div',
attributes: { attributes: { class: [ 'ck-math-scroll' ] },
class: [ 'ck-math-scroll' ] children: [ { tag: 'div', attributes: { class: [ 'ck-math-view' ] }, children } ]
},
children: [
{
tag: 'div',
attributes: {
class: [ 'ck-math-view' ]
},
children
}
]
}, },
{ {
tag: 'div', tag: 'div',
attributes: { attributes: { class: [ 'ck-math-button-row' ] },
class: [ 'ck-math-button-row' ] children: [ this.saveButtonView, this.cancelButtonView ]
},
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 { public override render(): void {
super.render(); super.render();
// Prevent default form submit event & trigger custom 'submit' submitHandler( { view: this } );
submitHandler( {
view: this
} );
// Register form elements to focusable elements // Register focusables
const childViews = [ [
this.mathLiveInputView, this.mathLiveInputView,
this.rawLatexInputView, this.rawLatexInputView,
this.displayButtonView, this.displayButtonView,
this.saveButtonView, this.saveButtonView,
this.cancelButtonView this.cancelButtonView
]; ].forEach( v => {
childViews.forEach( v => {
if ( v.element ) { if ( v.element ) {
this._focusables.add( v ); this._focusables.add( v );
this.focusTracker.add( v.element ); this.focusTracker.add( v.element );
} }
} ); } );
// Listen to keypresses inside form element if ( this.element ) this.keystrokes.listenTo( this.element );
if ( this.element ) {
this.keystrokes.listenTo( this.element );
}
}
public override destroy(): void {
super.destroy();
}
public focus(): void {
this._focusCycler.focusFirst();
} }
public get equation(): string { public get equation(): string {
@ -176,151 +138,82 @@ export default class MainFormView extends View {
} }
public set equation( equation: string ) { public set equation( equation: string ) {
const normalizedEquation = equation.trim(); const norm = equation.trim();
this.mathLiveInputView.value = normalizedEquation.length ? normalizedEquation : null; // Direct updates to the "source of truth"
this.rawLatexInputView.value = normalizedEquation; this.mathLiveInputView.value = norm.length ? norm : null;
if ( this.previewEnabled && this.mathView ) { this.rawLatexInputView.value = norm;
this.mathView.value = normalizedEquation; if ( this.mathView ) this.mathView.value = norm;
}
} }
public focusTracker: FocusTracker = new FocusTracker(); public focus(): void {
public keystrokes: KeystrokeHandler = new KeystrokeHandler(); this._focusCycler.focusFirst();
private _focusables = new ViewCollection<FocusableView>(); }
private _focusCycler: FocusCycler = new FocusCycler( {
focusables: this._focusables,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
focusPrevious: 'shift + tab',
focusNext: 'tab'
}
} );
/** /**
* Creates the MathLive visual equation editor. * Sets up split handlers for synchronization.
*
* Handles bidirectional synchronization with the raw LaTeX textarea and preview.
*/ */
private _createMathLiveInput() { private _setupInputSync( previewEnabled: boolean ): void {
const mathLiveInput = new MathLiveInputView( this.locale ); // Handler 1: MathLive -> Raw LaTeX
this.mathLiveInputView.on( 'change:value', () => {
let eq = ( this.mathLiveInputView.value ?? '' ).trim();
const onInput = () => { // Delimiter Normalization
let equationInput = ( mathLiveInput.value ?? '' ).trim(); if ( hasDelimiters( eq ) ) {
const params = extractDelimiters( eq );
// If input has delimiters, strip them and update the display mode. eq = params.equation;
if ( hasDelimiters( equationInput ) ) {
const params = extractDelimiters( equationInput );
equationInput = params.equation;
this.displayButtonView.isOn = params.display; 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; // Sync to Raw LaTeX
if ( this.rawLatexInputView.value !== eq ) {
// Update self if needed. this.rawLatexInputView.value = eq;
if ( mathLiveInput.value !== normalizedEquation ) {
mathLiveInput.value = normalizedEquation;
} }
// Sync to raw LaTeX textarea if its value is different. // Sync to Preview
if ( this.rawLatexInputView.value !== equationInput ) { if ( previewEnabled && this.mathView && this.mathView.value !== eq ) {
this.rawLatexInputView.value = equationInput; 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. // Sync to Preview
if ( this.previewEnabled && this.mathView && this.mathView.value !== equationInput ) { if ( previewEnabled && this.mathView && this.mathView.value !== eq ) {
this.mathView.value = equationInput; this.mathView.value = eq;
} }
}; } );
mathLiveInput.on( 'change:value', onInput );
return mathLiveInput;
} }
/** private _createButton( label: string, icon: string, className: string ): ButtonView {
* Creates the raw LaTeX textarea editor. const btn = new ButtonView( this.locale );
* btn.set( { label, icon, tooltip: true } );
* Provides direct LaTeX code editing and synchronizes changes with the MathLive visual editor. btn.extendTemplate( { attributes: { class: className } } );
*/ return btn;
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( private _createDisplayButton( t: ( str: string ) => string ): SwitchButtonView {
label: string, const btn = new SwitchButtonView( this.locale );
icon: string, btn.set( { label: t( 'Display mode' ), withText: true } );
className: string, btn.extendTemplate( { attributes: { class: 'ck-button-display-toggle' } } );
eventName: string | null
) {
const button = new ButtonView( this.locale );
button.set( { btn.on( 'execute', () => {
label, btn.isOn = !btn.isOn;
icon, // mathView updates automatically via bind()
tooltip: true
} ); } );
return btn;
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;
} }
} }

View File

@ -1,33 +1,27 @@
import { View, type Locale } from 'ckeditor5'; import { View, type Locale } from 'ckeditor5';
import { MathfieldElement } from 'mathlive'; import 'mathlive'; // Import side-effects only (registers the <math-field> tag)
/** /**
* A view that wraps the MathLive `<math-field>` web component for interactive LaTeX equation editing. * A wrapper for the MathLive <math-field> component.
* * Uses 'any' typing to avoid TypeScript module resolution errors.
* 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 { export default class MathLiveInputView extends View {
/** /**
* The current LaTeX value of the math field. * The current LaTeX value.
*
* @observable * @observable
*/ */
public declare value: string | null; public declare value: string | null;
/** /**
* Whether the input is in read-only mode. * Read-only state.
*
* @observable * @observable
*/ */
public declare isReadOnly: boolean; public declare isReadOnly: boolean;
/** /**
* Reference to the `<math-field>` 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 ) { constructor( locale: Locale ) {
super( locale ); super( locale );
@ -43,68 +37,53 @@ export default class MathLiveInputView extends View {
} ); } );
} }
/**
* @inheritDoc
*/
public override render(): void { public override render(): void {
super.render(); 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.) // 1. Create element using DOM API instead of Class constructor
// This avoids "Module has no exported member" errors.
// Create the MathLive math-field custom element
const mathfield = document.createElement( 'math-field' ) as any; const mathfield = document.createElement( 'math-field' ) as any;
this.mathfield = mathfield;
// Configure the virtual keyboard to be manually controlled (shown by user interaction) // 2. Configure Options
mathfield.setAttribute( 'virtual-keyboard-mode', 'manual' ); mathfield.mathVirtualKeyboardPolicy = 'manual';
// Set initial value // Disable sounds
const initialValue = this.value ?? ''; const MathfieldElement = customElements.get( 'math-field' );
if ( initialValue ) { if ( MathfieldElement ) {
( mathfield as any ).value = initialValue; ( MathfieldElement as any ).soundsDirectory = null;
( MathfieldElement as any ).plonkSound = null;
} }
// Bind readonly state // 3. Set Initial State
if ( this.isReadOnly ) { mathfield.value = this.value ?? '';
( mathfield as any ).readOnly = true; mathfield.readOnly = this.isReadOnly;
}
// Sync math-field changes to observable value // 4. Bind Events (DOM -> Observable)
mathfield.addEventListener( 'input', () => { mathfield.addEventListener( 'input', () => {
const nextValue: string = ( mathfield as any ).value; const val = mathfield.value;
this.value = nextValue.length ? nextValue : null; this.value = val.length ? val : null;
} ); } );
// Sync observable value changes back to math-field // 5. Bind Events (Observable -> DOM)
this.on( 'change:value', () => { this.on( 'change:value', ( _evt, _name, nextValue ) => {
const nextValue = this.value ?? ''; if ( mathfield.value !== nextValue ) {
if ( ( mathfield as any ).value !== nextValue ) { mathfield.value = nextValue ?? '';
( mathfield as any ).value = nextValue;
} }
} ); } );
// Sync readonly state to math-field this.on( 'change:isReadOnly', ( _evt, _name, nextValue ) => {
this.on( 'change:isReadOnly', () => { mathfield.readOnly = nextValue;
( mathfield as any ).readOnly = this.isReadOnly;
} ); } );
// 6. Mount
this.element?.appendChild( mathfield ); this.element?.appendChild( mathfield );
this.mathfield = mathfield;
} }
/**
* Focuses the math-field element.
*/
public focus(): void { public focus(): void {
this.mathfield?.focus(); this.mathfield?.focus();
} }
/**
* @inheritDoc
*/
public override destroy(): void { public override destroy(): void {
if ( this.mathfield ) { if ( this.mathfield ) {
this.mathfield.remove(); this.mathfield.remove();

View File

@ -2,44 +2,44 @@ import { View, type Locale } from 'ckeditor5';
import type { KatexOptions } from '../typings-external.js'; import type { KatexOptions } from '../typings-external.js';
import { renderEquation } from '../utils.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<void> );
previewUid: string;
previewClassName: Array<string>;
katexRenderOptions: KatexOptions;
}
export default class MathView extends View { export default class MathView extends View {
/**
* The LaTeX equation value to render.
* @observable
*/
public declare value: string; public declare value: string;
/**
* Whether to render in display mode (centered) or inline.
* @observable
*/
public declare display: boolean; public declare display: boolean;
public previewUid: string;
public previewClassName: Array<string>;
public katexRenderOptions: KatexOptions;
public engine:
| 'mathjax'
| 'katex'
| ( ( equation: string, element: HTMLElement, display: boolean ) => void );
public lazyLoad: undefined | ( () => Promise<void> );
constructor( /**
engine: * Configuration options passed during initialization.
| 'mathjax' */
| 'katex' private options: MathViewOptions;
| ( (
equation: string, constructor( locale: Locale, options: MathViewOptions ) {
element: HTMLElement,
display: boolean,
) => void ),
lazyLoad: undefined | ( () => Promise<void> ),
locale: Locale,
previewUid: string,
previewClassName: Array<string>,
katexRenderOptions: KatexOptions
) {
super( locale ); super( locale );
this.options = options;
this.engine = engine;
this.lazyLoad = lazyLoad;
this.previewUid = previewUid;
this.katexRenderOptions = katexRenderOptions;
this.previewClassName = previewClassName;
this.set( 'value', '' ); this.set( 'value', '' );
this.set( 'display', false ); this.set( 'display', false );
// Update rendering when state changes.
// Checking isRendered prevents errors during initialization.
this.on( 'change', () => { this.on( 'change', () => {
if ( this.isRendered ) { if ( this.isRendered ) {
this.updateMath(); this.updateMath();
@ -59,13 +59,13 @@ export default class MathView extends View {
void renderEquation( void renderEquation(
this.value, this.value,
this.element, this.element,
this.engine, this.options.engine,
this.lazyLoad, this.options.lazyLoad,
this.display, this.display,
true, true, // isPreview
this.previewUid, this.options.previewUid,
this.previewClassName, this.options.previewClassName,
this.katexRenderOptions this.options.katexRenderOptions
); );
} }
} }

View File

@ -2,21 +2,16 @@ import { LabeledFieldView, createLabeledTextarea, type Locale, type TextareaView
/** /**
* A labeled textarea view for direct LaTeX code editing. * 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<TextareaView> { export default class RawLatexInputView extends LabeledFieldView<TextareaView> {
/** /**
* The current LaTeX value. * The current LaTeX value.
*
* @observable * @observable
*/ */
public declare value: string; public declare value: string;
/** /**
* Whether the input is in read-only mode. * Whether the input is in read-only mode.
*
* @observable * @observable
*/ */
public declare isReadOnly: boolean; public declare isReadOnly: boolean;
@ -29,21 +24,23 @@ export default class RawLatexInputView extends LabeledFieldView<TextareaView> {
const fieldView = this.fieldView; 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', () => { fieldView.on( 'input', () => {
if ( fieldView.element ) { if ( fieldView.element ) {
this.value = fieldView.element.value; this.value = fieldView.element.value;
} }
} ); } );
// Sync observable value changes back to textarea // 2. Sync: Observable -> DOM (Textarea)
this.on( 'change:value', () => { this.on( 'change:value', () => {
// Check for difference to avoid cursor jumping or unnecessary updates
if ( fieldView.element && fieldView.element.value !== this.value ) { if ( fieldView.element && fieldView.element.value !== this.value ) {
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', () => { this.on( 'change:isReadOnly', () => {
if ( fieldView.element ) { if ( fieldView.element ) {
fieldView.element.readOnly = this.isReadOnly; fieldView.element.readOnly = this.isReadOnly;
@ -51,12 +48,7 @@ export default class RawLatexInputView extends LabeledFieldView<TextareaView> {
} ); } );
} }
/**
* @inheritDoc
*/
public override render(): void { public override render(): void {
super.render(); super.render();
// All styling is handled via CSS in mathform.css
// (Removed obsolete mousedown propagation; no longer needed after resize & gray-area click removal.)
} }
} }