@webwriter/quiz 1.0.10 → 1.1.2

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