@webwriter/quiz 1.0.10 → 1.1.1

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 (103) hide show
  1. package/LICENSE +6 -6
  2. package/custom.d.ts +175 -175
  3. package/dist/widgets/webwriter-choice-item.js +10 -7
  4. package/dist/widgets/webwriter-choice.js +39 -6
  5. package/dist/widgets/webwriter-order-item.js +14 -11
  6. package/dist/widgets/webwriter-order.js +30 -4
  7. package/dist/widgets/webwriter-quiz.js +2 -1
  8. package/dist/widgets/webwriter-text.js +26 -9
  9. package/lit-localize.json +14 -14
  10. package/localization/bg.xlf +69 -69
  11. package/localization/cs.xlf +69 -69
  12. package/localization/da.xlf +69 -69
  13. package/localization/de.xlf +74 -74
  14. package/localization/el.xlf +69 -69
  15. package/localization/es.xlf +69 -69
  16. package/localization/et.xlf +69 -69
  17. package/localization/fi.xlf +69 -69
  18. package/localization/fr.xlf +69 -69
  19. package/localization/generated/bg.ts +29 -29
  20. package/localization/generated/cs.ts +29 -29
  21. package/localization/generated/da.ts +29 -29
  22. package/localization/generated/de.ts +29 -29
  23. package/localization/generated/el.ts +29 -29
  24. package/localization/generated/es.ts +29 -29
  25. package/localization/generated/et.ts +29 -29
  26. package/localization/generated/fi.ts +29 -29
  27. package/localization/generated/fr.ts +29 -29
  28. package/localization/generated/hu.ts +29 -29
  29. package/localization/generated/id.ts +29 -29
  30. package/localization/generated/index.js +2 -2
  31. package/localization/generated/it.ts +29 -29
  32. package/localization/generated/ja.ts +29 -29
  33. package/localization/generated/ko.ts +29 -29
  34. package/localization/generated/locale-codes.js +79 -79
  35. package/localization/generated/lt.ts +29 -29
  36. package/localization/generated/lv.ts +29 -29
  37. package/localization/generated/nb.ts +29 -29
  38. package/localization/generated/nl.ts +29 -29
  39. package/localization/generated/pl.ts +29 -29
  40. package/localization/generated/pt-BR.ts +29 -29
  41. package/localization/generated/pt-PT.ts +29 -29
  42. package/localization/generated/ro.ts +29 -29
  43. package/localization/generated/ru.ts +29 -29
  44. package/localization/generated/sk.ts +29 -29
  45. package/localization/generated/sl.ts +29 -29
  46. package/localization/generated/sv.ts +29 -29
  47. package/localization/generated/tr.ts +29 -29
  48. package/localization/generated/uk.ts +29 -29
  49. package/localization/generated/zh-hans.ts +29 -29
  50. package/localization/hu.xlf +69 -69
  51. package/localization/id.xlf +69 -69
  52. package/localization/it.xlf +69 -69
  53. package/localization/ja.xlf +69 -69
  54. package/localization/ko.xlf +69 -69
  55. package/localization/lt.xlf +69 -69
  56. package/localization/lv.xlf +69 -69
  57. package/localization/nb.xlf +69 -69
  58. package/localization/nl.xlf +69 -69
  59. package/localization/pl.xlf +69 -69
  60. package/localization/pt-BR.xlf +69 -69
  61. package/localization/pt-PT.xlf +69 -69
  62. package/localization/ro.xlf +69 -69
  63. package/localization/ru.xlf +69 -69
  64. package/localization/sk.xlf +69 -69
  65. package/localization/sl.xlf +69 -69
  66. package/localization/sv.xlf +69 -69
  67. package/localization/tr.xlf +69 -69
  68. package/localization/uk.xlf +69 -69
  69. package/localization/zh-hans.xlf +69 -69
  70. package/package.json +7 -2
  71. package/src/lib/combobox.ts +455 -455
  72. package/src/lib/highlighter-fill.svg +5 -5
  73. package/src/snippets/Beispiel-Choice.html +57 -57
  74. package/src/snippets/Beispiel-Mark.html +13 -13
  75. package/src/snippets/Beispiel-Order.html +52 -52
  76. package/src/snippets/Beispiel-Text.html +20 -20
  77. package/src/snippets/blank.html +1 -0
  78. package/src/snippets/choice.html +7 -7
  79. package/src/snippets/cloze.html +3 -3
  80. package/src/snippets/mark.html +3 -3
  81. package/src/snippets/order.html +7 -7
  82. package/src/snippets/pairing.html +9 -9
  83. package/src/snippets/speech.html +3 -3
  84. package/src/snippets/text.html +3 -3
  85. package/src/snippets/wordsearch.html +3 -3
  86. package/src/widgets/webwriter-choice-item.ts +253 -250
  87. package/src/widgets/webwriter-choice.ts +339 -314
  88. package/src/widgets/webwriter-cloze-gap.ts +215 -215
  89. package/src/widgets/webwriter-cloze.ts +135 -135
  90. package/src/widgets/webwriter-mark.ts +431 -431
  91. package/src/widgets/webwriter-order-item.ts +440 -437
  92. package/src/widgets/webwriter-order.ts +311 -284
  93. package/src/widgets/webwriter-pairing-item.ts +160 -160
  94. package/src/widgets/webwriter-pairing.ts +192 -192
  95. package/src/widgets/webwriter-quiz.ts +282 -281
  96. package/src/widgets/webwriter-speech.ts +272 -272
  97. package/src/widgets/webwriter-task-explainer.ts +42 -42
  98. package/src/widgets/webwriter-task-hint.ts +22 -22
  99. package/src/widgets/webwriter-task-prompt.ts +23 -23
  100. package/src/widgets/webwriter-task.ts +546 -546
  101. package/src/widgets/webwriter-text.ts +155 -135
  102. package/tsconfig.json +6 -6
  103. package/.env +0 -1
@@ -1,315 +1,340 @@
1
- import {html, css, PropertyValueMap, TemplateResult, PropertyValues} from "lit"
2
- import {styleMap} from "lit/directives/style-map.js"
3
- import {LitElementWw, option, action} from "@webwriter/lit"
4
- import {customElement, property, queryAssignedElements, query} from "lit/decorators.js"
5
-
6
- import SlButton from "@shoelace-style/shoelace/dist/components/button/button.component.js"
7
- import SlIcon from "@shoelace-style/shoelace/dist/components/icon/icon.component.js"
8
- import SlRadio from "@shoelace-style/shoelace/dist/components/radio/radio.component.js"
9
- import SlCheckbox from "@shoelace-style/shoelace/dist/components/checkbox/checkbox.component.js"
10
- import SlRadioButton from "@shoelace-style/shoelace/dist/components/radio-button/radio-button.component.js"
11
- import SlRadioGroup from "@shoelace-style/shoelace/dist/components/radio-group/radio-group.component.js"
12
-
13
- import IconPlusSquare from "bootstrap-icons/icons/plus-square.svg"
14
- import IconPlusCircle from "bootstrap-icons/icons/plus-circle.svg"
15
- import type { WebwriterChoiceItem } from "./webwriter-choice-item.js"
16
- import "@shoelace-style/shoelace/dist/themes/light.css"
17
- import MiniMasonry from "minimasonry"
18
-
19
- import LOCALIZE from "../../localization/generated"
20
- import {msg} from "@lit/localize"
21
-
22
-
23
- export function shuffle<T>(a: T[]) {
24
- for (let i = a.length - 1; i > 0; i--) {
25
- const j = Math.floor(Math.random() * (i + 1));
26
- [a[i], a[j]] = [a[j], a[i]];
27
- }
28
- return a;
29
- }
30
-
31
- declare global {interface HTMLElementTagNameMap {
32
- "webwriter-choice": WebwriterChoice;
33
- }}
34
-
35
- @customElement("webwriter-choice")
36
- export class WebwriterChoice extends LitElementWw {
37
-
38
- localize = LOCALIZE
39
-
40
- @property({type: String, attribute: true, reflect: true})
41
- @option({
42
- type: "select",
43
- options: [
44
- {value: "truefalse", label: {"en": "True/False"}},
45
- {value: "single", label: {"en": "Single Choice"}},
46
- {value: "multiple", label: {"en": "Multiple Choice"}},
47
- ]
48
- })
49
- accessor mode: "truefalse" | "single" | "multiple" = "single"
50
-
51
-
52
- get layout(): "list" | "tiles" {
53
- return this.children?.item(0)?.getAttribute("layout") as any ?? "list"
54
- }
55
-
56
- @property({type: String, attribute: true, reflect: true}) //@ts-ignore
57
- @option({
58
- type: "select",
59
- options: [
60
- {value: "list", label: {"en": "List"}},
61
- {value: "tiles", label: {"en": "Tiles"}}
62
- ],
63
- })
64
- set layout(value) {
65
- this.querySelectorAll("webwriter-choice-item").forEach(el => el.setAttribute("layout", value))
66
- this.requestUpdate("layout")
67
- }
68
-
69
- @property({type: Boolean, attribute: true, reflect: true})
70
- @option({
71
- type: Boolean,
72
- label: {"en": "Random Choice Order"}
73
- })
74
- accessor randomOrder = false
75
-
76
-
77
- get solution() {
78
- const validIDs = this.items.filter(item => item.valid).map(item => item.id)
79
- return validIDs.length? validIDs: undefined
80
- }
81
-
82
- set solution(value: string[]) {
83
- this.items.forEach(item => value.includes(item.id)? item.valid=true: null)
84
- }
85
-
86
-
87
- reportSolution() {
88
- if(!this.solution) {
89
- this.items.forEach(item => item.valid = true)
90
- return
91
- }
92
- this.items.forEach(item => item.valid = (this.solution ?? []).includes(item.id))
93
- }
94
-
95
- reset() {
96
- this.items.forEach(item => {item.valid = undefined; item.active = false})
97
- this.shuffleItems()
98
- }
99
-
100
- static scopedElements = {
101
- "sl-button": SlButton,
102
- "sl-icon": SlIcon,
103
- "sl-radio": SlRadio,
104
- "sl-checkbox": SlCheckbox,
105
- "sl-radio-button": SlRadioButton,
106
- "sl-radio-group": SlRadioGroup
107
- }
108
-
109
- @query("slot")
110
- accessor slotEl: HTMLSlotElement
111
-
112
- @action()
113
- addItem() {
114
- /*
115
- if(this.ownerDocument.getSelection().containsNode(this, true)) {
116
- this.ownerDocument.getSelection().modify("move", "backward", "character")
117
- }
118
- setTimeout(() => {*/
119
- const choiceItem = this.ownerDocument.createElement("webwriter-choice-item")
120
- const p = this.ownerDocument.createElement("p")
121
- choiceItem.appendChild(p)
122
- choiceItem.setAttribute("layout", this.layout)
123
- this.appendChild(choiceItem)
124
- this.ownerDocument.getSelection().setBaseAndExtent(p, 0, p, 0)
125
- //})
126
- }
127
-
128
- shuffleItems() {
129
- const items = this.slotEl.assignedElements() as WebwriterChoiceItem[]
130
- const n = items.length
131
- const nums = shuffle([...(new Array(n)).keys()])
132
- items.forEach((el, i) => el.style.order = String(nums[i]))
133
- }
134
-
135
- connectedCallback(): void {
136
- super.connectedCallback()
137
- const observer = new MutationObserver(() => {
138
- if(!this.contentEditable && this.randomOrder) {
139
- this.shuffleItems()
140
- }
141
- })
142
- observer.observe(this, {childList: true})
143
- }
144
-
145
- protected firstUpdated(_changedProperties: PropertyValues): void {
146
- if(!this.contentEditable && this.randomOrder) {
147
- this.shuffleItems()
148
- }
149
- }
150
-
151
- static styles = css`
152
- :host {
153
- display: flex !important;
154
- flex-direction: column;
155
- align-items: flex-start;
156
- gap: 0.5rem;
157
- }
158
-
159
- :host([layout=tiles]) {
160
-
161
- flex-direction: row;
162
- flex-wrap: wrap;
163
- gap: 15px;
164
-
165
- & ::slotted(*) {
166
- aspect-ratio: 1;
167
- min-width: 125px;
168
- width: 125px;
169
- max-width: 350px;
170
- min-height: 125px;
171
- height: 125px;
172
- max-height: 350px;
173
- overflow: hidden;
174
- resize: both;
175
- border: 2px solid var(--sl-color-gray-500);
176
- border-radius: 5px;
177
- }
178
- }
179
-
180
- sl-button::part(label) {
181
- padding: 0;
182
- display: flex;
183
- flex-direction: row;
184
- align-items: center;
185
- }
186
-
187
- sl-button::part(base) {
188
- border: none;
189
- background: transparent;
190
- }
191
-
192
- sl-icon {
193
- width: 19px;
194
- height: 19px;
195
- }
196
-
197
- sl-icon::part(svg) {
198
- overflow: visible;
199
- }
200
-
201
- :host(:not([contenteditable=true]):not([contenteditable=""])) .author-only {
202
- display: none !important;
203
- }
204
-
205
- #add-option span {
206
- margin-inline-start: 0.5em;
207
- }
208
-
209
- :host(:is([contenteditable=true], [contenteditable=""])) ::slotted(*) {
210
- order: unset !important;
211
- }
212
-
213
- :host(:not([mode=multiple])) {
214
- --webwriter-choice-radius: 100%;
215
- }
216
-
217
- #add-option:not(:hover)::part(base) {
218
- color: darkgray;
219
- }
220
-
221
- :host([layout=tiles]) #add-option {
222
- width: 125px;
223
- height: 125px;
224
- overflow: hidden;
225
- border: 2px solid var(--sl-color-gray-300);
226
- border-radius: 5px;
227
- padding: 5px;
228
- display: flex;
229
- align-items: center;
230
- justify-content: center;
231
- }
232
-
233
- :host([mode=truefalse]) #add-option {
234
- display: none;
235
- }
236
- `
237
-
238
- @queryAssignedElements()
239
- accessor items: WebwriterChoiceItem[]
240
-
241
- #value: number[] = []
242
-
243
- get value() {
244
- return this.#value
245
- }
246
-
247
- @property({type: Array, attribute: false})
248
- set value(value: number[]) {
249
- this.#value = value
250
- this.items.forEach((item, i) => {
251
- this.isContentEditable
252
- ? item.valid = this.#value.includes(i)
253
- : item.active = this.#value.includes(i)
254
- })
255
- }
256
-
257
- private applyValue() {
258
- this.#value = this.items
259
- .map((item, i) => [item.valid, i] as [boolean, number])
260
- .filter(([valid]) => valid)
261
- .map(([_, i]) => i)
262
- }
263
-
264
- handleControlChange = (e: CustomEvent) => {
265
- if(this.mode === "single") {
266
- const item = e.target as WebwriterChoiceItem
267
- const otherItems = this.items.filter(el => el !== item)
268
- otherItems.forEach(el => this.isContentEditable? el.valid = false: el.active = false)
269
- }
270
- this.applyValue()
271
- this.dispatchEvent(new CustomEvent("ww-answer-change", {
272
- detail: {value: this.value},
273
- bubbles: true,
274
- composed: true
275
- }))
276
- }
277
-
278
- handleSlotChange(e: Event) {
279
- this.requestUpdate()
280
- }
281
-
282
- protected willUpdate(changed: PropertyValueMap<any>) {
283
- if(changed.has("mode") && this.mode === "single") {
284
- this.items
285
- .filter(el => this.isContentEditable? el.valid: el.active)
286
- .slice(1)
287
- .forEach(el => this.isContentEditable? el.valid = false: el.active = false)
288
- }
289
- if(changed.has("mode") && this.mode === "truefalse") {
290
- this.items.forEach(el => el.remove())
291
- }
292
- }
293
-
294
- @query("#items-slot")
295
- accessor itemsSlotEl: HTMLSlotElement
296
-
297
- render() {
298
- return html`
299
- ${this.mode === "truefalse"
300
- ? html`
301
- <sl-radio-group>
302
- <sl-radio-button value="true">${msg("True")}</sl-radio-button>
303
- <sl-radio-button value="false">${msg("False")}</sl-radio-button>
304
- </sl-radio-group>
305
- `
306
- : html`
307
- <slot id="items-slot" @sl-change=${this.handleControlChange} @slotchange=${this.handleSlotChange}></slot>
308
- `
309
- }
310
- <sl-button size="small" id="add-option" class="author-only" @click=${() => this.addItem()}>
311
- <sl-icon src=${this.mode === "multiple"? IconPlusSquare: IconPlusCircle}></sl-icon><span>${msg("Add Option")}</span>
312
- </sl-button>
313
- `
314
- }
1
+ import {html, css, PropertyValueMap, TemplateResult, PropertyValues} from "lit"
2
+ import {styleMap} from "lit/directives/style-map.js"
3
+ import {LitElementWw, option, action} from "@webwriter/lit"
4
+ import {customElement, property, queryAssignedElements, query} from "lit/decorators.js"
5
+
6
+ import SlButton from "@shoelace-style/shoelace/dist/components/button/button.component.js"
7
+ import SlIcon from "@shoelace-style/shoelace/dist/components/icon/icon.component.js"
8
+ import SlRadio from "@shoelace-style/shoelace/dist/components/radio/radio.component.js"
9
+ import SlCheckbox from "@shoelace-style/shoelace/dist/components/checkbox/checkbox.component.js"
10
+ import SlRadioButton from "@shoelace-style/shoelace/dist/components/radio-button/radio-button.component.js"
11
+ import SlRadioGroup from "@shoelace-style/shoelace/dist/components/radio-group/radio-group.component.js"
12
+
13
+ import IconPlusSquare from "bootstrap-icons/icons/plus-square.svg"
14
+ import IconPlusCircle from "bootstrap-icons/icons/plus-circle.svg"
15
+ import type { WebwriterChoiceItem } from "./webwriter-choice-item.js"
16
+ import "@shoelace-style/shoelace/dist/themes/light.css"
17
+ import MiniMasonry from "minimasonry"
18
+
19
+ import LOCALIZE from "../../localization/generated"
20
+ import {msg} from "@lit/localize"
21
+
22
+
23
+ export function shuffle<T>(a: T[]) {
24
+ for (let i = a.length - 1; i > 0; i--) {
25
+ const j = Math.floor(Math.random() * (i + 1));
26
+ [a[i], a[j]] = [a[j], a[i]];
27
+ }
28
+ return a;
29
+ }
30
+
31
+ declare global {interface HTMLElementTagNameMap {
32
+ "webwriter-choice": WebwriterChoice;
33
+ }}
34
+
35
+ @customElement("webwriter-choice")
36
+ export class WebwriterChoice extends LitElementWw {
37
+
38
+ localize = LOCALIZE
39
+
40
+ @property({type: String, attribute: true, reflect: true})
41
+ @option({
42
+ type: "select",
43
+ options: [
44
+ {value: "truefalse", label: {"en": "True/False"}},
45
+ {value: "single", label: {"en": "Single Choice"}},
46
+ {value: "multiple", label: {"en": "Multiple Choice"}},
47
+ ]
48
+ })
49
+ accessor mode: "truefalse" | "single" | "multiple" = "single"
50
+
51
+ get layout(): "list" | "tiles" {
52
+ return this.children?.item(0)?.getAttribute("layout") as any ?? "list"
53
+ }
54
+
55
+ @property({type: String, attribute: true, reflect: true}) //@ts-ignore
56
+ @option({
57
+ type: "select",
58
+ options: [
59
+ {value: "list", label: {"en": "List"}},
60
+ {value: "tiles", label: {"en": "Tiles"}}
61
+ ],
62
+ })
63
+ set layout(value) {
64
+ this.querySelectorAll("webwriter-choice-item").forEach(el => el.setAttribute("layout", value))
65
+ this.requestUpdate("layout")
66
+ }
67
+
68
+ @property({type: Boolean, attribute: true, reflect: true})
69
+ @option({
70
+ type: Boolean,
71
+ label: {"en": "Random Choice Order", "de": "Zufällige Reihenfolge"}
72
+ })
73
+ accessor randomOrder = false
74
+
75
+ @property({type: Boolean, attribute: true, reflect: true})
76
+ @option({
77
+ type: Boolean,
78
+ label: {"en": "show Solution", "de": "Lösung anzeigen"},
79
+ })
80
+ accessor showSolution = false
81
+
82
+ get solution() {
83
+ const validIDs = this.items.filter(item => item.valid).map(item => item.id)
84
+ return validIDs.length? validIDs: undefined
85
+ }
86
+
87
+ set solution(value: string[]) {
88
+ this.items.forEach(item => value.includes(item.id)? item.valid=true: null)
89
+ }
90
+
91
+
92
+ reportSolution() {
93
+
94
+ let itemActive: boolean = false
95
+ this.items.forEach(item => {itemActive = itemActive || item.active})
96
+ console.log(itemActive)
97
+ if(!itemActive){
98
+ this.items.forEach(item => {item.valid = undefined; item.active = false; item.style.pointerEvents = "auto"})
99
+ return
100
+ }
101
+ console.warn("h")
102
+ if(!this.solution) {
103
+ this.items.forEach(item => item.valid = true)
104
+ return
105
+ }
106
+ this.items.forEach(item => {
107
+ item.valid = (this.solution ?? []).includes(item.id)
108
+ console.log(this.solution, item.active, item.valid)
109
+ if(item.active != item.valid && !this.showSolution){
110
+ this.items.forEach(item => {
111
+ item.showSolution = false
112
+ })
113
+ }
114
+ item.style.pointerEvents = "none"
115
+ }
116
+ )
117
+
118
+ }
119
+
120
+ reset() {
121
+ this.items.forEach(item => {item.valid = undefined; item.active = false; item.showSolution = true; item.style.pointerEvents = "auto"})
122
+ this.shuffleItems()
123
+ }
124
+
125
+ static scopedElements = {
126
+ "sl-button": SlButton,
127
+ "sl-icon": SlIcon,
128
+ "sl-radio": SlRadio,
129
+ "sl-checkbox": SlCheckbox,
130
+ "sl-radio-button": SlRadioButton,
131
+ "sl-radio-group": SlRadioGroup
132
+ }
133
+
134
+ @query("slot")
135
+ accessor slotEl: HTMLSlotElement
136
+
137
+ @action()
138
+ addItem() {
139
+ /*
140
+ if(this.ownerDocument.getSelection().containsNode(this, true)) {
141
+ this.ownerDocument.getSelection().modify("move", "backward", "character")
142
+ }
143
+ setTimeout(() => {*/
144
+ const choiceItem = this.ownerDocument.createElement("webwriter-choice-item")
145
+ const p = this.ownerDocument.createElement("p")
146
+ choiceItem.appendChild(p)
147
+ choiceItem.setAttribute("layout", this.layout)
148
+ this.appendChild(choiceItem)
149
+ this.ownerDocument.getSelection().setBaseAndExtent(p, 0, p, 0)
150
+ //})
151
+ }
152
+
153
+ shuffleItems() {
154
+ const items = this.slotEl.assignedElements() as WebwriterChoiceItem[]
155
+ const n = items.length
156
+ const nums = shuffle([...(new Array(n)).keys()])
157
+ items.forEach((el, i) => el.style.order = String(nums[i]))
158
+ }
159
+
160
+ connectedCallback(): void {
161
+ super.connectedCallback()
162
+ const observer = new MutationObserver(() => {
163
+ if(!this.contentEditable && this.randomOrder) {
164
+ this.shuffleItems()
165
+ }
166
+ })
167
+ observer.observe(this, {childList: true})
168
+ }
169
+
170
+ protected firstUpdated(_changedProperties: PropertyValues): void {
171
+ if(!this.contentEditable && this.randomOrder) {
172
+ this.shuffleItems()
173
+ }
174
+ }
175
+
176
+ static styles = css`
177
+ :host {
178
+ display: flex !important;
179
+ flex-direction: column;
180
+ align-items: flex-start;
181
+ gap: 0.5rem;
182
+ }
183
+
184
+ :host([layout=tiles]) {
185
+
186
+ flex-direction: row;
187
+ flex-wrap: wrap;
188
+ gap: 15px;
189
+
190
+ & ::slotted(*) {
191
+ aspect-ratio: 1;
192
+ min-width: 125px;
193
+ width: 125px;
194
+ max-width: 350px;
195
+ min-height: 125px;
196
+ height: 125px;
197
+ max-height: 350px;
198
+ overflow: hidden;
199
+ resize: both;
200
+ border: 2px solid var(--sl-color-gray-500);
201
+ border-radius: 5px;
202
+ }
203
+ }
204
+
205
+ sl-button::part(label) {
206
+ padding: 0;
207
+ display: flex;
208
+ flex-direction: row;
209
+ align-items: center;
210
+ }
211
+
212
+ sl-button::part(base) {
213
+ border: none;
214
+ background: transparent;
215
+ }
216
+
217
+ sl-icon {
218
+ width: 19px;
219
+ height: 19px;
220
+ }
221
+
222
+ sl-icon::part(svg) {
223
+ overflow: visible;
224
+ }
225
+
226
+ :host(:not([contenteditable=true]):not([contenteditable=""])) .author-only {
227
+ display: none !important;
228
+ }
229
+
230
+ #add-option span {
231
+ margin-inline-start: 0.5em;
232
+ }
233
+
234
+ :host(:is([contenteditable=true], [contenteditable=""])) ::slotted(*) {
235
+ order: unset !important;
236
+ }
237
+
238
+ :host(:not([mode=multiple])) {
239
+ --webwriter-choice-radius: 100%;
240
+ }
241
+
242
+ #add-option:not(:hover)::part(base) {
243
+ color: darkgray;
244
+ }
245
+
246
+ :host([layout=tiles]) #add-option {
247
+ width: 125px;
248
+ height: 125px;
249
+ overflow: hidden;
250
+ border: 2px solid var(--sl-color-gray-300);
251
+ border-radius: 5px;
252
+ padding: 5px;
253
+ display: flex;
254
+ align-items: center;
255
+ justify-content: center;
256
+ }
257
+
258
+ :host([mode=truefalse]) #add-option {
259
+ display: none;
260
+ }
261
+ `
262
+
263
+ @queryAssignedElements()
264
+ accessor items: WebwriterChoiceItem[]
265
+
266
+ #value: number[] = []
267
+
268
+ get value() {
269
+ return this.#value
270
+ }
271
+
272
+ @property({type: Array, attribute: false})
273
+ set value(value: number[]) {
274
+ this.#value = value
275
+ this.items.forEach((item, i) => {
276
+ this.isContentEditable
277
+ ? item.valid = this.#value.includes(i)
278
+ : item.active = this.#value.includes(i)
279
+ })
280
+ }
281
+
282
+ private applyValue() {
283
+ this.#value = this.items
284
+ .map((item, i) => [item.valid, i] as [boolean, number])
285
+ .filter(([valid]) => valid)
286
+ .map(([_, i]) => i)
287
+ }
288
+
289
+ handleControlChange = (e: CustomEvent) => {
290
+ if(this.mode === "single") {
291
+ const item = e.target as WebwriterChoiceItem
292
+ const otherItems = this.items.filter(el => el !== item)
293
+ otherItems.forEach(el => this.isContentEditable? el.valid = false: el.active = false)
294
+ }
295
+ this.applyValue()
296
+ this.dispatchEvent(new CustomEvent("ww-answer-change", {
297
+ detail: {value: this.value},
298
+ bubbles: true,
299
+ composed: true
300
+ }))
301
+ }
302
+
303
+ handleSlotChange(e: Event) {
304
+ this.requestUpdate()
305
+ }
306
+
307
+ protected willUpdate(changed: PropertyValueMap<any>) {
308
+ if(changed.has("mode") && this.mode === "single") {
309
+ this.items
310
+ .filter(el => this.isContentEditable? el.valid: el.active)
311
+ .slice(1)
312
+ .forEach(el => this.isContentEditable? el.valid = false: el.active = false)
313
+ }
314
+ if(changed.has("mode") && this.mode === "truefalse") {
315
+ this.items.forEach(el => el.remove())
316
+ }
317
+ }
318
+
319
+ @query("#items-slot")
320
+ accessor itemsSlotEl: HTMLSlotElement
321
+
322
+ render() {
323
+ return html`
324
+ ${this.mode === "truefalse"
325
+ ? html`
326
+ <sl-radio-group>
327
+ <sl-radio-button value="true">${msg("True")}</sl-radio-button>
328
+ <sl-radio-button value="false">${msg("False")}</sl-radio-button>
329
+ </sl-radio-group>
330
+ `
331
+ : html`
332
+ <slot id="items-slot" @sl-change=${this.handleControlChange} @slotchange=${this.handleSlotChange}></slot>
333
+ `
334
+ }
335
+ <sl-button size="small" id="add-option" class="author-only" @click=${() => this.addItem()}>
336
+ <sl-icon src=${this.mode === "multiple"? IconPlusSquare: IconPlusCircle}></sl-icon><span>${msg("Add Option")}</span>
337
+ </sl-button>
338
+ `
339
+ }
315
340
  }