MOBILE-4470 quiz: Fix opacity and clickable elements on reviews

main
Pau Ferrer Ocaña 2024-05-16 12:01:50 +02:00
parent e775a066ae
commit 5c099471f3
15 changed files with 156 additions and 139 deletions

View File

@ -15,7 +15,7 @@
[contextInstanceId]="contextInstanceId" [courseId]="courseId" (afterRender)="textRendered()" /> [contextInstanceId]="contextInstanceId" [courseId]="courseId" (afterRender)="textRendered()" />
</ion-label> </ion-label>
</ion-item> </ion-item>
<div class="fake-ion-item ion-text-wrap" [hidden]="!question.loaded"> <div class="fake-ion-item ion-text-wrap" [class.readonly]="question.readOnly" [hidden]="!question.loaded">
<core-format-text *ngIf="question.ddArea" [adaptImg]="false" [component]="component" [componentId]="componentId" <core-format-text *ngIf="question.ddArea" [adaptImg]="false" [component]="component" [componentId]="componentId"
[text]="question.ddArea" [filter]="false" (afterRender)="ddAreaRendered()" /> [text]="question.ddArea" [filter]="false" (afterRender)="ddAreaRendered()" />
</div> </div>

View File

@ -4,12 +4,20 @@
:host { :host {
--ddimageortext-border-drop: var(--medium); --ddimageortext-border-drop: var(--medium);
--ddimageortext-draghome-background: var(--core-dd-question-color-2); --ddimageortext-draghome-background: var(--core-dd-question-color-2);
--cursor: pointer;
.readonly,
core-format-text ::ng-deep .readonly {
--cursor: auto;
}
} }
.addon-qtype-ddimageortext-container { .addon-qtype-ddimageortext-container {
min-height: 80px; // To display the loading. min-height: 80px; // To display the loading.
} }
core-format-text ::ng-deep { core-format-text ::ng-deep {
div.ddarea { div.ddarea {
@ -39,7 +47,7 @@ core-format-text ::ng-deep {
div.draghome { div.draghome {
border: 1px solid var(--core-dd-question-border); border: 1px solid var(--core-dd-question-border);
cursor: pointer; cursor: var(--cursor);
background-color: var(--ddimageortext-draghome-background); background-color: var(--ddimageortext-draghome-background);
display: inline-block; display: inline-block;
height: auto; height: auto;
@ -78,23 +86,17 @@ core-format-text ::ng-deep {
.drag { .drag {
border: 1px solid var(--core-dd-question-border); border: 1px solid var(--core-dd-question-border);
cursor: pointer; cursor: var(--cursor);
z-index: 2; z-index: 2;
} }
.drag.placed { .drag.placed {
border: 1px solid var(--ddimageortext-border-drop); border: 1px solid var(--ddimageortext-border-drop);
} }
.dragitems.readonly .drag { .dragitems > div {
cursor: auto;
}
.dragitems>div {
clear: both; clear: both;
} }
.dragitems { .dragitems {
cursor: pointer; cursor: var(--cursor);
}
.dragitems.readonly {
cursor: auto;
} }
.drag img { .drag img {
display: block; display: block;
@ -106,10 +108,7 @@ core-format-text ::ng-deep {
border: 1px solid var(--ddimageortext-border-drop); border: 1px solid var(--ddimageortext-border-drop);
position: absolute; position: absolute;
z-index: 1; z-index: 1;
cursor: pointer; cursor: var(--cursor);
}
.readonly .dropzone {
cursor: auto;
} }
div.dragitems div.draghome, div.dragitems div.drag { div.dragitems div.draghome, div.dragitems div.drag {

View File

@ -1,4 +1,13 @@
// Style ddmarker content a bit. Almost all these styles are copied from Moodle. // Style ddmarker content a bit. Almost all these styles are copied from Moodle.
:host {
--cursor: pointer;
.readonly,
core-format-text ::ng-deep .readonly {
--cursor: auto;
}
}
.addon-qtype-ddmarker-container { .addon-qtype-ddmarker-container {
min-height: 80px; // To display the loading. min-height: 80px; // To display the loading.
} }
@ -33,18 +42,11 @@ core-format-text ::ng-deep {
.dragitems, // Previous to 3.9. .dragitems, // Previous to 3.9.
.draghomes { .draghomes {
&.readonly {
.dragitem,
.marker {
cursor: auto;
}
}
.dragitem, // Previous to 3.9. .dragitem, // Previous to 3.9.
.draghome, .draghome,
.marker { .marker {
vertical-align: top; vertical-align: top;
cursor: pointer; cursor: var(--cursor);
position: relative; position: relative;
margin: 10px; margin: 10px;
display: inline-block; display: inline-block;
@ -70,7 +72,7 @@ core-format-text ::ng-deep {
.droparea { .droparea {
.dragitem, .dragitem,
.marker { .marker {
cursor: pointer; cursor: var(--cursor);
position: absolute; position: absolute;
vertical-align: top; vertical-align: top;
z-index: 2; z-index: 2;

View File

@ -1,6 +1,16 @@
@use "theme/globals" as *; @use "theme/globals" as *;
// Style ddwtos content a bit. Almost all these styles are copied from Moodle. // Style ddwtos content a bit. Almost all these styles are copied from Moodle.
:host {
--cursor: pointer;
.readonly,
core-format-text ::ng-deep .readonly {
--cursor: default;
}
}
.addon-qtype-ddwtos-container { .addon-qtype-ddwtos-container {
min-height: 80px; // To display the loading. min-height: 80px; // To display the loading.
position: relative; position: relative;
@ -27,7 +37,7 @@ core-format-text ::ng-deep, .drags ::ng-deep {
border: 1px solid var(--core-dd-question-border); border: 1px solid var(--core-dd-question-border);
margin-bottom: 2px; margin-bottom: 2px;
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: var(--cursor);
} }
.draghome, .drag { .draghome, .drag {
display: inline-block; display: inline-block;
@ -48,7 +58,7 @@ core-format-text ::ng-deep, .drags ::ng-deep {
z-index: 2; z-index: 2;
border-radius: 5px; border-radius: 5px;
line-height: 25px; line-height: 25px;
cursor: pointer; cursor: var(--cursor);
} }
.drag.selected, .drag.selected,
.drop.selected { .drop.selected {
@ -60,17 +70,10 @@ core-format-text ::ng-deep, .drags ::ng-deep {
&.notreadonly .draghome, &.notreadonly .draghome,
&.notreadonly .drop, &.notreadonly .drop,
&.notreadonly .answercontainer { &.notreadonly .answercontainer {
cursor: pointer; cursor: var(--cursor);
border-radius: 5px; border-radius: 5px;
} }
&.readonly .drag,
&.readonly .draghome,
&.readonly .drop,
&.readonly .answercontainer {
cursor: default;
}
span.incorrect { span.incorrect {
background-color: var(--core-question-incorrect-color-bg); background-color: var(--core-question-incorrect-color-bg);
} }

View File

@ -16,25 +16,27 @@
<!-- Checkbox for multiple choice. --> <!-- Checkbox for multiple choice. -->
<ng-container *ngIf="question.multi"> <ng-container *ngIf="question.multi">
<ion-item class="ion-text-wrap answer" *ngFor="let option of question.options"> <ion-item class="ion-text-wrap answer" *ngFor="let option of question.options">
<div class="flex-column">
<ion-checkbox [attr.name]="option.name" [(ngModel)]="option.checked" [disabled]="option.disabled" <ion-checkbox [attr.name]="option.name" [(ngModel)]="option.checked" [disabled]="option.disabled"
[color]='(option.isCorrect === 1 ? "success": "") + (option.isCorrect === 0 ? "danger": "")'> [color]='(option.isCorrect === 1 ? "success": "") + (option.isCorrect === 0 ? "danger": "")'>
<div> <div class="flex-grow ion-text-wrap">
<div [class]="option.class"> <div [class]="option.class">
<core-format-text [component]="component" [componentId]="componentId" [text]="option.text" <core-format-text [component]="component" [componentId]="componentId" [text]="option.text"
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" /> [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" />
</div> </div>
<div *ngIf="option.feedback" class="specificfeedback">
<core-format-text [component]="component" [componentId]="componentId" [text]="option.feedback"
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" />
</div>
</div> </div>
<ion-icon *ngIf="option.isCorrect === 1" class="core-correct-icon" name="fas-check" color="success" <ion-icon *ngIf="option.isCorrect === 1" class="core-correct-icon" name="fas-check" color="success"
[attr.aria-label]="'core.question.correct' | translate" /> [attr.aria-label]="'core.question.correct' | translate" />
<ion-icon *ngIf="option.isCorrect === 0" class="core-correct-icon" name="fas-xmark" color="danger" <ion-icon *ngIf="option.isCorrect === 0" class="core-correct-icon" name="fas-xmark" color="danger"
[attr.aria-label]="'core.question.incorrect' | translate" /> [attr.aria-label]="'core.question.incorrect' | translate" />
</ion-checkbox> </ion-checkbox>
<div *ngIf="option.feedback" class="specificfeedback">
<core-format-text [component]="component" [componentId]="componentId" [text]="option.feedback"
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" />
</div>
</div>
<!-- ion-checkbox doesn't use an input. Create a hidden input to hold the value. --> <!-- ion-checkbox doesn't use an input. Create a hidden input to hold the value. -->
<!-- @TODO Check if this is still needed -->
<input type="hidden" [ngModel]="option.checked" [attr.name]="option.name"> <input type="hidden" [ngModel]="option.checked" [attr.name]="option.name">
</ion-item> </ion-item>
</ng-container> </ng-container>
@ -42,23 +44,26 @@
<!-- Radio buttons for single choice. --> <!-- Radio buttons for single choice. -->
<ion-radio-group *ngIf="!question.multi" [(ngModel)]="question.singleChoiceModel" [name]="question.optionsName"> <ion-radio-group *ngIf="!question.multi" [(ngModel)]="question.singleChoiceModel" [name]="question.optionsName">
<ion-item class="ion-text-wrap answer" *ngFor="let option of question.options"> <ion-item class="ion-text-wrap answer" *ngFor="let option of question.options">
<div class="flex-column">
<ion-radio [value]="option.value" [disabled]="option.disabled" <ion-radio [value]="option.value" [disabled]="option.disabled"
[color]='(option.isCorrect === 1 ? "success": "") + (option.isCorrect === 0 ? "danger": "")'> [color]='(option.isCorrect === 1 ? "success": "") + (option.isCorrect === 0 ? "danger": "")'>
<div> <div class="flex-grow ion-text-wrap">
<div [class]="option.class"> <div [class]="option.class">
<core-format-text [component]="component" [componentId]="componentId" [text]="option.text" <core-format-text [component]="component" [componentId]="componentId" [text]="option.text"
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" /> [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" />
</div> </div>
<div *ngIf="option.feedback" class="specificfeedback">
<core-format-text [component]="component" [componentId]="componentId" [text]="option.feedback"
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" />
</div>
</div> </div>
<ion-icon *ngIf="option.isCorrect === 1" class="core-correct-icon" name="fas-check" color="success" <ion-icon *ngIf="option.isCorrect === 1" class="core-correct-icon" name="fas-check" color="success"
[attr.aria-label]="'core.question.correct' | translate" /> [attr.aria-label]="'core.question.correct' | translate" />
<ion-icon *ngIf="option.isCorrect === 0" class="core-correct-icon" name="fas-xmark" color="danger" <ion-icon *ngIf="option.isCorrect === 0" class="core-correct-icon" name="fas-xmark" color="danger"
[attr.aria-label]="'core.question.incorrect' | translate" /> [attr.aria-label]="'core.question.incorrect' | translate" />
</ion-radio> </ion-radio>
<div *ngIf="option.feedback" class="specificfeedback">
<core-format-text [component]="component" [componentId]="componentId" [text]="option.feedback"
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" />
</div>
</div>
</ion-item> </ion-item>
<ion-button *ngIf="!question.disabled" class="ion-text-wrap ion-margin-top" expand="block" fill="outline" <ion-button *ngIf="!question.disabled" class="ion-text-wrap ion-margin-top" expand="block" fill="outline"
[disabled]="!question.singleChoiceModel" (click)="clear()" type="button"> [disabled]="!question.singleChoiceModel" (click)="clear()" type="button">
@ -66,6 +71,7 @@
</ion-button> </ion-button>
<!-- ion-radio doesn't use an input. Create a hidden input to hold the selected value. --> <!-- ion-radio doesn't use an input. Create a hidden input to hold the selected value. -->
<!-- @TODO Check if this is still needed -->
<input type="hidden" [ngModel]="question.singleChoiceModel" [attr.name]="question.optionsName"> <input type="hidden" [ngModel]="question.singleChoiceModel" [attr.name]="question.optionsName">
</ion-radio-group> </ion-radio-group>
</ion-list> </ion-list>

View File

@ -1,9 +1,13 @@
@use "theme/globals" as *;
:host ::ng-deep { :host ::ng-deep {
.specificfeedback { .specificfeedback {
background-color: var(--core-question-feedback-color-bg); background-color: var(--core-question-feedback-color-bg);
color: var(--core-question-feedback-color); color: var(--core-question-feedback-color);
display: inline;
padding: 0 .7em; padding: 0 .7em;
font-size: var(--text-size);
@include pointer-events-on-buttons();
} }
.d-flex { .d-flex {

View File

@ -31,12 +31,10 @@
--contents-display: block; --contents-display: block;
@include core-transition(all, 200ms); @include core-transition(all, 200ms);
pointer-events: none;
display: var(--contents-display); display: var(--contents-display);
&.core-loading-loaded { &.core-loading-loaded {
position: static; position: static;
pointer-events: auto;
--contents-display: contents; --contents-display: contents;
--internal-loading-inline-min-height: 0px; --internal-loading-inline-min-height: 0px;

View File

@ -106,10 +106,4 @@
.fa.icon.questioncorrectnessicon { .fa.icon.questioncorrectnessicon {
font-size: var(--mdl-typography-icon-fontSize-md); font-size: var(--mdl-typography-icon-fontSize-md);
} }
.item.item-interactive.item-interactive-disabled ::ng-deep {
ion-label, ion-select, ion-checkbox {
opacity: 0.7;
}
}
} }

View File

@ -16,12 +16,20 @@ input[type=checkbox] {
} }
ion-checkbox { ion-checkbox {
&.md.checkbox-disabled::part(label), &.checkbox-disabled {
&.ios.checkbox-disabled { @include pointer-events-on-buttons();
&.md::part(label),
&.ios {
opacity: var(--mdl-input-disabled-opacity); opacity: var(--mdl-input-disabled-opacity);
} }
}
} }
.ios input[type=checkbox] { .ios input[type=checkbox] {
--outer-border-width: 1px; --outer-border-width: 1px;
} }
input[type=checkbox][disabled] {
opacity: var(--mdl-input-disabled-opacity);
}

View File

@ -3,3 +3,8 @@ ion-input {
opacity: var(--mdl-input-disabled-opacity); opacity: var(--mdl-input-disabled-opacity);
} }
} }
input[disabled],
input[readonly] {
opacity: var(--mdl-input-disabled-opacity);
}

View File

@ -47,6 +47,10 @@ ion-item.item {
&.item-has-interactive-control:focus-within { &.item-has-interactive-control:focus-within {
@include core-focus-outline(); @include core-focus-outline();
} }
&.item-has-interactive-control.item-interactive-disabled {
pointer-events: none;
}
} }
// Fake item. // Fake item.
@ -246,6 +250,22 @@ ion-item.item.item-file {
[slot=end] { [slot=end] {
@include margin-horizontal(var(--mdl-spacing-4), null); @include margin-horizontal(var(--mdl-spacing-4), null);
} }
// Disabled items.
&.item-disabled,
&.item-interactive-disabled:not(.item-multiple-inputs) ion-label {
opacity: var(--mdl-item-disabled-opacity) !important;
}
// No highlight on RTE.
&.item-rte {
--full-highlight-height: 0px !important;
}
&.item-multiple-inputs.only-links a {
cursor: pointer;
}
} }
.item-dimmed { .item-dimmed {
@ -255,56 +275,3 @@ ion-item.item.item-file {
--background: var(--light); --background: var(--light);
} }
} }
// No highlight on RTE.
ion-item.item-rte {
--full-highlight-height: 0px !important;
}
// Make links clickable when inside radio or checkbox items. Style part.
@media (hover: hover) {
ion-item.item-multiple-inputs:not(.item-rte):hover::part(native) {
color: var(--color-hover);
&::after {
background: var(--background-hover);
opacity: var(--background-hover-opacity);
}
}
ion-item.ion-color.item-multiple-inputs:hover::part(native) {
color: #{current-color(contrast)};
&::after {
background: #{current-color(contrast)};
}
}
}
// It fixes the click on links where ion-ripple-effect is present.
// Make links clickable when inside radio or checkbox items. Pointer and cursor part.
ion-item.item-multiple-inputs:not(.only-links):not(.item-rte),
ion-item.ion-activatable:not(.only-links) {
cursor: pointer;
ion-label {
z-index: 3;
pointer-events: none;
ion-anchor, a,
ion-button, button,
ion-item.ion-focusable,
audio, video, select, input, iframe {
pointer-events: visible;
}
}
ion-checkbox, ion-datetime, ion-radio, ion-select{
position: static;
}
}
ion-item.item-multiple-inputs.only-links {
a {
cursor: pointer;
}
}

View File

@ -57,8 +57,16 @@ input[type=radio],
} }
ion-radio { ion-radio {
&.md.radio-disabled::part(label), &.radio-disabled {
&.ios.radio-disabled { @include pointer-events-on-buttons();
&.md::part(label),
&.ios {
opacity: var(--mdl-input-disabled-opacity); opacity: var(--mdl-input-disabled-opacity);
} }
}
}
input[type=radio][disabled] {
opacity: var(--mdl-input-disabled-opacity);
} }

View File

@ -72,3 +72,7 @@ ion-select-popover {
} }
} }
} }
select[disabled] {
opacity: var(--mdl-input-disabled-opacity);
}

View File

@ -171,6 +171,20 @@
} }
} }
@mixin pointer-events-on-buttons() {
a,
ion-button,
button,
audio,
video,
select,
input,
iframe,
[role="button"] {
pointer-events: visible;
}
}
/** /**
* Same as item-push-svg-url but admits flip-rtl * Same as item-push-svg-url but admits flip-rtl
*/ */

View File

@ -63,6 +63,17 @@ html[dir=rtl] {
flex-direction: row; flex-direction: row;
} }
.flex-column {
display: flex;
flex-direction: column;
width: 100%;
}
.flex-grow {
flex-grow: 1;
}
.margin-bottom-sm { margin-bottom: 8px; } .margin-bottom-sm { margin-bottom: 8px; }
.margin-bottom-md { margin-bottom: 12px; } .margin-bottom-md { margin-bottom: 12px; }
@ -572,12 +583,6 @@ audio.core-media-adapt-width {
width: 100%; width: 100%;
} }
// Disabled items.
ion-item.item-disabled,
ion-item.item-interactive-disabled:not(.item-multiple-inputs) ion-label {
opacity: var(--mdl-item-disabled-opacity) !important;
}
ion-item-divider.item, ion-item-divider.item,
ion-item.item, ion-item.item,
td { td {