@webwriter/quiz 1.0.4 → 1.0.6

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