@webwriter/quiz 1.1.2 → 1.1.5

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