@webwriter/quiz 1.0.2 → 1.0.4

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 (45) hide show
  1. package/LICENSE +6 -6
  2. package/custom.d.ts +175 -175
  3. package/dist/widgets/webwriter-choice-item.js +3257 -59
  4. package/dist/widgets/webwriter-choice.js +4941 -79
  5. package/dist/widgets/webwriter-cloze-gap.js +4092 -179
  6. package/dist/widgets/webwriter-cloze.js +2415 -36
  7. package/dist/widgets/webwriter-mark.js +2471 -61
  8. package/dist/widgets/webwriter-order-item.js +2447 -68
  9. package/dist/widgets/webwriter-order.js +4990 -135
  10. package/dist/widgets/webwriter-pairing-item.js +1581 -48
  11. package/dist/widgets/webwriter-pairing.js +3686 -72
  12. package/dist/widgets/webwriter-quiz.js +6938 -76
  13. package/dist/widgets/webwriter-speech.js +6265 -76
  14. package/dist/widgets/webwriter-task-explainer.js +1564 -31
  15. package/dist/widgets/webwriter-task-hint.js +1523 -28
  16. package/dist/widgets/webwriter-task-prompt.js +1586 -29
  17. package/dist/widgets/webwriter-task.js +7132 -101
  18. package/dist/widgets/webwriter-text.js +4044 -54
  19. package/package.json +200 -200
  20. package/src/lib/combobox.ts +455 -455
  21. package/src/snippets/choice.html +7 -7
  22. package/src/snippets/cloze.html +3 -3
  23. package/src/snippets/mark.html +3 -3
  24. package/src/snippets/order.html +7 -7
  25. package/src/snippets/pairing.html +9 -9
  26. package/src/snippets/speech.html +3 -3
  27. package/src/snippets/text.html +3 -3
  28. package/src/snippets/wordsearch.html +3 -3
  29. package/src/widgets/webwriter-choice-item.ts +250 -250
  30. package/src/widgets/webwriter-choice.ts +309 -309
  31. package/src/widgets/webwriter-cloze-gap.ts +216 -216
  32. package/src/widgets/webwriter-cloze.ts +135 -135
  33. package/src/widgets/webwriter-mark.ts +434 -434
  34. package/src/widgets/webwriter-order-item.ts +444 -444
  35. package/src/widgets/webwriter-order.ts +272 -272
  36. package/src/widgets/webwriter-pairing-item.ts +155 -155
  37. package/src/widgets/webwriter-pairing.ts +187 -187
  38. package/src/widgets/webwriter-quiz.ts +280 -280
  39. package/src/widgets/webwriter-speech.ts +267 -267
  40. package/src/widgets/webwriter-task-explainer.ts +37 -37
  41. package/src/widgets/webwriter-task-hint.ts +16 -16
  42. package/src/widgets/webwriter-task-prompt.ts +17 -17
  43. package/src/widgets/webwriter-task.ts +543 -543
  44. package/src/widgets/webwriter-text.ts +130 -130
  45. package/tsconfig.json +6 -6
@@ -1,445 +1,445 @@
1
- import {css, html, PropertyValues} from "lit"
2
- import {styleMap} from "lit/directives/style-map.js"
3
- import {LitElementWw} from "@webwriter/lit"
4
- import {customElement, property, query} from "lit/decorators.js"
5
-
6
- import SlIconButton from "@shoelace-style/shoelace/dist/components/icon-button/icon-button.component.js"
7
-
8
- import IconArrowsMove from "bootstrap-icons/icons/arrows-move.svg"
9
- import IconArrowUp from "bootstrap-icons/icons/arrow-up.svg"
10
- import IconArrowDown from "bootstrap-icons/icons/arrow-down.svg"
11
- import IconTrash from "bootstrap-icons/icons/trash.svg"
12
- import "@shoelace-style/shoelace/dist/themes/light.css"
13
- import { keyed } from "lit/directives/keyed.js"
14
- import { ifDefined } from "lit/directives/if-defined.js"
15
-
16
- import IconX from "bootstrap-icons/icons/x.svg"
17
- import IconCheck from "bootstrap-icons/icons/check.svg"
18
- import { shuffle } from "./webwriter-choice"
19
-
20
- declare global {interface HTMLElementTagNameMap {
21
- "webwriter-order-item": WebwriterOrderItem;
22
- }}
23
-
24
- function exchangeElements<A extends Node, B extends Node>(a: A, b: B) {
25
- var clonedA = a.cloneNode(true) as A
26
- var clonedB = b.cloneNode(true) as B
27
- b.parentNode.replaceChild(clonedA, b)
28
- a.parentNode.replaceChild(clonedB, a)
29
- return clonedA
30
- }
31
-
32
- @customElement("webwriter-order-item")
33
- export class WebwriterOrderItem extends LitElementWw {
34
-
35
- static localization = {}
36
-
37
- static scopedElements = {
38
- "sl-icon-button": SlIconButton
39
- }
40
-
41
- @property({attribute: true, reflect: true, converter: {toAttribute: (v: boolean) => v? "true": "false", fromAttribute: (attr: string) => attr === "true"}})
42
- accessor draggable = true
43
-
44
- msg = (str: string) => this.lang in WebwriterOrderItem.localization? WebwriterOrderItem.localization[this.lang][str] ?? str: str
45
-
46
- get elementIndex() {
47
- return Array.from(this.parentElement?.children ?? []).indexOf(this)
48
- }
49
-
50
- getSibling(i: number) {
51
- return this.parentElement.children.item(i)
52
- }
53
-
54
- emitClearDropPreview = () => {
55
- this.dispatchEvent(new CustomEvent("webwriter-clear-drop-preview", {bubbles: true}))
56
- }
57
-
58
- connectedCallback(): void {
59
- super.connectedCallback()
60
- this.dropPreview = undefined
61
- this.addEventListener("dragstart", e => {
62
- this.dropPreview = undefined
63
- e.dataTransfer.setData("text/html", this.outerHTML)
64
- e.dataTransfer.setData("text/plain", this.id)
65
- // setTimeout(() => this.baseEl.style.visibility = "hidden", 0)
66
- e.stopPropagation()
67
- })
68
- this.addEventListener("drop", e => {
69
- e.stopPropagation()
70
- const id = e.dataTransfer.getData("text/plain")
71
- const inTopHalf = e.offsetY < this.offsetHeight / 2
72
- const inLeftHalf = e.offsetX < this.offsetWidth / 2
73
- const el = document.querySelector(`webwriter-order-item#${id}`)
74
- const offset = (this.layout === "tiles"? inLeftHalf: inTopHalf)? 0: 1
75
- el.dispatchEvent(new CustomEvent("ww-moveto", {bubbles: true, detail: {i: this.elementIndex + offset}}))
76
- // this.insertAdjacentHTML(inTopHalf? "beforebegin": "afterend", html)
77
- }, {passive: true})
78
- this.addEventListener("dragover", e => {
79
- const inTopHalf = e.offsetY < this.offsetHeight / 2
80
- const inLeftHalf = e.offsetX < this.offsetWidth / 2
81
- if(this.layout === "tiles") {
82
- this.dropPreview = inLeftHalf? "left": "right"
83
- }
84
- else {
85
- this.dropPreview = inTopHalf? "top": "bottom"
86
- }
87
- e.preventDefault()
88
- e.stopImmediatePropagation()
89
- })
90
- this.addEventListener("dragleave", () => this.emitClearDropPreview(), {passive: true})
91
- document.addEventListener("dragend", (e) => {
92
- // (e.target as HTMLElement)?.remove()
93
- this.emitClearDropPreview()
94
- }, {passive: true})
95
- }
96
-
97
- @property({type: String, attribute: true, reflect: true})
98
- accessor dropPreview: "top" | "bottom" | "left" | "right" | undefined = undefined
99
-
100
- @property({type: String, attribute: true, reflect: true})
101
- accessor layout: "list" | "tiles" = "list"
102
-
103
- @property({type: Number, attribute: false})
104
- accessor validOrder: number | undefined
105
-
106
- @property({type: Boolean, attribute: true, reflect: true})
107
- accessor hideOrderButtons = false
108
-
109
- static styles = css`
110
-
111
- :host {
112
- position: relative;
113
- }
114
-
115
- :host(:is([contenteditable=true], [contenteditable=""])) .user-only {
116
- display: none;
117
- }
118
-
119
- ::marker {
120
- font-weight: bold;
121
- }
122
-
123
- :host(:is([contenteditable=true], [contenteditable=""])) ::marker {
124
- color: var(--sl-color-success-800);
125
- }
126
-
127
- ol {
128
- padding-left: 0;
129
- margin: 0;
130
- width: 100%;
131
- position: relative;
132
- }
133
-
134
- .solution {
135
- display: flex;
136
- align-items: center;
137
- justify-content: center;
138
- position: absolute;
139
- top: -7px;
140
- left: -13px;
141
- border: 2px solid var(--sl-color-gray-500);
142
- border-radius: 100%;
143
- width: 15px;
144
- height: 15px;
145
- font-size: 10px;
146
- z-index: 1;
147
- color: white;
148
- font-weight: bold;
149
-
150
- &[data-valid] {
151
- background: var(--sl-color-success-600);
152
- }
153
-
154
- &:not([data-valid]) {
155
- background: var(--sl-color-danger-600);
156
- }
157
- }
158
-
159
-
160
- :host([layout=tiles]) {
161
- border: 2px solid var(--sl-color-gray-500);
162
- border-radius: 5px;
163
- aspect-ratio: 1;
164
- min-width: 125px;
165
- width: 125px;
166
- max-width: 350px;
167
- min-height: 125px;
168
- height: 125px;
169
- max-height: 350px;
170
-
171
- & .solution {
172
- left: unset;
173
- top: -10px;
174
- right: -10px;
175
- width: 20px;
176
- height: 20px;
177
- font-size: 15px;
178
- }
179
-
180
- & ol[part=base] {
181
- padding-left: 0;
182
- margin: 0;
183
- aspect-ratio: 1;
184
- min-width: 125px;
185
- width: 125px;
186
- max-width: 350px;
187
- min-height: 125px;
188
- height: 125px;
189
- max-height: 350px;
190
- overflow-y: auto;
191
- scrollbar-width: thin;
192
- resize: both;
193
- }
194
-
195
- & ::slotted(:is(picture, audio, video, img, iframe)) {
196
- height: 100%;
197
- width: 100%;
198
- }
199
-
200
- & ::slotted(:not(:is(picture, audio, video, img, iframe))) {
201
- margin: 5px !important;
202
- }
203
- }
204
-
205
- :host(:not([layout=tiles])) {
206
-
207
- width: 100% !important;
208
- height: unset !important;
209
-
210
- & ol[part=base] {
211
- width: 100% !important;
212
- height: auto !important;
213
- }
214
- }
215
-
216
- li {
217
- list-style-type: inherit;
218
- list-style-position: inside;
219
- flex-direction: row;
220
- align-items: center;
221
- font-size: var(--sl-input-font-size-medium);
222
- cursor: move;
223
- }
224
-
225
- :host([droppreview])::before {
226
- content: "";
227
- position: absolute;
228
- background: var(--sl-color-primary-600);
229
- }
230
-
231
- :host([droppreview=top])::before {
232
- height: 2px;
233
- width: 100%;
234
- top: -2px;
235
- }
236
-
237
- :host([droppreview=bottom])::before {
238
- height: 2px;
239
- width: 100%;
240
- bottom: -2px;
241
- }
242
-
243
- :host([droppreview=left])::before {
244
- left: -17px;
245
- height: 100%;
246
- width: 15px;
247
- }
248
-
249
- :host([droppreview=right])::before {
250
- right: -17px;
251
- height: 100%;
252
- width: 15px;
253
- }
254
-
255
- slot {
256
- display: inline-block;
257
- width: calc(100% - var(--offset));
258
- cursor: text;
259
- }
260
-
261
- #order-buttons {
262
- position: absolute;
263
- right: 0;
264
- top: 0;
265
-
266
- & .order-button::part(base) {
267
- padding: 0;
268
- }
269
- }
270
-
271
- :host([hideorderbuttons]) #order-buttons {
272
- display: none;
273
- }
274
-
275
- #handle {
276
- box-sizing: border-box;
277
- border-radius: 2px;
278
- position: absolute;
279
- left: 0px;
280
- bottom: 0px;
281
- width: 20px;
282
- height: 25px;
283
- display: inline-block;
284
- overflow: hidden;
285
- line-height: 5px;
286
- padding: 1.5px 1px;
287
- padding-bottom: 0;
288
- cursor: move;
289
- vertical-align: middle;
290
- font-size: 12px;
291
- font-family: sans-serif;
292
- letter-spacing: 2px;
293
- color: #cccccc;
294
- text-shadow: 1px 0 1px black;
295
- font-weight: 600;
296
- opacity: 0.95;
297
- transform: rotate(135deg);
298
- }
299
- #handle::after {
300
- content: '.. .. ..';
301
- }
302
-
303
- :host(:not([layout=tiles])) :is(#handle, #count) {
304
- display: none;
305
- }
306
-
307
- :host(:not([layout=list])) :is(#up, #down) {
308
- display: none;
309
- }
310
-
311
- :host([layout=tiles]) #main-li {
312
- display: contents;
313
- }
314
-
315
- :host([layout=tiles]) slot {
316
- width: 100%;
317
- box-sizing: border-box;
318
- }
319
-
320
-
321
- #count {
322
- display: flex;
323
- justify-content: center;
324
- align-items: center;
325
- display: inline-block;
326
- width: 3ch;
327
- position: absolute;
328
- bottom: -14px;
329
- left: -14px;
330
- z-index: 10;
331
- & li {
332
- display: list-item;
333
- width: 4ch;
334
- height: 4ch;
335
- font-size: 12px;
336
- font-weight: bold;
337
- border-radius: 100%;
338
- border: 2px solid var(--sl-color-gray-600);
339
- color: var(--sl-color-gray-600);
340
- box-sizing: border-box;
341
- background: white;
342
- padding-left: 7px;
343
- padding-top: 4px;
344
- overflow: hidden;
345
-
346
- &[data-two-digit] {
347
- padding-left: 4px;
348
- }
349
- }
350
-
351
- }
352
- `
353
-
354
- handleClick = (e: PointerEvent, up=false) => {
355
- if((e.target as HTMLElement).id === "up") {
356
- this.dispatchEvent(new CustomEvent("ww-moveup", {bubbles: true}))
357
- }
358
- else {
359
- this.dispatchEvent(new CustomEvent("ww-movedown", {bubbles: true}))
360
- }
361
- e.stopPropagation()
362
- }
363
-
364
- @query("[part=base]")
365
- accessor baseEl: HTMLElement
366
-
367
- @query("#content")
368
- accessor contentSlotEl: HTMLSlotElement
369
-
370
- observer: MutationObserver
371
-
372
- protected async updated(_changedProperties: PropertyValues) {
373
- if(_changedProperties.has("layout") && this.layout === "list") {
374
- console.log("switched to list")
375
- this.syncSize(true)
376
- this.observer?.disconnect()
377
- this.requestUpdate()
378
- }
379
- else if(_changedProperties.has("layout") && this.layout === "tiles") {
380
- this.syncSize()
381
- if(!this.observer) {
382
- this.observer = new MutationObserver(() => this.syncSize())
383
- this.observer.observe(this.baseEl, {attributeFilter: ["style"], attributes: true})
384
- }
385
- }
386
- }
387
-
388
- protected firstUpdated(_changedProperties: PropertyValues): void {
389
- this.requestUpdate()
390
- }
391
-
392
- syncSize(clear=false) {
393
- if(this.baseEl && !clear) {
394
- this.style.width = this.baseEl.style.width
395
- this.style.height = this.baseEl.style.height
396
- }
397
- else if(this.baseEl && clear) {
398
- this.style.width = this.baseEl.style.width = null
399
- this.style.height = this.baseEl.style.height = null
400
- }
401
- }
402
-
403
- disconnectedCallback(): void {
404
- super.disconnectedCallback()
405
- this.observer?.disconnect()
406
- this.observer = undefined
407
- }
408
-
409
- get nextElementInOrder() {
410
- return Array.from(this?.parentElement?.children ?? []).find((el: HTMLElement) => {
411
- const elOrder = parseInt(getComputedStyle(el).order)
412
- return elOrder === this.elementIndex + 1
413
- })
414
- }
415
-
416
- get prevElementInOrder() {
417
- return Array.from(this?.parentElement?.children ?? []).find((el: HTMLElement) => {
418
- const elOrder = parseInt(getComputedStyle(el).order)
419
- return elOrder === this.elementIndex - 1
420
- })
421
- }
422
-
423
- get inCorrectPosition() {
424
- return this.elementIndex === this.validOrder
425
- }
426
-
427
- render() {
428
- const solution = this.validOrder !== undefined? html`<span class="solution" ?data-valid=${this.elementIndex === this.validOrder}>${this.validOrder + 1}</span>`: null
429
- return html`<ol part="base" start=${this.elementIndex + 1}>
430
- ${this.layout === "list"? solution: null}
431
- <li id="main-li">
432
- <slot id="content" style=${styleMap({"--ww-placeholder": `"${this.msg("Option")}"`, "--offset": `${this.contentSlotEl?.offsetLeft + 1}px`})}></slot>
433
- </li>
434
- <div id="order-buttons" part="order-buttons" draggable="false">
435
- <sl-icon-button ?disabled=${!this.previousElementSibling} id="up" class="order-button user-only" src=${IconArrowUp} @click=${this.handleClick} @mouseup=${() => this.draggable = true}></sl-icon-button>
436
- <sl-icon-button ?disabled=${!this.nextElementSibling} id="down" class="order-button user-only" src=${IconArrowDown} @click=${this.handleClick}></sl-icon-button>
437
- </div>
438
- </ol>
439
- <ol id="count" start=${this.elementIndex + 1}>
440
- <li ?data-two-digit=${this.elementIndex + 1 >= 10}></li>
441
- ${this.layout === "tiles"? solution: null}
442
- </ol>
443
- <div id="handle"></div>`
444
- }
1
+ import {css, html, PropertyValues} from "lit"
2
+ import {styleMap} from "lit/directives/style-map.js"
3
+ import {LitElementWw} from "@webwriter/lit"
4
+ import {customElement, property, query} from "lit/decorators.js"
5
+
6
+ import SlIconButton from "@shoelace-style/shoelace/dist/components/icon-button/icon-button.component.js"
7
+
8
+ import IconArrowsMove from "bootstrap-icons/icons/arrows-move.svg"
9
+ import IconArrowUp from "bootstrap-icons/icons/arrow-up.svg"
10
+ import IconArrowDown from "bootstrap-icons/icons/arrow-down.svg"
11
+ import IconTrash from "bootstrap-icons/icons/trash.svg"
12
+ import "@shoelace-style/shoelace/dist/themes/light.css"
13
+ import { keyed } from "lit/directives/keyed.js"
14
+ import { ifDefined } from "lit/directives/if-defined.js"
15
+
16
+ import IconX from "bootstrap-icons/icons/x.svg"
17
+ import IconCheck from "bootstrap-icons/icons/check.svg"
18
+ import { shuffle } from "./webwriter-choice"
19
+
20
+ declare global {interface HTMLElementTagNameMap {
21
+ "webwriter-order-item": WebwriterOrderItem;
22
+ }}
23
+
24
+ function exchangeElements<A extends Node, B extends Node>(a: A, b: B) {
25
+ var clonedA = a.cloneNode(true) as A
26
+ var clonedB = b.cloneNode(true) as B
27
+ b.parentNode.replaceChild(clonedA, b)
28
+ a.parentNode.replaceChild(clonedB, a)
29
+ return clonedA
30
+ }
31
+
32
+ @customElement("webwriter-order-item")
33
+ export class WebwriterOrderItem extends LitElementWw {
34
+
35
+ static localization = {}
36
+
37
+ static scopedElements = {
38
+ "sl-icon-button": SlIconButton
39
+ }
40
+
41
+ @property({attribute: true, reflect: true, converter: {toAttribute: (v: boolean) => v? "true": "false", fromAttribute: (attr: string) => attr === "true"}})
42
+ accessor draggable = true
43
+
44
+ msg = (str: string) => this.lang in WebwriterOrderItem.localization? WebwriterOrderItem.localization[this.lang][str] ?? str: str
45
+
46
+ get elementIndex() {
47
+ return Array.from(this.parentElement?.children ?? []).indexOf(this)
48
+ }
49
+
50
+ getSibling(i: number) {
51
+ return this.parentElement.children.item(i)
52
+ }
53
+
54
+ emitClearDropPreview = () => {
55
+ this.dispatchEvent(new CustomEvent("webwriter-clear-drop-preview", {bubbles: true}))
56
+ }
57
+
58
+ connectedCallback(): void {
59
+ super.connectedCallback()
60
+ this.dropPreview = undefined
61
+ this.addEventListener("dragstart", e => {
62
+ this.dropPreview = undefined
63
+ e.dataTransfer.setData("text/html", this.outerHTML)
64
+ e.dataTransfer.setData("text/plain", this.id)
65
+ // setTimeout(() => this.baseEl.style.visibility = "hidden", 0)
66
+ e.stopPropagation()
67
+ })
68
+ this.addEventListener("drop", e => {
69
+ e.stopPropagation()
70
+ const id = e.dataTransfer.getData("text/plain")
71
+ const inTopHalf = e.offsetY < this.offsetHeight / 2
72
+ const inLeftHalf = e.offsetX < this.offsetWidth / 2
73
+ const el = document.querySelector(`webwriter-order-item#${id}`)
74
+ const offset = (this.layout === "tiles"? inLeftHalf: inTopHalf)? 0: 1
75
+ el.dispatchEvent(new CustomEvent("ww-moveto", {bubbles: true, detail: {i: this.elementIndex + offset}}))
76
+ // this.insertAdjacentHTML(inTopHalf? "beforebegin": "afterend", html)
77
+ }, {passive: true})
78
+ this.addEventListener("dragover", e => {
79
+ const inTopHalf = e.offsetY < this.offsetHeight / 2
80
+ const inLeftHalf = e.offsetX < this.offsetWidth / 2
81
+ if(this.layout === "tiles") {
82
+ this.dropPreview = inLeftHalf? "left": "right"
83
+ }
84
+ else {
85
+ this.dropPreview = inTopHalf? "top": "bottom"
86
+ }
87
+ e.preventDefault()
88
+ e.stopImmediatePropagation()
89
+ })
90
+ this.addEventListener("dragleave", () => this.emitClearDropPreview(), {passive: true})
91
+ document.addEventListener("dragend", (e) => {
92
+ // (e.target as HTMLElement)?.remove()
93
+ this.emitClearDropPreview()
94
+ }, {passive: true})
95
+ }
96
+
97
+ @property({type: String, attribute: true, reflect: true})
98
+ accessor dropPreview: "top" | "bottom" | "left" | "right" | undefined = undefined
99
+
100
+ @property({type: String, attribute: true, reflect: true})
101
+ accessor layout: "list" | "tiles" = "list"
102
+
103
+ @property({type: Number, attribute: false})
104
+ accessor validOrder: number | undefined
105
+
106
+ @property({type: Boolean, attribute: true, reflect: true})
107
+ accessor hideOrderButtons = false
108
+
109
+ static styles = css`
110
+
111
+ :host {
112
+ position: relative;
113
+ }
114
+
115
+ :host(:is([contenteditable=true], [contenteditable=""])) .user-only {
116
+ display: none;
117
+ }
118
+
119
+ ::marker {
120
+ font-weight: bold;
121
+ }
122
+
123
+ :host(:is([contenteditable=true], [contenteditable=""])) ::marker {
124
+ color: var(--sl-color-success-800);
125
+ }
126
+
127
+ ol {
128
+ padding-left: 0;
129
+ margin: 0;
130
+ width: 100%;
131
+ position: relative;
132
+ }
133
+
134
+ .solution {
135
+ display: flex;
136
+ align-items: center;
137
+ justify-content: center;
138
+ position: absolute;
139
+ top: -7px;
140
+ left: -13px;
141
+ border: 2px solid var(--sl-color-gray-500);
142
+ border-radius: 100%;
143
+ width: 15px;
144
+ height: 15px;
145
+ font-size: 10px;
146
+ z-index: 1;
147
+ color: white;
148
+ font-weight: bold;
149
+
150
+ &[data-valid] {
151
+ background: var(--sl-color-success-600);
152
+ }
153
+
154
+ &:not([data-valid]) {
155
+ background: var(--sl-color-danger-600);
156
+ }
157
+ }
158
+
159
+
160
+ :host([layout=tiles]) {
161
+ border: 2px solid var(--sl-color-gray-500);
162
+ border-radius: 5px;
163
+ aspect-ratio: 1;
164
+ min-width: 125px;
165
+ width: 125px;
166
+ max-width: 350px;
167
+ min-height: 125px;
168
+ height: 125px;
169
+ max-height: 350px;
170
+
171
+ & .solution {
172
+ left: unset;
173
+ top: -10px;
174
+ right: -10px;
175
+ width: 20px;
176
+ height: 20px;
177
+ font-size: 15px;
178
+ }
179
+
180
+ & ol[part=base] {
181
+ padding-left: 0;
182
+ margin: 0;
183
+ aspect-ratio: 1;
184
+ min-width: 125px;
185
+ width: 125px;
186
+ max-width: 350px;
187
+ min-height: 125px;
188
+ height: 125px;
189
+ max-height: 350px;
190
+ overflow-y: auto;
191
+ scrollbar-width: thin;
192
+ resize: both;
193
+ }
194
+
195
+ & ::slotted(:is(picture, audio, video, img, iframe)) {
196
+ height: 100%;
197
+ width: 100%;
198
+ }
199
+
200
+ & ::slotted(:not(:is(picture, audio, video, img, iframe))) {
201
+ margin: 5px !important;
202
+ }
203
+ }
204
+
205
+ :host(:not([layout=tiles])) {
206
+
207
+ width: 100% !important;
208
+ height: unset !important;
209
+
210
+ & ol[part=base] {
211
+ width: 100% !important;
212
+ height: auto !important;
213
+ }
214
+ }
215
+
216
+ li {
217
+ list-style-type: inherit;
218
+ list-style-position: inside;
219
+ flex-direction: row;
220
+ align-items: center;
221
+ font-size: var(--sl-input-font-size-medium);
222
+ cursor: move;
223
+ }
224
+
225
+ :host([droppreview])::before {
226
+ content: "";
227
+ position: absolute;
228
+ background: var(--sl-color-primary-600);
229
+ }
230
+
231
+ :host([droppreview=top])::before {
232
+ height: 2px;
233
+ width: 100%;
234
+ top: -2px;
235
+ }
236
+
237
+ :host([droppreview=bottom])::before {
238
+ height: 2px;
239
+ width: 100%;
240
+ bottom: -2px;
241
+ }
242
+
243
+ :host([droppreview=left])::before {
244
+ left: -17px;
245
+ height: 100%;
246
+ width: 15px;
247
+ }
248
+
249
+ :host([droppreview=right])::before {
250
+ right: -17px;
251
+ height: 100%;
252
+ width: 15px;
253
+ }
254
+
255
+ slot {
256
+ display: inline-block;
257
+ width: calc(100% - var(--offset));
258
+ cursor: text;
259
+ }
260
+
261
+ #order-buttons {
262
+ position: absolute;
263
+ right: 0;
264
+ top: 0;
265
+
266
+ & .order-button::part(base) {
267
+ padding: 0;
268
+ }
269
+ }
270
+
271
+ :host([hideorderbuttons]) #order-buttons {
272
+ display: none;
273
+ }
274
+
275
+ #handle {
276
+ box-sizing: border-box;
277
+ border-radius: 2px;
278
+ position: absolute;
279
+ left: 0px;
280
+ bottom: 0px;
281
+ width: 20px;
282
+ height: 25px;
283
+ display: inline-block;
284
+ overflow: hidden;
285
+ line-height: 5px;
286
+ padding: 1.5px 1px;
287
+ padding-bottom: 0;
288
+ cursor: move;
289
+ vertical-align: middle;
290
+ font-size: 12px;
291
+ font-family: sans-serif;
292
+ letter-spacing: 2px;
293
+ color: #cccccc;
294
+ text-shadow: 1px 0 1px black;
295
+ font-weight: 600;
296
+ opacity: 0.95;
297
+ transform: rotate(135deg);
298
+ }
299
+ #handle::after {
300
+ content: '.. .. ..';
301
+ }
302
+
303
+ :host(:not([layout=tiles])) :is(#handle, #count) {
304
+ display: none;
305
+ }
306
+
307
+ :host(:not([layout=list])) :is(#up, #down) {
308
+ display: none;
309
+ }
310
+
311
+ :host([layout=tiles]) #main-li {
312
+ display: contents;
313
+ }
314
+
315
+ :host([layout=tiles]) slot {
316
+ width: 100%;
317
+ box-sizing: border-box;
318
+ }
319
+
320
+
321
+ #count {
322
+ display: flex;
323
+ justify-content: center;
324
+ align-items: center;
325
+ display: inline-block;
326
+ width: 3ch;
327
+ position: absolute;
328
+ bottom: -14px;
329
+ left: -14px;
330
+ z-index: 10;
331
+ & li {
332
+ display: list-item;
333
+ width: 4ch;
334
+ height: 4ch;
335
+ font-size: 12px;
336
+ font-weight: bold;
337
+ border-radius: 100%;
338
+ border: 2px solid var(--sl-color-gray-600);
339
+ color: var(--sl-color-gray-600);
340
+ box-sizing: border-box;
341
+ background: white;
342
+ padding-left: 7px;
343
+ padding-top: 4px;
344
+ overflow: hidden;
345
+
346
+ &[data-two-digit] {
347
+ padding-left: 4px;
348
+ }
349
+ }
350
+
351
+ }
352
+ `
353
+
354
+ handleClick = (e: PointerEvent, up=false) => {
355
+ if((e.target as HTMLElement).id === "up") {
356
+ this.dispatchEvent(new CustomEvent("ww-moveup", {bubbles: true}))
357
+ }
358
+ else {
359
+ this.dispatchEvent(new CustomEvent("ww-movedown", {bubbles: true}))
360
+ }
361
+ e.stopPropagation()
362
+ }
363
+
364
+ @query("[part=base]")
365
+ accessor baseEl: HTMLElement
366
+
367
+ @query("#content")
368
+ accessor contentSlotEl: HTMLSlotElement
369
+
370
+ observer: MutationObserver
371
+
372
+ protected async updated(_changedProperties: PropertyValues) {
373
+ if(_changedProperties.has("layout") && this.layout === "list") {
374
+ console.log("switched to list")
375
+ this.syncSize(true)
376
+ this.observer?.disconnect()
377
+ this.requestUpdate()
378
+ }
379
+ else if(_changedProperties.has("layout") && this.layout === "tiles") {
380
+ this.syncSize()
381
+ if(!this.observer) {
382
+ this.observer = new MutationObserver(() => this.syncSize())
383
+ this.observer.observe(this.baseEl, {attributeFilter: ["style"], attributes: true})
384
+ }
385
+ }
386
+ }
387
+
388
+ protected firstUpdated(_changedProperties: PropertyValues): void {
389
+ this.requestUpdate()
390
+ }
391
+
392
+ syncSize(clear=false) {
393
+ if(this.baseEl && !clear) {
394
+ this.style.width = this.baseEl.style.width
395
+ this.style.height = this.baseEl.style.height
396
+ }
397
+ else if(this.baseEl && clear) {
398
+ this.style.width = this.baseEl.style.width = null
399
+ this.style.height = this.baseEl.style.height = null
400
+ }
401
+ }
402
+
403
+ disconnectedCallback(): void {
404
+ super.disconnectedCallback()
405
+ this.observer?.disconnect()
406
+ this.observer = undefined
407
+ }
408
+
409
+ get nextElementInOrder() {
410
+ return Array.from(this?.parentElement?.children ?? []).find((el: HTMLElement) => {
411
+ const elOrder = parseInt(getComputedStyle(el).order)
412
+ return elOrder === this.elementIndex + 1
413
+ })
414
+ }
415
+
416
+ get prevElementInOrder() {
417
+ return Array.from(this?.parentElement?.children ?? []).find((el: HTMLElement) => {
418
+ const elOrder = parseInt(getComputedStyle(el).order)
419
+ return elOrder === this.elementIndex - 1
420
+ })
421
+ }
422
+
423
+ get inCorrectPosition() {
424
+ return this.elementIndex === this.validOrder
425
+ }
426
+
427
+ render() {
428
+ const solution = this.validOrder !== undefined? html`<span class="solution" ?data-valid=${this.elementIndex === this.validOrder}>${this.validOrder + 1}</span>`: null
429
+ return html`<ol part="base" start=${this.elementIndex + 1}>
430
+ ${this.layout === "list"? solution: null}
431
+ <li id="main-li">
432
+ <slot id="content" style=${styleMap({"--ww-placeholder": `"${this.msg("Option")}"`, "--offset": `${this.contentSlotEl?.offsetLeft + 1}px`})}></slot>
433
+ </li>
434
+ <div id="order-buttons" part="order-buttons" draggable="false">
435
+ <sl-icon-button ?disabled=${!this.previousElementSibling} id="up" class="order-button user-only" src=${IconArrowUp} @click=${this.handleClick} @mouseup=${() => this.draggable = true}></sl-icon-button>
436
+ <sl-icon-button ?disabled=${!this.nextElementSibling} id="down" class="order-button user-only" src=${IconArrowDown} @click=${this.handleClick}></sl-icon-button>
437
+ </div>
438
+ </ol>
439
+ <ol id="count" start=${this.elementIndex + 1}>
440
+ <li ?data-two-digit=${this.elementIndex + 1 >= 10}></li>
441
+ ${this.layout === "tiles"? solution: null}
442
+ </ol>
443
+ <div id="handle"></div>`
444
+ }
445
445
  }