@webwriter/quiz 1.0.2 → 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 (45) hide show
  1. package/LICENSE +6 -6
  2. package/custom.d.ts +175 -175
  3. package/dist/widgets/webwriter-choice-item.js +3257 -59
  4. package/dist/widgets/webwriter-choice.js +4941 -79
  5. package/dist/widgets/webwriter-cloze-gap.js +4092 -179
  6. package/dist/widgets/webwriter-cloze.js +2415 -36
  7. package/dist/widgets/webwriter-mark.js +2471 -61
  8. package/dist/widgets/webwriter-order-item.js +2447 -68
  9. package/dist/widgets/webwriter-order.js +4990 -135
  10. package/dist/widgets/webwriter-pairing-item.js +1581 -48
  11. package/dist/widgets/webwriter-pairing.js +3686 -72
  12. package/dist/widgets/webwriter-quiz.js +6938 -76
  13. package/dist/widgets/webwriter-speech.js +6265 -76
  14. package/dist/widgets/webwriter-task-explainer.js +1564 -31
  15. package/dist/widgets/webwriter-task-hint.js +1523 -28
  16. package/dist/widgets/webwriter-task-prompt.js +1586 -29
  17. package/dist/widgets/webwriter-task.js +7132 -101
  18. package/dist/widgets/webwriter-text.js +4044 -54
  19. package/package.json +200 -200
  20. package/src/lib/combobox.ts +455 -455
  21. package/src/snippets/choice.html +7 -7
  22. package/src/snippets/cloze.html +3 -3
  23. package/src/snippets/mark.html +3 -3
  24. package/src/snippets/order.html +7 -7
  25. package/src/snippets/pairing.html +9 -9
  26. package/src/snippets/speech.html +3 -3
  27. package/src/snippets/text.html +3 -3
  28. package/src/snippets/wordsearch.html +3 -3
  29. package/src/widgets/webwriter-choice-item.ts +250 -250
  30. package/src/widgets/webwriter-choice.ts +309 -309
  31. package/src/widgets/webwriter-cloze-gap.ts +216 -216
  32. package/src/widgets/webwriter-cloze.ts +135 -135
  33. package/src/widgets/webwriter-mark.ts +434 -434
  34. package/src/widgets/webwriter-order-item.ts +444 -444
  35. package/src/widgets/webwriter-order.ts +272 -272
  36. package/src/widgets/webwriter-pairing-item.ts +155 -155
  37. package/src/widgets/webwriter-pairing.ts +187 -187
  38. package/src/widgets/webwriter-quiz.ts +280 -280
  39. package/src/widgets/webwriter-speech.ts +267 -267
  40. package/src/widgets/webwriter-task-explainer.ts +37 -37
  41. package/src/widgets/webwriter-task-hint.ts +16 -16
  42. package/src/widgets/webwriter-task-prompt.ts +17 -17
  43. package/src/widgets/webwriter-task.ts +543 -543
  44. package/src/widgets/webwriter-text.ts +130 -130
  45. 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
  }