@octolearn/training-ui 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -5
- package/dist/chunks/{core-DR1lxtEg.js → core-rJUDTpGS.js} +49 -19
- package/dist/chunks/{define-elements-BKBBQkbv.js → define-elements-CeSpZJXj.js} +661 -65
- package/dist/core.js +1 -1
- package/dist/define.js +13 -10
- package/dist/index.js +12 -9
- package/dist/octolearn-training-ui.bundle.js +1592 -1044
- package/dist/types/base-element.d.ts +7 -0
- package/dist/types/d-icons.d.ts +1 -1
- package/dist/types/elements/card.d.ts +2 -1
- package/dist/types/elements/dictation-question.d.ts +11 -2
- package/dist/types/elements/picture-question.d.ts +14 -2
- package/dist/types/elements/question-answer.d.ts +0 -2
- package/dist/types/elements/question-parts.d.ts +11 -3
- package/dist/types/elements/text-answer.d.ts +7 -0
- package/dist/types/i18n/strings.d.ts +5 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/speech.d.ts +19 -2
- package/dist/types/styles/d-parts.d.ts +2 -1
- package/dist/types/viewport.d.ts +35 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var _a;
|
|
2
2
|
import { html, svg, css, unsafeCSS, LitElement, nothing } from "lit";
|
|
3
3
|
import { property, state, query } from "lit/decorators.js";
|
|
4
|
-
import { g as getSpeechController, A as ANSWER_EVENT, D as DONT_KNOW_ANSWER } from "./core-
|
|
4
|
+
import { g as getSpeechController, S as SpeechController, A as ANSWER_EVENT, D as DONT_KNOW_ANSWER } from "./core-rJUDTpGS.js";
|
|
5
5
|
import { styleMap } from "lit/directives/style-map.js";
|
|
6
6
|
import { repeat } from "lit/directives/repeat.js";
|
|
7
7
|
import { live } from "lit/directives/live.js";
|
|
@@ -16,7 +16,8 @@ const paths = {
|
|
|
16
16
|
},
|
|
17
17
|
check: { box: 22, body: svg`<path d="M4 11.5l5 5 9-11"></path>` },
|
|
18
18
|
cross: { box: 30, body: svg`<path d="M8 8l14 14M22 8L8 22"></path>` },
|
|
19
|
-
arrow: { box: 22, body: svg`<path d="M4 11h14M12 5l6 6-6 6"></path>` }
|
|
19
|
+
arrow: { box: 22, body: svg`<path d="M4 11h14M12 5l6 6-6 6"></path>` },
|
|
20
|
+
zoom: { box: 24, body: svg`<circle cx="10.5" cy="10.5" r="6.5"></circle><path d="M15.5 15.5L21 21M10.5 7.5v6M7.5 10.5h6"></path>` }
|
|
20
21
|
};
|
|
21
22
|
function dIcon(name, size, strokeWidth = 3) {
|
|
22
23
|
const { box, body } = paths[name];
|
|
@@ -49,6 +50,7 @@ const de = {
|
|
|
49
50
|
stop: "Sprachausgabe stoppen",
|
|
50
51
|
playHintMessage: "Dein Browser benötigt eine Interaktion, bevor Audio abgespielt werden kann.",
|
|
51
52
|
playHintButton: "Abspielen",
|
|
53
|
+
noVoiceMessage: "Dein Gerät hat keine Stimme für {language}, deshalb klingt das Wort falsch. Füge in den Sprachausgabe-Einstellungen deines Geräts eine Stimme für {language} hinzu.",
|
|
52
54
|
feedbackCorrect: "Richtige Antwort",
|
|
53
55
|
feedbackWrong: "Falsche Antwort",
|
|
54
56
|
yourAnswer: "Deine Antwort",
|
|
@@ -84,6 +86,8 @@ const de = {
|
|
|
84
86
|
pictureAlt: "Bild zur Aufgabe",
|
|
85
87
|
pictureCaptionVocabulary: "Bild zur Vokabelaufgabe",
|
|
86
88
|
pictureCaptionRiddle: "Bilderrätsel: Was ist auf dem Bild zu sehen?",
|
|
89
|
+
enlargePicture: "Bild vergrößern",
|
|
90
|
+
closePicture: "Großes Bild schließen",
|
|
87
91
|
enterPictureVocabulary: "Vokabel zu diesem Bild eingeben",
|
|
88
92
|
enterPictureRiddle: "Eingeben, was auf dem Bild zu sehen ist",
|
|
89
93
|
arithmeticHeading: "Rechenaufgabe",
|
|
@@ -219,6 +223,7 @@ const en = {
|
|
|
219
223
|
stop: "Stop dictation",
|
|
220
224
|
playHintMessage: "Your browser requires interaction before playing audio.",
|
|
221
225
|
playHintButton: "Play",
|
|
226
|
+
noVoiceMessage: "Your device has no {language} voice, so the word sounds wrong. Add a {language} voice in your device's speech settings.",
|
|
222
227
|
feedbackCorrect: "Correct answer",
|
|
223
228
|
feedbackWrong: "Wrong answer",
|
|
224
229
|
yourAnswer: "Your answer",
|
|
@@ -254,6 +259,8 @@ const en = {
|
|
|
254
259
|
pictureAlt: "Picture for the task",
|
|
255
260
|
pictureCaptionVocabulary: "Picture for the vocabulary task",
|
|
256
261
|
pictureCaptionRiddle: "Picture riddle: what can you see in the picture?",
|
|
262
|
+
enlargePicture: "Enlarge picture",
|
|
263
|
+
closePicture: "Close the large picture",
|
|
257
264
|
enterPictureVocabulary: "Enter the vocabulary word for this picture",
|
|
258
265
|
enterPictureRiddle: "Enter what you see in the picture",
|
|
259
266
|
arithmeticHeading: "Arithmetic task",
|
|
@@ -727,6 +734,83 @@ const baseStyles = [
|
|
|
727
734
|
}
|
|
728
735
|
`
|
|
729
736
|
];
|
|
737
|
+
const SHORT_VIEWPORT_PX = 520;
|
|
738
|
+
class ViewportWatcher {
|
|
739
|
+
constructor() {
|
|
740
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
741
|
+
this.viewport = null;
|
|
742
|
+
this.current = false;
|
|
743
|
+
this.onResize = () => {
|
|
744
|
+
const short = ViewportWatcher.measure(this.viewport);
|
|
745
|
+
if (short === this.current) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
this.current = short;
|
|
749
|
+
for (const listener of [...this.listeners]) {
|
|
750
|
+
listener(short);
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
/** The visible area is short right now (false without a visual viewport, e.g. old browsers). */
|
|
755
|
+
get short() {
|
|
756
|
+
return this.listeners.size > 0 ? this.current : ViewportWatcher.measure(ViewportWatcher.visualViewport());
|
|
757
|
+
}
|
|
758
|
+
/** Calls `listener` whenever `short` flips; returns the function that ends it. */
|
|
759
|
+
subscribe(listener) {
|
|
760
|
+
if (this.listeners.size === 0) {
|
|
761
|
+
this.attach();
|
|
762
|
+
}
|
|
763
|
+
this.listeners.add(listener);
|
|
764
|
+
return () => {
|
|
765
|
+
this.listeners.delete(listener);
|
|
766
|
+
if (this.listeners.size === 0) {
|
|
767
|
+
this.detach();
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
attach() {
|
|
772
|
+
this.viewport = ViewportWatcher.visualViewport();
|
|
773
|
+
this.current = ViewportWatcher.measure(this.viewport);
|
|
774
|
+
this.viewport?.addEventListener("resize", this.onResize);
|
|
775
|
+
}
|
|
776
|
+
detach() {
|
|
777
|
+
this.viewport?.removeEventListener("resize", this.onResize);
|
|
778
|
+
this.viewport = null;
|
|
779
|
+
this.current = false;
|
|
780
|
+
}
|
|
781
|
+
static visualViewport() {
|
|
782
|
+
return typeof window === "undefined" ? null : window.visualViewport ?? null;
|
|
783
|
+
}
|
|
784
|
+
/** Height times scale: zooming in with two fingers makes the visual viewport smaller, but the page isn't short. */
|
|
785
|
+
static measure(viewport) {
|
|
786
|
+
if (!viewport || !(viewport.height > 0)) {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
return viewport.height * (viewport.scale || 1) < SHORT_VIEWPORT_PX;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
let defaultWatcher;
|
|
793
|
+
function getViewportWatcher() {
|
|
794
|
+
defaultWatcher ??= new ViewportWatcher();
|
|
795
|
+
return defaultWatcher;
|
|
796
|
+
}
|
|
797
|
+
function revealAtTop(element) {
|
|
798
|
+
element.scrollIntoView({ block: "start" });
|
|
799
|
+
const offset = typeof window === "undefined" ? 0 : window.visualViewport?.offsetTop ?? 0;
|
|
800
|
+
if (offset > 0) {
|
|
801
|
+
window.scrollBy(0, -offset);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
function hasFocusInside(element) {
|
|
805
|
+
let active = document.activeElement;
|
|
806
|
+
while (active) {
|
|
807
|
+
if (active === element) {
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
active = active.shadowRoot?.activeElement ?? null;
|
|
811
|
+
}
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
730
814
|
var __defProp$i = Object.defineProperty;
|
|
731
815
|
var __decorateClass$i = (decorators, target, key, kind) => {
|
|
732
816
|
var result = void 0;
|
|
@@ -737,6 +821,21 @@ var __decorateClass$i = (decorators, target, key, kind) => {
|
|
|
737
821
|
return result;
|
|
738
822
|
};
|
|
739
823
|
const _OctolearnElement = class _OctolearnElement extends LitElement {
|
|
824
|
+
constructor() {
|
|
825
|
+
super(...arguments);
|
|
826
|
+
this.short = false;
|
|
827
|
+
}
|
|
828
|
+
connectedCallback() {
|
|
829
|
+
super.connectedCallback();
|
|
830
|
+
const watcher = getViewportWatcher();
|
|
831
|
+
this.endViewportWatch = watcher.subscribe((short) => this.short = short);
|
|
832
|
+
this.short = watcher.short;
|
|
833
|
+
}
|
|
834
|
+
disconnectedCallback() {
|
|
835
|
+
super.disconnectedCallback();
|
|
836
|
+
this.endViewportWatch?.();
|
|
837
|
+
this.endViewportWatch = void 0;
|
|
838
|
+
}
|
|
740
839
|
get resolvedLanguage() {
|
|
741
840
|
return this.language ? toTrainingLanguage(this.language) : resolveLanguage(this);
|
|
742
841
|
}
|
|
@@ -761,6 +860,9 @@ __decorateClass$i([
|
|
|
761
860
|
__decorateClass$i([
|
|
762
861
|
property({ reflect: true })
|
|
763
862
|
], OctolearnElement.prototype, "theme");
|
|
863
|
+
__decorateClass$i([
|
|
864
|
+
property({ type: Boolean, reflect: true })
|
|
865
|
+
], OctolearnElement.prototype, "short");
|
|
764
866
|
const dParts = css`
|
|
765
867
|
:focus-visible {
|
|
766
868
|
outline: 4px solid ${d.ink};
|
|
@@ -948,6 +1050,60 @@ const dParts = css`
|
|
|
948
1050
|
}
|
|
949
1051
|
}
|
|
950
1052
|
|
|
1053
|
+
:host([short]) .prompt {
|
|
1054
|
+
font-size: 18px;
|
|
1055
|
+
line-height: 1.15;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
:host([short]) .field {
|
|
1059
|
+
height: 52px;
|
|
1060
|
+
padding: 0 6px 0 14px;
|
|
1061
|
+
border-radius: 16px;
|
|
1062
|
+
box-shadow: 0 0 0 4px ${d.cream};
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
:host([short]) .field input {
|
|
1066
|
+
font-size: 24px;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/* the field's label as placeholder ("Schreib das Wort"): readable, not answer-sized */
|
|
1070
|
+
:host([short]) .field input::placeholder {
|
|
1071
|
+
font-size: 14px;
|
|
1072
|
+
font-weight: 600;
|
|
1073
|
+
letter-spacing: 0;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
:host([short]) .pill {
|
|
1077
|
+
min-height: 40px;
|
|
1078
|
+
padding: 0 14px;
|
|
1079
|
+
font-size: 14px;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
:host([short]) .listen {
|
|
1083
|
+
width: 48px;
|
|
1084
|
+
height: 48px;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
:host([short]) .listen.medium {
|
|
1088
|
+
width: 44px;
|
|
1089
|
+
height: 44px;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
:host([short]) .listen.small {
|
|
1093
|
+
width: 40px;
|
|
1094
|
+
height: 40px;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
:host([short]) .listen .d-icon {
|
|
1098
|
+
width: 24px;
|
|
1099
|
+
height: 24px;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
:host([short]) .listen.small .d-icon {
|
|
1103
|
+
width: 20px;
|
|
1104
|
+
height: 20px;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
951
1107
|
kbd {
|
|
952
1108
|
padding: 2px 8px;
|
|
953
1109
|
border: 1.5px solid ${d.openStar};
|
|
@@ -1053,13 +1209,39 @@ const questionStyles = css`
|
|
|
1053
1209
|
font-size: 22px;
|
|
1054
1210
|
}
|
|
1055
1211
|
}
|
|
1212
|
+
|
|
1213
|
+
/* a phone's keyboard is open */
|
|
1214
|
+
:host([short]) .question {
|
|
1215
|
+
gap: 10px;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
:host([short]) .row {
|
|
1219
|
+
gap: 10px;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
:host([short]) .big {
|
|
1223
|
+
font-size: 30px;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
:host([short]) .big.sentence {
|
|
1227
|
+
font-size: 18px;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
:host([short]) .instruction {
|
|
1231
|
+
font-size: 15px;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
:host([short]) .play-hint {
|
|
1235
|
+
padding: 8px 12px;
|
|
1236
|
+
font-size: 14px;
|
|
1237
|
+
}
|
|
1056
1238
|
`;
|
|
1057
1239
|
const _QuestionElement = class _QuestionElement extends OctolearnElement {
|
|
1058
1240
|
constructor() {
|
|
1059
1241
|
super(...arguments);
|
|
1060
1242
|
this.noKeys = false;
|
|
1243
|
+
this.noAutofocus = false;
|
|
1061
1244
|
this.needsGesture = false;
|
|
1062
|
-
this.autofocusAnswer = false;
|
|
1063
1245
|
this.speech = getSpeechController();
|
|
1064
1246
|
}
|
|
1065
1247
|
connectedCallback() {
|
|
@@ -1092,6 +1274,9 @@ const _QuestionElement = class _QuestionElement extends OctolearnElement {
|
|
|
1092
1274
|
</div>` : nothing;
|
|
1093
1275
|
}
|
|
1094
1276
|
updated(changed) {
|
|
1277
|
+
if (this.short && (changed.has("short") || changed.has("content"))) {
|
|
1278
|
+
void this.revealWhenFocused();
|
|
1279
|
+
}
|
|
1095
1280
|
if (!changed.has("content")) {
|
|
1096
1281
|
return;
|
|
1097
1282
|
}
|
|
@@ -1103,11 +1288,30 @@ const _QuestionElement = class _QuestionElement extends OctolearnElement {
|
|
|
1103
1288
|
"octolearn-tile-answer, octolearn-true-false, octolearn-flashcard"
|
|
1104
1289
|
);
|
|
1105
1290
|
if (field) {
|
|
1106
|
-
if (this.
|
|
1291
|
+
if (!this.noAutofocus) {
|
|
1107
1292
|
void field.updateComplete.then(() => field.focus());
|
|
1108
1293
|
}
|
|
1109
1294
|
}
|
|
1110
1295
|
}
|
|
1296
|
+
/** After the field took the focus (autofocus waits for the field's own update) and the layout settled. */
|
|
1297
|
+
async revealWhenFocused() {
|
|
1298
|
+
await this.updateComplete;
|
|
1299
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
1300
|
+
if (this.short && this.isConnected && hasFocusInside(this)) {
|
|
1301
|
+
revealAtTop(this.closestCard() ?? this);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
/** The `<octolearn-card>` around the question, across shadow roots. */
|
|
1305
|
+
closestCard() {
|
|
1306
|
+
let node = this.parentNode;
|
|
1307
|
+
while (node) {
|
|
1308
|
+
if (node instanceof Element && node.localName === "octolearn-card") {
|
|
1309
|
+
return node;
|
|
1310
|
+
}
|
|
1311
|
+
node = node.parentNode ?? (node instanceof ShadowRoot ? node.host : null);
|
|
1312
|
+
}
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1111
1315
|
/** Puts the focus on the answer: the field, the first tile or the first button (e.g. when an overlay opens). */
|
|
1112
1316
|
focusAnswer() {
|
|
1113
1317
|
this.renderRoot.querySelector(
|
|
@@ -1126,6 +1330,9 @@ __decorateClass$h([
|
|
|
1126
1330
|
__decorateClass$h([
|
|
1127
1331
|
property({ attribute: false })
|
|
1128
1332
|
], QuestionElement.prototype, "revealFlashcard");
|
|
1333
|
+
__decorateClass$h([
|
|
1334
|
+
property({ type: Boolean, attribute: "no-autofocus" })
|
|
1335
|
+
], QuestionElement.prototype, "noAutofocus");
|
|
1129
1336
|
__decorateClass$h([
|
|
1130
1337
|
state()
|
|
1131
1338
|
], QuestionElement.prototype, "needsGesture");
|
|
@@ -1315,6 +1522,21 @@ _OctolearnArithmeticQuestion.styles = [
|
|
|
1315
1522
|
font-size: 56px;
|
|
1316
1523
|
}
|
|
1317
1524
|
}
|
|
1525
|
+
|
|
1526
|
+
/* a phone's keyboard is open */
|
|
1527
|
+
:host([short]) .task {
|
|
1528
|
+
font-size: 40px;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
:host([short]) .head-row {
|
|
1532
|
+
gap: 8px;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
:host([short]) .head-row .pill {
|
|
1536
|
+
min-height: 36px;
|
|
1537
|
+
padding: 0 12px;
|
|
1538
|
+
font-size: 13px;
|
|
1539
|
+
}
|
|
1318
1540
|
`
|
|
1319
1541
|
];
|
|
1320
1542
|
let OctolearnArithmeticQuestion = _OctolearnArithmeticQuestion;
|
|
@@ -1499,6 +1721,37 @@ _OctolearnCard.styles = [
|
|
|
1499
1721
|
width: 72px;
|
|
1500
1722
|
}
|
|
1501
1723
|
}
|
|
1724
|
+
|
|
1725
|
+
/* the on-screen keyboard covers half the screen: only the question and the field; label and stars for screen readers */
|
|
1726
|
+
:host([short]) {
|
|
1727
|
+
scroll-margin-top: 8px;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
:host([short]) .card {
|
|
1731
|
+
--band-height: 8px;
|
|
1732
|
+
border-radius: 18px;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
:host([short]) .content {
|
|
1736
|
+
gap: 10px;
|
|
1737
|
+
padding: calc(var(--band-height) + 10px) 14px 12px;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
:host([short]) .head {
|
|
1741
|
+
position: absolute;
|
|
1742
|
+
width: 1px;
|
|
1743
|
+
height: 1px;
|
|
1744
|
+
margin: -1px;
|
|
1745
|
+
padding: 0;
|
|
1746
|
+
overflow: hidden;
|
|
1747
|
+
clip: rect(0, 0, 0, 0);
|
|
1748
|
+
white-space: nowrap;
|
|
1749
|
+
border: 0;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
:host([short]) .inkie {
|
|
1753
|
+
display: none;
|
|
1754
|
+
}
|
|
1502
1755
|
`
|
|
1503
1756
|
];
|
|
1504
1757
|
let OctolearnCard = _OctolearnCard;
|
|
@@ -1544,6 +1797,7 @@ const _OctolearnDictationQuestion = class _OctolearnDictationQuestion extends Qu
|
|
|
1544
1797
|
super(...arguments);
|
|
1545
1798
|
this.settings = false;
|
|
1546
1799
|
this.noAutospeak = false;
|
|
1800
|
+
this.handleVoicesChanged = () => this.requestUpdate();
|
|
1547
1801
|
}
|
|
1548
1802
|
/** Where the keyboard focus belongs when the page's voice settings close. */
|
|
1549
1803
|
focusSettings() {
|
|
@@ -1551,13 +1805,26 @@ const _OctolearnDictationQuestion = class _OctolearnDictationQuestion extends Qu
|
|
|
1551
1805
|
}
|
|
1552
1806
|
/** Reads the word aloud, as the listen button does. */
|
|
1553
1807
|
speak(reason = "repeat") {
|
|
1554
|
-
|
|
1808
|
+
const lang = this.wordLanguage;
|
|
1809
|
+
this.speakText(this.content?.question, reason, reason === "slow" ? { lang, rate: SLOW_RATE } : { lang });
|
|
1555
1810
|
}
|
|
1556
1811
|
stop() {
|
|
1557
1812
|
if (this.emit("octolearn-stop", {}, { cancelable: true })) {
|
|
1558
1813
|
this.speech.stop();
|
|
1559
1814
|
}
|
|
1560
1815
|
}
|
|
1816
|
+
connectedCallback() {
|
|
1817
|
+
super.connectedCallback();
|
|
1818
|
+
if (SpeechController.supported) {
|
|
1819
|
+
speechSynthesis.addEventListener("voiceschanged", this.handleVoicesChanged);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
disconnectedCallback() {
|
|
1823
|
+
super.disconnectedCallback();
|
|
1824
|
+
if (SpeechController.supported) {
|
|
1825
|
+
speechSynthesis.removeEventListener("voiceschanged", this.handleVoicesChanged);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1561
1828
|
updated(changed) {
|
|
1562
1829
|
super.updated(changed);
|
|
1563
1830
|
if (changed.has("content") && !this.noAutospeak) {
|
|
@@ -1583,7 +1850,7 @@ const _OctolearnDictationQuestion = class _OctolearnDictationQuestion extends Qu
|
|
|
1583
1850
|
const words = content.queryFormat === "WordTiles";
|
|
1584
1851
|
return html`<section class="root question" lang=${this.resolvedLanguage} aria-labelledby="dictation-heading">
|
|
1585
1852
|
<h2 id="dictation-heading" class="prompt">${words ? t.promptWordSalad : t.promptLetterSalad}</h2>
|
|
1586
|
-
${this.playHint(t)}
|
|
1853
|
+
${this.playHint(t)} ${this.noVoiceHint(t)}
|
|
1587
1854
|
<div class="row">
|
|
1588
1855
|
<button type="button" class="listen medium repeat-button" aria-label=${t.listenWord} @click=${this.repeat}>
|
|
1589
1856
|
${dIcon("speaker", 36, 3.4)}
|
|
@@ -1597,7 +1864,7 @@ const _OctolearnDictationQuestion = class _OctolearnDictationQuestion extends Qu
|
|
|
1597
1864
|
</section>`;
|
|
1598
1865
|
}
|
|
1599
1866
|
return html`<section class="root question" lang=${this.resolvedLanguage} aria-labelledby="dictation-heading">
|
|
1600
|
-
${this.playHint(t)}
|
|
1867
|
+
${this.playHint(t)} ${this.noVoiceHint(t)}
|
|
1601
1868
|
<div class="row">
|
|
1602
1869
|
<button type="button" class="listen repeat-button" aria-label=${t.listenWord} @click=${this.repeat}>
|
|
1603
1870
|
${dIcon("speaker", 46, 3.4)}
|
|
@@ -1612,6 +1879,20 @@ const _OctolearnDictationQuestion = class _OctolearnDictationQuestion extends Qu
|
|
|
1612
1879
|
${answer}
|
|
1613
1880
|
</section>`;
|
|
1614
1881
|
}
|
|
1882
|
+
/** The word's language: the content's, else German (octolearn's dictations are German). */
|
|
1883
|
+
get wordLanguage() {
|
|
1884
|
+
return this.content?.sourceLanguage?.trim() || "de";
|
|
1885
|
+
}
|
|
1886
|
+
/** Shown once the browser's voice list is known and has no voice for the word's language. */
|
|
1887
|
+
noVoiceHint(t) {
|
|
1888
|
+
const lang = this.wordLanguage;
|
|
1889
|
+
if (this.speech.hasVoiceFor(lang) !== false) {
|
|
1890
|
+
return nothing;
|
|
1891
|
+
}
|
|
1892
|
+
return html`<div class="play-hint no-voice" role="status">
|
|
1893
|
+
<span>${format(t.noVoiceMessage, { language: languageName(lang, this.resolvedLanguage) })}</span>
|
|
1894
|
+
</div>`;
|
|
1895
|
+
}
|
|
1615
1896
|
controls() {
|
|
1616
1897
|
const t = this.t;
|
|
1617
1898
|
return html`<button type="button" class="pill slow-button" @click=${this.slow}>${t.listenSlowly}</button> ${this.settings ? html`<button type="button" class="pill settings-button" aria-haspopup="dialog" @click=${this.openSettings}>${t.settings}</button>` : nothing}`;
|
|
@@ -1634,6 +1915,35 @@ _OctolearnDictationQuestion.styles = [
|
|
|
1634
1915
|
flex-wrap: wrap;
|
|
1635
1916
|
gap: 8px;
|
|
1636
1917
|
}
|
|
1918
|
+
|
|
1919
|
+
/*
|
|
1920
|
+
* a phone's keyboard is open: speaker and prompt side by side, "Langsam anhören" and "Einstellungen" in one slim
|
|
1921
|
+
* row below them (next to the speaker there is no room for both)
|
|
1922
|
+
*/
|
|
1923
|
+
:host([short]) .row {
|
|
1924
|
+
flex-wrap: wrap;
|
|
1925
|
+
row-gap: 8px;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
:host([short]) .stack {
|
|
1929
|
+
display: contents;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
:host([short]) .stack .prompt {
|
|
1933
|
+
flex: 1 1 0;
|
|
1934
|
+
min-width: 0;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
:host([short]) .controls {
|
|
1938
|
+
flex: 1 0 100%;
|
|
1939
|
+
gap: 6px;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
:host([short]) .controls .pill {
|
|
1943
|
+
min-height: 36px;
|
|
1944
|
+
padding: 0 12px;
|
|
1945
|
+
font-size: 13px;
|
|
1946
|
+
}
|
|
1637
1947
|
`
|
|
1638
1948
|
];
|
|
1639
1949
|
let OctolearnDictationQuestion = _OctolearnDictationQuestion;
|
|
@@ -3647,7 +3957,54 @@ __decorateClass$a([
|
|
|
3647
3957
|
__decorateClass$a([
|
|
3648
3958
|
state()
|
|
3649
3959
|
], OctolearnMultipleChoice.prototype, "chosen");
|
|
3960
|
+
var __defProp$9 = Object.defineProperty;
|
|
3961
|
+
var __decorateClass$9 = (decorators, target, key, kind) => {
|
|
3962
|
+
var result = void 0;
|
|
3963
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
3964
|
+
if (decorator = decorators[i])
|
|
3965
|
+
result = decorator(target, key, result) || result;
|
|
3966
|
+
if (result) __defProp$9(target, key, result);
|
|
3967
|
+
return result;
|
|
3968
|
+
};
|
|
3650
3969
|
const _OctolearnPictureQuestion = class _OctolearnPictureQuestion extends QuestionElement {
|
|
3970
|
+
constructor() {
|
|
3971
|
+
super(...arguments);
|
|
3972
|
+
this.zoomed = false;
|
|
3973
|
+
}
|
|
3974
|
+
/** Shows the picture large (same as tapping it). */
|
|
3975
|
+
enlargePicture() {
|
|
3976
|
+
this.zoomed = true;
|
|
3977
|
+
}
|
|
3978
|
+
/** Closes the large picture. */
|
|
3979
|
+
closePicture() {
|
|
3980
|
+
this.zoomed = false;
|
|
3981
|
+
}
|
|
3982
|
+
willUpdate(changed) {
|
|
3983
|
+
super.willUpdate(changed);
|
|
3984
|
+
if (changed.has("content")) {
|
|
3985
|
+
this.zoomed = false;
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
updated(changed) {
|
|
3989
|
+
super.updated(changed);
|
|
3990
|
+
const dialog = this.zoomDialog;
|
|
3991
|
+
if (!dialog) {
|
|
3992
|
+
return;
|
|
3993
|
+
}
|
|
3994
|
+
if (this.zoomed && !dialog.open) {
|
|
3995
|
+
if (typeof dialog.showModal === "function") {
|
|
3996
|
+
dialog.showModal();
|
|
3997
|
+
} else {
|
|
3998
|
+
dialog.setAttribute("open", "");
|
|
3999
|
+
}
|
|
4000
|
+
} else if (!this.zoomed && dialog.open) {
|
|
4001
|
+
if (typeof dialog.close === "function") {
|
|
4002
|
+
dialog.close();
|
|
4003
|
+
} else {
|
|
4004
|
+
dialog.removeAttribute("open");
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
3651
4008
|
render() {
|
|
3652
4009
|
const content = this.content;
|
|
3653
4010
|
if (!content) {
|
|
@@ -3657,8 +4014,14 @@ const _OctolearnPictureQuestion = class _OctolearnPictureQuestion extends Questi
|
|
|
3657
4014
|
const lang = this.resolvedLanguage;
|
|
3658
4015
|
const vocabulary = content.isItVocabulary === true;
|
|
3659
4016
|
const choice = content.queryFormat === "MultipleChoice";
|
|
4017
|
+
const src = content.question ?? "";
|
|
4018
|
+
const alt = vocabulary ? t.pictureCaptionVocabulary : t.pictureCaptionRiddle;
|
|
3660
4019
|
const picture = html`<figure class="frame">
|
|
3661
|
-
<
|
|
4020
|
+
<button type="button" class="enlarge" aria-haspopup="dialog" title=${t.enlargePicture} @click=${this.enlargePicture}>
|
|
4021
|
+
<img src=${src} alt=${alt} />
|
|
4022
|
+
<span class="badge" aria-hidden="true">${dIcon("zoom", 22, 2.6)}</span>
|
|
4023
|
+
<span class="visually-hidden">${t.enlargePicture}</span>
|
|
4024
|
+
</button>
|
|
3662
4025
|
</figure>`;
|
|
3663
4026
|
const answer = answerSection({
|
|
3664
4027
|
content,
|
|
@@ -3674,8 +4037,22 @@ const _OctolearnPictureQuestion = class _OctolearnPictureQuestion extends Questi
|
|
|
3674
4037
|
return html`<section class="root question" lang=${lang} aria-labelledby="picture-heading">
|
|
3675
4038
|
<h2 id="picture-heading" class="prompt">${vocabulary ? vocabularyPrompt(content, lang, t) : t.promptPicture}</h2>
|
|
3676
4039
|
${choice ? html`<div class="centered">${picture}</div>` : nothing} ${answer}
|
|
4040
|
+
<dialog class="zoom" aria-label=${alt} @click=${this.closePicture} @keydown=${this.onZoomKeydown} @close=${this.onZoomClosed}>
|
|
4041
|
+
${this.zoomed ? html`<div class="stage"><img src=${src} alt=${alt} /></div>
|
|
4042
|
+
<button type="button" class="close" aria-label=${t.closePicture} title=${t.closePicture}>${dIcon("cross", 26, 3.4)}</button>` : nothing}
|
|
4043
|
+
</dialog>
|
|
3677
4044
|
</section>`;
|
|
3678
4045
|
}
|
|
4046
|
+
onZoomKeydown(event) {
|
|
4047
|
+
event.stopPropagation();
|
|
4048
|
+
if (event.key === "Escape") {
|
|
4049
|
+
event.preventDefault();
|
|
4050
|
+
this.zoomed = false;
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
onZoomClosed() {
|
|
4054
|
+
this.zoomed = false;
|
|
4055
|
+
}
|
|
3679
4056
|
};
|
|
3680
4057
|
_OctolearnPictureQuestion.styles = [
|
|
3681
4058
|
QuestionElement.styles,
|
|
@@ -3703,6 +4080,96 @@ _OctolearnPictureQuestion.styles = [
|
|
|
3703
4080
|
border-radius: 14px;
|
|
3704
4081
|
}
|
|
3705
4082
|
|
|
4083
|
+
.enlarge {
|
|
4084
|
+
position: relative;
|
|
4085
|
+
display: flex;
|
|
4086
|
+
align-items: center;
|
|
4087
|
+
justify-content: center;
|
|
4088
|
+
width: 100%;
|
|
4089
|
+
height: 100%;
|
|
4090
|
+
padding: 0;
|
|
4091
|
+
border: 0;
|
|
4092
|
+
border-radius: 14px;
|
|
4093
|
+
background: none;
|
|
4094
|
+
cursor: zoom-in;
|
|
4095
|
+
}
|
|
4096
|
+
|
|
4097
|
+
.enlarge .badge {
|
|
4098
|
+
position: absolute;
|
|
4099
|
+
right: 0;
|
|
4100
|
+
bottom: 0;
|
|
4101
|
+
display: flex;
|
|
4102
|
+
align-items: center;
|
|
4103
|
+
justify-content: center;
|
|
4104
|
+
width: 36px;
|
|
4105
|
+
height: 36px;
|
|
4106
|
+
border: 2px solid ${d.frameLine};
|
|
4107
|
+
border-radius: 999px;
|
|
4108
|
+
background: ${d.creamLight};
|
|
4109
|
+
color: ${d.ink};
|
|
4110
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
|
|
4111
|
+
}
|
|
4112
|
+
|
|
4113
|
+
.enlarge:hover .badge {
|
|
4114
|
+
background: ${d.cream};
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4117
|
+
.zoom {
|
|
4118
|
+
position: fixed;
|
|
4119
|
+
inset: 0;
|
|
4120
|
+
width: 100%;
|
|
4121
|
+
height: 100%;
|
|
4122
|
+
max-width: none;
|
|
4123
|
+
max-height: none;
|
|
4124
|
+
margin: 0;
|
|
4125
|
+
padding: 0;
|
|
4126
|
+
border: 0;
|
|
4127
|
+
background: transparent;
|
|
4128
|
+
cursor: zoom-out;
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
.zoom::backdrop {
|
|
4132
|
+
background: ${d.backdrop};
|
|
4133
|
+
-webkit-backdrop-filter: blur(4px);
|
|
4134
|
+
backdrop-filter: blur(4px);
|
|
4135
|
+
}
|
|
4136
|
+
|
|
4137
|
+
.zoom .stage {
|
|
4138
|
+
display: flex;
|
|
4139
|
+
align-items: center;
|
|
4140
|
+
justify-content: center;
|
|
4141
|
+
width: 100%;
|
|
4142
|
+
height: 100%;
|
|
4143
|
+
padding: 24px;
|
|
4144
|
+
}
|
|
4145
|
+
|
|
4146
|
+
.zoom img {
|
|
4147
|
+
display: block;
|
|
4148
|
+
max-width: min(92vw, 1100px);
|
|
4149
|
+
max-height: 86vh;
|
|
4150
|
+
object-fit: contain;
|
|
4151
|
+
border: 3px solid ${d.frameLine};
|
|
4152
|
+
border-radius: 24px;
|
|
4153
|
+
background: ${d.creamLight};
|
|
4154
|
+
}
|
|
4155
|
+
|
|
4156
|
+
.zoom .close {
|
|
4157
|
+
position: fixed;
|
|
4158
|
+
top: 16px;
|
|
4159
|
+
right: 16px;
|
|
4160
|
+
display: flex;
|
|
4161
|
+
align-items: center;
|
|
4162
|
+
justify-content: center;
|
|
4163
|
+
width: 52px;
|
|
4164
|
+
height: 52px;
|
|
4165
|
+
padding: 0;
|
|
4166
|
+
border: 2px solid ${d.ink};
|
|
4167
|
+
border-radius: 999px;
|
|
4168
|
+
background: ${d.paper};
|
|
4169
|
+
color: ${d.ink};
|
|
4170
|
+
cursor: pointer;
|
|
4171
|
+
}
|
|
4172
|
+
|
|
3706
4173
|
.centered {
|
|
3707
4174
|
display: flex;
|
|
3708
4175
|
justify-content: center;
|
|
@@ -3725,16 +4192,47 @@ _OctolearnPictureQuestion.styles = [
|
|
|
3725
4192
|
height: 180px;
|
|
3726
4193
|
}
|
|
3727
4194
|
}
|
|
4195
|
+
|
|
4196
|
+
/* a phone's keyboard is open: a small picture left of the field (a tap still shows it large) */
|
|
4197
|
+
:host([short]) .frame {
|
|
4198
|
+
align-self: center;
|
|
4199
|
+
width: 88px;
|
|
4200
|
+
height: 88px;
|
|
4201
|
+
padding: 4px;
|
|
4202
|
+
border-width: 2px;
|
|
4203
|
+
border-radius: 16px;
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
:host([short]) .frame img,
|
|
4207
|
+
:host([short]) .enlarge {
|
|
4208
|
+
border-radius: 10px;
|
|
4209
|
+
}
|
|
4210
|
+
|
|
4211
|
+
:host([short]) .enlarge .badge {
|
|
4212
|
+
width: 26px;
|
|
4213
|
+
height: 26px;
|
|
4214
|
+
}
|
|
4215
|
+
|
|
4216
|
+
:host([short]) .enlarge .badge .d-icon {
|
|
4217
|
+
width: 16px;
|
|
4218
|
+
height: 16px;
|
|
4219
|
+
}
|
|
3728
4220
|
`
|
|
3729
4221
|
];
|
|
3730
4222
|
let OctolearnPictureQuestion = _OctolearnPictureQuestion;
|
|
3731
|
-
|
|
3732
|
-
|
|
4223
|
+
__decorateClass$9([
|
|
4224
|
+
state()
|
|
4225
|
+
], OctolearnPictureQuestion.prototype, "zoomed");
|
|
4226
|
+
__decorateClass$9([
|
|
4227
|
+
query("dialog.zoom")
|
|
4228
|
+
], OctolearnPictureQuestion.prototype, "zoomDialog");
|
|
4229
|
+
var __defProp$8 = Object.defineProperty;
|
|
4230
|
+
var __decorateClass$8 = (decorators, target, key, kind) => {
|
|
3733
4231
|
var result = void 0;
|
|
3734
4232
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
3735
4233
|
if (decorator = decorators[i])
|
|
3736
4234
|
result = decorator(target, key, result) || result;
|
|
3737
|
-
if (result) __defProp$
|
|
4235
|
+
if (result) __defProp$8(target, key, result);
|
|
3738
4236
|
return result;
|
|
3739
4237
|
};
|
|
3740
4238
|
const _OctolearnProgress = class _OctolearnProgress extends OctolearnElement {
|
|
@@ -3834,22 +4332,22 @@ _OctolearnProgress.styles = [
|
|
|
3834
4332
|
`
|
|
3835
4333
|
];
|
|
3836
4334
|
let OctolearnProgress = _OctolearnProgress;
|
|
3837
|
-
__decorateClass$
|
|
4335
|
+
__decorateClass$8([
|
|
3838
4336
|
property({ type: Number })
|
|
3839
4337
|
], OctolearnProgress.prototype, "current");
|
|
3840
|
-
__decorateClass$
|
|
4338
|
+
__decorateClass$8([
|
|
3841
4339
|
property({ type: Number })
|
|
3842
4340
|
], OctolearnProgress.prototype, "total");
|
|
3843
|
-
__decorateClass$
|
|
4341
|
+
__decorateClass$8([
|
|
3844
4342
|
property({ attribute: false })
|
|
3845
4343
|
], OctolearnProgress.prototype, "results");
|
|
3846
|
-
var __defProp$
|
|
3847
|
-
var __decorateClass$
|
|
4344
|
+
var __defProp$7 = Object.defineProperty;
|
|
4345
|
+
var __decorateClass$7 = (decorators, target, key, kind) => {
|
|
3848
4346
|
var result = void 0;
|
|
3849
4347
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
3850
4348
|
if (decorator = decorators[i])
|
|
3851
4349
|
result = decorator(target, key, result) || result;
|
|
3852
|
-
if (result) __defProp$
|
|
4350
|
+
if (result) __defProp$7(target, key, result);
|
|
3853
4351
|
return result;
|
|
3854
4352
|
};
|
|
3855
4353
|
const ELEMENT_BY_FORMAT = {
|
|
@@ -3903,6 +4401,7 @@ class OctolearnQuestion extends OctolearnElement {
|
|
|
3903
4401
|
.content=${this.content}
|
|
3904
4402
|
.strings=${this.strings}
|
|
3905
4403
|
lang=${lang}
|
|
4404
|
+
?no-autofocus=${this.noAutofocus}
|
|
3906
4405
|
?no-keys=${this.noKeys}
|
|
3907
4406
|
></octolearn-arithmetic-question>`;
|
|
3908
4407
|
case "octolearn-dictation-question":
|
|
@@ -3912,6 +4411,7 @@ class OctolearnQuestion extends OctolearnElement {
|
|
|
3912
4411
|
lang=${lang}
|
|
3913
4412
|
?settings=${this.settings}
|
|
3914
4413
|
?no-autospeak=${this.noAutospeak}
|
|
4414
|
+
?no-autofocus=${this.noAutofocus}
|
|
3915
4415
|
?no-keys=${this.noKeys}
|
|
3916
4416
|
></octolearn-dictation-question>`;
|
|
3917
4417
|
case "octolearn-picture-question":
|
|
@@ -3919,6 +4419,7 @@ class OctolearnQuestion extends OctolearnElement {
|
|
|
3919
4419
|
.content=${this.content}
|
|
3920
4420
|
.strings=${this.strings}
|
|
3921
4421
|
lang=${lang}
|
|
4422
|
+
?no-autofocus=${this.noAutofocus}
|
|
3922
4423
|
?no-keys=${this.noKeys}
|
|
3923
4424
|
></octolearn-picture-question>`;
|
|
3924
4425
|
default:
|
|
@@ -3933,41 +4434,25 @@ class OctolearnQuestion extends OctolearnElement {
|
|
|
3933
4434
|
}
|
|
3934
4435
|
}
|
|
3935
4436
|
}
|
|
3936
|
-
__decorateClass$
|
|
4437
|
+
__decorateClass$7([
|
|
3937
4438
|
property({ attribute: false })
|
|
3938
4439
|
], OctolearnQuestion.prototype, "content");
|
|
3939
|
-
__decorateClass$
|
|
4440
|
+
__decorateClass$7([
|
|
3940
4441
|
property({ type: Boolean })
|
|
3941
4442
|
], OctolearnQuestion.prototype, "settings");
|
|
3942
|
-
__decorateClass$
|
|
4443
|
+
__decorateClass$7([
|
|
3943
4444
|
property({ type: Boolean, attribute: "no-autospeak" })
|
|
3944
4445
|
], OctolearnQuestion.prototype, "noAutospeak");
|
|
3945
|
-
__decorateClass$
|
|
4446
|
+
__decorateClass$7([
|
|
3946
4447
|
property({ type: Boolean, attribute: "no-autofocus" })
|
|
3947
4448
|
], OctolearnQuestion.prototype, "noAutofocus");
|
|
3948
|
-
__decorateClass$
|
|
4449
|
+
__decorateClass$7([
|
|
3949
4450
|
property({ type: Boolean, attribute: "no-keys" })
|
|
3950
4451
|
], OctolearnQuestion.prototype, "noKeys");
|
|
3951
|
-
__decorateClass$
|
|
4452
|
+
__decorateClass$7([
|
|
3952
4453
|
property({ attribute: false })
|
|
3953
4454
|
], OctolearnQuestion.prototype, "revealFlashcard");
|
|
3954
|
-
var __defProp$7 = Object.defineProperty;
|
|
3955
|
-
var __decorateClass$7 = (decorators, target, key, kind) => {
|
|
3956
|
-
var result = void 0;
|
|
3957
|
-
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
3958
|
-
if (decorator = decorators[i])
|
|
3959
|
-
result = decorator(target, key, result) || result;
|
|
3960
|
-
if (result) __defProp$7(target, key, result);
|
|
3961
|
-
return result;
|
|
3962
|
-
};
|
|
3963
4455
|
class OctolearnQuestionAnswer extends QuestionElement {
|
|
3964
|
-
constructor() {
|
|
3965
|
-
super(...arguments);
|
|
3966
|
-
this.noAutofocus = false;
|
|
3967
|
-
}
|
|
3968
|
-
willUpdate() {
|
|
3969
|
-
this.autofocusAnswer = !this.noAutofocus;
|
|
3970
|
-
}
|
|
3971
4456
|
render() {
|
|
3972
4457
|
const content = this.content;
|
|
3973
4458
|
if (!content) {
|
|
@@ -4030,9 +4515,6 @@ class OctolearnQuestionAnswer extends QuestionElement {
|
|
|
4030
4515
|
}
|
|
4031
4516
|
}
|
|
4032
4517
|
}
|
|
4033
|
-
__decorateClass$7([
|
|
4034
|
-
property({ type: Boolean, attribute: "no-autofocus" })
|
|
4035
|
-
], OctolearnQuestionAnswer.prototype, "noAutofocus");
|
|
4036
4518
|
var __defProp$6 = Object.defineProperty;
|
|
4037
4519
|
var __decorateClass$6 = (decorators, target, key, kind) => {
|
|
4038
4520
|
var result = void 0;
|
|
@@ -4061,13 +4543,19 @@ const _OctolearnTextAnswer = class _OctolearnTextAnswer extends OctolearnElement
|
|
|
4061
4543
|
const t = this.t;
|
|
4062
4544
|
const empty = this.value.trim() === "";
|
|
4063
4545
|
const keyHint = t.pressKeyOr.split("{key}");
|
|
4546
|
+
const short = this.short;
|
|
4547
|
+
const placeholder = short && this.visibleLabel ? this.label : this.placeholder;
|
|
4548
|
+
const send = html`<button type="submit" class="send-round" aria-label=${t.done} title=${t.done} ?disabled=${empty}>
|
|
4549
|
+
${dIcon("check", 22, 3.4)}
|
|
4550
|
+
</button>`;
|
|
4064
4551
|
const input = html`<input
|
|
4065
4552
|
id="answer"
|
|
4066
4553
|
class=${this.variant === "box" ? "box" : ""}
|
|
4067
4554
|
.type=${this.type}
|
|
4068
4555
|
inputmode=${this.type === "number" ? "numeric" : "text"}
|
|
4556
|
+
enterkeyhint="done"
|
|
4069
4557
|
.value=${live(this.value)}
|
|
4070
|
-
placeholder=${
|
|
4558
|
+
placeholder=${placeholder || nothing}
|
|
4071
4559
|
lang=${this.inputLang ?? nothing}
|
|
4072
4560
|
autocomplete="off"
|
|
4073
4561
|
autocapitalize="off"
|
|
@@ -4075,30 +4563,35 @@ const _OctolearnTextAnswer = class _OctolearnTextAnswer extends OctolearnElement
|
|
|
4075
4563
|
spellcheck="false"
|
|
4076
4564
|
@input=${this.onInput}
|
|
4077
4565
|
/>`;
|
|
4078
|
-
const label = html`<label for="answer" class=${this.visibleLabel ? "field-label" : "visually-hidden"}>${this.label}</label>`;
|
|
4566
|
+
const label = html`<label for="answer" class=${this.visibleLabel && !short ? "field-label" : "visually-hidden"}>${this.label}</label>`;
|
|
4567
|
+
const field = html`<div class="field">${input}${short ? send : nothing}</div>`;
|
|
4079
4568
|
return html`<div class="root" lang=${this.resolvedLanguage}>
|
|
4080
4569
|
<form novalidate @submit=${this.onSubmit}>
|
|
4081
4570
|
${this.renderHint()}
|
|
4082
|
-
${this.variant === "box" ? html`<div class="equation"><slot name="before"></slot>${label}${input}</div>` : this.variant === "media" ? html`<div class="with-media">
|
|
4571
|
+
${this.variant === "box" ? html`<div class="equation"><slot name="before"></slot>${label}${input}${short ? send : nothing}</div>` : this.variant === "media" ? html`<div class="with-media">
|
|
4083
4572
|
<slot name="media"></slot>
|
|
4084
|
-
<div class="labelled grow">
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
</div>
|
|
4088
|
-
</div>` : html`<div class="labelled">
|
|
4089
|
-
${label}
|
|
4090
|
-
<div class="field">${input}</div>
|
|
4091
|
-
</div>`}
|
|
4092
|
-
<div class="actions">
|
|
4093
|
-
${this.noDontKnow ? nothing : html`<button type="button" class="pill dont-know" @click=${this.onDontKnow}>${t.dontKnow}</button>`}
|
|
4094
|
-
<span class="send">
|
|
4095
|
-
<span class="hint keys-only">${keyHint[0]}<kbd>Enter</kbd>${keyHint[1]}</span>
|
|
4096
|
-
<button type="submit" class="pill primary" ?disabled=${empty}>${dIcon("check", 22)} ${t.done}</button>
|
|
4097
|
-
</span>
|
|
4098
|
-
</div>
|
|
4573
|
+
<div class="labelled grow">${label} ${field}</div>
|
|
4574
|
+
</div>` : html`<div class="labelled">${label} ${field}</div>`}
|
|
4575
|
+
${short ? this.renderShortActions() : this.renderActions(empty, keyHint)}
|
|
4099
4576
|
</form>
|
|
4100
4577
|
</div>`;
|
|
4101
4578
|
}
|
|
4579
|
+
renderActions(empty, keyHint) {
|
|
4580
|
+
const t = this.t;
|
|
4581
|
+
return html`<div class="actions">
|
|
4582
|
+
${this.noDontKnow ? nothing : html`<button type="button" class="pill dont-know" @click=${this.onDontKnow}>${t.dontKnow}</button>`}
|
|
4583
|
+
<span class="send">
|
|
4584
|
+
<span class="hint keys-only">${keyHint[0]}<kbd>Enter</kbd>${keyHint[1]}</span>
|
|
4585
|
+
<button type="submit" class="pill primary" ?disabled=${empty}>${dIcon("check", 22)} ${t.done}</button>
|
|
4586
|
+
</span>
|
|
4587
|
+
</div>`;
|
|
4588
|
+
}
|
|
4589
|
+
/** Short: the send button is in the field; only "Weiß ich nicht", small, below it. */
|
|
4590
|
+
renderShortActions() {
|
|
4591
|
+
return this.noDontKnow ? nothing : html`<div class="actions">
|
|
4592
|
+
<button type="button" class="dont-know link" @click=${this.onDontKnow}>${this.t.dontKnow}</button>
|
|
4593
|
+
</div>`;
|
|
4594
|
+
}
|
|
4102
4595
|
renderHint() {
|
|
4103
4596
|
const letters2 = Array.from((this.hint ?? "").normalize("NFC"));
|
|
4104
4597
|
if (letters2.length === 0) {
|
|
@@ -4265,6 +4758,96 @@ _OctolearnTextAnswer.styles = [
|
|
|
4265
4758
|
height: 80px;
|
|
4266
4759
|
font-size: 48px;
|
|
4267
4760
|
}
|
|
4761
|
+
|
|
4762
|
+
/* "Weiß ich nicht" and "Fertig" share one row on a 320 px phone */
|
|
4763
|
+
.actions {
|
|
4764
|
+
gap: 8px;
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4767
|
+
.actions .pill {
|
|
4768
|
+
padding: 0 14px;
|
|
4769
|
+
}
|
|
4770
|
+
|
|
4771
|
+
.actions .pill.primary {
|
|
4772
|
+
padding: 0 18px;
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
|
|
4776
|
+
/* a phone's keyboard is open */
|
|
4777
|
+
:host([short]) form {
|
|
4778
|
+
gap: 6px;
|
|
4779
|
+
}
|
|
4780
|
+
|
|
4781
|
+
:host([short]) .with-media {
|
|
4782
|
+
flex-direction: row;
|
|
4783
|
+
align-items: center;
|
|
4784
|
+
gap: 10px;
|
|
4785
|
+
}
|
|
4786
|
+
|
|
4787
|
+
:host([short]) .equation {
|
|
4788
|
+
flex-wrap: nowrap;
|
|
4789
|
+
gap: 10px;
|
|
4790
|
+
}
|
|
4791
|
+
|
|
4792
|
+
:host([short]) .box {
|
|
4793
|
+
width: 88px;
|
|
4794
|
+
height: 56px;
|
|
4795
|
+
border-radius: 16px;
|
|
4796
|
+
box-shadow: 0 0 0 4px ${d.cream};
|
|
4797
|
+
font-size: 32px;
|
|
4798
|
+
}
|
|
4799
|
+
|
|
4800
|
+
:host([short]) .fragment {
|
|
4801
|
+
font-size: 26px;
|
|
4802
|
+
}
|
|
4803
|
+
|
|
4804
|
+
:host([short]) .fragment .gap {
|
|
4805
|
+
width: 18px;
|
|
4806
|
+
height: 24px;
|
|
4807
|
+
margin: 0 2px;
|
|
4808
|
+
border-width: 2px;
|
|
4809
|
+
border-radius: 6px;
|
|
4810
|
+
vertical-align: -3px;
|
|
4811
|
+
}
|
|
4812
|
+
|
|
4813
|
+
.send-round {
|
|
4814
|
+
display: inline-flex;
|
|
4815
|
+
flex: none;
|
|
4816
|
+
align-items: center;
|
|
4817
|
+
justify-content: center;
|
|
4818
|
+
width: 40px;
|
|
4819
|
+
height: 40px;
|
|
4820
|
+
padding: 0;
|
|
4821
|
+
border: 0;
|
|
4822
|
+
border-radius: 50%;
|
|
4823
|
+
background: ${d.accent};
|
|
4824
|
+
color: ${d.onAccent};
|
|
4825
|
+
}
|
|
4826
|
+
|
|
4827
|
+
.send-round:disabled {
|
|
4828
|
+
opacity: 0.5;
|
|
4829
|
+
}
|
|
4830
|
+
|
|
4831
|
+
.equation .send-round {
|
|
4832
|
+
width: 48px;
|
|
4833
|
+
height: 48px;
|
|
4834
|
+
}
|
|
4835
|
+
|
|
4836
|
+
:host([short]) .actions {
|
|
4837
|
+
justify-content: flex-start;
|
|
4838
|
+
gap: 0;
|
|
4839
|
+
}
|
|
4840
|
+
|
|
4841
|
+
.dont-know.link {
|
|
4842
|
+
min-height: 40px;
|
|
4843
|
+
padding: 0 4px;
|
|
4844
|
+
border: 0;
|
|
4845
|
+
background: none;
|
|
4846
|
+
color: ${d.inkSoft};
|
|
4847
|
+
font-size: 14px;
|
|
4848
|
+
font-weight: 600;
|
|
4849
|
+
text-decoration: underline;
|
|
4850
|
+
text-underline-offset: 3px;
|
|
4268
4851
|
}
|
|
4269
4852
|
`
|
|
4270
4853
|
];
|
|
@@ -5054,6 +5637,16 @@ _OctolearnTrainerOverlay.styles = [
|
|
|
5054
5637
|
padding: 76px 8px 16px;
|
|
5055
5638
|
}
|
|
5056
5639
|
}
|
|
5640
|
+
|
|
5641
|
+
/* a phone's keyboard is open: the card at the top, no note over it */
|
|
5642
|
+
:host([short]) .stage {
|
|
5643
|
+
align-items: flex-start;
|
|
5644
|
+
padding: 8px;
|
|
5645
|
+
}
|
|
5646
|
+
|
|
5647
|
+
:host([short]) .status {
|
|
5648
|
+
display: none;
|
|
5649
|
+
}
|
|
5057
5650
|
`
|
|
5058
5651
|
];
|
|
5059
5652
|
let OctolearnTrainerOverlay = _OctolearnTrainerOverlay;
|
|
@@ -5479,13 +6072,16 @@ function defineTrainingElements(options = {}) {
|
|
|
5479
6072
|
}
|
|
5480
6073
|
export {
|
|
5481
6074
|
AUTO_CONTINUE_MS as A,
|
|
5482
|
-
|
|
5483
|
-
|
|
6075
|
+
loadFonts as B,
|
|
6076
|
+
parseArithmeticTask as C,
|
|
6077
|
+
trainingElements as D,
|
|
5484
6078
|
FALSE_ANSWER as F,
|
|
5485
6079
|
KNOWN_ANSWER as K,
|
|
5486
6080
|
NOT_KNOWN_ANSWER as N,
|
|
5487
6081
|
OctolearnArithmeticQuestion as O,
|
|
6082
|
+
SHORT_VIEWPORT_PX as S,
|
|
5488
6083
|
TRUE_ANSWER as T,
|
|
6084
|
+
ViewportWatcher as V,
|
|
5489
6085
|
OctolearnCard as a,
|
|
5490
6086
|
OctolearnDictationQuestion as b,
|
|
5491
6087
|
OctolearnFeedback as c,
|
|
@@ -5509,7 +6105,7 @@ export {
|
|
|
5509
6105
|
describeMatchingRound as u,
|
|
5510
6106
|
describeQuestion as v,
|
|
5511
6107
|
en as w,
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
6108
|
+
getViewportWatcher as x,
|
|
6109
|
+
languageName as y,
|
|
6110
|
+
letterDiff as z
|
|
5515
6111
|
};
|