Small improvements

This commit is contained in:
meinzzzz 2025-11-26 22:48:57 +01:00
parent 64ab1c4116
commit c46cf41842
2 changed files with 118 additions and 109 deletions

View File

@ -1,94 +1,103 @@
import { View, type Locale } from 'ckeditor5'; import { View, type Locale } from 'ckeditor5';
import 'mathlive'; // Import side-effects only (registers the <math-field> tag) import 'mathlive'; // Import side-effects only (registers the <math-field> tag)
/**
* Interface describing the custom <math-field> element.
*/
interface MathFieldElement extends HTMLElement {
value: string;
readOnly: boolean;
mathVirtualKeyboardPolicy: string;
}
/** /**
* A wrapper for the MathLive <math-field> component. * A wrapper for the MathLive <math-field> component.
* Uses 'any' typing to avoid TypeScript module resolution errors.
*/ */
export default class MathLiveInputView extends View { export default class MathLiveInputView extends View {
/** /**
* The current LaTeX value. * The current LaTeX value.
* @observable * @observable
*/ */
public declare value: string | null; public declare value: string | null;
/** /**
* Read-only state. * Read-only state.
* @observable * @observable
*/ */
public declare isReadOnly: boolean; public declare isReadOnly: boolean;
/** /**
* Reference to the DOM element (typed as any to prevent TS errors). * Reference to the DOM element.
*/ * Typed as MathFieldElement | null for proper TS support.
public mathfield: any = null; */
public mathfield: MathFieldElement | null = null;
constructor( locale: Locale ) { constructor( locale: Locale ) {
super( locale ); super( locale );
this.set( 'value', null ); this.set( 'value', null );
this.set( 'isReadOnly', false ); this.set( 'isReadOnly', false );
this.setTemplate( { this.setTemplate( {
tag: 'div', tag: 'div',
attributes: { attributes: {
class: [ 'ck', 'ck-mathlive-input' ] class: [ 'ck', 'ck-mathlive-input' ]
} }
} ); } );
} }
public override render(): void { public override render(): void {
super.render(); super.render();
// 1. Create element using DOM API instead of Class constructor // 1. Create element with the specific type
// This avoids "Module has no exported member" errors. const mathfield = document.createElement( 'math-field' ) as MathFieldElement;
const mathfield = document.createElement( 'math-field' ) as any;
// 2. Configure Options // 2. Configure Options
mathfield.mathVirtualKeyboardPolicy = 'manual'; mathfield.mathVirtualKeyboardPolicy = 'manual';
// Disable sounds // Disable sounds
const MathfieldElement = customElements.get( 'math-field' ); const MathfieldConstructor = customElements.get( 'math-field' );
if ( MathfieldElement ) { if ( MathfieldConstructor ) {
( MathfieldElement as any ).soundsDirectory = null; ( MathfieldConstructor as any ).soundsDirectory = null;
( MathfieldElement as any ).plonkSound = null; ( MathfieldConstructor as any ).plonkSound = null;
} }
// 3. Set Initial State // 3. Set Initial State
mathfield.value = this.value ?? ''; mathfield.value = this.value ?? '';
mathfield.readOnly = this.isReadOnly; mathfield.readOnly = this.isReadOnly;
// 4. Bind Events (DOM -> Observable) // 4. Bind Events (DOM -> Observable)
mathfield.addEventListener( 'input', () => { mathfield.addEventListener( 'input', () => {
const val = mathfield.value; const val = mathfield.value;
this.value = val.length ? val : null; this.value = val.length ? val : null;
} ); } );
// 5. Bind Events (Observable -> DOM) // 5. Bind Events (Observable -> DOM)
this.on( 'change:value', ( _evt, _name, nextValue ) => { this.on( 'change:value', ( _evt, _name, nextValue ) => {
if ( mathfield.value !== nextValue ) { if ( mathfield.value !== nextValue ) {
mathfield.value = nextValue ?? ''; mathfield.value = nextValue ?? '';
} }
} ); } );
this.on( 'change:isReadOnly', ( _evt, _name, nextValue ) => { this.on( 'change:isReadOnly', ( _evt, _name, nextValue ) => {
mathfield.readOnly = nextValue; mathfield.readOnly = nextValue;
} ); } );
// 6. Mount // 6. Mount
this.element?.appendChild( mathfield ); this.element?.appendChild( mathfield );
this.mathfield = mathfield; this.mathfield = mathfield;
} }
public focus(): void { public focus(): void {
this.mathfield?.focus(); this.mathfield?.focus();
} }
public override destroy(): void { public override destroy(): void {
if ( this.mathfield ) { if ( this.mathfield ) {
this.mathfield.remove(); this.mathfield.remove();
this.mathfield = null; this.mathfield = null;
} }
super.destroy(); super.destroy();
} }
} }

View File

@ -4,51 +4,51 @@ 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.
*/ */
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;
constructor( locale: Locale ) { constructor( locale: Locale ) {
super( locale, createLabeledTextarea ); super( locale, createLabeledTextarea );
this.set( 'value', '' ); this.set( 'value', '' );
this.set( 'isReadOnly', false ); this.set( 'isReadOnly', false );
const fieldView = this.fieldView; const fieldView = this.fieldView;
// 1. Sync: DOM (Textarea) -> Observable // 1. Sync: DOM (Textarea) -> Observable
// We listen to the native 'input' event on the child view fieldView.on( 'input', () => {
fieldView.on( 'input', () => { // We cast strictly to HTMLTextAreaElement to access '.value' safely
if ( fieldView.element ) { const textarea = fieldView.element as HTMLTextAreaElement;
this.value = fieldView.element.value; if ( textarea ) {
} this.value = textarea.value;
} ); }
} );
// 2. Sync: Observable -> DOM (Textarea) // 2. Sync: Observable -> DOM (Textarea)
this.on( 'change:value', () => { this.on( 'change:value', () => {
// Check for difference to avoid cursor jumping or unnecessary updates const textarea = fieldView.element as HTMLTextAreaElement;
if ( fieldView.element && fieldView.element.value !== this.value ) { // Check for difference to avoid cursor jumping
fieldView.element.value = this.value; if ( textarea && textarea.value !== this.value ) {
} textarea.value = this.value;
} ); }
} );
// 3. Sync: ReadOnly State // 3. Sync: ReadOnly State
this.on( 'change:isReadOnly', () => { this.on( 'change:isReadOnly', ( _evt, _name, nextValue ) => {
if ( fieldView.element ) { fieldView.isReadOnly = nextValue;
fieldView.element.readOnly = this.isReadOnly; } );
} }
} );
}
public override render(): void { public override render(): void {
super.render(); super.render();
} }
} }