@webwriter/quiz 1.1.5 → 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 (165) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +778 -0
  3. package/assets/icons/grip-horizontal.svg +8 -0
  4. package/assets/icons/grip-vertical.svg +8 -0
  5. package/assets/icons/truefalse.svg +6 -0
  6. package/custom-elements.json +2779 -0
  7. package/custom.d.ts +5 -1
  8. package/dist/api/index.d.ts +27 -0
  9. package/dist/api/index.js +40 -0
  10. package/dist/migrate.js +1434 -0
  11. package/dist/widgets/webwriter-choice-item.js +4095 -3818
  12. package/dist/widgets/webwriter-choice.js +2179 -5598
  13. package/dist/widgets/webwriter-gap-item.js +3987 -0
  14. package/dist/widgets/webwriter-gap.js +2908 -0
  15. package/dist/widgets/webwriter-mark.js +4347 -3368
  16. package/dist/widgets/webwriter-order-item.js +4038 -3197
  17. package/dist/widgets/webwriter-order.js +2885 -4200
  18. package/dist/widgets/webwriter-pairing-item.js +1070 -2330
  19. package/dist/widgets/webwriter-pairing.js +2968 -4231
  20. package/dist/widgets/webwriter-quiz-hint.js +2469 -0
  21. package/dist/widgets/webwriter-quiz.js +3337 -4364
  22. package/dist/widgets/webwriter-speech.js +6100 -5120
  23. package/dist/widgets/webwriter-task-prompt.js +4787 -2068
  24. package/dist/widgets/webwriter-task.js +2264 -8157
  25. package/dist/widgets/webwriter-text.js +4075 -4132
  26. package/dist/widgets/webwriter-true-false.js +4674 -0
  27. package/editing-config.json +106 -0
  28. package/lit-localize.json +20 -14
  29. package/localization/de.xlf +395 -65
  30. package/localization/es.xlf +396 -61
  31. package/localization/fr.xlf +396 -61
  32. package/localization/generated/de.ts +97 -15
  33. package/localization/generated/es.ts +97 -15
  34. package/localization/generated/fr.ts +98 -16
  35. package/localization/generated/index.d.ts +5 -0
  36. package/localization/generated/it.ts +96 -14
  37. package/localization/generated/locale-codes.js +0 -48
  38. package/localization/generated/nl.ts +99 -17
  39. package/localization/it.xlf +396 -61
  40. package/localization/nl.xlf +396 -61
  41. package/package.json +120 -254
  42. package/scripts/apply-translations.mjs +97 -0
  43. package/src/api/index.ts +72 -0
  44. package/src/lib/artboard.ts +686 -0
  45. package/src/lib/draggable-grid.ts +432 -0
  46. package/src/lib/encrypted-property.ts +202 -0
  47. package/src/lib/hint-popup.ts +152 -0
  48. package/src/lib/utils.ts +97 -0
  49. package/src/lib/webwriter-quirks.ts +121 -0
  50. package/src/migrate/index.ts +622 -0
  51. package/src/migrate/legacy-solution.ts +362 -0
  52. package/src/snippets/order.html +8 -7
  53. package/src/widgets/choice/webwriter-choice-item.ts +316 -0
  54. package/src/widgets/choice/webwriter-choice.ts +328 -0
  55. package/src/widgets/choice/webwriter-true-false.ts +113 -0
  56. package/src/widgets/dnd/webwriter-dnd-draggable.ts +28 -0
  57. package/src/widgets/dnd/webwriter-dnd-dropzone.ts +9 -0
  58. package/src/widgets/dnd/webwriter-dnd.ts +36 -0
  59. package/src/widgets/gap/webwriter-gap-item.ts +250 -0
  60. package/src/widgets/gap/webwriter-gap.ts +447 -0
  61. package/src/widgets/order/webwriter-order-item.ts +243 -0
  62. package/src/widgets/order/webwriter-order.ts +282 -0
  63. package/src/widgets/pairing/webwriter-pairing-item.ts +46 -0
  64. package/src/widgets/pairing/webwriter-pairing.ts +934 -0
  65. package/src/widgets/speech/audio-player.ts +325 -0
  66. package/src/widgets/speech/audio-recorder.ts +285 -0
  67. package/src/widgets/speech/waveform.ts +113 -0
  68. package/src/widgets/speech/webwriter-speech.ts +87 -0
  69. package/src/widgets/webwriter-mark.ts +366 -424
  70. package/src/widgets/webwriter-quiz-hint.ts +36 -0
  71. package/src/widgets/webwriter-quiz.ts +318 -280
  72. package/src/widgets/webwriter-task-prompt.ts +43 -18
  73. package/src/widgets/webwriter-task.ts +212 -551
  74. package/src/widgets/webwriter-text.ts +286 -172
  75. package/tsconfig.api.json +12 -0
  76. package/tsconfig.json +7 -6
  77. package/dist/widgets/webwriter-choice.css +0 -444
  78. package/dist/widgets/webwriter-cloze-gap.css +0 -444
  79. package/dist/widgets/webwriter-cloze-gap.js +0 -5727
  80. package/dist/widgets/webwriter-cloze.css +0 -444
  81. package/dist/widgets/webwriter-cloze.js +0 -3653
  82. package/dist/widgets/webwriter-mark.css +0 -444
  83. package/dist/widgets/webwriter-order.css +0 -444
  84. package/dist/widgets/webwriter-pairing-item.css +0 -444
  85. package/dist/widgets/webwriter-pairing.css +0 -444
  86. package/dist/widgets/webwriter-quiz.css +0 -444
  87. package/dist/widgets/webwriter-task-explainer.css +0 -444
  88. package/dist/widgets/webwriter-task-explainer.js +0 -2769
  89. package/dist/widgets/webwriter-task-hint.css +0 -444
  90. package/dist/widgets/webwriter-task-hint.js +0 -2708
  91. package/dist/widgets/webwriter-task.css +0 -444
  92. package/dist/widgets/webwriter-text.css +0 -444
  93. package/localization/bg.xlf +0 -72
  94. package/localization/cs.xlf +0 -72
  95. package/localization/da.xlf +0 -72
  96. package/localization/el.xlf +0 -72
  97. package/localization/et.xlf +0 -72
  98. package/localization/fi.xlf +0 -72
  99. package/localization/generated/bg.ts +0 -31
  100. package/localization/generated/cs.ts +0 -31
  101. package/localization/generated/da.ts +0 -31
  102. package/localization/generated/el.ts +0 -31
  103. package/localization/generated/et.ts +0 -31
  104. package/localization/generated/fi.ts +0 -31
  105. package/localization/generated/hu.ts +0 -31
  106. package/localization/generated/id.ts +0 -31
  107. package/localization/generated/ja.ts +0 -31
  108. package/localization/generated/ko.ts +0 -31
  109. package/localization/generated/lt.ts +0 -31
  110. package/localization/generated/lv.ts +0 -31
  111. package/localization/generated/nb.ts +0 -31
  112. package/localization/generated/pl.ts +0 -31
  113. package/localization/generated/pt-BR.ts +0 -31
  114. package/localization/generated/pt-PT.ts +0 -31
  115. package/localization/generated/ro.ts +0 -31
  116. package/localization/generated/ru.ts +0 -31
  117. package/localization/generated/sk.ts +0 -31
  118. package/localization/generated/sl.ts +0 -31
  119. package/localization/generated/sv.ts +0 -31
  120. package/localization/generated/tr.ts +0 -31
  121. package/localization/generated/uk.ts +0 -31
  122. package/localization/generated/zh-hans.ts +0 -31
  123. package/localization/hu.xlf +0 -72
  124. package/localization/id.xlf +0 -72
  125. package/localization/ja.xlf +0 -72
  126. package/localization/ko.xlf +0 -72
  127. package/localization/lt.xlf +0 -72
  128. package/localization/lv.xlf +0 -72
  129. package/localization/nb.xlf +0 -72
  130. package/localization/pl.xlf +0 -72
  131. package/localization/pt-BR.xlf +0 -72
  132. package/localization/pt-PT.xlf +0 -72
  133. package/localization/ro.xlf +0 -72
  134. package/localization/ru.xlf +0 -72
  135. package/localization/sk.xlf +0 -72
  136. package/localization/sl.xlf +0 -72
  137. package/localization/sv.xlf +0 -72
  138. package/localization/tr.xlf +0 -72
  139. package/localization/uk.xlf +0 -72
  140. package/localization/zh-hans.xlf +0 -72
  141. package/src/lib/combobox.ts +0 -456
  142. package/src/lib/highlighter-fill.svg +0 -6
  143. package/src/snippets/Beispiel-Choice.html +0 -57
  144. package/src/snippets/Beispiel-Mark.html +0 -14
  145. package/src/snippets/Beispiel-Order.html +0 -52
  146. package/src/snippets/Beispiel-Text.html +0 -20
  147. package/src/snippets/blank.html +0 -1
  148. package/src/snippets/choice.html +0 -8
  149. package/src/snippets/cloze.html +0 -4
  150. package/src/snippets/mark.html +0 -4
  151. package/src/snippets/pairing.html +0 -10
  152. package/src/snippets/speech.html +0 -4
  153. package/src/snippets/text.html +0 -4
  154. package/src/snippets/wordsearch.html +0 -4
  155. package/src/widgets/webwriter-choice-item.ts +0 -257
  156. package/src/widgets/webwriter-choice.ts +0 -353
  157. package/src/widgets/webwriter-cloze-gap.ts +0 -216
  158. package/src/widgets/webwriter-cloze.ts +0 -136
  159. package/src/widgets/webwriter-order-item.ts +0 -441
  160. package/src/widgets/webwriter-order.ts +0 -327
  161. package/src/widgets/webwriter-pairing-item.ts +0 -161
  162. package/src/widgets/webwriter-pairing.ts +0 -193
  163. package/src/widgets/webwriter-speech.ts +0 -273
  164. package/src/widgets/webwriter-task-explainer.ts +0 -43
  165. 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
- }