@webwriter/quiz 1.1.3 → 2.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 (167) hide show
  1. package/.github/workflows/publish.yml +8 -0
  2. package/LICENSE +6 -6
  3. package/README.md +778 -0
  4. package/assets/icons/grip-horizontal.svg +8 -0
  5. package/assets/icons/grip-vertical.svg +8 -0
  6. package/assets/icons/truefalse.svg +6 -0
  7. package/custom-elements.json +2779 -0
  8. package/custom.d.ts +180 -176
  9. package/dist/api/index.d.ts +27 -0
  10. package/dist/api/index.js +40 -0
  11. package/dist/migrate.js +1434 -0
  12. package/dist/widgets/webwriter-choice-item.js +4036 -3895
  13. package/dist/widgets/webwriter-choice.js +2269 -5821
  14. package/dist/widgets/webwriter-gap-item.js +3987 -0
  15. package/dist/widgets/webwriter-gap.js +2908 -0
  16. package/dist/widgets/webwriter-mark.js +4335 -3489
  17. package/dist/widgets/webwriter-order-item.js +4014 -3306
  18. package/dist/widgets/webwriter-order.js +2944 -4386
  19. package/dist/widgets/webwriter-pairing-item.js +1058 -2421
  20. package/dist/widgets/webwriter-pairing.js +2993 -4383
  21. package/dist/widgets/webwriter-quiz-hint.js +2469 -0
  22. package/dist/widgets/webwriter-quiz.js +4933 -6099
  23. package/dist/widgets/webwriter-speech.js +6077 -5230
  24. package/dist/widgets/webwriter-task-prompt.js +4775 -2165
  25. package/dist/widgets/webwriter-task.js +2364 -8382
  26. package/dist/widgets/webwriter-text.js +4169 -4337
  27. package/dist/widgets/webwriter-true-false.js +4674 -0
  28. package/editing-config.json +106 -0
  29. package/lit-localize.json +21 -15
  30. package/localization/de.xlf +407 -74
  31. package/localization/es.xlf +407 -69
  32. package/localization/fr.xlf +407 -69
  33. package/localization/generated/de.ts +112 -29
  34. package/localization/generated/es.ts +112 -29
  35. package/localization/generated/fr.ts +112 -29
  36. package/localization/generated/index.d.ts +5 -0
  37. package/localization/generated/index.js +2 -2
  38. package/localization/generated/it.ts +112 -29
  39. package/localization/generated/locale-codes.js +31 -79
  40. package/localization/generated/nl.ts +112 -29
  41. package/localization/it.xlf +407 -69
  42. package/localization/nl.xlf +407 -69
  43. package/package.json +119 -252
  44. package/scripts/apply-translations.mjs +97 -0
  45. package/src/api/index.ts +72 -0
  46. package/src/lib/artboard.ts +686 -0
  47. package/src/lib/draggable-grid.ts +432 -0
  48. package/src/lib/encrypted-property.ts +202 -0
  49. package/src/lib/hint-popup.ts +152 -0
  50. package/src/lib/utils.ts +97 -0
  51. package/src/lib/webwriter-quirks.ts +121 -0
  52. package/src/migrate/index.ts +622 -0
  53. package/src/migrate/legacy-solution.ts +362 -0
  54. package/src/snippets/order.html +9 -8
  55. package/src/widgets/choice/webwriter-choice-item.ts +316 -0
  56. package/src/widgets/choice/webwriter-choice.ts +328 -0
  57. package/src/widgets/choice/webwriter-true-false.ts +113 -0
  58. package/src/widgets/dnd/webwriter-dnd-draggable.ts +28 -0
  59. package/src/widgets/dnd/webwriter-dnd-dropzone.ts +9 -0
  60. package/src/widgets/dnd/webwriter-dnd.ts +36 -0
  61. package/src/widgets/gap/webwriter-gap-item.ts +250 -0
  62. package/src/widgets/gap/webwriter-gap.ts +447 -0
  63. package/src/widgets/order/webwriter-order-item.ts +243 -0
  64. package/src/widgets/order/webwriter-order.ts +282 -0
  65. package/src/widgets/pairing/webwriter-pairing-item.ts +46 -0
  66. package/src/widgets/pairing/webwriter-pairing.ts +934 -0
  67. package/src/widgets/speech/audio-player.ts +325 -0
  68. package/src/widgets/speech/audio-recorder.ts +285 -0
  69. package/src/widgets/speech/waveform.ts +113 -0
  70. package/src/widgets/speech/webwriter-speech.ts +87 -0
  71. package/src/widgets/webwriter-mark.ts +374 -432
  72. package/src/widgets/webwriter-quiz-hint.ts +36 -0
  73. package/src/widgets/webwriter-quiz.ts +320 -282
  74. package/src/widgets/webwriter-task-prompt.ts +49 -24
  75. package/src/widgets/webwriter-task.ts +216 -547
  76. package/src/widgets/webwriter-text.ts +291 -156
  77. package/tsconfig.api.json +12 -0
  78. package/tsconfig.json +8 -7
  79. package/dist/widgets/webwriter-choice.css +0 -444
  80. package/dist/widgets/webwriter-cloze-gap.css +0 -444
  81. package/dist/widgets/webwriter-cloze-gap.js +0 -5866
  82. package/dist/widgets/webwriter-cloze.css +0 -444
  83. package/dist/widgets/webwriter-cloze.js +0 -3786
  84. package/dist/widgets/webwriter-mark.css +0 -444
  85. package/dist/widgets/webwriter-order.css +0 -444
  86. package/dist/widgets/webwriter-pairing-item.css +0 -444
  87. package/dist/widgets/webwriter-pairing.css +0 -444
  88. package/dist/widgets/webwriter-quiz.css +0 -444
  89. package/dist/widgets/webwriter-task-explainer.css +0 -444
  90. package/dist/widgets/webwriter-task-explainer.js +0 -2872
  91. package/dist/widgets/webwriter-task-hint.css +0 -444
  92. package/dist/widgets/webwriter-task-hint.js +0 -2811
  93. package/dist/widgets/webwriter-task.css +0 -444
  94. package/dist/widgets/webwriter-text.css +0 -444
  95. package/localization/bg.xlf +0 -69
  96. package/localization/cs.xlf +0 -69
  97. package/localization/da.xlf +0 -69
  98. package/localization/el.xlf +0 -69
  99. package/localization/et.xlf +0 -69
  100. package/localization/fi.xlf +0 -69
  101. package/localization/generated/bg.ts +0 -30
  102. package/localization/generated/cs.ts +0 -30
  103. package/localization/generated/da.ts +0 -30
  104. package/localization/generated/el.ts +0 -30
  105. package/localization/generated/et.ts +0 -30
  106. package/localization/generated/fi.ts +0 -30
  107. package/localization/generated/hu.ts +0 -30
  108. package/localization/generated/id.ts +0 -30
  109. package/localization/generated/ja.ts +0 -30
  110. package/localization/generated/ko.ts +0 -30
  111. package/localization/generated/lt.ts +0 -30
  112. package/localization/generated/lv.ts +0 -30
  113. package/localization/generated/nb.ts +0 -30
  114. package/localization/generated/pl.ts +0 -30
  115. package/localization/generated/pt-BR.ts +0 -30
  116. package/localization/generated/pt-PT.ts +0 -30
  117. package/localization/generated/ro.ts +0 -30
  118. package/localization/generated/ru.ts +0 -30
  119. package/localization/generated/sk.ts +0 -30
  120. package/localization/generated/sl.ts +0 -30
  121. package/localization/generated/sv.ts +0 -30
  122. package/localization/generated/tr.ts +0 -30
  123. package/localization/generated/uk.ts +0 -30
  124. package/localization/generated/zh-hans.ts +0 -30
  125. package/localization/hu.xlf +0 -69
  126. package/localization/id.xlf +0 -69
  127. package/localization/ja.xlf +0 -69
  128. package/localization/ko.xlf +0 -69
  129. package/localization/lt.xlf +0 -69
  130. package/localization/lv.xlf +0 -69
  131. package/localization/nb.xlf +0 -69
  132. package/localization/pl.xlf +0 -69
  133. package/localization/pt-BR.xlf +0 -69
  134. package/localization/pt-PT.xlf +0 -69
  135. package/localization/ro.xlf +0 -69
  136. package/localization/ru.xlf +0 -69
  137. package/localization/sk.xlf +0 -69
  138. package/localization/sl.xlf +0 -69
  139. package/localization/sv.xlf +0 -69
  140. package/localization/tr.xlf +0 -69
  141. package/localization/uk.xlf +0 -69
  142. package/localization/zh-hans.xlf +0 -69
  143. package/src/lib/combobox.ts +0 -456
  144. package/src/lib/highlighter-fill.svg +0 -6
  145. package/src/snippets/Beispiel-Choice.html +0 -57
  146. package/src/snippets/Beispiel-Mark.html +0 -14
  147. package/src/snippets/Beispiel-Order.html +0 -52
  148. package/src/snippets/Beispiel-Text.html +0 -20
  149. package/src/snippets/blank.html +0 -1
  150. package/src/snippets/choice.html +0 -8
  151. package/src/snippets/cloze.html +0 -4
  152. package/src/snippets/mark.html +0 -4
  153. package/src/snippets/pairing.html +0 -10
  154. package/src/snippets/speech.html +0 -4
  155. package/src/snippets/text.html +0 -4
  156. package/src/snippets/wordsearch.html +0 -4
  157. package/src/widgets/webwriter-choice-item.ts +0 -254
  158. package/src/widgets/webwriter-choice.ts +0 -353
  159. package/src/widgets/webwriter-cloze-gap.ts +0 -216
  160. package/src/widgets/webwriter-cloze.ts +0 -136
  161. package/src/widgets/webwriter-order-item.ts +0 -441
  162. package/src/widgets/webwriter-order.ts +0 -327
  163. package/src/widgets/webwriter-pairing-item.ts +0 -161
  164. package/src/widgets/webwriter-pairing.ts +0 -193
  165. package/src/widgets/webwriter-speech.ts +0 -273
  166. package/src/widgets/webwriter-task-explainer.ts +0 -43
  167. package/src/widgets/webwriter-task-hint.ts +0 -23
@@ -1,327 +0,0 @@
1
- import {css, html, PropertyValues} from "lit"
2
- import {LitElementWw, option} from "@webwriter/lit"
3
- import {customElement, property, query, queryAssignedElements} from "lit/decorators.js"
4
-
5
- import SlButton from "@shoelace-style/shoelace/dist/components/button/button.component.js"
6
- import SlIcon from "@shoelace-style/shoelace/dist/components/icon/icon.component.js"
7
- import "@shoelace-style/shoelace/dist/themes/light.css"
8
- import { Sortable } from "@shopify/draggable"
9
- import IconPlus from "bootstrap-icons/icons/plus.svg"
10
-
11
- import { WebwriterOrderItem } from "./webwriter-order-item"
12
- import { ifDefined } from "lit/directives/if-defined.js"
13
-
14
- import LOCALIZE from "../../localization/generated"
15
- import {msg} from "@lit/localize"
16
-
17
- export function shuffle<T>(a: T[]) {
18
- for (let i = a.length - 1; i > 0; i--) {
19
- const j = Math.floor(Math.random() * (i + 1));
20
- [a[i], a[j]] = [a[j], a[i]];
21
- }
22
- return a;
23
- }
24
-
25
- declare global {interface HTMLElementTagNameMap {
26
- "webwriter-order": WebwriterOrder;
27
- }}
28
-
29
- @customElement("webwriter-order")
30
- export class WebwriterOrder extends LitElementWw {
31
-
32
- localize = LOCALIZE
33
-
34
- static scopedElements = {
35
- "sl-button": SlButton,
36
- "sl-icon": SlIcon
37
- }
38
-
39
- static styles = css`
40
- :host {
41
- display: flex !important;
42
- flex-direction: column;
43
- align-items: flex-start;
44
- counter-reset: orderItem;
45
- gap: 2px;
46
- }
47
-
48
- :host([layout=tiles]) {
49
- flex-direction: row;
50
- flex-wrap: wrap;
51
- gap: 15px;
52
- }
53
-
54
- :host(:is([contenteditable=true], [contenteditable=""])) {
55
- --text-color: var(--sl-color-success-700);
56
- }
57
-
58
- :host ::slotted(*::part(counter)) {
59
- counter-increment: orderItem;
60
- content: counter(orderItem) '. ';
61
- cursor: move;
62
- text-align: right;
63
- padding-right: 0.5em;
64
- min-width: 1.5em;
65
- color: var(--text-color, auto);
66
- }
67
-
68
- sl-button::part(label) {
69
- padding: 0;
70
- display: flex;
71
- flex-direction: row;
72
- align-items: center;
73
- }
74
-
75
- sl-button::part(base) {
76
- border: none;
77
- background: transparent;
78
- }
79
-
80
- sl-icon {
81
- width: 19px;
82
- height: 19px;
83
- padding: var(--sl-spacing-x-small);
84
- padding-left: 0;
85
- }
86
-
87
- #add-option {
88
- order: 2147483647;
89
- }
90
-
91
- #add-option:not(:hover)::part(base) {
92
- color: darkgray;
93
- }
94
-
95
- :host([layout=tiles]) #add-option {
96
- width: 125px;
97
- height: 125px;
98
- overflow: hidden;
99
- border: 2px solid var(--sl-color-gray-300);
100
- border-radius: 5px;
101
- padding: 5px;
102
- display: flex;
103
- align-items: center;
104
- justify-content: center;
105
- }
106
- // broken for some reason o.O
107
- /*
108
- :host(:not([contenteditable=true]):not([contenteditable=""])) .author-only {
109
- display: none;
110
- } */
111
- `
112
-
113
- get layout(): "list" | "tiles" {
114
- return this.children?.item(0)?.getAttribute("layout") as any ?? "list"
115
- }
116
-
117
- @property({type: String, attribute: true, reflect: true}) //@ts-ignore
118
- @option({
119
- type: "select",
120
- options: [
121
- {value: "list", label: {"en": "List"}},
122
- {value: "tiles", label: {"en": "Tiles"}}
123
- ]
124
- })
125
- set layout(value) {
126
- this.querySelectorAll("webwriter-order-item").forEach(el => el.setAttribute("layout", value))
127
- this.requestUpdate("layout")
128
- }
129
-
130
- get hideOrderButtons() {
131
- return !!this.items[0]?.hideOrderButtons
132
- }
133
-
134
- @property({type: Boolean, attribute: true, reflect: true}) //@ts-ignore
135
- @option({type: Boolean, label: {"en": "Hide order buttons", "de": "Pfeile zum Verschieben verstecken"}})
136
- set hideOrderButtons(value: boolean) {
137
- this.items.forEach(item => item.hideOrderButtons = value)
138
- }
139
-
140
- @property({type: String, attribute: true, reflect: true})
141
- @option({
142
- type: "select",
143
- label: {"de": "Lösung anzeigen", "en": "Show solution"},
144
- options: [
145
- {value: "right", label: {"en": "Right answers", "de": "Korrekte Antworten"}},
146
- {value: "both", label: {"en": "all (indicator)", "de": "Alle (Indikator)"}},
147
- {value: "full", label: {"en": "all (conclusive)", "de": "Alle (vollständig)"}}
148
- ],
149
- })
150
- accessor showSolution = "right"
151
-
152
- observer: MutationObserver
153
-
154
- connectedCallback() {
155
- super.connectedCallback()
156
- this.addEventListener("ww-moveup", this.handleMove)
157
- this.addEventListener("ww-movedown", this.handleMove)
158
- this.addEventListener("ww-moveto", this.handleMove)
159
- this.observer = new MutationObserver(() => {
160
- this.items.forEach(item => item.requestUpdate())
161
- if(this.isContentEditable) {
162
- this.solution = this.items.map(item => item.id)
163
- }
164
- this.clearDropPreviews()
165
- })
166
- this.observer.observe(this, {childList: true})
167
- // this.shadowRoot.adoptedStyleSheets = [...this.shadowRoot.adoptedStyleSheets, this.orderSheet]
168
- }
169
-
170
- serializedCallback() {
171
- this.shuffleItems()
172
- }
173
-
174
- disconnectedCallback(): void {
175
- super.disconnectedCallback()
176
- this.observer?.disconnect()
177
- this.observer = undefined
178
- }
179
-
180
- orderSheet = new CSSStyleSheet()
181
-
182
- addItem() {
183
- const orderItem = this.ownerDocument.createElement("webwriter-order-item")
184
- const p = this.ownerDocument.createElement("p")
185
- orderItem.appendChild(p)
186
- orderItem.setAttribute("layout", this.layout)
187
- this.append(orderItem)
188
- this.solution = [...this.#solution, orderItem.id]
189
- this.ownerDocument.getSelection().setBaseAndExtent(p, 0, p, 0)
190
- }
191
-
192
- shuffleItems() {
193
- const n = this.items.length
194
- const nums = shuffle([...(new Array(n)).keys()])
195
- this.innerHTML = nums.map(i => this.children.item(i).outerHTML).join("")
196
- }
197
-
198
- clearDropPreviews = () => {
199
- this.items.forEach(item => item.dropPreview = undefined)
200
- }
201
-
202
- @query("#items-slot")
203
- accessor itemsSlot: HTMLSlotElement
204
-
205
- @queryAssignedElements()
206
- accessor items: WebwriterOrderItem[]
207
-
208
- handleKeyDown(e: KeyboardEvent) {
209
- if(e.key == "ArrowDown") {
210
- e.stopPropagation()
211
- e.preventDefault()
212
- }
213
- else if(e.key === "ArrowUp") {
214
- e.stopPropagation()
215
- e.preventDefault()
216
- }
217
- }
218
-
219
- /*
220
- If contenteditable: Reorder solution -> Triggers orderSheet change
221
- If not contenteditable: Reorder value -> Triggers orderSheet change
222
- */
223
-
224
- moveChild(elem: HTMLElement, i: number) {
225
- /*
226
- let items = this.items.map(el => el !== elem? el: undefined)
227
- items.splice(i, 0, elem as WebwriterOrderItem)
228
- items = items.filter(child => child)
229
- this.replaceChildren(...items)
230
- this.items.forEach(item => item.requestUpdate())*/
231
- if(i === 0) {
232
- this.insertAdjacentElement("afterbegin", elem)
233
- }
234
- else if(i < this.items.length) {
235
- this.items[i].insertAdjacentElement("beforebegin", elem)
236
- }
237
- else {
238
- this.insertAdjacentElement("beforeend", elem)
239
- }
240
- }
241
-
242
- handleMove = (e: CustomEvent) => {
243
- const elem = e.target as HTMLElement
244
- const children = Array.from(this.children)
245
- const pos = children.indexOf(elem)
246
- let i: number
247
- if(e.type === "ww-moveup") {
248
- i = Math.max(0, pos - 1)
249
- }
250
- else if(e.type === "ww-movedown") {
251
- i = Math.min(children.length, pos + 2)
252
-
253
- }
254
- else if(e.type === "ww-moveto") {
255
- i = e.detail.i
256
- }
257
- this.moveChild(elem, i)
258
- }
259
-
260
-
261
- #solution: string[] = []
262
-
263
- get solution() {
264
- return this.#solution?.length? this.#solution: undefined
265
- }
266
-
267
- @property({attribute: false})
268
- set solution(value: string[]) {
269
- this.#solution = value
270
- if(value) {
271
- this.dispatchEvent(new CustomEvent("ww-answer-change", {
272
- bubbles: true,
273
- composed: true
274
- }))
275
- }
276
- }
277
-
278
-
279
- reportSolution() {
280
- this.solution.forEach((id, i) => (this.querySelector(`#${id}`) as any).validOrder = i)
281
- let wrongSolution: boolean = false
282
- this.items.forEach(item => {
283
- if(this.items.indexOf(item) != this.solution.indexOf(item.id)){
284
- wrongSolution = true
285
- if(this.showSolution != "full"){
286
- this.items.forEach(i => {
287
- i.showSolution = false
288
- })
289
- }
290
- }
291
- })
292
- if(wrongSolution){
293
- if(this.showSolution != "right"){
294
- this.style.backgroundColor = "#F9B5C4"
295
- }
296
- }else{
297
- this.style.backgroundColor = "#BCE194"
298
- }
299
- this.items.forEach(item=>{
300
- item.style.pointerEvents="none"
301
- })
302
- }
303
-
304
- reset() {
305
- this.solution = undefined
306
- this.style.backgroundColor = ""
307
- this.shuffleItems()
308
- this.items.forEach(item=>{
309
- item.style.pointerEvents="auto"
310
- })
311
- }
312
-
313
- protected firstUpdated(_changedProperties: PropertyValues): void {
314
- this.showSolution = this.showSolution
315
- }
316
-
317
- render() {
318
- return html`
319
- <slot id="items-slot" @webwriter-clear-drop-preview=${this.clearDropPreviews}></slot>
320
- ${this.hasAttribute("contenteditable")?html`
321
- <sl-button size="small" id="add-option" @click=${() => this.addItem()}>
322
- <sl-icon src=${IconPlus}></sl-icon><span>${msg("Add Option")}</span>
323
- </sl-button>
324
- `:""}
325
- `
326
- }
327
- }
@@ -1,161 +0,0 @@
1
- import {html, css} from "lit"
2
- import {LitElementWw, option} from "@webwriter/lit"
3
- import {customElement, property} from "lit/decorators.js"
4
- import "@shoelace-style/shoelace/dist/themes/light.css"
5
-
6
- import LOCALIZE from "../../localization/generated"
7
- import {msg} from "@lit/localize"
8
-
9
- declare global {interface HTMLElementTagNameMap {
10
- "webwriter-pairing-item": WebwriterPairingItem;
11
- }}
12
-
13
- @customElement("webwriter-pairing-item")
14
- export class WebwriterPairingItem extends LitElementWw {
15
-
16
- localize = LOCALIZE
17
-
18
- tabIndex = -1
19
-
20
- static styles = css`
21
-
22
- :host {
23
- aspect-ratio: 1;
24
- min-width: 125px;
25
- max-width: 350px;
26
- min-height: 125px;
27
- max-height: 350px;
28
- position: relative;
29
- overflow: hidden;
30
- overflow-y: auto;
31
- scrollbar-width: thin;
32
- resize: both;
33
- border: 2px solid var(--sl-color-gray-500);
34
- border-radius: 5px;
35
- background: white;
36
- }
37
-
38
- slot {
39
- width: 100%;
40
- height: 100%;
41
- display: flex;
42
- box-sizing: border-box;
43
- position: relative;
44
- font-size: 0.8em;
45
- z-index: 1;
46
- }
47
-
48
- :host([droppreview]) slot {
49
- background: lightblue;
50
- }
51
-
52
- ::slotted(*) {
53
- height: 100%;
54
- width: 100%;
55
- box-sizing: border-box;
56
- }
57
-
58
- ::slotted(:not(:is(picture, audio, video, img, iframe))) {
59
- padding: 5px !important;
60
- }
61
-
62
- #handle {
63
- box-sizing: border-box;
64
- border-radius: 2px;
65
- position: absolute;
66
- left: 1px;
67
- bottom: 1px;
68
- width: 20px;
69
- height: 30px;
70
- display: inline-block;
71
- overflow: hidden;
72
- line-height: 5px;
73
- padding: 6px 4px;
74
- cursor: move;
75
- vertical-align: middle;
76
- font-size: 12px;
77
- font-family: sans-serif;
78
- letter-spacing: 2px;
79
- color: #cccccc;
80
- text-shadow: 1px 0 1px black;
81
- font-weight: 600;
82
- opacity: 0.95;
83
- z-index: 100;
84
- }
85
- #handle::after {
86
- content: '.. .. ..';
87
- }
88
-
89
- :host(:not([contenteditable=true]):not([contenteditable=""])) .author-only {
90
- display: none !important;
91
- }
92
-
93
- #click-overlay {
94
- position: absolute;
95
- top: 0;
96
- width: 100%;
97
- height: 100%;
98
- cursor: pointer;
99
- }
100
-
101
- #click-overlay:hover {
102
- background: var(--sl-color-primary-600);
103
- opacity: 10%;
104
- }
105
-
106
- :host(.ww-selected) #click-overlay {
107
- display: none;
108
- }
109
- `
110
-
111
- @property({attribute: true, reflect: true, converter: {toAttribute: (v: boolean) => v? "true": "false", fromAttribute: (attr: string) => attr === "true"}})
112
- accessor draggable = true
113
-
114
- @property({type: Boolean, attribute: true, reflect: true})
115
- accessor dropPreview = false
116
-
117
- connectedCallback(): void {
118
- super.connectedCallback()
119
- this.addEventListener("dragstart", e => {
120
- if(e.target !== this) {
121
- e.preventDefault()
122
- }
123
- // this.dropPreview = undefined
124
- e.dataTransfer.setData("text/html", this.outerHTML)
125
- e.dataTransfer.setData("text/plain", this.id)
126
- // setTimeout(() => this.baseEl.style.visibility = "hidden")
127
- e.stopPropagation()
128
- })
129
- this.addEventListener("drop", e => {
130
- e.stopPropagation()
131
- const id = e.dataTransfer.getData("text/plain")
132
- const el = document.querySelector(`webwriter-pairing-item#${id}`)
133
- if(!el) {
134
- const html = e.dataTransfer.getData("text/html")
135
- this.parentElement.insertAdjacentHTML("beforeend", html)
136
- }
137
- else if(el && !this.contains(el)) {
138
- this.parentElement.insertAdjacentElement("beforeend", el)
139
- }
140
- this.dispatchEvent(new CustomEvent("ww-pairwith", {bubbles: true, detail: {with: id}}))
141
- }, {passive: true})
142
- this.addEventListener("dragover", e => {
143
- this.dropPreview = true
144
- e.preventDefault()
145
- e.stopImmediatePropagation()
146
- })
147
- this.addEventListener("dragleave", () => this.dropPreview = false, {passive: true})
148
- document.addEventListener("dragend", (e) => {
149
- // (e.target as HTMLElement)?.remove()
150
- this.dropPreview = false
151
- }, {passive: true})
152
- }
153
-
154
- render() {
155
- return html`
156
- <slot></slot>
157
- <div class="author-only" id="handle" @click=${() => this.dispatchEvent(new FocusEvent("focus"))}></div>
158
-
159
- `
160
- }
161
- }
@@ -1,193 +0,0 @@
1
- import {html, css, PropertyValues} from "lit"
2
- import {LitElementWw, option} from "@webwriter/lit"
3
- import {customElement, property, queryAll} from "lit/decorators.js"
4
- import "@shoelace-style/shoelace/dist/themes/light.css"
5
-
6
- import IconPlus from "bootstrap-icons/icons/plus.svg"
7
- import SlButton from "@shoelace-style/shoelace/dist/components/button/button.component.js"
8
- import SlIcon from "@shoelace-style/shoelace/dist/components/icon/icon.component.js"
9
- import type { WebwriterPairingItem } from "./webwriter-pairing-item"
10
-
11
- import LOCALIZE from "../../localization/generated"
12
- import {msg} from "@lit/localize"
13
-
14
- declare global {interface HTMLElementTagNameMap {
15
- "webwriter-pairing": WebwriterPairing;
16
- }}
17
-
18
- @customElement("webwriter-pairing")
19
- export class WebwriterPairing extends LitElementWw {
20
-
21
- localize = LOCALIZE
22
-
23
- static shadowRootOptions = {...LitElementWw.shadowRootOptions, slotAssignment: "manual" as const}
24
-
25
- static scopedElements = {
26
- "sl-button": SlButton,
27
- "sl-icon": SlIcon
28
- }
29
-
30
- static styles = css`
31
- :host {
32
- display: flex !important;
33
- flex-direction: row;
34
- flex-wrap: wrap;
35
- gap: 30px;
36
- padding-bottom: 30px;
37
- }
38
-
39
- sl-button {
40
-
41
- &::part(base) {
42
- width: 125px;
43
- height: 125px;
44
- border: 1px solid darkgray;
45
- border-radius: 2px;
46
- padding: 10px;
47
- overflow: hidden;
48
- }
49
-
50
- &::part(label) {
51
- padding: 0;
52
- display: flex;
53
- flex-direction: row;
54
- align-items: center;
55
- justify-content: center;
56
- }
57
- }
58
-
59
- .pair {
60
- position: relative;
61
-
62
- & .a-slot, & .b-slot {
63
- display: block;
64
-
65
- &:is(:focus-within, :has(::selection)) {
66
- background: green;
67
- }
68
- }
69
-
70
- & .b-slot {
71
- position: absolute;
72
- left: 20px;
73
- top: 20px;
74
- z-index: 1000;
75
- }
76
- }
77
-
78
- `
79
-
80
- @property({attribute: true, reflect: true})
81
- @option({type: "select", options: [
82
- {value: "drag"},
83
- {value: "memory"}
84
- ]})
85
- accessor mode: "drag" | "memory" = "drag"
86
-
87
- @property({type: Array, attribute: true, reflect: true})
88
- accessor solution: ([string, string] | string)[] = []
89
-
90
- addItem = () => {
91
- const item = document.createElement("webwriter-pairing-item")
92
- const picture = document.createElement("picture")
93
- item.appendChild(picture)
94
- this.appendChild(item)
95
- }
96
-
97
- Pair = (i: number) => {
98
- return html`<div class="pair">
99
- <slot class="a-slot" name=${`${i}a`}></slot>
100
- <slot class="b-slot" name=${`${i}b`}></slot>
101
- </div>`
102
- }
103
-
104
- getPairSlot() {}
105
-
106
-
107
- get pairs() {
108
- let allPairs = Array.from(this.children) as (WebwriterPairingItem | [WebwriterPairingItem, WebwriterPairingItem])[]
109
- for(const [a, b] of this.solution) {
110
- const elA = this.querySelector(`webwriter-pairing-item#${a}`) as WebwriterPairingItem
111
- const elB = this.querySelector(`webwriter-pairing-item#${b}`) as WebwriterPairingItem
112
- allPairs.splice(allPairs.indexOf(elA), 1, [elA, elB])
113
- allPairs.splice(allPairs.indexOf(elB), 1)
114
- }
115
- return allPairs
116
- }
117
-
118
- pair(a: string, b?: string) {
119
- let pairIds = this.pairs.map(entry => Array.isArray(entry)? [entry[0].id, entry[1].id]: entry.id)
120
- const pairIndexOfA = this.solution.findIndex(val => Array.isArray(val) && val.includes(a))
121
- const pairIndexOfB = this.solution.findIndex(val => Array.isArray(val) && val.includes(b))
122
- if(!b) {
123
- pairIds.splice(pairIndexOfA, 1, (pairIds[pairIndexOfA] as [string, string]).filter(id => id !== a)[0])
124
- pairIds.push(a)
125
- }
126
- else {
127
- if(pairIndexOfB !== -1) {
128
- pairIds.splice(pairIndexOfB, 1, (pairIds[pairIndexOfB] as [string, string]).filter(id => id !== b)[0])
129
- }
130
- const indexOfA = this.solution.indexOf(a)
131
- pairIds.splice(indexOfA, 1, [a, b])
132
- pairIds = pairIds.filter(id => id !== b)
133
- }
134
- this.solution = pairIds.filter(entry => Array.isArray(entry)) as any
135
- }
136
-
137
- handlePairWith(e: CustomEvent) {
138
- if(e.type === "ww-pairwith") {
139
- this.pair((e.target as HTMLElement).id, e.detail.with)
140
- }
141
- }
142
-
143
- observer: MutationObserver
144
-
145
- connectedCallback(): void {
146
- super.connectedCallback()
147
- this.observer = new MutationObserver(() => {
148
- let childIds = Array.from(this.children).map(child => child.id)
149
- let solution = [...this.solution]
150
- // solution = solution.filter(k => childIds.includes(k as any))
151
- // solution = [...solution, ...childIds]
152
- this.requestUpdate()
153
- })
154
- this.observer.observe(this, {childList: true})
155
- this.addEventListener("ww-pairwith", this.handlePairWith)
156
- }
157
-
158
- disconnectedCallback(): void {
159
- super.disconnectedCallback()
160
- this.observer.disconnect()
161
- }
162
-
163
- protected willUpdate(_changedProperties: PropertyValues): void {
164
- // this.solution = this.solution.filter(id => childIds.includes(id))
165
- }
166
-
167
- protected updated(_changedProperties: PropertyValues): void {
168
- const slots = Array.from(this.slots)
169
- for(const value of this.pairs) {
170
- if(Array.isArray(value)) {
171
- const [a, b] = value
172
- slots.shift().assign(a)
173
- slots.shift().assign(b)
174
- }
175
- else {
176
- slots.shift().assign(value)
177
- slots.shift()
178
- }
179
- }
180
- }
181
-
182
- @queryAll("slot")
183
- accessor slots: NodeListOf<HTMLSlotElement>
184
-
185
- render() {
186
- return html`
187
- ${this.pairs.map((el, i) => this.Pair(i))}
188
- <sl-button @click=${this.addItem}>
189
- <sl-icon src=${IconPlus}></sl-icon>
190
- </sl-button>
191
- `
192
- }
193
- }