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