@webwriter/quiz 1.0.0

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 (62) hide show
  1. package/LICENSE +7 -0
  2. package/custom.d.ts +176 -0
  3. package/dist/widgets/webwriter-choice-item.css +444 -0
  4. package/dist/widgets/webwriter-choice-item.js +288 -0
  5. package/dist/widgets/webwriter-choice.css +444 -0
  6. package/dist/widgets/webwriter-choice.js +327 -0
  7. package/dist/widgets/webwriter-cloze-gap.css +444 -0
  8. package/dist/widgets/webwriter-cloze-gap.js +722 -0
  9. package/dist/widgets/webwriter-cloze.css +444 -0
  10. package/dist/widgets/webwriter-cloze.js +177 -0
  11. package/dist/widgets/webwriter-mark.css +444 -0
  12. package/dist/widgets/webwriter-mark.js +410 -0
  13. package/dist/widgets/webwriter-order-item.css +444 -0
  14. package/dist/widgets/webwriter-order-item.js +464 -0
  15. package/dist/widgets/webwriter-order.css +444 -0
  16. package/dist/widgets/webwriter-order.js +560 -0
  17. package/dist/widgets/webwriter-pairing-item.css +444 -0
  18. package/dist/widgets/webwriter-pairing-item.js +203 -0
  19. package/dist/widgets/webwriter-pairing.css +444 -0
  20. package/dist/widgets/webwriter-pairing.js +224 -0
  21. package/dist/widgets/webwriter-quiz.css +444 -0
  22. package/dist/widgets/webwriter-quiz.js +323 -0
  23. package/dist/widgets/webwriter-speech.css +444 -0
  24. package/dist/widgets/webwriter-speech.js +320 -0
  25. package/dist/widgets/webwriter-task-explainer.css +444 -0
  26. package/dist/widgets/webwriter-task-explainer.js +90 -0
  27. package/dist/widgets/webwriter-task-hint.css +444 -0
  28. package/dist/widgets/webwriter-task-hint.js +67 -0
  29. package/dist/widgets/webwriter-task-prompt.css +444 -0
  30. package/dist/widgets/webwriter-task-prompt.js +68 -0
  31. package/dist/widgets/webwriter-task.css +444 -0
  32. package/dist/widgets/webwriter-task.js +546 -0
  33. package/dist/widgets/webwriter-text.css +444 -0
  34. package/dist/widgets/webwriter-text.js +174 -0
  35. package/package.json +199 -0
  36. package/src/lib/combobox.ts +456 -0
  37. package/src/lib/highlighter-fill.svg +6 -0
  38. package/src/snippets/choice.html +8 -0
  39. package/src/snippets/cloze.html +4 -0
  40. package/src/snippets/mark.html +4 -0
  41. package/src/snippets/order.html +8 -0
  42. package/src/snippets/pairing.html +10 -0
  43. package/src/snippets/speech.html +4 -0
  44. package/src/snippets/text.html +4 -0
  45. package/src/snippets/wordsearch.html +4 -0
  46. package/src/widgets/webwriter-choice-item.ts +251 -0
  47. package/src/widgets/webwriter-choice.ts +310 -0
  48. package/src/widgets/webwriter-cloze-gap.ts +217 -0
  49. package/src/widgets/webwriter-cloze.ts +136 -0
  50. package/src/widgets/webwriter-mark.ts +435 -0
  51. package/src/widgets/webwriter-order-item.ts +445 -0
  52. package/src/widgets/webwriter-order.ts +273 -0
  53. package/src/widgets/webwriter-pairing-item.ts +156 -0
  54. package/src/widgets/webwriter-pairing.ts +188 -0
  55. package/src/widgets/webwriter-quiz.ts +281 -0
  56. package/src/widgets/webwriter-speech.ts +268 -0
  57. package/src/widgets/webwriter-task-explainer.ts +38 -0
  58. package/src/widgets/webwriter-task-hint.ts +17 -0
  59. package/src/widgets/webwriter-task-prompt.ts +18 -0
  60. package/src/widgets/webwriter-task.ts +544 -0
  61. package/src/widgets/webwriter-text.ts +131 -0
  62. package/tsconfig.json +7 -0
package/package.json ADDED
@@ -0,0 +1,199 @@
1
+ {
2
+ "name": "@webwriter/quiz",
3
+ "version": "1.0.0",
4
+ "keywords": [
5
+ "webwriter-widget"
6
+ ],
7
+ "scripts": {
8
+ "prepublishOnly": "npx @webwriter/build"
9
+ },
10
+ "license": "MIT",
11
+ "author": "Frederic Salmen <frederic@fsalmen.de>",
12
+ "dependencies": {
13
+ "@shoelace-style/shoelace": "^2.13.1",
14
+ "@shopify/draggable": "^1.1.3",
15
+ "@webwriter/lit": "file:../lit",
16
+ "bootstrap-icons": "^1.11.3",
17
+ "interactjs": "^1.10.26",
18
+ "lit": "^3.2.0",
19
+ "minimasonry": "^1.3.2",
20
+ "sortablejs": "^1.15.2"
21
+ },
22
+ "devDependencies": {
23
+ "@types/minimasonry": "^1.3.5",
24
+ "@types/sortablejs": "^1.15.7"
25
+ },
26
+ "peerDependencies": {
27
+ "@webwriter/phet-simulation": "^1.0.0"
28
+ },
29
+ "exports": {
30
+ "./widgets/webwriter-quiz.*": {
31
+ "source": "./src/widgets/webwriter-quiz.ts",
32
+ "default": "./dist/widgets/webwriter-quiz.*"
33
+ },
34
+ "./snippets/choice.html": "./src/snippets/choice.html",
35
+ "./snippets/order.html": "./src/snippets/order.html",
36
+ "./snippets/text.html": "./src/snippets/text.html",
37
+ "./snippets/mark.html": "./src/snippets/mark.html",
38
+ "./snippets/speech.html": "./src/snippets/speech.html",
39
+ "./widgets/webwriter-task.*": {
40
+ "source": "./src/widgets/webwriter-task.ts",
41
+ "default": "./dist/widgets/webwriter-task.*"
42
+ },
43
+ "./widgets/webwriter-task-prompt.*": {
44
+ "source": "./src/widgets/webwriter-task-prompt.ts",
45
+ "default": "./dist/widgets/webwriter-task-prompt.*"
46
+ },
47
+ "./widgets/webwriter-task-hint.*": {
48
+ "source": "./src/widgets/webwriter-task-hint.ts",
49
+ "default": "./dist/widgets/webwriter-task-hint.*"
50
+ },
51
+ "./widgets/webwriter-task-explainer.*": {
52
+ "source": "./src/widgets/webwriter-task-explainer.ts",
53
+ "default": "./dist/widgets/webwriter-task-explainer.*"
54
+ },
55
+ "./widgets/webwriter-choice.*": {
56
+ "source": "./src/widgets/webwriter-choice.ts",
57
+ "default": "./dist/widgets/webwriter-choice.*"
58
+ },
59
+ "./widgets/webwriter-choice-item.*": {
60
+ "source": "./src/widgets/webwriter-choice-item.ts",
61
+ "default": "./dist/widgets/webwriter-choice-item.*"
62
+ },
63
+ "./widgets/webwriter-order.*": {
64
+ "source": "./src/widgets/webwriter-order.ts",
65
+ "default": "./dist/widgets/webwriter-order.*"
66
+ },
67
+ "./widgets/webwriter-order-item.*": {
68
+ "source": "./src/widgets/webwriter-order-item.ts",
69
+ "default": "./dist/widgets/webwriter-order-item.*"
70
+ },
71
+ "./widgets/webwriter-pairing.*": {
72
+ "source": "./src/widgets/webwriter-pairing.ts",
73
+ "default": "./dist/widgets/webwriter-pairing.*"
74
+ },
75
+ "./widgets/webwriter-pairing-item.*": {
76
+ "source": "./src/widgets/webwriter-pairing-item.ts",
77
+ "default": "./dist/widgets/webwriter-pairing-item.*"
78
+ },
79
+ "./widgets/webwriter-text.*": {
80
+ "source": "./src/widgets/webwriter-text.ts",
81
+ "default": "./dist/widgets/webwriter-text.*"
82
+ },
83
+ "./widgets/webwriter-mark.*": {
84
+ "source": "./src/widgets/webwriter-mark.ts",
85
+ "default": "./dist/widgets/webwriter-mark.*"
86
+ },
87
+ "./widgets/webwriter-speech.*": {
88
+ "source": "./src/widgets/webwriter-speech.ts",
89
+ "default": "./dist/widgets/webwriter-speech.*"
90
+ },
91
+ "./widgets/webwriter-cloze.*": {
92
+ "source": "./src/widgets/webwriter-cloze.ts",
93
+ "default": "./dist/widgets/webwriter-cloze.*"
94
+ },
95
+ "./widgets/webwriter-cloze-gap.*": {
96
+ "source": "./src/widgets/webwriter-cloze-gap.ts",
97
+ "default": "./dist/widgets/webwriter-cloze-gap.*"
98
+ },
99
+ "./package.json": "./package.json"
100
+ },
101
+ "editingConfig": {
102
+ "./widgets/webwriter-choice-item": {
103
+ "group": "",
104
+ "content": "(p | flow)+",
105
+ "uninsertable": true,
106
+ "draggable": false,
107
+ "selectable": false,
108
+ "isolating": false
109
+ },
110
+ "./widgets/webwriter-choice": {
111
+ "group": "answer",
112
+ "content": "webwriter-choice-item+",
113
+ "uninsertable": true
114
+ },
115
+ "./widgets/webwriter-order-item": {
116
+ "group": "",
117
+ "content": "(p | flow)+",
118
+ "uninsertable": true,
119
+ "draggable": true,
120
+ "selectable": false,
121
+ "isolating": false
122
+ },
123
+ "./widgets/webwriter-order": {
124
+ "group": "answer",
125
+ "content": "webwriter-order-item+",
126
+ "uninsertable": true
127
+ },
128
+ "./widgets/webwriter-pairing": {
129
+ "group": "answer",
130
+ "content": "webwriter-pairing-item+",
131
+ "uninsertable": true,
132
+ "isolating": true
133
+ },
134
+ "./widgets/webwriter-pairing-item": {
135
+ "group": "",
136
+ "content": "picture | flow",
137
+ "uninsertable": true,
138
+ "draggable": true,
139
+ "isolating": true
140
+ },
141
+ "./widgets/webwriter-text": {
142
+ "group": "answer",
143
+ "uninsertable": true
144
+ },
145
+ "./widgets/webwriter-mark": {
146
+ "group": "answer inlinecontainer",
147
+ "content": "(text | br | wbr)*",
148
+ "isolating": true,
149
+ "defining": true,
150
+ "marks": "",
151
+ "uninsertable": true
152
+ },
153
+ "./widgets/webwriter-speech": {
154
+ "group": "answer",
155
+ "uninsertable": true
156
+ },
157
+ "./widgets/webwriter-cloze": {
158
+ "group": "answer",
159
+ "content": "p",
160
+ "uninsertable": true,
161
+ "isolating": true
162
+ },
163
+ "./widgets/webwriter-cloze-gap": {
164
+ "group": "phrasing",
165
+ "inline": true,
166
+ "atom": true,
167
+ "uninsertable": true,
168
+ "isolating": false
169
+ },
170
+ "./widgets/webwriter-task": {
171
+ "content": "webwriter-task-prompt webwriter-task-hint? answer webwriter-task-explainer*",
172
+ "uninsertable": true,
173
+ "isolating": true
174
+ },
175
+ "./widgets/webwriter-task-prompt": {
176
+ "group": "",
177
+ "defining": true,
178
+ "content": "(p | flow)+",
179
+ "uninsertable": true
180
+ },
181
+ "./widgets/webwriter-task-hint": {
182
+ "group": "containerinline",
183
+ "defining": true,
184
+ "content": "p",
185
+ "isolating": true,
186
+ "uninsertable": true
187
+ },
188
+ "./widgets/webwriter-task-explainer": {
189
+ "group": "",
190
+ "defining": true,
191
+ "isolating": true,
192
+ "content": "(p | flow)+",
193
+ "uninsertable": true
194
+ },
195
+ "./widgets/webwriter-quiz": {
196
+ "content": "webwriter-task*"
197
+ }
198
+ }
199
+ }
@@ -0,0 +1,456 @@
1
+ import { LitElement, html, css, CSSResultArray, CSSResult, PropertyValueMap, PropertyValues } from "lit"
2
+ import { customElement, property, query, queryAssignedElements } from "lit/decorators.js"
3
+ import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js"
4
+ import SlOption from "@shoelace-style/shoelace/dist/components/option/option.component.js"
5
+ import IconChevronDown from "bootstrap-icons/icons/chevron-down.svg"
6
+
7
+ import { ifDefined } from "lit/directives/if-defined.js"
8
+ import { styleMap } from "lit/directives/style-map.js"
9
+
10
+ @customElement("ww-combobox")
11
+ export class Combobox extends LitElement {
12
+
13
+ static shadowRootOptions = {...LitElement.shadowRootOptions, delegatesFocus: true}
14
+
15
+ constructor() {
16
+ super()
17
+ this.addEventListener("focus", e => {this.active = true; this.open = true})
18
+ this.addEventListener("focusout", e => {
19
+ this.active = this.open = false
20
+ })
21
+ this.addEventListener("blur", e => {
22
+ this.active = this.open = false
23
+ })
24
+ this.value = this.defaultValue = this.multiple? []: ""
25
+ }
26
+
27
+ tabIndex = 0
28
+
29
+ @property({attribute: false}) //@ts-ignore
30
+ accessor value: string[] | string
31
+
32
+ @property({attribute: false}) //@ts-ignore
33
+ accessor defaultValue: string[] | string
34
+
35
+ @query("input") //@ts-ignore
36
+ accessor input: HTMLInputElement
37
+
38
+ @query("#toggle")
39
+ accessor toggleButton: HTMLInputElement
40
+
41
+ @query("slot:not([name])")
42
+ accessor slotElement: HTMLSlotElement
43
+
44
+ @queryAssignedElements()
45
+ accessor optionElements: HTMLElement[]
46
+
47
+ @property({type: String, attribute: true}) //@ts-ignore
48
+ accessor autocomplete: SlInput["autocomplete"] = "off"
49
+
50
+ @property({type: Boolean, attribute: true, reflect: true})
51
+ accessor active: boolean
52
+
53
+ @property({type: Boolean, attribute: true, reflect: true})
54
+ accessor inputDisabled: boolean = false
55
+
56
+ @property({type: Boolean, attribute: true, reflect: true})
57
+ accessor multiple: boolean = false
58
+
59
+ @property({type: String}) //@ts-ignore
60
+ accessor placeholder: string
61
+
62
+ @property({type: String, attribute: true, reflect: true})
63
+ accessor emptyValue: string
64
+
65
+ @property({type: Boolean, attribute: true, reflect: true})
66
+ accessor open: boolean = false
67
+
68
+ @property({type: Boolean, attribute: true, reflect: true})
69
+ accessor suggestions: boolean = false
70
+
71
+ @property({type: String, attribute: "help-text"}) //@ts-ignore
72
+ accessor helpText: string
73
+
74
+ @property({type: Number, attribute: true})
75
+ accessor fixLength: number
76
+
77
+ @property({type: String, attribute: true})
78
+ accessor separator: string = " "
79
+
80
+ @property({type: String, attribute: true})
81
+ accessor placement = "bottom" as const
82
+
83
+ @property({type: Boolean, attribute: true, reflect: true})
84
+ accessor autosize = false
85
+
86
+ @property({type: Boolean, attribute: true, reflect: true})
87
+ accessor disabled = false
88
+
89
+ @property({type: Boolean, attribute: true, reflect: true})
90
+ accessor required = false
91
+
92
+ @property({type: String, attribute: true, reflect: true})
93
+ accessor type: "button" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "hidden" | "image" | "month" | "number" | "password" | "radio" | "range" | "reset" | "search" | "submit" | "tel" | "text" | "time" | "url" | "week"
94
+
95
+ @property({type: String, attribute: true, reflect: true})
96
+ accessor label: string
97
+
98
+ get validity() {
99
+ return this.input.validity
100
+ }
101
+
102
+ get validationMessage() {
103
+ return this.input.validationMessage
104
+ }
105
+
106
+ setValidity(isValid: boolean) {
107
+ const host = this
108
+ const required = Boolean(this.required)
109
+ host.toggleAttribute('data-required', required)
110
+ host.toggleAttribute('data-optional', !required)
111
+ host.toggleAttribute('data-invalid', !isValid)
112
+ host.toggleAttribute('data-valid', isValid)
113
+ }
114
+
115
+ focus() {
116
+ if(this.inputDisabled && this.suggestions) {
117
+ this.open = !this.open
118
+ super.focus()
119
+ }
120
+ else {
121
+ this.input?.focus()
122
+ }
123
+ }
124
+
125
+ blur() {
126
+ this.open = false
127
+ }
128
+
129
+ getForm() {
130
+ return this.input.form
131
+ }
132
+
133
+ static styles: CSSResult | CSSResultArray = [SlInput.styles, css`
134
+ * {
135
+ font-family: var(--sl-font-sans);
136
+ }
137
+
138
+ :host {
139
+ display: flex;
140
+ flex-direction: column;
141
+ }
142
+
143
+ [part=base] {
144
+ display: flex;
145
+ flex-direction: row;
146
+ justify-content: space-between;
147
+ align-items: center;
148
+ position: relative;
149
+ border: solid var(--sl-input-border-width) var(--sl-input-border-color);
150
+ border-radius: var(--sl-input-border-radius-medium);
151
+ max-width: 100%;
152
+ min-height: var(--sl-input-height-medium);
153
+ padding: 0 var(--sl-input-spacing-medium);
154
+ padding-right: 0;
155
+ background: white;
156
+ }
157
+
158
+ :host(:not([inputDisabled]):not([disabled])) {
159
+ cursor: text;
160
+ }
161
+
162
+ :host([inputDisabled]:not([disabled])), :host([inputDisabled]:not([disabled])) input {
163
+ cursor: pointer;
164
+ }
165
+
166
+ :host([multiple]) [part=base] {
167
+ flex-wrap: wrap;
168
+ }
169
+
170
+ :host([filled]) [part=base] {
171
+ background: var(--sl-input-filled-background-color);
172
+ }
173
+
174
+ :host([active]) [part=base] {
175
+ border-color: var(--sl-input-border-color-focus);
176
+ box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-input-focus-ring-color);
177
+ }
178
+
179
+ :host([disabled]) [part=base] {
180
+ background: none;
181
+ }
182
+
183
+ :host(:hover) [part=base] {
184
+ border-color: var(--sl-color-gray-400);
185
+ }
186
+
187
+ #values {
188
+ display: flex;
189
+ flex-direction: row;
190
+ align-items: center;
191
+ gap: 0.25rem;
192
+ padding-top: 0.25rem;
193
+ padding-bottom: 0.25rem;
194
+ flex-wrap: wrap;
195
+ }
196
+
197
+ #values > * {
198
+ margin-top: 0.125rem;
199
+ margin-bottom: 0.125rem;
200
+ }
201
+
202
+ #options {
203
+ display: block;
204
+ font-family: var(--sl-font-sans);
205
+ font-size: var(--sl-font-size-medium);
206
+ font-weight: var(--sl-font-weight-normal);
207
+ box-shadow: var(--sl-shadow-large);
208
+ background: var(--sl-panel-background-color);
209
+ border: solid var(--sl-panel-border-width) var(--sl-panel-border-color);
210
+ border-radius: var(--sl-border-radius-medium);
211
+ padding-block: var(--sl-spacing-x-small);
212
+ padding-inline: 0;
213
+ overflow: auto;
214
+ overscroll-behavior: none;
215
+ width: 100%;
216
+ max-height: var(--auto-size-available-height);
217
+ }
218
+
219
+ :host(:is(:not([open]), :not([suggestions]))) #options {
220
+ display: none;
221
+ }
222
+
223
+ #options::part(base) {
224
+ width: 100%;
225
+ border-top: none;
226
+ }
227
+
228
+ input {
229
+ flex-grow: 1;
230
+ display: inline-flex;
231
+ border: none;
232
+ outline: none;
233
+ background: transparent;
234
+ font-size: var(--sl-input-font-size-small);
235
+ }
236
+
237
+ :host(:not([suggestions])) sl-menu, :host(:not([suggestions])) #toggle {
238
+ display: none;
239
+ }
240
+
241
+ #toggle {
242
+ padding-right: var(--sl-input-spacing-medium);
243
+ }
244
+
245
+ :host([size=small]) #toggle {
246
+ padding-right: var(--sl-spacing-2x-small);
247
+ font-size: var(--sl-input-font-size-small);
248
+ }
249
+
250
+ #toggle::part(base) {
251
+ padding: 0;
252
+ }
253
+
254
+ #toggle::part(base) {
255
+ transition: transform 0.25s ease;
256
+ }
257
+
258
+ :host([open]) #toggle::part(base) {
259
+ transform: rotate(180deg);
260
+ }
261
+
262
+ :host([open]) {
263
+ z-index: 100;
264
+ }
265
+
266
+ sl-dropdown::part(panel) {
267
+ width: 100%;
268
+ }
269
+
270
+ [part=suffix] {
271
+ display: flex;
272
+ flex-direction: row;
273
+ align-items: center;
274
+ justify-content: flex-end;
275
+ max-width: 50%;
276
+ flex-grow: 0;
277
+ }
278
+
279
+ [part=label]::slotted(*), label {
280
+ align-self: flex-start;
281
+ margin-bottom: var(--sl-spacing-3x-small);
282
+ }
283
+
284
+ :host([required]) [part=label]::after {
285
+ content: "*";
286
+ margin-left: 0.25ch;
287
+ }
288
+
289
+ :host(:not([help-text])) #help-text {
290
+ display: none;
291
+ }
292
+
293
+ #help-text {
294
+ font-size: var(--sl-input-help-text-font-size-medium);
295
+ color: var(--sl-input-help-text-color);
296
+ margin-top: var(--sl-spacing-3x-small);
297
+ }
298
+
299
+ slot[name=prefix]::slotted(:last-child) {
300
+ margin-right: 1ch;
301
+ }
302
+
303
+ :host([size=small]) [part=base] {
304
+ min-height: var(--sl-input-height-small);
305
+ padding: 0 var(--sl-input-spacing-small);
306
+ }
307
+
308
+ #hide {
309
+ height: 0;
310
+ position: absolute;
311
+ right: 0;
312
+ bottom: 0;
313
+ white-space: pre;
314
+ overflow: hidden;
315
+ font-size: var(--sl-input-font-size-small);
316
+ }
317
+ `]
318
+
319
+ handleTextInput(e: Event) {
320
+ const input = e.target as HTMLInputElement
321
+ e.preventDefault()
322
+ if(!this.multiple && this.fixLength && input.value.length < this.fixLength) {
323
+ input.value = this.value as string
324
+ }
325
+ else if(!this.multiple) {
326
+ this.value = input.value
327
+ }
328
+ else if(input.value === this.separator) {
329
+ input.value = ""
330
+ }
331
+ else {
332
+ this.value = [...this.valueList.slice(0, -1), ...input.value.split(this.separator)]
333
+ input.value = this.valueList.at(-1)!
334
+ }
335
+ this.dispatchInput()
336
+ this.setValidity(this.validity.valid)
337
+ }
338
+
339
+ handleInputKeydown(e: KeyboardEvent) {
340
+ const input = e.target as HTMLInputElement
341
+ if(this.multiple && e.key === "Backspace" && input.selectionStart === 0 && input.selectionEnd === 0) {
342
+ e.preventDefault()
343
+ this.value = this.valueList.slice(0, -1)
344
+ }
345
+ this.requestUpdate()
346
+ }
347
+
348
+ handleOptionClick = (e: PointerEvent) => {
349
+ const option = (e.target as HTMLElement).closest("sl-option") as SlOption | null
350
+ if(option) {
351
+ this.value = option.value
352
+ this.focus()
353
+ this.open = false
354
+ this.dispatchChange()
355
+ this.requestUpdate()
356
+ }
357
+ }
358
+
359
+ dispatchChange() {
360
+ this.dispatchEvent(new CustomEvent("sl-change", {composed: true, bubbles: true}))
361
+ }
362
+
363
+ dispatchInput() {
364
+ this.dispatchEvent(new CustomEvent("sl-input", {composed: true, bubbles: true}))
365
+ }
366
+
367
+ get valueList() {
368
+ return (this.multiple? this.value || []: [this.value]) as string[]
369
+ }
370
+
371
+ get inputSize() {
372
+ return !this.multiple? undefined: Math.min(
373
+ Math.max(this.placeholder?.length ?? 1, this.valueList.at(-1)!?.length ?? 1 - 1),
374
+ 40
375
+ )
376
+ }
377
+
378
+ protected firstUpdated(_changedProperties: PropertyValues): void {
379
+ super.firstUpdated(_changedProperties)
380
+ this.requestUpdate()
381
+ }
382
+
383
+ connectedCallback(): void {
384
+ super.connectedCallback()
385
+ this.requestUpdate()
386
+ }
387
+
388
+ @query("#hide")
389
+ accessor hideEl: HTMLElement
390
+
391
+ @query("input")
392
+ accessor inputEl: HTMLInputElement
393
+
394
+ get inputWidth() {
395
+ return this.hideEl? this.hideEl.offsetWidth: 0
396
+ }
397
+
398
+ get inputValue() {
399
+ const value = this.multiple? this.valueList.at(-1) ?? "": String(this.value ?? "")
400
+ return value !== this.emptyValue? value: ""
401
+ }
402
+
403
+ render() {
404
+ const input = html`
405
+ <input
406
+ part="input"
407
+ type=${this.type}
408
+ size=${ifDefined(this.inputSize)}
409
+ .value=${this.inputValue}
410
+ placeholder=${this.multiple && this.valueList.length > 1? "": this.placeholder}
411
+ ?disabled=${this.disabled || this.inputDisabled}
412
+ @input=${this.handleTextInput}
413
+ @change=${this.dispatchChange}
414
+ @keydown=${this.handleInputKeydown}
415
+ autocomplete=${this.autocomplete as any}
416
+ style=${this.autosize? styleMap({width: `calc(${this.inputWidth}px + 2ch)`}): undefined}
417
+ >
418
+ `
419
+ return html`
420
+ <slot name="label" part="label" @click=${this.focus}>
421
+ <label>${this.label}</label>
422
+ </slot>
423
+ <div part="base" id="anchor" tabindex=${0}>
424
+ <slot name="prefix"></slot>
425
+ ${this.multiple? html`
426
+ <span id="values">
427
+ <slot name="values"></slot>
428
+ ${this.valueList.slice(0, -1).map(v => html`
429
+ <sl-tag size="small" variant="primary">${v}</sl-tag>
430
+ `)}
431
+ ${input}
432
+ </span>
433
+ `: input}
434
+ <div part="suffix">
435
+ <slot name="suffix"></slot>
436
+ <sl-icon-button
437
+ slot="trigger"
438
+ part="trigger"
439
+ id="toggle"
440
+ src=${IconChevronDown}
441
+ @focus=${(e: Event) => e.stopPropagation()}
442
+ @click=${(e: Event) => {this.open = !this.open; e.stopPropagation()}}
443
+ @mousedown=${(e: Event) => {e.stopPropagation(); e.preventDefault()}}
444
+ ></sl-icon-button>
445
+ </div>
446
+ </div>
447
+ <sl-popup anchor="anchor" placement=${this.placement} strategy="fixed" ?active=${this.open} auto-size="vertical" auto-size-padding="10" flip shift>
448
+ <sl-menu id="options" @click=${this.handleOptionClick}>
449
+ <slot></slot>
450
+ </sl-menu>
451
+ </sl-popup>
452
+ <div id="help-text">${this.helpText}</div>
453
+ <span id="hide">${this.inputEl?.value}</span>
454
+ `
455
+ }
456
+ }
@@ -0,0 +1,6 @@
1
+ <svg width="20" height="20" fill="currentColor" class="bi bi-highlighter" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
2
+ <path style="fill:#ffffff;stroke-width:0.0388979" d="M 12.979378,2.8366808 11.180665,1.1061689 11.312696,0.99938614 c 0.224798,-0.18180931 0.45821,-0.26011138 0.787279,-0.26410617 0.520565,-0.006315 0.605828,0.0505579 1.812273,1.20892583 1.20551,1.1574723 1.264697,1.239359 1.259768,1.7428933 -0.003,0.3118719 -0.08504,0.5291149 -0.285756,0.7571853 l -0.108172,0.1229081 z" id="path310" />
3
+ <path style="fill:#ffffff;stroke-width:0.0327363" d="M 4.3293683,11.575933 C 2.3884046,9.6659098 2.3670623,9.6434959 2.4318197,9.5830841 2.4678415,9.5494798 4.3833896,7.8593211 6.6885933,5.827176 8.993797,3.795031 10.924803,2.0910543 10.97972,2.0405615 l 0.09985,-0.091804 1.524504,1.5011639 1.524504,1.501164 -3.894388,4.2701545 C 8.0922741,11.569823 6.329137,13.495058 6.316105,13.499537 6.3030731,13.504037 5.4090417,12.638394 4.3293683,11.575933 Z" id="path306" />
4
+ <path style="fill:#ffffff;stroke-width:0.0401442" d="m 3.0376721,12.881037 -0.8289193,-0.721793 0.011549,-0.90713 0.011549,-0.907129 1.8721139,1.628923 1.8721139,1.628923 H 4.9213333 3.8665914 Z" id="path308" />
5
+ <path fill-rule="evenodd" d="m 11.096,0.644 a 2,2 0 0 1 2.791,0.036 l 1.433,1.433 a 2,2 0 0 1 0.035,2.791 l -0.413,0.435 -8.07,8.995 A 0.5,0.5 0 0 1 6.5,14.5 h -3 A 0.5,0.5 0 0 1 3.266,14.442 L 2.854,14.854 A 0.5,0.5 0 0 1 2.5,15 h -2 A 0.5,0.5 0 0 1 0.146,14.146 L 1.558,12.734 A 0.5,0.5 0 0 1 1.5,12.5 v -3 A 0.5,0.5 0 0 1 1.666,9.128 l 8.995,-8.07 z M 10.981,2.114 2.727,9.52 6.48,13.273 13.886,5.019 Z m 3.585,2.17 0.064,-0.068 A 1,1 0 0 0 14.613,2.82 L 13.18,1.387 A 1,1 0 0 0 11.784,1.369 L 11.716,1.434 Z M 5.293,13.5 2.5,10.707 v 1.586 L 3.707,13.5 Z" id="path2" />
6
+ </svg>
@@ -0,0 +1,8 @@
1
+ <webwriter-task>
2
+ <webwriter-task-prompt slot="prompt"><p></p></webwriter-task-prompt>
3
+ <webwriter-choice>
4
+ <webwriter-choice-item>
5
+ <p></p>
6
+ </webwriter-choice-item>
7
+ </webwriter-choice>
8
+ </webwriter-task>
@@ -0,0 +1,4 @@
1
+ <webwriter-task>
2
+ <webwriter-task-prompt slot="prompt"><p></p></webwriter-task-prompt>
3
+ <webwriter-cloze><p></p></webwriter-cloze>
4
+ </webwriter-task>
@@ -0,0 +1,4 @@
1
+ <webwriter-task>
2
+ <webwriter-task-prompt slot="prompt"><p></p></webwriter-task-prompt>
3
+ <webwriter-mark><p></p></webwriter-mark>
4
+ </webwriter-task>
@@ -0,0 +1,8 @@
1
+ <webwriter-task>
2
+ <webwriter-task-prompt slot="prompt"><p></p></webwriter-task-prompt>
3
+ <webwriter-order>
4
+ <webwriter-order-item>
5
+ <p></p>
6
+ </webwriter-order-item>
7
+ </webwriter-order>
8
+ </webwriter-task>