From 37c16a07ab8e8e7faaf9451f472293e81be21faf Mon Sep 17 00:00:00 2001
From: jojohoch <joachim.hoch@iqb.hu-berlin.de>
Date: Tue, 26 Nov 2024 16:28:16 +0100
Subject: [PATCH] [player] Add container for math keyboard

---
 docs/unit_definition_changelog.txt            | 17 ++--
 .../input-elements/math-field.component.ts    | 14 ++--
 .../text-area-math/area-segment.component.ts  |  6 +-
 .../text-area-math.component.ts               | 17 ++--
 .../text-input-component.directive.ts         |  2 +-
 projects/common/math-editor.module.ts         | 17 ++--
 .../elements/input-elements/text-area-math.ts | 26 +-----
 .../services/range-selection-service.ts       |  7 +-
 .../modules/logging/services/log.service.ts   |  2 +-
 projects/player/src/app/app.module.ts         |  6 +-
 .../compound-group-element.component.ts       |  4 +-
 .../element-group-selection.component.ts      |  4 +-
 .../input-group-element.component.html        |  5 --
 .../input-group-element.component.ts          |  2 -
 .../text-input-group-element.component.html   |  7 ++
 .../text-input-group-element.component.ts     |  4 +
 .../floating-keypad.component.ts              |  2 +-
 .../player-layout.component.html              | 48 +++++------
 .../player-layout.component.spec.ts           | 15 +++-
 .../player-layout/player-layout.component.ts  |  2 +
 .../math-keyboard-container.component.html    |  3 +
 .../math-keyboard-container.component.scss    |  2 +
 .../math-keyboard-container.component.ts      | 34 ++++++++
 .../directives/text-input-group.directive.ts  | 80 +++++++++++--------
 .../app/models/text-input-component.type.ts   |  5 +-
 .../player/src/app/services/input-service.ts  |  5 +-
 .../src/app/services/keyboard.service.ts      | 26 +-----
 .../services/math-keyboard.service.spec.ts    | 16 ++++
 .../src/app/services/math-keyboard.service.ts | 31 +++++++
 .../app/services/scroll-to-input.service.ts   | 27 +++++++
 30 files changed, 276 insertions(+), 160 deletions(-)
 create mode 100644 projects/player/src/app/components/math-keyboard-container/math-keyboard-container.component.html
 create mode 100644 projects/player/src/app/components/math-keyboard-container/math-keyboard-container.component.scss
 create mode 100644 projects/player/src/app/components/math-keyboard-container/math-keyboard-container.component.ts
 create mode 100644 projects/player/src/app/services/math-keyboard.service.spec.ts
 create mode 100644 projects/player/src/app/services/math-keyboard.service.ts
 create mode 100644 projects/player/src/app/services/scroll-to-input.service.ts

diff --git a/docs/unit_definition_changelog.txt b/docs/unit_definition_changelog.txt
index 042e4f737..666cb4c08 100644
--- a/docs/unit_definition_changelog.txt
+++ b/docs/unit_definition_changelog.txt
@@ -200,10 +200,13 @@ iqb-aspect-definition@1.0.0
 - all elements: new property "alias"
 
 - TextAreaMath
-  - new property: inputAssistancePreset
-  - new property: inputAssistancePosition
-  - new property: inputAssistanceFloatingStartPosition
-  - new property: showSoftwareKeyboard
-  - new property: hideNativeKeyboard
-  - new property: addInputAssistanceToKeyboard
-  - new property: hasArrowKeys
+  - new property:inputAssistancePreset
+  - new property:inputAssistanceCustomKeys
+  - new property:inputAssistancePosition
+  - new property:inputAssistanceFloatingStartPosition;
+  - new property:restrictedToInputAssistanceChars
+  - new property:hasArrowKeys
+  - new property:hasBackspaceKey
+  - new property:showSoftwareKeyboard
+  - new property:addInputAssistanceToKeyboard
+  - new property:hideNativeKeyboard
diff --git a/projects/common/components/input-elements/math-field.component.ts b/projects/common/components/input-elements/math-field.component.ts
index e9d1c7c50..98d728e90 100644
--- a/projects/common/components/input-elements/math-field.component.ts
+++ b/projects/common/components/input-elements/math-field.component.ts
@@ -3,8 +3,8 @@ import {
   Component, Input, Pipe, PipeTransform
 } from '@angular/core';
 import { UntypedFormGroup } from '@angular/forms';
-import { FormElementComponent } from 'common/directives/form-element-component.directive';
 import { MathFieldElement } from 'common/models/elements/input-elements/math-field';
+import { TextInputComponent } from 'common/directives/text-input-component.directive';
 
 @Component({
   selector: 'aspect-math-field',
@@ -18,10 +18,12 @@ import { MathFieldElement } from 'common/models/elements/input-elements/math-fie
        [style.background-color]="elementModel.styling.backgroundColor">
       <label>{{elementModel.label}}</label><br>
       <aspect-math-input [value]="$any(elementModel.value) | getValue: elementFormControl.value : parentForm"
-                                  [readonly]="elementModel.readOnly"
-                                  [enableModeSwitch]="elementModel.enableModeSwitch"
-                                  (input)="elementFormControl.setValue($any($event.target).value)"
-                                  (focusout)="elementFormControl.markAsTouched()">
+                         [readonly]="elementModel.readOnly"
+                         [enableModeSwitch]="elementModel.enableModeSwitch"
+                         (input)="elementFormControl.setValue($any($event.target).value)"
+                         (focusIn)="focusChanged.emit({ inputElement: $event, focused: true })"
+                         (focusOut)="elementFormControl.markAsTouched();
+                                     focusChanged.emit({ inputElement: $event, focused: true })">
       </aspect-math-input>
       <mat-error *ngIf="elementFormControl.errors && elementFormControl.touched"
                  class="error-message">
@@ -31,7 +33,7 @@ import { MathFieldElement } from 'common/models/elements/input-elements/math-fie
   `,
   styles: ['.error-message {font-size: 75%; margin-top: 15px; margin-left: 10px;}']
 })
-export class MathFieldComponent extends FormElementComponent {
+export class MathFieldComponent extends TextInputComponent {
   @Input() elementModel!: MathFieldElement;
 }
 
diff --git a/projects/common/components/input-elements/text-area-math/area-segment.component.ts b/projects/common/components/input-elements/text-area-math/area-segment.component.ts
index e4734a675..ba27b24f0 100644
--- a/projects/common/components/input-elements/text-area-math/area-segment.component.ts
+++ b/projects/common/components/input-elements/text-area-math/area-segment.component.ts
@@ -12,7 +12,8 @@ import { BehaviorSubject } from 'rxjs';
       <aspect-math-input #inputComponent
                                   [fullWidth]="false"
                                   [value]="value"
-                                  (focusIn)="selectedFocus.next(this.index)"
+                                  (focusIn)="onFocusIn($event)"
+                                  (focusOut)="focusOut.emit($event)"
                                   (valueChange)="valueChanged.emit({ index: index, value: $event})">
       </aspect-math-input>
     } @else {
@@ -50,9 +51,6 @@ export class AreaSegmentComponent {
   @ViewChild('inputComponent') inputComponent!: AreaTextInputComponent | MathInputComponent;
 
   setFocus(offset?: number) {
-    // if (document.activeElement instanceof HTMLElement) {
-    //   document.activeElement.blur();
-    // }
     this.inputComponent.setFocus(offset);
   }
 
diff --git a/projects/common/components/input-elements/text-area-math/text-area-math.component.ts b/projects/common/components/input-elements/text-area-math/text-area-math.component.ts
index 758ea4c31..4c3c86e74 100644
--- a/projects/common/components/input-elements/text-area-math/text-area-math.component.ts
+++ b/projects/common/components/input-elements/text-area-math/text-area-math.component.ts
@@ -2,16 +2,17 @@ import {
   Component, ElementRef, EventEmitter, Input, OnInit, Output, QueryList, ViewChild, ViewChildren
 } from '@angular/core';
 import { TextAreaMathElement, TextAreaMath } from 'common/models/elements/input-elements/text-area-math';
-import { FormElementComponent } from 'common/directives/form-element-component.directive';
 import {
   AreaSegmentComponent
 } from 'common/components/input-elements/text-area-math/area-segment.component';
 import { BehaviorSubject } from 'rxjs';
 import { RangeSelectionService } from 'common/services/range-selection-service';
+import { TextInputComponent } from 'common/directives/text-input-component.directive';
 
 @Component({
   selector: 'aspect-text-area-math',
   template: `
+    <label class="label">{{elementModel.label}}</label><br>
     <button class="insert-formula-button"
             mat-button
             cdkOverlayOrigin #trigger="cdkOverlayOrigin"
@@ -42,9 +43,9 @@ import { RangeSelectionService } from 'common/services/range-selection-service';
           [selectedFocus]="selectedFocus"
           (valueChanged)="onValueChanged($event)"
           [index]="i"
+          (onKeyDown)="onKeyDown.emit($event)"
           (focusIn)="focusChanged.emit({ inputElement: $event, focused: true })"
           (focusOut)="focusChanged.emit({ inputElement: $event, focused: false })"
-          (onKeyDown)="onKeyDown.emit($event)"
           (remove)="removeSegment($event)">
         </aspect-text-area-math-segment>
       }
@@ -54,22 +55,16 @@ import { RangeSelectionService } from 'common/services/range-selection-service';
     </mat-error>
   `,
   styles: [
+    '.label {font-size: 20px; line-height: 135%;}',
     '.alignment-fix {padding: 15px 0; display: inline-block; width: 0;}',
-    '.text-area {border: 1px solid black; border-radius: 3px; padding: 5px;}',
-    ':host {display: flex; flex-direction: column; height: 100%;}',
+    '.text-area {border: 1px solid black; border-radius: 3px; padding: 3px;}',
     '.insert-formula-button {font-size: large; width: 160px; background-color: #ddd; padding: 15px 10px; height: 55px;}'
   ]
 })
-export class TextAreaMathComponent extends FormElementComponent implements OnInit {
+export class TextAreaMathComponent extends TextInputComponent implements OnInit {
   @Input() elementModel!: TextAreaMathElement;
   @Output() mathInputFocusIn: EventEmitter<FocusEvent> = new EventEmitter();
   @Output() mathInputFocusOut: EventEmitter<FocusEvent> = new EventEmitter();
-  @Output() focusChanged = new EventEmitter<{ inputElement: HTMLElement; focused: boolean }>();
-  @Output() onKeyDown = new EventEmitter<{
-    keyboardEvent: KeyboardEvent;
-    inputElement: HTMLElement;
-  }>();
-
   @ViewChildren(AreaSegmentComponent) segmentComponents!: QueryList<AreaSegmentComponent>;
   @ViewChild('textArea') textArea!: ElementRef;
 
diff --git a/projects/common/directives/text-input-component.directive.ts b/projects/common/directives/text-input-component.directive.ts
index a2cfa91ee..06755a8c4 100644
--- a/projects/common/directives/text-input-component.directive.ts
+++ b/projects/common/directives/text-input-component.directive.ts
@@ -8,6 +8,6 @@ export abstract class TextInputComponent extends FormElementComponent implements
   @Output() focusChanged = new EventEmitter<{ inputElement: HTMLElement; focused: boolean }>();
   @Output() onKeyDown = new EventEmitter<{
     keyboardEvent: KeyboardEvent;
-    inputElement: HTMLInputElement | HTMLTextAreaElement
+    inputElement: HTMLInputElement | HTMLTextAreaElement | HTMLElement;
   }>();
 }
diff --git a/projects/common/math-editor.module.ts b/projects/common/math-editor.module.ts
index edc3fa49d..9d83c806a 100644
--- a/projects/common/math-editor.module.ts
+++ b/projects/common/math-editor.module.ts
@@ -21,8 +21,8 @@ import { MatButtonToggleChange, MatButtonToggleModule } from '@angular/material/
          [class.full-width]="fullWidth"
          [class.inline-block]="!fullWidth"
          [class.read-only]="readonly"
-         (focusin)="onFocusIn($event)"
-         (focusout)="onFocusOut($event)">
+         (focusin)="onFocusIn()"
+         (focusout)="onFocusOut()">
     </div>
   `,
   styles: [`
@@ -58,8 +58,8 @@ export class MathInputComponent implements AfterViewInit, OnChanges {
   @Input() readonly: boolean = false;
   @Input() enableModeSwitch: boolean = false;
   @Output() valueChange: EventEmitter<string> = new EventEmitter();
-  @Output() focusIn: EventEmitter<FocusEvent> = new EventEmitter();
-  @Output() focusOut: EventEmitter<FocusEvent> = new EventEmitter();
+  @Output() focusIn: EventEmitter<MathfieldElement> = new EventEmitter();
+  @Output() focusOut: EventEmitter<MathfieldElement> = new EventEmitter();
   @ViewChild('inputRef') inputRef!: ElementRef;
   @ViewChild('container') container!: ElementRef;
 
@@ -79,6 +79,7 @@ export class MathInputComponent implements AfterViewInit, OnChanges {
     this.inputRef.nativeElement.appendChild(this.mathFieldElement);
     this.mathFieldElement.value = this.value;
     this.mathFieldElement.readOnly = this.readonly;
+    setTimeout(() => { this.mathFieldElement.menuItems = []; }); // Disable context menu
   }
 
   ngOnChanges(changes: SimpleChanges): void {
@@ -105,14 +106,14 @@ export class MathInputComponent implements AfterViewInit, OnChanges {
     this.valueChange.emit(this.mathFieldElement.getValue());
   }
 
-  onFocusIn(event: FocusEvent) {
-    this.focusIn.emit(event);
+  onFocusIn() {
+    this.focusIn.emit(this.mathFieldElement);
     window.mathVirtualKeyboard.show();
   }
 
-  onFocusOut(event: FocusEvent) {
+  onFocusOut() {
+    this.focusOut.emit(this.mathFieldElement);
     window.mathVirtualKeyboard.hide();
-    this.focusOut.emit(event);
   }
 }
 
diff --git a/projects/common/models/elements/input-elements/text-area-math.ts b/projects/common/models/elements/input-elements/text-area-math.ts
index 7f3d3e22c..e244d59d5 100644
--- a/projects/common/models/elements/input-elements/text-area-math.ts
+++ b/projects/common/models/elements/input-elements/text-area-math.ts
@@ -1,6 +1,4 @@
-import {
-  InputElement
-} from 'common/models/elements/element';
+import { TextInputElement } from 'common/models/elements/element';
 import {
   BasicStyles,
   PositionProperties,
@@ -13,25 +11,16 @@ import { TextAreaMathComponent } from 'common/components/input-elements/text-are
 import { environment } from 'common/environment';
 import {
   AbstractIDService,
-  InputAssistancePreset,
-  InputElementProperties,
-  KeyInputElementProperties,
+  KeyInputElementProperties, TextInputElementProperties,
   UIElementType
 } from 'common/interfaces';
 import { InstantiationEror } from 'common/errors';
 
-export class TextAreaMathElement extends InputElement implements TextAreaMathProperties {
+export class TextAreaMathElement extends TextInputElement implements TextAreaMathProperties {
   type: UIElementType = 'text-area-math';
   value: TextAreaMath[] = [];
   rowCount: number = 2;
   hasAutoHeight: boolean = false;
-  inputAssistancePreset: InputAssistancePreset = null;
-  inputAssistancePosition: 'floating' | 'right' = 'floating';
-  inputAssistanceFloatingStartPosition: 'startBottom' | 'endCenter' = 'startBottom';
-  showSoftwareKeyboard: boolean = false;
-  addInputAssistanceToKeyboard: boolean = false;
-  hideNativeKeyboard: boolean = false;
-  hasArrowKeys: boolean = false;
   position: PositionProperties;
   styling: BasicStyles & {
     lineHeight: number;
@@ -45,13 +34,6 @@ export class TextAreaMathElement extends InputElement implements TextAreaMathPro
     if (isTextAreaMathProperties(element)) {
       this.rowCount = element.rowCount;
       this.hasAutoHeight = element.hasAutoHeight;
-      this.inputAssistancePreset = element.inputAssistancePreset;
-      this.inputAssistancePosition = element.inputAssistancePosition;
-      this.inputAssistanceFloatingStartPosition = element.inputAssistanceFloatingStartPosition;
-      this.showSoftwareKeyboard = element.showSoftwareKeyboard;
-      this.addInputAssistanceToKeyboard = element.addInputAssistanceToKeyboard;
-      this.hideNativeKeyboard = element.hideNativeKeyboard;
-      this.hasArrowKeys = element.hasArrowKeys;
       this.position = { ...element.position };
       this.styling = { ...element.styling };
     } else {
@@ -91,7 +73,7 @@ export class TextAreaMathElement extends InputElement implements TextAreaMathPro
   }
 }
 
-export interface TextAreaMathProperties extends InputElementProperties, KeyInputElementProperties {
+export interface TextAreaMathProperties extends TextInputElementProperties, KeyInputElementProperties {
   rowCount: number;
   hasAutoHeight: boolean;
   position: PositionProperties;
diff --git a/projects/common/services/range-selection-service.ts b/projects/common/services/range-selection-service.ts
index 43d391442..816961d70 100644
--- a/projects/common/services/range-selection-service.ts
+++ b/projects/common/services/range-selection-service.ts
@@ -51,24 +51,21 @@ export class RangeSelectionService {
       if (node.nodeType === Node.TEXT_NODE) {
         const textLength = node.textContent?.length || 0;
 
-        // Prüfen, ob Startpunkt im aktuellen Textknoten liegt
         if (start >= charCount && start <= charCount + textLength) {
           range.setStart(node, start - charCount);
         }
 
-        // Prüfen, ob Endpunkt im aktuellen Textknoten liegt
         if (end >= charCount && end <= charCount + textLength) {
           range.setEnd(node, end - charCount);
-          return true; // Fertig, wenn beide Punkte gesetzt sind
+          return true;
         }
 
         charCount += textLength;
       } else {
-        // Rekursive Durchsuchung von Kindknoten
         const childNodes = node.childNodes;
         for (let i = 0; i < childNodes.length; i++) {
           if (setRangeOffsets(childNodes[i])) {
-            return true; // Fertig, wenn beide Punkte gesetzt sind
+            return true;
           }
         }
       }
diff --git a/projects/player/modules/logging/services/log.service.ts b/projects/player/modules/logging/services/log.service.ts
index 47ad6e147..651ce17b3 100644
--- a/projects/player/modules/logging/services/log.service.ts
+++ b/projects/player/modules/logging/services/log.service.ts
@@ -6,7 +6,7 @@ export enum LogLevel { NONE = 0, ERROR = 1, WARN = 2, INFO = 3, DEBUG = 4 }
   providedIn: 'root'
 })
 export class LogService {
-  static level: LogLevel = 4;
+  static level: LogLevel = 0;
 
   static error(...args: unknown[]): void {
     if (LogService.level >= LogLevel.ERROR) {
diff --git a/projects/player/src/app/app.module.ts b/projects/player/src/app/app.module.ts
index 3aed1aae1..645d65a63 100644
--- a/projects/player/src/app/app.module.ts
+++ b/projects/player/src/app/app.module.ts
@@ -67,6 +67,9 @@ import { ExternalAppGroupElementComponent } from
 import { InputAssistanceCustomKeysPipe } from './pipes/input-assistance-custom-keys.pipe';
 import { HasNextPagePipe } from './pipes/has-next-page.pipe';
 import { IsValidPagePipe } from './pipes/is-valid-page.pipe';
+import {
+  MathKeyboardContainerComponent
+} from 'player/src/app/components/math-keyboard-container/math-keyboard-container.component';
 
 @NgModule({
   declarations: [
@@ -125,7 +128,8 @@ import { IsValidPagePipe } from './pipes/is-valid-page.pipe';
     MarkablesContainerComponent,
     IsEnabledNavigationTargetPipe,
     MarkingPanelComponent,
-    UnitNavNextComponent
+    UnitNavNextComponent,
+    MathKeyboardContainerComponent
   ],
   providers: [
     { provide: APIService, useExisting: MetaDataService },
diff --git a/projects/player/src/app/components/elements/compound-group-element/compound-group-element.component.ts b/projects/player/src/app/components/elements/compound-group-element/compound-group-element.component.ts
index 6d04165a0..3086e8532 100644
--- a/projects/player/src/app/components/elements/compound-group-element/compound-group-element.component.ts
+++ b/projects/player/src/app/components/elements/compound-group-element/compound-group-element.component.ts
@@ -45,6 +45,7 @@ import { ValidationService } from '../../../services/validation.service';
 import { KeypadService } from '../../../services/keypad.service';
 import { KeyboardService } from '../../../services/keyboard.service';
 import { DeviceService } from '../../../services/device.service';
+import { MathKeyboardService } from 'player/src/app/services/math-keyboard.service';
 
 @Component({
   selector: 'aspect-compound-group-element',
@@ -74,6 +75,7 @@ export class CompoundGroupElementComponent extends TextInputGroupDirective imple
     public keyboardService: KeyboardService,
     public deviceService: DeviceService,
     public keypadService: KeypadService,
+    public mathKeyboardService: MathKeyboardService,
     public unitStateService: UnitStateService,
     public elementModelElementCodeMappingService: ElementModelElementCodeMappingService,
     public veronaSubscriptionService: VeronaSubscriptionService,
@@ -330,7 +332,7 @@ export class CompoundGroupElementComponent extends TextInputGroupDirective imple
 
   private onKeyDown(event: {
     keyboardEvent: KeyboardEvent;
-    inputElement: HTMLInputElement | HTMLTextAreaElement
+    inputElement: HTMLInputElement | HTMLTextAreaElement | HTMLElement
   }, elementModel: InputElement): void {
     this.detectHardwareKeyboard(elementModel);
     this.checkInputLimitation(event, elementModel);
diff --git a/projects/player/src/app/components/elements/element-group-selection/element-group-selection.component.ts b/projects/player/src/app/components/elements/element-group-selection/element-group-selection.component.ts
index f2e4e1505..1ca7ff0cc 100644
--- a/projects/player/src/app/components/elements/element-group-selection/element-group-selection.component.ts
+++ b/projects/player/src/app/components/elements/element-group-selection/element-group-selection.component.ts
@@ -13,13 +13,13 @@ export class ElementGroupSelectionComponent implements OnInit {
   @Input() pageIndex!: number;
 
   groups: ElementGroupInterface[] = [
-    { name: 'textInputGroup', types: ['text-field', 'text-area', 'spell-correct', 'text-area-math'] },
+    { name: 'textInputGroup', types: ['text-field', 'text-area', 'spell-correct', 'text-area-math', 'math-field'] },
     { name: 'mediaPlayerGroup', types: ['audio', 'video'] },
     {
       name: 'inputGroup',
       types: [
         'checkbox', 'slider', 'drop-list', 'radio', 'radio-group-images',
-        'dropdown', 'hotspot-image', 'math-field'
+        'dropdown', 'hotspot-image'
       ]
     },
     { name: 'compoundGroup', types: ['cloze', 'likert', 'table'] },
diff --git a/projects/player/src/app/components/elements/input-group-element/input-group-element.component.html b/projects/player/src/app/components/elements/input-group-element/input-group-element.component.html
index 95562c52c..9fb3b161a 100644
--- a/projects/player/src/app/components/elements/input-group-element/input-group-element.component.html
+++ b/projects/player/src/app/components/elements/input-group-element/input-group-element.component.html
@@ -35,9 +35,4 @@
                    [parentForm]="form"
                    [elementModel]="elementModel | cast: DropdownElement">
   </aspect-dropdown>
-  <aspect-math-field *ngIf="elementModel.type === 'math-field'"
-                     #elementComponent
-                     [parentForm]="form"
-                     [elementModel]="elementModel | cast: MathFieldElement">
-  </aspect-math-field>
 </form>
diff --git a/projects/player/src/app/components/elements/input-group-element/input-group-element.component.ts b/projects/player/src/app/components/elements/input-group-element/input-group-element.component.ts
index 53ddef17a..8efde3f17 100644
--- a/projects/player/src/app/components/elements/input-group-element/input-group-element.component.ts
+++ b/projects/player/src/app/components/elements/input-group-element/input-group-element.component.ts
@@ -11,7 +11,6 @@ import { RadioButtonGroupComplexElement } from 'common/models/elements/input-ele
 import { DropdownElement } from 'common/models/elements/input-elements/dropdown';
 import { InputElement } from 'common/models/elements/element';
 import { HotspotImageElement } from 'common/models/elements/input-elements/hotspot-image';
-import { MathFieldElement } from 'common/models/elements/input-elements/math-field';
 import { ValidationService } from '../../../services/validation.service';
 import { ElementFormGroupDirective } from '../../../directives/element-form-group.directive';
 import { ElementModelElementCodeMappingService } from '../../../services/element-model-element-code-mapping.service';
@@ -31,7 +30,6 @@ export class InputGroupElementComponent extends ElementFormGroupDirective implem
   RadioButtonGroupComplexElement!: RadioButtonGroupComplexElement;
   DropdownElement!: DropdownElement;
   HotspotImageElement!: HotspotImageElement;
-  MathFieldElement!: MathFieldElement;
 
   constructor(
     public unitStateService: UnitStateService,
diff --git a/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.html b/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.html
index 1485b28a8..d8cda8bdc 100644
--- a/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.html
+++ b/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.html
@@ -32,6 +32,13 @@
     (onKeyDown)="detectHardwareKeyboard(elementModel)"
     (focusChanged)="toggleKeyInput($event, elementComponent)">
   </aspect-text-area-math>
+  <aspect-math-field
+    *ngIf="elementModel.type === 'math-field'"
+    #elementComponent
+    [parentForm]="form"
+    [elementModel]="elementModel | cast: MathFieldElement"
+    (focusChanged)="toggleKeyInput($event, elementComponent)">
+  </aspect-math-field>
 </form>
 
 <aspect-floating-keypad
diff --git a/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.ts b/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.ts
index e877382e9..84cb33d6e 100644
--- a/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.ts
+++ b/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.ts
@@ -7,8 +7,10 @@ import { TextAreaElement } from 'common/models/elements/input-elements/text-area
 import { TextFieldElement } from 'common/models/elements/input-elements/text-field';
 import { SpellCorrectElement } from 'common/models/elements/input-elements/spell-correct';
 import { TextAreaMathElement } from 'common/models/elements/input-elements/text-area-math';
+import { MathFieldElement } from 'common/models/elements/input-elements/math-field';
 import { InputElement } from 'common/models/elements/element';
 import { TextInputGroupDirective } from 'player/src/app/directives/text-input-group.directive';
+import { MathKeyboardService } from 'player/src/app/services/math-keyboard.service';
 import { DeviceService } from '../../../services/device.service';
 import { KeyboardService } from '../../../services/keyboard.service';
 import { ValidationService } from '../../../services/validation.service';
@@ -29,10 +31,12 @@ export class TextInputGroupElementComponent
   TextFieldElement!: TextFieldElement;
   SpellCorrectElement!: SpellCorrectElement;
   TextAreaMathElement!: TextAreaMathElement;
+  MathFieldElement!: MathFieldElement;
 
   constructor(
     public keyboardService: KeyboardService,
     public keypadService: KeypadService,
+    public mathKeyboardService: MathKeyboardService,
     public unitStateService: UnitStateService,
     public elementModelElementCodeMappingService: ElementModelElementCodeMappingService,
     public veronaSubscriptionService: VeronaSubscriptionService,
diff --git a/projects/player/src/app/components/floating-keypad/floating-keypad.component.ts b/projects/player/src/app/components/floating-keypad/floating-keypad.component.ts
index cfd646f89..1779df500 100644
--- a/projects/player/src/app/components/floating-keypad/floating-keypad.component.ts
+++ b/projects/player/src/app/components/floating-keypad/floating-keypad.component.ts
@@ -56,7 +56,7 @@ export class FloatingKeypadComponent implements OnChanges {
             ...position,
             offsetY: this.getOffsetY(this.keypadService.elementComponent.elementModel.type, index > 0)
           }));
-      } else {
+      } else if (startPosition === 'endCenter') {
         this.overlayPositions = [...FloatingKeypadComponent.overlayPositionsConfig[startPosition]];
       }
     }
diff --git a/projects/player/src/app/components/layouts/player-layout/player-layout.component.html b/projects/player/src/app/components/layouts/player-layout/player-layout.component.html
index a601a2307..3a2b161e5 100644
--- a/projects/player/src/app/components/layouts/player-layout/player-layout.component.html
+++ b/projects/player/src/app/components/layouts/player-layout/player-layout.component.html
@@ -4,31 +4,33 @@
       <ng-content></ng-content>
     </div>
       <aspect-keypad
-          *ngIf="keypadService.isOpen && keypadService.position === 'right'"
-          @keypadSlideInOut
-          [@.disabled] = "isKeypadAnimationDisabled"
-          [inputElement]="keypadService.inputElement"
-          [position]="keypadService.position"
-          [preset]="keypadService.preset"
-          [customKeys]="keypadService.elementComponent.elementModel.inputAssistanceCustomKeys | inputAssistanceCustomKeys"
-          [restrictToAllowedKeys]="!!keypadService.elementComponent.elementModel.restrictedToInputAssistanceChars"
-          [hasArrowKeys]="!!keypadService.elementComponent.elementModel.hasArrowKeys"
-          [hasBackspaceKey]="!!keypadService.elementComponent.elementModel.hasBackspaceKey"
-          [hasReturnKey]="keypadService.elementComponent.elementModel | hasReturnKey"
-          (backSpaceClicked)="keypadService.deleteCharacters.emit(true)"
-          (keyClicked)="keypadService.enterKey.emit($event)"
-          (select)="keypadService.select.emit($event)">
+        *ngIf="keypadService.isOpen && keypadService.position === 'right'"
+        @keypadSlideInOut
+        [@.disabled] = "isKeypadAnimationDisabled"
+        [inputElement]="keypadService.inputElement"
+        [position]="keypadService.position"
+        [preset]="keypadService.preset"
+        [customKeys]="keypadService.elementComponent.elementModel.inputAssistanceCustomKeys | inputAssistanceCustomKeys"
+        [restrictToAllowedKeys]="!!keypadService.elementComponent.elementModel.restrictedToInputAssistanceChars"
+        [hasArrowKeys]="!!keypadService.elementComponent.elementModel.hasArrowKeys"
+        [hasBackspaceKey]="!!keypadService.elementComponent.elementModel.hasBackspaceKey"
+        [hasReturnKey]="keypadService.elementComponent.elementModel | hasReturnKey"
+        (backSpaceClicked)="keypadService.deleteCharacters.emit(true)"
+        (keyClicked)="keypadService.enterKey.emit($event)"
+        (select)="keypadService.select.emit($event)">
       </aspect-keypad>
   </div>
   <aspect-keyboard
-      *ngIf="keyboardService.isOpen"
-      @keyboardSlideInOut
-      [@.disabled] = "isKeyboardAnimationDisabled"
-      (@keyboardSlideInOut.done)="keyboardService.scrollElement()"
-      [addInputAssistanceToKeyboard]="keyboardService.addInputAssistanceToKeyboard"
-      [preset]="keyboardService.preset"
-      [customKeys]="keyboardService.elementComponent.elementModel.inputAssistanceCustomKeys | inputAssistanceCustomKeys"
-      (keyClicked)="keyboardService.enterKey.emit($event)"
-      (backspaceClicked)="keyboardService.deleteCharacters.emit(true)">
+    *ngIf="keyboardService.isOpen"
+    @keyboardSlideInOut
+    [@.disabled] = "isKeyboardAnimationDisabled"
+    (@keyboardSlideInOut.done)="keyboardService.scrollElement()"
+    [addInputAssistanceToKeyboard]="keyboardService.addInputAssistanceToKeyboard"
+    [preset]="keyboardService.preset"
+    [customKeys]="keyboardService.elementComponent.elementModel.inputAssistanceCustomKeys | inputAssistanceCustomKeys"
+    (keyClicked)="keyboardService.enterKey.emit($event)"
+    (backspaceClicked)="keyboardService.deleteCharacters.emit(true)">
   </aspect-keyboard>
+  <aspect-math-keyboard-container>
+  </aspect-math-keyboard-container>
 </div>
diff --git a/projects/player/src/app/components/layouts/player-layout/player-layout.component.spec.ts b/projects/player/src/app/components/layouts/player-layout/player-layout.component.spec.ts
index b5cfac127..42e8848c3 100644
--- a/projects/player/src/app/components/layouts/player-layout/player-layout.component.spec.ts
+++ b/projects/player/src/app/components/layouts/player-layout/player-layout.component.spec.ts
@@ -6,11 +6,13 @@ import { Component, Directive, Input } from '@angular/core';
 import { Page } from 'common/models/page';
 import { APIService } from 'common/shared.module';
 import { PagingMode } from 'player/modules/verona/models/verona';
+import { BrowserAnimationsModule, provideAnimations } from '@angular/platform-browser/animations';
 import { PlayerLayoutComponent } from './player-layout.component';
 
 describe('PlayerLayoutComponent', () => {
   let component: PlayerLayoutComponent;
   let fixture: ComponentFixture<PlayerLayoutComponent>;
+
   @Directive({ selector: '[aspectPlayerState]' })
   class PlayerStateStubDirective {
     @Input() validPages!: Record<string, string>;
@@ -33,6 +35,9 @@ describe('PlayerLayoutComponent', () => {
     @Input() alwaysVisiblePagePosition!: 'top' | 'bottom' | 'left' | 'right';
   }
 
+  @Component({ selector: 'aspect-math-keyboard-container', template: '' })
+  class MockMathKeyboardContainerComponent {}
+
   beforeEach(async () => {
     await TestBed.configureTestingModule({
       declarations: [
@@ -40,9 +45,15 @@ describe('PlayerLayoutComponent', () => {
         PagesLayoutStubComponent,
         AlwaysVisiblePagePipe,
         ScrollPagesPipe,
-        PlayerStateStubDirective
+        PlayerStateStubDirective,
+        MockMathKeyboardContainerComponent
+      ],
+      imports: [
+        BrowserAnimationsModule
       ],
-      providers: [{ provide: APIService, useClass: ApiStubService }]
+      providers: [
+        provideAnimations(),
+        { provide: APIService, useClass: ApiStubService }]
     })
       .compileComponents();
   });
diff --git a/projects/player/src/app/components/layouts/player-layout/player-layout.component.ts b/projects/player/src/app/components/layouts/player-layout/player-layout.component.ts
index 32d3e432a..9df6e040d 100644
--- a/projects/player/src/app/components/layouts/player-layout/player-layout.component.ts
+++ b/projects/player/src/app/components/layouts/player-layout/player-layout.component.ts
@@ -39,6 +39,8 @@ export class PlayerLayoutComponent implements OnDestroy {
   private isKeyboardToggling: number = 0;
   private ngUnsubscribe = new Subject<void>();
 
+  protected readonly window = window;
+
   constructor(
     public keypadService: KeypadService,
     public keyboardService: KeyboardService
diff --git a/projects/player/src/app/components/math-keyboard-container/math-keyboard-container.component.html b/projects/player/src/app/components/math-keyboard-container/math-keyboard-container.component.html
new file mode 100644
index 000000000..0b0428089
--- /dev/null
+++ b/projects/player/src/app/components/math-keyboard-container/math-keyboard-container.component.html
@@ -0,0 +1,3 @@
+<div #mathKeyboard
+     [style.height.px]="mathKeyboardService.keyboardHeight"
+     class="math-keyboard-container"></div>
diff --git a/projects/player/src/app/components/math-keyboard-container/math-keyboard-container.component.scss b/projects/player/src/app/components/math-keyboard-container/math-keyboard-container.component.scss
new file mode 100644
index 000000000..139597f9c
--- /dev/null
+++ b/projects/player/src/app/components/math-keyboard-container/math-keyboard-container.component.scss
@@ -0,0 +1,2 @@
+
+
diff --git a/projects/player/src/app/components/math-keyboard-container/math-keyboard-container.component.ts b/projects/player/src/app/components/math-keyboard-container/math-keyboard-container.component.ts
new file mode 100644
index 000000000..ee1e06a55
--- /dev/null
+++ b/projects/player/src/app/components/math-keyboard-container/math-keyboard-container.component.ts
@@ -0,0 +1,34 @@
+import {
+  AfterViewInit, Component, ElementRef, OnDestroy, ViewChild
+} from '@angular/core';
+import { MathKeyboardService } from 'player/src/app/services/math-keyboard.service';
+
+@Component({
+  selector: 'aspect-math-keyboard-container',
+  standalone: true,
+  imports: [],
+  templateUrl: './math-keyboard-container.component.html',
+  styleUrl: './math-keyboard-container.component.scss'
+})
+export class MathKeyboardContainerComponent implements AfterViewInit, OnDestroy {
+  @ViewChild('mathKeyboard') mathKeyboard!: ElementRef;
+
+  constructor(public mathKeyboardService: MathKeyboardService) {}
+
+  ngAfterViewInit(): void {
+    window.mathVirtualKeyboard.container = this.mathKeyboard.nativeElement;
+    window.mathVirtualKeyboard.addEventListener('geometrychange', () => this.updateKeyboard());
+  }
+
+  updateKeyboard(): void {
+    this.mathKeyboardService.keyboardHeight =
+      window.mathVirtualKeyboard.boundingRect.height;
+    if (this.mathKeyboardService.keyboardHeight) {
+      setTimeout(() => this.mathKeyboardService.scrollElement());
+    }
+  }
+
+  ngOnDestroy(): void {
+    window.mathVirtualKeyboard.removeEventListener('geometrychange', () => this.updateKeyboard());
+  }
+}
diff --git a/projects/player/src/app/directives/text-input-group.directive.ts b/projects/player/src/app/directives/text-input-group.directive.ts
index 2c02fed1d..9fd1c36ee 100644
--- a/projects/player/src/app/directives/text-input-group.directive.ts
+++ b/projects/player/src/app/directives/text-input-group.directive.ts
@@ -8,8 +8,10 @@ import { DeviceService } from 'player/src/app/services/device.service';
 import { KeypadService } from 'player/src/app/services/keypad.service';
 import { KeyboardService } from 'player/src/app/services/keyboard.service';
 import { TextInputComponentType } from 'player/src/app/models/text-input-component.type';
-import { TextAreaMathComponent } from 'common/components/input-elements/text-area-math/text-area-math.component';
 import { RangeSelectionService } from 'common/services/range-selection-service';
+import { MathfieldElement } from 'mathlive';
+import { MathKeyboardService } from 'player/src/app/services/math-keyboard.service';
+import { MathFieldComponent } from 'common/components/input-elements/math-field.component';
 
 @Directive()
 export abstract class TextInputGroupDirective extends ElementFormGroupDirective implements OnDestroy {
@@ -25,6 +27,7 @@ export abstract class TextInputGroupDirective extends ElementFormGroupDirective
   abstract deviceService: DeviceService;
   abstract keypadService: KeypadService;
   abstract keyboardService: KeyboardService;
+  abstract mathKeyboardService: MathKeyboardService;
 
   private shallOpenKeypad(elementModel: InputElement): boolean {
     return !!elementModel.inputAssistancePreset &&
@@ -34,50 +37,59 @@ export abstract class TextInputGroupDirective extends ElementFormGroupDirective
   }
 
   async toggleKeyInput(focusedTextInput: { inputElement: HTMLElement; focused: boolean },
-                       elementComponent: TextInputComponentType | TextAreaMathComponent): Promise<void> {
+                       elementComponent: TextInputComponentType | MathFieldComponent): Promise<void> {
+    const isMathInput = focusedTextInput.inputElement instanceof MathfieldElement;
     const promises: Promise<boolean>[] = [];
-    if (elementComponent.elementModel.showSoftwareKeyboard && !elementComponent.elementModel.readOnly) {
-      promises.push(this.keyboardService
-        .toggleAsync(focusedTextInput, elementComponent, this.deviceService.isMobileWithoutHardwareKeyboard));
-    }
-    if (this.shallOpenKeypad(elementComponent.elementModel)) {
-      promises.push(this.keypadService.toggleAsync(focusedTextInput, elementComponent));
-    }
-    if (promises.length) {
-      await Promise.all(promises)
-        .then(() => {
-          if (this.keyboardService.isOpen) {
-            this.subscribeForKeyboardEvents(elementComponent.elementModel, elementComponent);
-          } else {
-            this.unsubscribeFromKeyboardEvents();
-          }
-          if (this.keypadService.isOpen) {
-            this.subscribeForKeypadEvents(elementComponent.elementModel, elementComponent);
-          } else {
-            this.unsubscribeFromKeypadEvents();
-          }
-          this.isKeypadOpen = this.keypadService.isOpen;
-          if (this.keyboardService.isOpen || this.keypadService.isOpen) {
-            this.inputElement = this.getInputElement(focusedTextInput.inputElement);
-          }
-        });
+    if (isMathInput) {
+      this.mathKeyboardService
+        .toggle(focusedTextInput as { inputElement: MathfieldElement; focused: boolean },
+          elementComponent);
+    } else if (!(elementComponent instanceof MathFieldComponent)) {
+      if (elementComponent.elementModel.showSoftwareKeyboard && !elementComponent.elementModel.readOnly) {
+        promises.push(this.keyboardService
+          .toggleAsync(focusedTextInput, elementComponent, this.deviceService.isMobileWithoutHardwareKeyboard));
+      }
+      if (this.shallOpenKeypad(elementComponent.elementModel)) {
+        promises.push(this.keypadService.toggleAsync(focusedTextInput, elementComponent));
+      }
+      if (promises.length) {
+        await Promise.all(promises)
+          .then(() => {
+            if (this.keyboardService.isOpen) {
+              this.subscribeForKeyboardEvents(elementComponent.elementModel, elementComponent);
+            } else {
+              this.unsubscribeFromKeyboardEvents();
+            }
+            if (this.keypadService.isOpen) {
+              this.subscribeForKeypadEvents(elementComponent.elementModel, elementComponent);
+            } else {
+              this.unsubscribeFromKeypadEvents();
+            }
+            this.isKeypadOpen = this.keypadService.isOpen;
+            if (this.keyboardService.isOpen || this.keypadService.isOpen) {
+              this.inputElement = this.getInputElement(focusedTextInput.inputElement);
+            }
+          });
+      }
     }
   }
 
   // eslint-disable-next-line class-methods-use-this
   checkInputLimitation(event: {
     keyboardEvent: KeyboardEvent;
-    inputElement: HTMLInputElement | HTMLTextAreaElement
+    inputElement: HTMLInputElement | HTMLTextAreaElement | HTMLElement;
   }, elementModel: UIElement): void {
+    const inputValue = TextInputGroupDirective.getValueOfInput(event.inputElement);
     if (elementModel.maxLength &&
       elementModel.isLimitedToMaxLength &&
-      event.inputElement.value.length === elementModel.maxLength &&
+      inputValue.length === elementModel.maxLength &&
       !['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'ArrowDown', 'ArrowUp'].includes(event.keyboardEvent.key)) {
       event.keyboardEvent.preventDefault();
     }
   }
 
   detectHardwareKeyboard(elementModel: UIElement): void {
+    console.log('detectHardwareKeyboard', elementModel);
     if (elementModel.showSoftwareKeyboard) {
       this.deviceService.hasHardwareKeyboard = true;
       this.keyboardService.close();
@@ -228,10 +240,14 @@ export abstract class TextInputGroupDirective extends ElementFormGroupDirective
   }
 
   private getInputElementValue(): string {
-    if (this.inputElement instanceof HTMLInputElement || this.inputElement instanceof HTMLTextAreaElement) {
-      return this.inputElement.value;
+    return TextInputGroupDirective.getValueOfInput(this.inputElement);
+  }
+
+  private static getValueOfInput(inputElement: HTMLElement | HTMLInputElement | HTMLTextAreaElement): string {
+    if (inputElement instanceof HTMLInputElement || inputElement instanceof HTMLTextAreaElement) {
+      return inputElement.value;
     }
-    return this.inputElement.textContent || '';
+    return inputElement.textContent || '';
   }
 
   private insert(keyAtPosition: {
diff --git a/projects/player/src/app/models/text-input-component.type.ts b/projects/player/src/app/models/text-input-component.type.ts
index 58209b816..c11cc285a 100644
--- a/projects/player/src/app/models/text-input-component.type.ts
+++ b/projects/player/src/app/models/text-input-component.type.ts
@@ -4,6 +4,9 @@ import { SpellCorrectComponent } from 'common/components/input-elements/spell-co
 import {
   TextFieldSimpleComponent
 } from 'common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component';
+import { MathTableComponent } from 'common/components/input-elements/math-table.component';
+import { TextAreaMathComponent } from 'common/components/input-elements/text-area-math/text-area-math.component';
+import { MathFieldComponent } from 'common/components/input-elements/math-field.component';
 
 export type TextInputComponentType =
-  TextAreaComponent | TextFieldComponent | SpellCorrectComponent | TextFieldSimpleComponent;
+  TextAreaComponent | TextFieldComponent | SpellCorrectComponent | TextFieldSimpleComponent | TextAreaMathComponent;
diff --git a/projects/player/src/app/services/input-service.ts b/projects/player/src/app/services/input-service.ts
index 123c0c069..4b412b11d 100644
--- a/projects/player/src/app/services/input-service.ts
+++ b/projects/player/src/app/services/input-service.ts
@@ -3,13 +3,14 @@ import { TextInputComponentType } from 'player/src/app/models/text-input-compone
 import { MathTableComponent } from 'common/components/input-elements/math-table.component';
 import { InputAssistancePreset } from 'common/interfaces';
 import { TextAreaMathComponent } from 'common/components/input-elements/text-area-math/text-area-math.component';
+import { MathFieldComponent } from 'common/components/input-elements/math-field.component';
 
 @Injectable({
   providedIn: 'root'
 })
 export abstract class InputService {
   preset: InputAssistancePreset = null;
-  elementComponent!: TextInputComponentType | MathTableComponent | TextAreaMathComponent;
+  elementComponent!: TextInputComponentType | MathTableComponent | TextAreaMathComponent | MathFieldComponent;
   inputElement!: HTMLTextAreaElement | HTMLInputElement | HTMLElement;
   isOpen: boolean = false;
 
@@ -20,7 +21,7 @@ export abstract class InputService {
 
   setCurrentKeyInputElement(
     focusedElement: HTMLElement,
-    elementComponent: TextInputComponentType | MathTableComponent | TextAreaMathComponent
+    elementComponent: TextInputComponentType | MathTableComponent | TextAreaMathComponent | MathFieldComponent
   ): void {
     this.inputElement = focusedElement;
     this.elementComponent = elementComponent;
diff --git a/projects/player/src/app/services/keyboard.service.ts b/projects/player/src/app/services/keyboard.service.ts
index d48a7e508..54fb8355a 100644
--- a/projects/player/src/app/services/keyboard.service.ts
+++ b/projects/player/src/app/services/keyboard.service.ts
@@ -2,12 +2,12 @@ import { Injectable } from '@angular/core';
 import { TextInputComponentType } from 'player/src/app/models/text-input-component.type';
 import { MathTableComponent } from 'common/components/input-elements/math-table.component';
 import { TextAreaMathComponent } from 'common/components/input-elements/text-area-math/text-area-math.component';
-import { InputService } from './input-service';
+import { ScrollToInputService } from 'player/src/app/services/scroll-to-input.service';
 
 @Injectable({
   providedIn: 'root'
 })
-export class KeyboardService extends InputService {
+export class KeyboardService extends ScrollToInputService {
   addInputAssistanceToKeyboard: boolean = false;
 
   async toggleAsync(focusedTextInput: { inputElement: HTMLElement; focused: boolean },
@@ -37,28 +37,8 @@ export class KeyboardService extends InputService {
        elementComponent: TextInputComponentType | MathTableComponent | TextAreaMathComponent): void {
     this.addInputAssistanceToKeyboard = elementComponent.elementModel.addInputAssistanceToKeyboard;
     this.preset = elementComponent.elementModel.inputAssistancePreset;
+    this.keyboardHeight = this.addInputAssistanceToKeyboard ? 380 : 280;
     this.setCurrentKeyInputElement(inputElement, elementComponent);
     this.isOpen = true;
   }
-
-  scrollElement(): void {
-    if (this.isOpen && this.isElementHiddenByKeyboard()) {
-      const scrollPositionTarget = this.isViewHighEnoughToCenterElement() ? 'center' : 'start';
-      this.elementComponent.domElement.scrollIntoView({ block: scrollPositionTarget });
-    }
-  }
-
-  private isViewHighEnoughToCenterElement(): boolean {
-    return window.innerHeight - this.getKeyboardHeight() >
-      this.elementComponent.domElement.getBoundingClientRect().height;
-  }
-
-  private isElementHiddenByKeyboard(): boolean {
-    return window.innerHeight - this.elementComponent.domElement.getBoundingClientRect().bottom <
-      this.getKeyboardHeight();
-  }
-
-  private getKeyboardHeight(): number {
-    return this.addInputAssistanceToKeyboard ? 380 : 280;
-  }
 }
diff --git a/projects/player/src/app/services/math-keyboard.service.spec.ts b/projects/player/src/app/services/math-keyboard.service.spec.ts
new file mode 100644
index 000000000..c4101a6ca
--- /dev/null
+++ b/projects/player/src/app/services/math-keyboard.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { MathKeyboardService } from './math-keyboard.service';
+
+describe('MathKeyboardService', () => {
+  let service: MathKeyboardService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(MathKeyboardService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/projects/player/src/app/services/math-keyboard.service.ts b/projects/player/src/app/services/math-keyboard.service.ts
new file mode 100644
index 000000000..b94e24c91
--- /dev/null
+++ b/projects/player/src/app/services/math-keyboard.service.ts
@@ -0,0 +1,31 @@
+import { Injectable } from '@angular/core';
+import { MathfieldElement } from 'mathlive';
+import { TextAreaMathComponent } from 'common/components/input-elements/text-area-math/text-area-math.component';
+import { InputService } from 'player/src/app/services/input-service';
+import { ScrollToInputService } from 'player/src/app/services/scroll-to-input.service';
+import { TextInputComponentType } from 'player/src/app/models/text-input-component.type';
+import { MathFieldComponent } from 'common/components/input-elements/math-field.component';
+import { MathTableComponent } from 'common/components/input-elements/math-table.component';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class MathKeyboardService extends ScrollToInputService {
+  isOpen: boolean = false;
+
+  toggle(focusedTextInput: { inputElement: MathfieldElement; focused: boolean },
+         elementComponent: TextInputComponentType | MathFieldComponent): boolean {
+    if (focusedTextInput.focused) {
+      console.log('focusedTextInput.focused', focusedTextInput.inputElement);
+      this.open(focusedTextInput.inputElement, elementComponent);
+    } else {
+      this.close();
+    }
+    return this.isOpen;
+  }
+
+  private open(inputElement: MathfieldElement, elementComponent: TextInputComponentType | MathFieldComponent): void {
+    this.setCurrentKeyInputElement(inputElement, elementComponent);
+    this.isOpen = true;
+  }
+}
diff --git a/projects/player/src/app/services/scroll-to-input.service.ts b/projects/player/src/app/services/scroll-to-input.service.ts
new file mode 100644
index 000000000..d483e4b8b
--- /dev/null
+++ b/projects/player/src/app/services/scroll-to-input.service.ts
@@ -0,0 +1,27 @@
+import { Injectable } from '@angular/core';
+import { InputService } from 'player/src/app/services/input-service';
+
+@Injectable({
+  providedIn: 'root'
+})
+export abstract class ScrollToInputService extends InputService {
+  keyboardHeight: number = 0;
+
+  scrollElement(): void {
+    if (this.isOpen && this.isElementHiddenByKeyboard()) {
+      const scrollPositionTarget = this.isViewHighEnoughToCenterElement() ? 'center' : 'start';
+      console.log('scrollPositionTarget', scrollPositionTarget);
+      this.elementComponent.domElement.scrollIntoView({ block: scrollPositionTarget });
+    }
+  }
+
+  private isViewHighEnoughToCenterElement(): boolean {
+    return window.innerHeight - this.keyboardHeight >
+      this.elementComponent.domElement.getBoundingClientRect().height;
+  }
+
+  private isElementHiddenByKeyboard(): boolean {
+    return window.innerHeight - this.elementComponent.domElement.getBoundingClientRect().bottom <
+      this.keyboardHeight;
+  }
+}
-- 
GitLab