@webwriter/quiz 1.0.8 → 1.0.10

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 (112) hide show
  1. package/.env +1 -0
  2. package/LICENSE +6 -6
  3. package/custom.d.ts +175 -175
  4. package/dist/widgets/webwriter-choice-item.js +174 -29
  5. package/dist/widgets/webwriter-choice.js +174 -29
  6. package/dist/widgets/webwriter-cloze-gap.js +174 -29
  7. package/dist/widgets/webwriter-cloze.js +174 -29
  8. package/dist/widgets/webwriter-mark.js +174 -29
  9. package/dist/widgets/webwriter-order-item.js +174 -29
  10. package/dist/widgets/webwriter-order.js +174 -29
  11. package/dist/widgets/webwriter-pairing-item.js +174 -29
  12. package/dist/widgets/webwriter-pairing.js +174 -29
  13. package/dist/widgets/webwriter-quiz.js +174 -29
  14. package/dist/widgets/webwriter-speech.js +174 -29
  15. package/dist/widgets/webwriter-task-explainer.js +174 -29
  16. package/dist/widgets/webwriter-task-hint.js +174 -29
  17. package/dist/widgets/webwriter-task-prompt.js +174 -29
  18. package/dist/widgets/webwriter-task.js +176 -31
  19. package/dist/widgets/webwriter-text.js +174 -29
  20. package/lit-localize.json +14 -14
  21. package/localization/bg.xlf +69 -54
  22. package/localization/cs.xlf +69 -54
  23. package/localization/da.xlf +69 -54
  24. package/localization/de.xlf +74 -54
  25. package/localization/el.xlf +69 -54
  26. package/localization/es.xlf +69 -54
  27. package/localization/et.xlf +69 -54
  28. package/localization/fi.xlf +69 -54
  29. package/localization/fr.xlf +69 -54
  30. package/localization/generated/bg.ts +29 -24
  31. package/localization/generated/cs.ts +29 -24
  32. package/localization/generated/da.ts +29 -24
  33. package/localization/generated/de.ts +29 -24
  34. package/localization/generated/el.ts +29 -24
  35. package/localization/generated/es.ts +29 -24
  36. package/localization/generated/et.ts +29 -24
  37. package/localization/generated/fi.ts +29 -24
  38. package/localization/generated/fr.ts +29 -24
  39. package/localization/generated/hu.ts +29 -24
  40. package/localization/generated/id.ts +29 -24
  41. package/localization/generated/index.js +2 -2
  42. package/localization/generated/it.ts +29 -24
  43. package/localization/generated/ja.ts +29 -24
  44. package/localization/generated/ko.ts +29 -24
  45. package/localization/generated/locale-codes.js +79 -79
  46. package/localization/generated/lt.ts +29 -24
  47. package/localization/generated/lv.ts +29 -24
  48. package/localization/generated/nb.ts +29 -24
  49. package/localization/generated/nl.ts +29 -24
  50. package/localization/generated/pl.ts +29 -24
  51. package/localization/generated/pt-BR.ts +29 -24
  52. package/localization/generated/pt-PT.ts +29 -24
  53. package/localization/generated/ro.ts +29 -24
  54. package/localization/generated/ru.ts +29 -24
  55. package/localization/generated/sk.ts +29 -24
  56. package/localization/generated/sl.ts +29 -24
  57. package/localization/generated/sv.ts +29 -24
  58. package/localization/generated/tr.ts +29 -24
  59. package/localization/generated/uk.ts +29 -24
  60. package/localization/generated/zh-hans.ts +29 -24
  61. package/localization/hu.xlf +69 -54
  62. package/localization/id.xlf +69 -54
  63. package/localization/it.xlf +69 -54
  64. package/localization/ja.xlf +69 -54
  65. package/localization/ko.xlf +69 -54
  66. package/localization/lt.xlf +69 -54
  67. package/localization/lv.xlf +69 -54
  68. package/localization/nb.xlf +69 -54
  69. package/localization/nl.xlf +69 -54
  70. package/localization/pl.xlf +69 -54
  71. package/localization/pt-BR.xlf +69 -54
  72. package/localization/pt-PT.xlf +69 -54
  73. package/localization/ro.xlf +69 -54
  74. package/localization/ru.xlf +69 -54
  75. package/localization/sk.xlf +69 -54
  76. package/localization/sl.xlf +69 -54
  77. package/localization/sv.xlf +69 -54
  78. package/localization/tr.xlf +69 -54
  79. package/localization/uk.xlf +69 -54
  80. package/localization/zh-hans.xlf +69 -54
  81. package/package.json +4 -43
  82. package/src/lib/combobox.ts +455 -455
  83. package/src/lib/highlighter-fill.svg +5 -5
  84. package/src/snippets/Beispiel-Choice.html +57 -57
  85. package/src/snippets/Beispiel-Mark.html +13 -13
  86. package/src/snippets/Beispiel-Order.html +52 -52
  87. package/src/snippets/Beispiel-Text.html +20 -20
  88. package/src/snippets/choice.html +7 -7
  89. package/src/snippets/cloze.html +3 -3
  90. package/src/snippets/mark.html +3 -3
  91. package/src/snippets/order.html +7 -7
  92. package/src/snippets/pairing.html +9 -9
  93. package/src/snippets/speech.html +3 -3
  94. package/src/snippets/text.html +3 -3
  95. package/src/snippets/wordsearch.html +3 -3
  96. package/src/widgets/webwriter-choice-item.ts +250 -250
  97. package/src/widgets/webwriter-choice.ts +314 -314
  98. package/src/widgets/webwriter-cloze-gap.ts +215 -215
  99. package/src/widgets/webwriter-cloze.ts +135 -135
  100. package/src/widgets/webwriter-mark.ts +431 -431
  101. package/src/widgets/webwriter-order-item.ts +437 -437
  102. package/src/widgets/webwriter-order.ts +284 -284
  103. package/src/widgets/webwriter-pairing-item.ts +160 -160
  104. package/src/widgets/webwriter-pairing.ts +192 -192
  105. package/src/widgets/webwriter-quiz.ts +281 -281
  106. package/src/widgets/webwriter-speech.ts +272 -272
  107. package/src/widgets/webwriter-task-explainer.ts +42 -42
  108. package/src/widgets/webwriter-task-hint.ts +22 -22
  109. package/src/widgets/webwriter-task-prompt.ts +23 -23
  110. package/src/widgets/webwriter-task.ts +546 -546
  111. package/src/widgets/webwriter-text.ts +135 -135
  112. package/tsconfig.json +6 -6
@@ -1,315 +1,315 @@
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
+
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
+ }
315
315
  }