diff --git a/docs/release-notes-editor.txt b/docs/release-notes-editor.txt index 0226e5131caaca30a2d0ec3fa0077094b9062e90..ad85ab5dc0a5d9abb83efcc8ec06b9d9bf303577 100644 --- a/docs/release-notes-editor.txt +++ b/docs/release-notes-editor.txt @@ -16,6 +16,8 @@ Editor - Ermöglicht die Definition eigener Zeichen für die Eingabehilfe Durch Auswahl der Option "Eigene Zeichen" öffnet sich ein Textfeld, in das die gewünschten Zeichen als ein Wort eingegeben werden. +- Bietet die Möglichkeit, die maximale Zeichenanzahl von Eingabefeldern zu begrenzen + 1.34.0 - Implement GeoGebra applet element diff --git a/docs/release-notes-player.txt b/docs/release-notes-player.txt index 9292b01027bb3c95541d47ae7e2d4befc2747cd5..d5ce682f958cac74e21d3b958c791c19a25d5013 100644 --- a/docs/release-notes-player.txt +++ b/docs/release-notes-player.txt @@ -14,6 +14,8 @@ Player - Korrigiert fehlende Warnmeldungen beim Schieberegler/Zahlenstrahl - Die Eingabehilfe "Eigene Zeichen" nutzt weitestgehend das Layout der Eingabehilfe "Französische Sonderzeichen" +- Verhindert die Eingabe bei Eingabefeldern, wenn die festgelegte Maximallänge erreicht ist + Bei festgelegter Maximallänge ist die Möglichkeit unterbunden, Text über Tastenkürzel einzufügen - Ändert die Metadaten entsprechend der Verona Inferfaces Specification diff --git a/docs/unit_definition_changelog.txt b/docs/unit_definition_changelog.txt index 62db90b94fba41ac50e7cade84d167835b14304c..3320c54252a95d8f45b87773d3d83b06dbc03a05 100644 --- a/docs/unit_definition_changelog.txt +++ b/docs/unit_definition_changelog.txt @@ -52,5 +52,7 @@ iqb-aspect-definition@1.0.0 +expectedCharactersCount: number; +hasKeyboardIcon: boolean; - TextFieldElement: +hasKeyboardIcon: boolean; + +isLimitedToMaxLength: boolean; +- TextFieldSimpleElement: +isLimitedToMaxLength: boolean; - TextFieldElement/TextFieldSimpleElement/SpellCorrectElement/TextAreaElement: InputAssistancePreset +'custom' - Remove drop-list-simple as cloze child; is now a drop-list diff --git a/projects/common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component.ts b/projects/common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component.ts index 4dc3aa47a38a209d5b9ce2290d6161f491d12f16..b3a223bd1f66a811c4fa8275c61e206f8cbbb3da 100644 --- a/projects/common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component.ts +++ b/projects/common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component.ts @@ -31,7 +31,8 @@ import { TextInputComponent } from 'common/directives/text-input-component.direc [readonly]="elementModel.readOnly" [formControl]="elementFormControl" [value]="elementModel.value" - (keydown)="onKeyDown($event)" + (paste)="elementModel.isLimitedToMaxLength && elementModel.maxLength ? $event.preventDefault() : null" + (keydown)="onKeyDown.emit({keyboardEvent: $event, inputElement: input})" (focus)="focusChanged.emit({ inputElement: input, focused: true })" (blur)="focusChanged.emit({ inputElement: input, focused: false })"> `, diff --git a/projects/common/components/input-elements/spell-correct.component.ts b/projects/common/components/input-elements/spell-correct.component.ts index 4d65a1946aab0ae402d791cd4db8a52f6cc6871a..84b564e46f49f59081a13d9a477e0c5311aa949f 100644 --- a/projects/common/components/input-elements/spell-correct.component.ts +++ b/projects/common/components/input-elements/spell-correct.component.ts @@ -31,7 +31,7 @@ import { TextInputComponent } from 'common/directives/text-input-component.direc [style.font-style]="elementModel.styling.italic ? 'italic' : ''" [style.text-decoration]="elementModel.styling.underline ? 'underline' : ''" [formControl]="elementFormControl" - (keydown)="onKeyDown($event)" + (keydown)="onKeyDown.emit({keyboardEvent: $event, inputElement: input})" (focus)="focusChanged.emit({ inputElement: input, focused: true })" (blur)="focusChanged.emit({ inputElement: input, focused: false })"> </mat-form-field> diff --git a/projects/common/components/input-elements/text-area.component.ts b/projects/common/components/input-elements/text-area.component.ts index 82e08ba622d5badfd8c13079a99cb1a508e2308e..773d62b88ec388272345df35bbcfe6c8b659d1a4 100644 --- a/projects/common/components/input-elements/text-area.component.ts +++ b/projects/common/components/input-elements/text-area.component.ts @@ -44,7 +44,7 @@ import { TextInputComponent } from 'common/directives/text-input-component.direc [style.min-width.%]="100" [style.line-height.%]="elementModel.styling.lineHeight" [style.resize]="elementModel.resizeEnabled ? 'both' : 'none'" - (keydown)="onKeyDown($event)" + (keydown)="onKeyDown.emit({keyboardEvent: $event, inputElement: input})" (focus)="focusChanged.emit({ inputElement: input, focused: true })" (blur)="focusChanged.emit({ inputElement: input, focused: false })"> </textarea> diff --git a/projects/common/components/input-elements/text-field.component.ts b/projects/common/components/input-elements/text-field.component.ts index f6216dfd6ff3e659ad941fc4687b88ccfa17fe30..e7b5bff247b21ff72527e6bf44d96630bf881efc 100644 --- a/projects/common/components/input-elements/text-field.component.ts +++ b/projects/common/components/input-elements/text-field.component.ts @@ -28,7 +28,8 @@ import { TextInputComponent } from 'common/directives/text-input-component.direc [formControl]="elementFormControl" [pattern]="$any(elementModel.pattern)" [readonly]="elementModel.readOnly" - (keydown)="onKeyDown($event)" + (paste)="elementModel.isLimitedToMaxLength && elementModel.maxLength ? $event.preventDefault() : null" + (keydown)="onKeyDown.emit({keyboardEvent: $event, inputElement: input})" (focus)="focusChanged.emit({ inputElement: input, focused: true })" (blur)="focusChanged.emit({ inputElement: input, focused: false })"> <div matSuffix fxLayout="row" fxLayoutAlign="center baseline"> diff --git a/projects/common/directives/text-input-component.directive.ts b/projects/common/directives/text-input-component.directive.ts index e064a84785ba5ba0f33b79079e605776aaf0f27b..a2cfa91eee777e60e2768c8400161b5ebeb6e025 100644 --- a/projects/common/directives/text-input-component.directive.ts +++ b/projects/common/directives/text-input-component.directive.ts @@ -5,12 +5,9 @@ import { FormElementComponent } from 'common/directives/form-element-component.d @Directive() export abstract class TextInputComponent extends FormElementComponent implements OnInit { - @Output() hardwareKeyDetected = new EventEmitter(); @Output() focusChanged = new EventEmitter<{ inputElement: HTMLElement; focused: boolean }>(); - - onKeyDown(event: KeyboardEvent): void { - if (this.elementModel.showSoftwareKeyboard) { - this.hardwareKeyDetected.emit(); - } - } + @Output() onKeyDown = new EventEmitter<{ + keyboardEvent: KeyboardEvent; + inputElement: HTMLInputElement | HTMLTextAreaElement + }>(); } diff --git a/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple.ts b/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple.ts index 48fdd07568837671a29f5cfadb9378e037e30c8e..5ba0f3b2ffa4c25f2a89f058559f23ff4ca23c67 100644 --- a/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple.ts +++ b/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple.ts @@ -12,6 +12,7 @@ export class TextFieldSimpleElement extends TextInputElement { minLengthWarnMessage: string = 'Eingabe zu kurz'; maxLength: number | null = null; maxLengthWarnMessage: string = 'Eingabe zu lang'; + isLimitedToMaxLength: boolean = false; pattern: string | null = null; patternWarnMessage: string = 'Eingabe entspricht nicht der Vorgabe'; clearable: boolean = false; @@ -25,6 +26,7 @@ export class TextFieldSimpleElement extends TextInputElement { if (element.minLengthWarnMessage !== undefined) this.minLengthWarnMessage = element.minLengthWarnMessage; if (element.maxLength) this.maxLength = element.maxLength; if (element.maxLengthWarnMessage !== undefined) this.maxLengthWarnMessage = element.maxLengthWarnMessage; + if (element.isLimitedToMaxLength) this.isLimitedToMaxLength = element.isLimitedToMaxLength; if (element.pattern) this.pattern = element.pattern; if (element.patternWarnMessage !== undefined) this.patternWarnMessage = element.patternWarnMessage; if (element.clearable) this.clearable = element.clearable; diff --git a/projects/common/models/elements/input-elements/text-field.ts b/projects/common/models/elements/input-elements/text-field.ts index 0b6661c5d53fe54f8ca60de81d6d7124479e3cdb..ea425b1a055978dc7d2604da4c65c07c22c7e0c3 100644 --- a/projects/common/models/elements/input-elements/text-field.ts +++ b/projects/common/models/elements/input-elements/text-field.ts @@ -11,6 +11,7 @@ export class TextFieldElement extends TextInputElement implements PositionedUIEl minLengthWarnMessage: string = 'Eingabe zu kurz'; maxLength: number | null = null; maxLengthWarnMessage: string = 'Eingabe zu lang'; + isLimitedToMaxLength: boolean = false; pattern: string | null = null; patternWarnMessage: string = 'Eingabe entspricht nicht der Vorgabe'; hasKeyboardIcon: boolean = false; @@ -27,6 +28,7 @@ export class TextFieldElement extends TextInputElement implements PositionedUIEl if (element.minLengthWarnMessage !== undefined) this.minLengthWarnMessage = element.minLengthWarnMessage; if (element.maxLength !== undefined) this.maxLength = element.maxLength; if (element.maxLengthWarnMessage !== undefined) this.maxLengthWarnMessage = element.maxLengthWarnMessage; + if (element.isLimitedToMaxLength) this.isLimitedToMaxLength = element.isLimitedToMaxLength; if (element.pattern !== undefined) this.pattern = element.pattern; if (element.patternWarnMessage !== undefined) this.patternWarnMessage = element.patternWarnMessage; if (element.clearable) this.clearable = element.clearable; diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/text-field-element-properties.component.ts b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/text-field-element-properties.component.ts index 018a2616332f5272aa45834adb2bba8272200566..7c9c44980c67de0fca80f26c0d3f0e34c908cb9d 100644 --- a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/text-field-element-properties.component.ts +++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/text-field-element-properties.component.ts @@ -47,6 +47,12 @@ import { CombinedProperties } from 'editor/src/app/components/properties-panel/e value: $event, isInputValid: maxLength.valid })"> </mat-form-field> + <mat-checkbox *ngIf="combinedProperties.isLimitedToMaxLength !== undefined" + [disabled]="!combinedProperties.maxLength" + [checked]="$any(combinedProperties.isLimitedToMaxLength)" + (change)="updateModel.emit({ property: 'isLimitedToMaxLength', value: $event.checked })"> + {{'propertiesPanel.isLimitedToMaxLength' | translate }} + </mat-checkbox> <mat-form-field class="wide-form-field" appearance="fill"> <mat-label>{{'propertiesPanel.maxLengthWarnMessage' | translate }}</mat-label> <input matInput type="text" diff --git a/projects/editor/src/assets/i18n/de.json b/projects/editor/src/assets/i18n/de.json index d4e26b450e5023158fc27fcb6cd1d1dd03327049..64ebc55fe1b60085d7146202e9ffb2e034eab7ea 100644 --- a/projects/editor/src/assets/i18n/de.json +++ b/projects/editor/src/assets/i18n/de.json @@ -111,6 +111,7 @@ "minLengthWarnMessage": "Minimalwert Warnmeldung", "maxLength": "Maximallänge", "maxValue": "Maximalwert", + "isLimitedToMaxLength": "Eingabe auf Maximallänge begrenzen", "showValues": "Zeige Start- und Endwert", "barStyle": "Zahlenstrahl-Modus", "thumbLabel": "Zeige gewählten Wert", diff --git a/projects/player/src/app/classes/input-service.ts b/projects/player/src/app/classes/input-service.ts index 1de8e63353802b759f0c9842da7bd1bf11d71a99..92cf40c03d7f7f766b309f86f159d7674a6f8cc7 100644 --- a/projects/player/src/app/classes/input-service.ts +++ b/projects/player/src/app/classes/input-service.ts @@ -25,12 +25,16 @@ export abstract class InputService { } enterKey(key: string): void { - const selectionStart = this.inputElement.selectionStart || 0; - const selectionEnd = this.inputElement.selectionEnd || 0; - const newSelection = selectionStart ? selectionStart + 1 : 1; - this.insert({ - selectionStart, selectionEnd, newSelection, key - }); + if (!(this.elementComponent.elementModel.maxLength && + this.elementComponent.elementModel.isLimitedToMaxLength && + this.inputElement.value.length === this.elementComponent.elementModel.maxLength)) { + const selectionStart = this.inputElement.selectionStart || 0; + const selectionEnd = this.inputElement.selectionEnd || 0; + const newSelection = selectionStart ? selectionStart + 1 : 1; + this.insert({ + selectionStart, selectionEnd, newSelection, key + }); + } } deleteCharacters(backspace: boolean): void { 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 4a94043734b08cb82c1bfac7b3ff27cd3712b7d7..bb77a8304a60109380d1b1bb51e336e2ab55d5d8 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 @@ -62,7 +62,7 @@ export class CompoundGroupElementComponent extends ElementFormGroupDirective imp this.registerAtUnitStateService(childModel.id, initialValue, child, this.pageIndex); if (childModel.type === 'text-field-simple') { this.manageKeyInputToggling(child as TextFieldSimpleComponent, childModel); - this.manageHardwareKeyBoardDetection(child as TextFieldSimpleComponent); + this.manageOnKeyDown(child as TextFieldSimpleComponent, childModel); } if (childModel.type === 'button') { this.addNavigationEventListener(child as ButtonComponent); @@ -70,12 +70,12 @@ export class CompoundGroupElementComponent extends ElementFormGroupDirective imp }); } - private manageHardwareKeyBoardDetection(textFieldSimpleComponent: TextFieldSimpleComponent): void { + private manageOnKeyDown(textFieldSimpleComponent: TextFieldSimpleComponent, elementModel: InputElement): void { (textFieldSimpleComponent) - .hardwareKeyDetected + .onKeyDown .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe(() => { - this.detectHardwareKeyboard(); + .subscribe(event => { + this.onKeyDown(event, elementModel); }); } @@ -101,9 +101,31 @@ export class CompoundGroupElementComponent extends ElementFormGroupDirective imp } } - private detectHardwareKeyboard(): void { - this.deviceService.hasHardwareKeyboard = true; - this.keyboardService.close(); + private onKeyDown(event: { + keyboardEvent: KeyboardEvent; + inputElement: HTMLInputElement | HTMLTextAreaElement + }, elementModel: InputElement): void { + this.detectHardwareKeyboard(elementModel); + CompoundGroupElementComponent.checkInputLimitation(event, elementModel); + } + + private static checkInputLimitation(event: { + keyboardEvent: KeyboardEvent; + inputElement: HTMLInputElement | HTMLTextAreaElement + }, elementModel: InputElement): void { + if (elementModel.maxLength && + elementModel.isLimitedToMaxLength && + event.inputElement.value.length === elementModel.maxLength && + !['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'ArrowDown', 'ArrowUp'].includes(event.keyboardEvent.key)) { + event.keyboardEvent.preventDefault(); + } + } + + private detectHardwareKeyboard(elementModel: InputElement): void { + if (elementModel.showSoftwareKeyboard) { + this.deviceService.hasHardwareKeyboard = true; + this.keyboardService.close(); + } } private addNavigationEventListener(button: ButtonComponent) { 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 4d4e116eab76ee7bde9aa50da601db7b82ff27d0..74a6e0d55a48680734cc2ff11ddf8eda1d68976a 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 @@ -5,7 +5,7 @@ #elementComponent [parentForm]="form" [elementModel]="elementModel | cast: TextAreaElement" - (hardwareKeyDetected)="detectHardwareKeyboard()" + (onKeyDown)="detectHardwareKeyboard(); checkInputLimitation($event)" (focusChanged)="toggleKeyInput($event, elementComponent)"> </aspect-text-area> <aspect-text-field @@ -13,7 +13,7 @@ #elementComponent [parentForm]="form" [elementModel]="elementModel | cast: TextFieldElement" - (hardwareKeyDetected)="detectHardwareKeyboard()" + (onKeyDown)="detectHardwareKeyboard(); checkInputLimitation($event)" (focusChanged)="toggleKeyInput($event, elementComponent)"> </aspect-text-field> <aspect-spell-correct @@ -21,7 +21,7 @@ #elementComponent [parentForm]="form" [elementModel]="elementModel | cast: SpellCorrectElement" - (hardwareKeyDetected)="detectHardwareKeyboard()" + (onKeyDown)="detectHardwareKeyboard(); checkInputLimitation($event)" (focusChanged)="toggleKeyInput($event, elementComponent)"> </aspect-spell-correct> </form> 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 8e03ae2f978b5340159a4845b472ef485470d2cd..d480d070f1f2ad95f8bedbd1320ca3f09ca807a7 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 @@ -73,8 +73,22 @@ export class TextInputGroupElementComponent extends ElementFormGroupDirective im } } + checkInputLimitation(event: { + keyboardEvent: KeyboardEvent; + inputElement: HTMLInputElement | HTMLTextAreaElement + }): void { + if (this.elementModel.maxLength && + this.elementModel.isLimitedToMaxLength && + event.inputElement.value.length === this.elementModel.maxLength && + !['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'ArrowDown', 'ArrowUp'].includes(event.keyboardEvent.key)) { + event.keyboardEvent.preventDefault(); + } + } + detectHardwareKeyboard(): void { - this.deviceService.hasHardwareKeyboard = true; - this.keyboardService.close(); + if (this.elementModel.showSoftwareKeyboard) { + this.deviceService.hasHardwareKeyboard = true; + this.keyboardService.close(); + } } }