@webwriter/quiz 1.0.3 → 1.0.4

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.
Files changed (41) hide show
  1. package/LICENSE +6 -6
  2. package/custom.d.ts +175 -175
  3. package/dist/widgets/webwriter-choice-item.js +58 -13
  4. package/dist/widgets/webwriter-choice.js +64 -19
  5. package/dist/widgets/webwriter-cloze-gap.js +59 -14
  6. package/dist/widgets/webwriter-cloze.js +56 -11
  7. package/dist/widgets/webwriter-mark.js +56 -11
  8. package/dist/widgets/webwriter-order-item.js +56 -11
  9. package/dist/widgets/webwriter-order.js +64 -19
  10. package/dist/widgets/webwriter-pairing.js +59 -14
  11. package/dist/widgets/webwriter-quiz.js +67 -22
  12. package/dist/widgets/webwriter-speech.js +66 -20
  13. package/dist/widgets/webwriter-task.js +534 -308
  14. package/dist/widgets/webwriter-text.js +60 -14
  15. package/package.json +200 -200
  16. package/src/lib/combobox.ts +455 -455
  17. package/src/snippets/choice.html +7 -7
  18. package/src/snippets/cloze.html +3 -3
  19. package/src/snippets/mark.html +3 -3
  20. package/src/snippets/order.html +7 -7
  21. package/src/snippets/pairing.html +9 -9
  22. package/src/snippets/speech.html +3 -3
  23. package/src/snippets/text.html +3 -3
  24. package/src/snippets/wordsearch.html +3 -3
  25. package/src/widgets/webwriter-choice-item.ts +250 -250
  26. package/src/widgets/webwriter-choice.ts +309 -309
  27. package/src/widgets/webwriter-cloze-gap.ts +216 -216
  28. package/src/widgets/webwriter-cloze.ts +135 -135
  29. package/src/widgets/webwriter-mark.ts +434 -434
  30. package/src/widgets/webwriter-order-item.ts +444 -444
  31. package/src/widgets/webwriter-order.ts +272 -272
  32. package/src/widgets/webwriter-pairing-item.ts +155 -155
  33. package/src/widgets/webwriter-pairing.ts +187 -187
  34. package/src/widgets/webwriter-quiz.ts +280 -280
  35. package/src/widgets/webwriter-speech.ts +267 -267
  36. package/src/widgets/webwriter-task-explainer.ts +37 -37
  37. package/src/widgets/webwriter-task-hint.ts +16 -16
  38. package/src/widgets/webwriter-task-prompt.ts +17 -17
  39. package/src/widgets/webwriter-task.ts +543 -543
  40. package/src/widgets/webwriter-text.ts +130 -130
  41. package/tsconfig.json +6 -6
@@ -1,273 +1,273 @@
1
- import {css, html, PropertyValues} from "lit"
2
- import {LitElementWw, option} from "@webwriter/lit"
3
- import {customElement, property, query, queryAssignedElements} from "lit/decorators.js"
4
-
5
- import SlButton from "@shoelace-style/shoelace/dist/components/button/button.component.js"
6
- import SlIcon from "@shoelace-style/shoelace/dist/components/icon/icon.component.js"
7
- import "@shoelace-style/shoelace/dist/themes/light.css"
8
- import { Sortable } from "@shopify/draggable"
9
- import IconPlus from "bootstrap-icons/icons/plus.svg"
10
-
11
- import { WebwriterOrderItem } from "./webwriter-order-item"
12
- import { ifDefined } from "lit/directives/if-defined.js"
13
- import { shuffle } from "./webwriter-choice"
14
-
15
- declare global {interface HTMLElementTagNameMap {
16
- "webwriter-order": WebwriterOrder;
17
- }}
18
-
19
- @customElement("webwriter-order")
20
- export class WebwriterOrder extends LitElementWw {
21
-
22
- static scopedElements = {
23
- "sl-button": SlButton,
24
- "sl-icon": SlIcon
25
- }
26
-
27
- static styles = css`
28
- :host {
29
- display: flex !important;
30
- flex-direction: column;
31
- align-items: flex-start;
32
- counter-reset: orderItem;
33
- gap: 2px;
34
- }
35
-
36
- :host([layout=tiles]) {
37
- flex-direction: row;
38
- flex-wrap: wrap;
39
- gap: 15px;
40
- }
41
-
42
- :host(:is([contenteditable=true], [contenteditable=""])) {
43
- --text-color: var(--sl-color-success-700);
44
- }
45
-
46
- :host ::slotted(*::part(counter)) {
47
- counter-increment: orderItem;
48
- content: counter(orderItem) '. ';
49
- cursor: move;
50
- text-align: right;
51
- padding-right: 0.5em;
52
- min-width: 1.5em;
53
- color: var(--text-color, auto);
54
- }
55
-
56
- sl-button::part(label) {
57
- padding: 0;
58
- display: flex;
59
- flex-direction: row;
60
- align-items: center;
61
- }
62
-
63
- sl-button::part(base) {
64
- border: none;
65
- background: transparent;
66
- }
67
-
68
- sl-icon {
69
- width: 19px;
70
- height: 19px;
71
- padding: var(--sl-spacing-x-small);
72
- padding-left: 0;
73
- }
74
-
75
- #add-option {
76
- order: 2147483647;
77
- }
78
-
79
- #add-option:not(:hover)::part(base) {
80
- color: darkgray;
81
- }
82
-
83
- :host([layout=tiles]) #add-option {
84
- width: 125px;
85
- height: 125px;
86
- overflow: hidden;
87
- border: 2px solid var(--sl-color-gray-300);
88
- border-radius: 5px;
89
- padding: 5px;
90
- display: flex;
91
- align-items: center;
92
- justify-content: center;
93
- }
94
-
95
-
96
- :host(:not([contenteditable=true]):not([contenteditable=""])) .author-only {
97
- display: none;
98
- }
99
- `
100
-
101
- get layout(): "list" | "tiles" {
102
- return this.children?.item(0)?.getAttribute("layout") as any ?? "list"
103
- }
104
-
105
- @property({type: String, attribute: true, reflect: true}) //@ts-ignore
106
- @option({
107
- type: "select",
108
- options: [
109
- {value: "list", label: {"en": "List"}},
110
- {value: "tiles", label: {"en": "Tiles"}}
111
- ]
112
- })
113
- set layout(value) {
114
- this.querySelectorAll("webwriter-order-item").forEach(el => el.setAttribute("layout", value))
115
- this.requestUpdate("layout")
116
- }
117
-
118
- get hideOrderButtons() {
119
- return !!this.items[0]?.hideOrderButtons
120
- }
121
-
122
- @property({type: Boolean, attribute: true, reflect: true}) //@ts-ignore
123
- @option({type: Boolean, label: {_: "Hide order buttons"}})
124
- set hideOrderButtons(value: boolean) {
125
- this.items.forEach(item => item.hideOrderButtons = value)
126
- }
127
-
128
-
129
- observer: MutationObserver
130
-
131
- connectedCallback() {
132
- super.connectedCallback()
133
- this.addEventListener("ww-moveup", this.handleMove)
134
- this.addEventListener("ww-movedown", this.handleMove)
135
- this.addEventListener("ww-moveto", this.handleMove)
136
- this.observer = new MutationObserver(() => {
137
- this.items.forEach(item => item.requestUpdate())
138
- if(this.isContentEditable) {
139
- this.solution = this.items.map(item => item.id)
140
- }
141
- this.clearDropPreviews()
142
- })
143
- this.observer.observe(this, {childList: true})
144
- // this.shadowRoot.adoptedStyleSheets = [...this.shadowRoot.adoptedStyleSheets, this.orderSheet]
145
- }
146
-
147
- serializedCallback() {
148
- this.shuffleItems()
149
- }
150
-
151
- disconnectedCallback(): void {
152
- super.disconnectedCallback()
153
- this.observer?.disconnect()
154
- this.observer = undefined
155
- }
156
-
157
- orderSheet = new CSSStyleSheet()
158
-
159
- addItem() {
160
- const orderItem = this.ownerDocument.createElement("webwriter-order-item")
161
- const p = this.ownerDocument.createElement("p")
162
- orderItem.appendChild(p)
163
- orderItem.setAttribute("layout", this.layout)
164
- this.append(orderItem)
165
- this.solution = [...this.#solution, orderItem.id]
166
- this.ownerDocument.getSelection().setBaseAndExtent(p, 0, p, 0)
167
- }
168
-
169
- shuffleItems() {
170
- const n = this.items.length
171
- const nums = shuffle([...(new Array(n)).keys()])
172
- this.innerHTML = nums.map(i => this.children.item(i).outerHTML).join("")
173
- }
174
-
175
- clearDropPreviews = () => {
176
- this.items.forEach(item => item.dropPreview = undefined)
177
- }
178
-
179
- @query("#items-slot")
180
- accessor itemsSlot: HTMLSlotElement
181
-
182
- @queryAssignedElements()
183
- accessor items: WebwriterOrderItem[]
184
-
185
- handleKeyDown(e: KeyboardEvent) {
186
- if(e.key == "ArrowDown") {
187
- e.stopPropagation()
188
- e.preventDefault()
189
- }
190
- else if(e.key === "ArrowUp") {
191
- e.stopPropagation()
192
- e.preventDefault()
193
- }
194
- }
195
-
196
- /*
197
- If contenteditable: Reorder solution -> Triggers orderSheet change
198
- If not contenteditable: Reorder value -> Triggers orderSheet change
199
- */
200
-
201
- moveChild(elem: HTMLElement, i: number) {
202
- /*
203
- let items = this.items.map(el => el !== elem? el: undefined)
204
- items.splice(i, 0, elem as WebwriterOrderItem)
205
- items = items.filter(child => child)
206
- this.replaceChildren(...items)
207
- this.items.forEach(item => item.requestUpdate())*/
208
- if(i === 0) {
209
- this.insertAdjacentElement("afterbegin", elem)
210
- }
211
- else if(i < this.items.length) {
212
- this.items[i].insertAdjacentElement("beforebegin", elem)
213
- }
214
- else {
215
- this.insertAdjacentElement("beforeend", elem)
216
- }
217
- }
218
-
219
- handleMove = (e: CustomEvent) => {
220
- const elem = e.target as HTMLElement
221
- const children = Array.from(this.children)
222
- const pos = children.indexOf(elem)
223
- let i: number
224
- if(e.type === "ww-moveup") {
225
- i = Math.max(0, pos - 1)
226
- }
227
- else if(e.type === "ww-movedown") {
228
- i = Math.min(children.length, pos + 2)
229
-
230
- }
231
- else if(e.type === "ww-moveto") {
232
- i = e.detail.i
233
- }
234
- this.moveChild(elem, i)
235
- }
236
-
237
-
238
- #solution: string[] = []
239
-
240
- get solution() {
241
- return this.#solution?.length? this.#solution: undefined
242
- }
243
-
244
- @property({attribute: false})
245
- set solution(value: string[]) {
246
- this.#solution = value
247
- if(value) {
248
- this.dispatchEvent(new CustomEvent("ww-answer-change", {
249
- bubbles: true,
250
- composed: true
251
- }))
252
- }
253
- }
254
-
255
-
256
- reportSolution() {
257
- this.solution.forEach((id, i) => this.querySelector(`#${id}`).validOrder = i)
258
- }
259
-
260
- reset() {
261
- this.solution = undefined
262
- this.shuffleItems()
263
- }
264
-
265
- render() {
266
- return html`
267
- <slot id="items-slot" @webwriter-clear-drop-preview=${this.clearDropPreviews}></slot>
268
- <sl-button size="small" id="add-option" class="author-only" @click=${() => this.addItem()}>
269
- <sl-icon src=${IconPlus}></sl-icon><span>Add Option</span>
270
- </sl-button>
271
- `
272
- }
1
+ import {css, html, PropertyValues} from "lit"
2
+ import {LitElementWw, option} from "@webwriter/lit"
3
+ import {customElement, property, query, queryAssignedElements} from "lit/decorators.js"
4
+
5
+ import SlButton from "@shoelace-style/shoelace/dist/components/button/button.component.js"
6
+ import SlIcon from "@shoelace-style/shoelace/dist/components/icon/icon.component.js"
7
+ import "@shoelace-style/shoelace/dist/themes/light.css"
8
+ import { Sortable } from "@shopify/draggable"
9
+ import IconPlus from "bootstrap-icons/icons/plus.svg"
10
+
11
+ import { WebwriterOrderItem } from "./webwriter-order-item"
12
+ import { ifDefined } from "lit/directives/if-defined.js"
13
+ import { shuffle } from "./webwriter-choice"
14
+
15
+ declare global {interface HTMLElementTagNameMap {
16
+ "webwriter-order": WebwriterOrder;
17
+ }}
18
+
19
+ @customElement("webwriter-order")
20
+ export class WebwriterOrder extends LitElementWw {
21
+
22
+ static scopedElements = {
23
+ "sl-button": SlButton,
24
+ "sl-icon": SlIcon
25
+ }
26
+
27
+ static styles = css`
28
+ :host {
29
+ display: flex !important;
30
+ flex-direction: column;
31
+ align-items: flex-start;
32
+ counter-reset: orderItem;
33
+ gap: 2px;
34
+ }
35
+
36
+ :host([layout=tiles]) {
37
+ flex-direction: row;
38
+ flex-wrap: wrap;
39
+ gap: 15px;
40
+ }
41
+
42
+ :host(:is([contenteditable=true], [contenteditable=""])) {
43
+ --text-color: var(--sl-color-success-700);
44
+ }
45
+
46
+ :host ::slotted(*::part(counter)) {
47
+ counter-increment: orderItem;
48
+ content: counter(orderItem) '. ';
49
+ cursor: move;
50
+ text-align: right;
51
+ padding-right: 0.5em;
52
+ min-width: 1.5em;
53
+ color: var(--text-color, auto);
54
+ }
55
+
56
+ sl-button::part(label) {
57
+ padding: 0;
58
+ display: flex;
59
+ flex-direction: row;
60
+ align-items: center;
61
+ }
62
+
63
+ sl-button::part(base) {
64
+ border: none;
65
+ background: transparent;
66
+ }
67
+
68
+ sl-icon {
69
+ width: 19px;
70
+ height: 19px;
71
+ padding: var(--sl-spacing-x-small);
72
+ padding-left: 0;
73
+ }
74
+
75
+ #add-option {
76
+ order: 2147483647;
77
+ }
78
+
79
+ #add-option:not(:hover)::part(base) {
80
+ color: darkgray;
81
+ }
82
+
83
+ :host([layout=tiles]) #add-option {
84
+ width: 125px;
85
+ height: 125px;
86
+ overflow: hidden;
87
+ border: 2px solid var(--sl-color-gray-300);
88
+ border-radius: 5px;
89
+ padding: 5px;
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ }
94
+
95
+
96
+ :host(:not([contenteditable=true]):not([contenteditable=""])) .author-only {
97
+ display: none;
98
+ }
99
+ `
100
+
101
+ get layout(): "list" | "tiles" {
102
+ return this.children?.item(0)?.getAttribute("layout") as any ?? "list"
103
+ }
104
+
105
+ @property({type: String, attribute: true, reflect: true}) //@ts-ignore
106
+ @option({
107
+ type: "select",
108
+ options: [
109
+ {value: "list", label: {"en": "List"}},
110
+ {value: "tiles", label: {"en": "Tiles"}}
111
+ ]
112
+ })
113
+ set layout(value) {
114
+ this.querySelectorAll("webwriter-order-item").forEach(el => el.setAttribute("layout", value))
115
+ this.requestUpdate("layout")
116
+ }
117
+
118
+ get hideOrderButtons() {
119
+ return !!this.items[0]?.hideOrderButtons
120
+ }
121
+
122
+ @property({type: Boolean, attribute: true, reflect: true}) //@ts-ignore
123
+ @option({type: Boolean, label: {_: "Hide order buttons"}})
124
+ set hideOrderButtons(value: boolean) {
125
+ this.items.forEach(item => item.hideOrderButtons = value)
126
+ }
127
+
128
+
129
+ observer: MutationObserver
130
+
131
+ connectedCallback() {
132
+ super.connectedCallback()
133
+ this.addEventListener("ww-moveup", this.handleMove)
134
+ this.addEventListener("ww-movedown", this.handleMove)
135
+ this.addEventListener("ww-moveto", this.handleMove)
136
+ this.observer = new MutationObserver(() => {
137
+ this.items.forEach(item => item.requestUpdate())
138
+ if(this.isContentEditable) {
139
+ this.solution = this.items.map(item => item.id)
140
+ }
141
+ this.clearDropPreviews()
142
+ })
143
+ this.observer.observe(this, {childList: true})
144
+ // this.shadowRoot.adoptedStyleSheets = [...this.shadowRoot.adoptedStyleSheets, this.orderSheet]
145
+ }
146
+
147
+ serializedCallback() {
148
+ this.shuffleItems()
149
+ }
150
+
151
+ disconnectedCallback(): void {
152
+ super.disconnectedCallback()
153
+ this.observer?.disconnect()
154
+ this.observer = undefined
155
+ }
156
+
157
+ orderSheet = new CSSStyleSheet()
158
+
159
+ addItem() {
160
+ const orderItem = this.ownerDocument.createElement("webwriter-order-item")
161
+ const p = this.ownerDocument.createElement("p")
162
+ orderItem.appendChild(p)
163
+ orderItem.setAttribute("layout", this.layout)
164
+ this.append(orderItem)
165
+ this.solution = [...this.#solution, orderItem.id]
166
+ this.ownerDocument.getSelection().setBaseAndExtent(p, 0, p, 0)
167
+ }
168
+
169
+ shuffleItems() {
170
+ const n = this.items.length
171
+ const nums = shuffle([...(new Array(n)).keys()])
172
+ this.innerHTML = nums.map(i => this.children.item(i).outerHTML).join("")
173
+ }
174
+
175
+ clearDropPreviews = () => {
176
+ this.items.forEach(item => item.dropPreview = undefined)
177
+ }
178
+
179
+ @query("#items-slot")
180
+ accessor itemsSlot: HTMLSlotElement
181
+
182
+ @queryAssignedElements()
183
+ accessor items: WebwriterOrderItem[]
184
+
185
+ handleKeyDown(e: KeyboardEvent) {
186
+ if(e.key == "ArrowDown") {
187
+ e.stopPropagation()
188
+ e.preventDefault()
189
+ }
190
+ else if(e.key === "ArrowUp") {
191
+ e.stopPropagation()
192
+ e.preventDefault()
193
+ }
194
+ }
195
+
196
+ /*
197
+ If contenteditable: Reorder solution -> Triggers orderSheet change
198
+ If not contenteditable: Reorder value -> Triggers orderSheet change
199
+ */
200
+
201
+ moveChild(elem: HTMLElement, i: number) {
202
+ /*
203
+ let items = this.items.map(el => el !== elem? el: undefined)
204
+ items.splice(i, 0, elem as WebwriterOrderItem)
205
+ items = items.filter(child => child)
206
+ this.replaceChildren(...items)
207
+ this.items.forEach(item => item.requestUpdate())*/
208
+ if(i === 0) {
209
+ this.insertAdjacentElement("afterbegin", elem)
210
+ }
211
+ else if(i < this.items.length) {
212
+ this.items[i].insertAdjacentElement("beforebegin", elem)
213
+ }
214
+ else {
215
+ this.insertAdjacentElement("beforeend", elem)
216
+ }
217
+ }
218
+
219
+ handleMove = (e: CustomEvent) => {
220
+ const elem = e.target as HTMLElement
221
+ const children = Array.from(this.children)
222
+ const pos = children.indexOf(elem)
223
+ let i: number
224
+ if(e.type === "ww-moveup") {
225
+ i = Math.max(0, pos - 1)
226
+ }
227
+ else if(e.type === "ww-movedown") {
228
+ i = Math.min(children.length, pos + 2)
229
+
230
+ }
231
+ else if(e.type === "ww-moveto") {
232
+ i = e.detail.i
233
+ }
234
+ this.moveChild(elem, i)
235
+ }
236
+
237
+
238
+ #solution: string[] = []
239
+
240
+ get solution() {
241
+ return this.#solution?.length? this.#solution: undefined
242
+ }
243
+
244
+ @property({attribute: false})
245
+ set solution(value: string[]) {
246
+ this.#solution = value
247
+ if(value) {
248
+ this.dispatchEvent(new CustomEvent("ww-answer-change", {
249
+ bubbles: true,
250
+ composed: true
251
+ }))
252
+ }
253
+ }
254
+
255
+
256
+ reportSolution() {
257
+ this.solution.forEach((id, i) => this.querySelector(`#${id}`).validOrder = i)
258
+ }
259
+
260
+ reset() {
261
+ this.solution = undefined
262
+ this.shuffleItems()
263
+ }
264
+
265
+ render() {
266
+ return html`
267
+ <slot id="items-slot" @webwriter-clear-drop-preview=${this.clearDropPreviews}></slot>
268
+ <sl-button size="small" id="add-option" class="author-only" @click=${() => this.addItem()}>
269
+ <sl-icon src=${IconPlus}></sl-icon><span>Add Option</span>
270
+ </sl-button>
271
+ `
272
+ }
273
273
  }