@webwriter/quiz 1.1.3 → 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 +72 -205
  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 +70 -197
  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 -352
  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 -326
  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,456 +1,456 @@
1
- import { LitElement, html, css, CSSResultArray, CSSResult, PropertyValueMap, PropertyValues } from "lit"
2
- import { customElement, property, query, queryAssignedElements } from "lit/decorators.js"
3
- import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js"
4
- import SlOption from "@shoelace-style/shoelace/dist/components/option/option.component.js"
5
- import IconChevronDown from "bootstrap-icons/icons/chevron-down.svg"
6
-
7
- import { ifDefined } from "lit/directives/if-defined.js"
8
- import { styleMap } from "lit/directives/style-map.js"
9
-
10
- @customElement("ww-combobox")
11
- export class Combobox extends LitElement {
12
-
13
- static shadowRootOptions = {...LitElement.shadowRootOptions, delegatesFocus: true}
14
-
15
- constructor() {
16
- super()
17
- this.addEventListener("focus", e => {this.active = true; this.open = true})
18
- this.addEventListener("focusout", e => {
19
- this.active = this.open = false
20
- })
21
- this.addEventListener("blur", e => {
22
- this.active = this.open = false
23
- })
24
- this.value = this.defaultValue = this.multiple? []: ""
25
- }
26
-
27
- tabIndex = 0
28
-
29
- @property({attribute: false}) //@ts-ignore
30
- accessor value: string[] | string
31
-
32
- @property({attribute: false}) //@ts-ignore
33
- accessor defaultValue: string[] | string
34
-
35
- @query("input") //@ts-ignore
36
- accessor input: HTMLInputElement
37
-
38
- @query("#toggle")
39
- accessor toggleButton: HTMLInputElement
40
-
41
- @query("slot:not([name])")
42
- accessor slotElement: HTMLSlotElement
43
-
44
- @queryAssignedElements()
45
- accessor optionElements: HTMLElement[]
46
-
47
- @property({type: String, attribute: true}) //@ts-ignore
48
- accessor autocomplete: SlInput["autocomplete"] = "off"
49
-
50
- @property({type: Boolean, attribute: true, reflect: true})
51
- accessor active: boolean
52
-
53
- @property({type: Boolean, attribute: true, reflect: true})
54
- accessor inputDisabled: boolean = false
55
-
56
- @property({type: Boolean, attribute: true, reflect: true})
57
- accessor multiple: boolean = false
58
-
59
- @property({type: String}) //@ts-ignore
60
- accessor placeholder: string
61
-
62
- @property({type: String, attribute: true, reflect: true})
63
- accessor emptyValue: string
64
-
65
- @property({type: Boolean, attribute: true, reflect: true})
66
- accessor open: boolean = false
67
-
68
- @property({type: Boolean, attribute: true, reflect: true})
69
- accessor suggestions: boolean = false
70
-
71
- @property({type: String, attribute: "help-text"}) //@ts-ignore
72
- accessor helpText: string
73
-
74
- @property({type: Number, attribute: true})
75
- accessor fixLength: number
76
-
77
- @property({type: String, attribute: true})
78
- accessor separator: string = " "
79
-
80
- @property({type: String, attribute: true})
81
- accessor placement = "bottom" as const
82
-
83
- @property({type: Boolean, attribute: true, reflect: true})
84
- accessor autosize = false
85
-
86
- @property({type: Boolean, attribute: true, reflect: true})
87
- accessor disabled = false
88
-
89
- @property({type: Boolean, attribute: true, reflect: true})
90
- accessor required = false
91
-
92
- @property({type: String, attribute: true, reflect: true})
93
- accessor type: "button" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "hidden" | "image" | "month" | "number" | "password" | "radio" | "range" | "reset" | "search" | "submit" | "tel" | "text" | "time" | "url" | "week"
94
-
95
- @property({type: String, attribute: true, reflect: true})
96
- accessor label: string
97
-
98
- get validity() {
99
- return this.input.validity
100
- }
101
-
102
- get validationMessage() {
103
- return this.input.validationMessage
104
- }
105
-
106
- setValidity(isValid: boolean) {
107
- const host = this
108
- const required = Boolean(this.required)
109
- host.toggleAttribute('data-required', required)
110
- host.toggleAttribute('data-optional', !required)
111
- host.toggleAttribute('data-invalid', !isValid)
112
- host.toggleAttribute('data-valid', isValid)
113
- }
114
-
115
- focus() {
116
- if(this.inputDisabled && this.suggestions) {
117
- this.open = !this.open
118
- super.focus()
119
- }
120
- else {
121
- this.input?.focus()
122
- }
123
- }
124
-
125
- blur() {
126
- this.open = false
127
- }
128
-
129
- getForm() {
130
- return this.input.form
131
- }
132
-
133
- static styles: CSSResult | CSSResultArray = [SlInput.styles, css`
134
- * {
135
- font-family: var(--sl-font-sans);
136
- }
137
-
138
- :host {
139
- display: flex;
140
- flex-direction: column;
141
- }
142
-
143
- [part=base] {
144
- display: flex;
145
- flex-direction: row;
146
- justify-content: space-between;
147
- align-items: center;
148
- position: relative;
149
- border: solid var(--sl-input-border-width) var(--sl-input-border-color);
150
- border-radius: var(--sl-input-border-radius-medium);
151
- max-width: 100%;
152
- min-height: var(--sl-input-height-medium);
153
- padding: 0 var(--sl-input-spacing-medium);
154
- padding-right: 0;
155
- background: white;
156
- }
157
-
158
- :host(:not([inputDisabled]):not([disabled])) {
159
- cursor: text;
160
- }
161
-
162
- :host([inputDisabled]:not([disabled])), :host([inputDisabled]:not([disabled])) input {
163
- cursor: pointer;
164
- }
165
-
166
- :host([multiple]) [part=base] {
167
- flex-wrap: wrap;
168
- }
169
-
170
- :host([filled]) [part=base] {
171
- background: var(--sl-input-filled-background-color);
172
- }
173
-
174
- :host([active]) [part=base] {
175
- border-color: var(--sl-input-border-color-focus);
176
- box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-input-focus-ring-color);
177
- }
178
-
179
- :host([disabled]) [part=base] {
180
- background: none;
181
- }
182
-
183
- :host(:hover) [part=base] {
184
- border-color: var(--sl-color-gray-400);
185
- }
186
-
187
- #values {
188
- display: flex;
189
- flex-direction: row;
190
- align-items: center;
191
- gap: 0.25rem;
192
- padding-top: 0.25rem;
193
- padding-bottom: 0.25rem;
194
- flex-wrap: wrap;
195
- }
196
-
197
- #values > * {
198
- margin-top: 0.125rem;
199
- margin-bottom: 0.125rem;
200
- }
201
-
202
- #options {
203
- display: block;
204
- font-family: var(--sl-font-sans);
205
- font-size: var(--sl-font-size-medium);
206
- font-weight: var(--sl-font-weight-normal);
207
- box-shadow: var(--sl-shadow-large);
208
- background: var(--sl-panel-background-color);
209
- border: solid var(--sl-panel-border-width) var(--sl-panel-border-color);
210
- border-radius: var(--sl-border-radius-medium);
211
- padding-block: var(--sl-spacing-x-small);
212
- padding-inline: 0;
213
- overflow: auto;
214
- overscroll-behavior: none;
215
- width: 100%;
216
- max-height: var(--auto-size-available-height);
217
- }
218
-
219
- :host(:is(:not([open]), :not([suggestions]))) #options {
220
- display: none;
221
- }
222
-
223
- #options::part(base) {
224
- width: 100%;
225
- border-top: none;
226
- }
227
-
228
- input {
229
- flex-grow: 1;
230
- display: inline-flex;
231
- border: none;
232
- outline: none;
233
- background: transparent;
234
- font-size: var(--sl-input-font-size-small);
235
- }
236
-
237
- :host(:not([suggestions])) sl-menu, :host(:not([suggestions])) #toggle {
238
- display: none;
239
- }
240
-
241
- #toggle {
242
- padding-right: var(--sl-input-spacing-medium);
243
- }
244
-
245
- :host([size=small]) #toggle {
246
- padding-right: var(--sl-spacing-2x-small);
247
- font-size: var(--sl-input-font-size-small);
248
- }
249
-
250
- #toggle::part(base) {
251
- padding: 0;
252
- }
253
-
254
- #toggle::part(base) {
255
- transition: transform 0.25s ease;
256
- }
257
-
258
- :host([open]) #toggle::part(base) {
259
- transform: rotate(180deg);
260
- }
261
-
262
- :host([open]) {
263
- z-index: 100;
264
- }
265
-
266
- sl-dropdown::part(panel) {
267
- width: 100%;
268
- }
269
-
270
- [part=suffix] {
271
- display: flex;
272
- flex-direction: row;
273
- align-items: center;
274
- justify-content: flex-end;
275
- max-width: 50%;
276
- flex-grow: 0;
277
- }
278
-
279
- [part=label]::slotted(*), label {
280
- align-self: flex-start;
281
- margin-bottom: var(--sl-spacing-3x-small);
282
- }
283
-
284
- :host([required]) [part=label]::after {
285
- content: "*";
286
- margin-left: 0.25ch;
287
- }
288
-
289
- :host(:not([help-text])) #help-text {
290
- display: none;
291
- }
292
-
293
- #help-text {
294
- font-size: var(--sl-input-help-text-font-size-medium);
295
- color: var(--sl-input-help-text-color);
296
- margin-top: var(--sl-spacing-3x-small);
297
- }
298
-
299
- slot[name=prefix]::slotted(:last-child) {
300
- margin-right: 1ch;
301
- }
302
-
303
- :host([size=small]) [part=base] {
304
- min-height: var(--sl-input-height-small);
305
- padding: 0 var(--sl-input-spacing-small);
306
- }
307
-
308
- #hide {
309
- height: 0;
310
- position: absolute;
311
- right: 0;
312
- bottom: 0;
313
- white-space: pre;
314
- overflow: hidden;
315
- font-size: var(--sl-input-font-size-small);
316
- }
317
- `]
318
-
319
- handleTextInput(e: Event) {
320
- const input = e.target as HTMLInputElement
321
- e.preventDefault()
322
- if(!this.multiple && this.fixLength && input.value.length < this.fixLength) {
323
- input.value = this.value as string
324
- }
325
- else if(!this.multiple) {
326
- this.value = input.value
327
- }
328
- else if(input.value === this.separator) {
329
- input.value = ""
330
- }
331
- else {
332
- this.value = [...this.valueList.slice(0, -1), ...input.value.split(this.separator)]
333
- input.value = this.valueList.at(-1)!
334
- }
335
- this.dispatchInput()
336
- this.setValidity(this.validity.valid)
337
- }
338
-
339
- handleInputKeydown(e: KeyboardEvent) {
340
- const input = e.target as HTMLInputElement
341
- if(this.multiple && e.key === "Backspace" && input.selectionStart === 0 && input.selectionEnd === 0) {
342
- e.preventDefault()
343
- this.value = this.valueList.slice(0, -1)
344
- }
345
- this.requestUpdate()
346
- }
347
-
348
- handleOptionClick = (e: PointerEvent) => {
349
- const option = (e.target as HTMLElement).closest("sl-option") as SlOption | null
350
- if(option) {
351
- this.value = option.value
352
- this.focus()
353
- this.open = false
354
- this.dispatchChange()
355
- this.requestUpdate()
356
- }
357
- }
358
-
359
- dispatchChange() {
360
- this.dispatchEvent(new CustomEvent("sl-change", {composed: true, bubbles: true}))
361
- }
362
-
363
- dispatchInput() {
364
- this.dispatchEvent(new CustomEvent("sl-input", {composed: true, bubbles: true}))
365
- }
366
-
367
- get valueList() {
368
- return (this.multiple? this.value || []: [this.value]) as string[]
369
- }
370
-
371
- get inputSize() {
372
- return !this.multiple? undefined: Math.min(
373
- Math.max(this.placeholder?.length ?? 1, this.valueList.at(-1)!?.length ?? 1 - 1),
374
- 40
375
- )
376
- }
377
-
378
- protected firstUpdated(_changedProperties: PropertyValues): void {
379
- super.firstUpdated(_changedProperties)
380
- this.requestUpdate()
381
- }
382
-
383
- connectedCallback(): void {
384
- super.connectedCallback()
385
- this.requestUpdate()
386
- }
387
-
388
- @query("#hide")
389
- accessor hideEl: HTMLElement
390
-
391
- @query("input")
392
- accessor inputEl: HTMLInputElement
393
-
394
- get inputWidth() {
395
- return this.hideEl? this.hideEl.offsetWidth: 0
396
- }
397
-
398
- get inputValue() {
399
- const value = this.multiple? this.valueList.at(-1) ?? "": String(this.value ?? "")
400
- return value !== this.emptyValue? value: ""
401
- }
402
-
403
- render() {
404
- const input = html`
405
- <input
406
- part="input"
407
- type=${this.type}
408
- size=${ifDefined(this.inputSize)}
409
- .value=${this.inputValue}
410
- placeholder=${this.multiple && this.valueList.length > 1? "": this.placeholder}
411
- ?disabled=${this.disabled || this.inputDisabled}
412
- @input=${this.handleTextInput}
413
- @change=${this.dispatchChange}
414
- @keydown=${this.handleInputKeydown}
415
- autocomplete=${this.autocomplete as any}
416
- style=${this.autosize? styleMap({width: `calc(${this.inputWidth}px + 2ch)`}): undefined}
417
- >
418
- `
419
- return html`
420
- <slot name="label" part="label" @click=${this.focus}>
421
- <label>${this.label}</label>
422
- </slot>
423
- <div part="base" id="anchor" tabindex=${0}>
424
- <slot name="prefix"></slot>
425
- ${this.multiple? html`
426
- <span id="values">
427
- <slot name="values"></slot>
428
- ${this.valueList.slice(0, -1).map(v => html`
429
- <sl-tag size="small" variant="primary">${v}</sl-tag>
430
- `)}
431
- ${input}
432
- </span>
433
- `: input}
434
- <div part="suffix">
435
- <slot name="suffix"></slot>
436
- <sl-icon-button
437
- slot="trigger"
438
- part="trigger"
439
- id="toggle"
440
- src=${IconChevronDown}
441
- @focus=${(e: Event) => e.stopPropagation()}
442
- @click=${(e: Event) => {this.open = !this.open; e.stopPropagation()}}
443
- @mousedown=${(e: Event) => {e.stopPropagation(); e.preventDefault()}}
444
- ></sl-icon-button>
445
- </div>
446
- </div>
447
- <sl-popup anchor="anchor" placement=${this.placement} strategy="fixed" ?active=${this.open} auto-size="vertical" auto-size-padding="10" flip shift>
448
- <sl-menu id="options" @click=${this.handleOptionClick}>
449
- <slot></slot>
450
- </sl-menu>
451
- </sl-popup>
452
- <div id="help-text">${this.helpText}</div>
453
- <span id="hide">${this.inputEl?.value}</span>
454
- `
455
- }
1
+ import { LitElement, html, css, CSSResultArray, CSSResult, PropertyValueMap, PropertyValues } from "lit"
2
+ import { customElement, property, query, queryAssignedElements } from "lit/decorators.js"
3
+ import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js"
4
+ import SlOption from "@shoelace-style/shoelace/dist/components/option/option.component.js"
5
+ import IconChevronDown from "bootstrap-icons/icons/chevron-down.svg"
6
+
7
+ import { ifDefined } from "lit/directives/if-defined.js"
8
+ import { styleMap } from "lit/directives/style-map.js"
9
+
10
+ @customElement("ww-combobox")
11
+ export class Combobox extends LitElement {
12
+
13
+ static shadowRootOptions = {...LitElement.shadowRootOptions, delegatesFocus: true}
14
+
15
+ constructor() {
16
+ super()
17
+ this.addEventListener("focus", e => {this.active = true; this.open = true})
18
+ this.addEventListener("focusout", e => {
19
+ this.active = this.open = false
20
+ })
21
+ this.addEventListener("blur", e => {
22
+ this.active = this.open = false
23
+ })
24
+ this.value = this.defaultValue = this.multiple? []: ""
25
+ }
26
+
27
+ tabIndex = 0
28
+
29
+ @property({attribute: false}) //@ts-ignore
30
+ accessor value: string[] | string
31
+
32
+ @property({attribute: false}) //@ts-ignore
33
+ accessor defaultValue: string[] | string
34
+
35
+ @query("input") //@ts-ignore
36
+ accessor input: HTMLInputElement
37
+
38
+ @query("#toggle")
39
+ accessor toggleButton: HTMLInputElement
40
+
41
+ @query("slot:not([name])")
42
+ accessor slotElement: HTMLSlotElement
43
+
44
+ @queryAssignedElements()
45
+ accessor optionElements: HTMLElement[]
46
+
47
+ @property({type: String, attribute: true}) //@ts-ignore
48
+ accessor autocomplete: SlInput["autocomplete"] = "off"
49
+
50
+ @property({type: Boolean, attribute: true, reflect: true})
51
+ accessor active: boolean
52
+
53
+ @property({type: Boolean, attribute: true, reflect: true})
54
+ accessor inputDisabled: boolean = false
55
+
56
+ @property({type: Boolean, attribute: true, reflect: true})
57
+ accessor multiple: boolean = false
58
+
59
+ @property({type: String}) //@ts-ignore
60
+ accessor placeholder: string
61
+
62
+ @property({type: String, attribute: true, reflect: true})
63
+ accessor emptyValue: string
64
+
65
+ @property({type: Boolean, attribute: true, reflect: true})
66
+ accessor open: boolean = false
67
+
68
+ @property({type: Boolean, attribute: true, reflect: true})
69
+ accessor suggestions: boolean = false
70
+
71
+ @property({type: String, attribute: "help-text"}) //@ts-ignore
72
+ accessor helpText: string
73
+
74
+ @property({type: Number, attribute: true})
75
+ accessor fixLength: number
76
+
77
+ @property({type: String, attribute: true})
78
+ accessor separator: string = " "
79
+
80
+ @property({type: String, attribute: true})
81
+ accessor placement = "bottom" as const
82
+
83
+ @property({type: Boolean, attribute: true, reflect: true})
84
+ accessor autosize = false
85
+
86
+ @property({type: Boolean, attribute: true, reflect: true})
87
+ accessor disabled = false
88
+
89
+ @property({type: Boolean, attribute: true, reflect: true})
90
+ accessor required = false
91
+
92
+ @property({type: String, attribute: true, reflect: true})
93
+ accessor type: "button" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "hidden" | "image" | "month" | "number" | "password" | "radio" | "range" | "reset" | "search" | "submit" | "tel" | "text" | "time" | "url" | "week"
94
+
95
+ @property({type: String, attribute: true, reflect: true})
96
+ accessor label: string
97
+
98
+ get validity() {
99
+ return this.input.validity
100
+ }
101
+
102
+ get validationMessage() {
103
+ return this.input.validationMessage
104
+ }
105
+
106
+ setValidity(isValid: boolean) {
107
+ const host = this
108
+ const required = Boolean(this.required)
109
+ host.toggleAttribute('data-required', required)
110
+ host.toggleAttribute('data-optional', !required)
111
+ host.toggleAttribute('data-invalid', !isValid)
112
+ host.toggleAttribute('data-valid', isValid)
113
+ }
114
+
115
+ focus() {
116
+ if(this.inputDisabled && this.suggestions) {
117
+ this.open = !this.open
118
+ super.focus()
119
+ }
120
+ else {
121
+ this.input?.focus()
122
+ }
123
+ }
124
+
125
+ blur() {
126
+ this.open = false
127
+ }
128
+
129
+ getForm() {
130
+ return this.input.form
131
+ }
132
+
133
+ static styles: CSSResult | CSSResultArray = [SlInput.styles, css`
134
+ * {
135
+ font-family: var(--sl-font-sans);
136
+ }
137
+
138
+ :host {
139
+ display: flex;
140
+ flex-direction: column;
141
+ }
142
+
143
+ [part=base] {
144
+ display: flex;
145
+ flex-direction: row;
146
+ justify-content: space-between;
147
+ align-items: center;
148
+ position: relative;
149
+ border: solid var(--sl-input-border-width) var(--sl-input-border-color);
150
+ border-radius: var(--sl-input-border-radius-medium);
151
+ max-width: 100%;
152
+ min-height: var(--sl-input-height-medium);
153
+ padding: 0 var(--sl-input-spacing-medium);
154
+ padding-right: 0;
155
+ background: white;
156
+ }
157
+
158
+ :host(:not([inputDisabled]):not([disabled])) {
159
+ cursor: text;
160
+ }
161
+
162
+ :host([inputDisabled]:not([disabled])), :host([inputDisabled]:not([disabled])) input {
163
+ cursor: pointer;
164
+ }
165
+
166
+ :host([multiple]) [part=base] {
167
+ flex-wrap: wrap;
168
+ }
169
+
170
+ :host([filled]) [part=base] {
171
+ background: var(--sl-input-filled-background-color);
172
+ }
173
+
174
+ :host([active]) [part=base] {
175
+ border-color: var(--sl-input-border-color-focus);
176
+ box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-input-focus-ring-color);
177
+ }
178
+
179
+ :host([disabled]) [part=base] {
180
+ background: none;
181
+ }
182
+
183
+ :host(:hover) [part=base] {
184
+ border-color: var(--sl-color-gray-400);
185
+ }
186
+
187
+ #values {
188
+ display: flex;
189
+ flex-direction: row;
190
+ align-items: center;
191
+ gap: 0.25rem;
192
+ padding-top: 0.25rem;
193
+ padding-bottom: 0.25rem;
194
+ flex-wrap: wrap;
195
+ }
196
+
197
+ #values > * {
198
+ margin-top: 0.125rem;
199
+ margin-bottom: 0.125rem;
200
+ }
201
+
202
+ #options {
203
+ display: block;
204
+ font-family: var(--sl-font-sans);
205
+ font-size: var(--sl-font-size-medium);
206
+ font-weight: var(--sl-font-weight-normal);
207
+ box-shadow: var(--sl-shadow-large);
208
+ background: var(--sl-panel-background-color);
209
+ border: solid var(--sl-panel-border-width) var(--sl-panel-border-color);
210
+ border-radius: var(--sl-border-radius-medium);
211
+ padding-block: var(--sl-spacing-x-small);
212
+ padding-inline: 0;
213
+ overflow: auto;
214
+ overscroll-behavior: none;
215
+ width: 100%;
216
+ max-height: var(--auto-size-available-height);
217
+ }
218
+
219
+ :host(:is(:not([open]), :not([suggestions]))) #options {
220
+ display: none;
221
+ }
222
+
223
+ #options::part(base) {
224
+ width: 100%;
225
+ border-top: none;
226
+ }
227
+
228
+ input {
229
+ flex-grow: 1;
230
+ display: inline-flex;
231
+ border: none;
232
+ outline: none;
233
+ background: transparent;
234
+ font-size: var(--sl-input-font-size-small);
235
+ }
236
+
237
+ :host(:not([suggestions])) sl-menu, :host(:not([suggestions])) #toggle {
238
+ display: none;
239
+ }
240
+
241
+ #toggle {
242
+ padding-right: var(--sl-input-spacing-medium);
243
+ }
244
+
245
+ :host([size=small]) #toggle {
246
+ padding-right: var(--sl-spacing-2x-small);
247
+ font-size: var(--sl-input-font-size-small);
248
+ }
249
+
250
+ #toggle::part(base) {
251
+ padding: 0;
252
+ }
253
+
254
+ #toggle::part(base) {
255
+ transition: transform 0.25s ease;
256
+ }
257
+
258
+ :host([open]) #toggle::part(base) {
259
+ transform: rotate(180deg);
260
+ }
261
+
262
+ :host([open]) {
263
+ z-index: 100;
264
+ }
265
+
266
+ sl-dropdown::part(panel) {
267
+ width: 100%;
268
+ }
269
+
270
+ [part=suffix] {
271
+ display: flex;
272
+ flex-direction: row;
273
+ align-items: center;
274
+ justify-content: flex-end;
275
+ max-width: 50%;
276
+ flex-grow: 0;
277
+ }
278
+
279
+ [part=label]::slotted(*), label {
280
+ align-self: flex-start;
281
+ margin-bottom: var(--sl-spacing-3x-small);
282
+ }
283
+
284
+ :host([required]) [part=label]::after {
285
+ content: "*";
286
+ margin-left: 0.25ch;
287
+ }
288
+
289
+ :host(:not([help-text])) #help-text {
290
+ display: none;
291
+ }
292
+
293
+ #help-text {
294
+ font-size: var(--sl-input-help-text-font-size-medium);
295
+ color: var(--sl-input-help-text-color);
296
+ margin-top: var(--sl-spacing-3x-small);
297
+ }
298
+
299
+ slot[name=prefix]::slotted(:last-child) {
300
+ margin-right: 1ch;
301
+ }
302
+
303
+ :host([size=small]) [part=base] {
304
+ min-height: var(--sl-input-height-small);
305
+ padding: 0 var(--sl-input-spacing-small);
306
+ }
307
+
308
+ #hide {
309
+ height: 0;
310
+ position: absolute;
311
+ right: 0;
312
+ bottom: 0;
313
+ white-space: pre;
314
+ overflow: hidden;
315
+ font-size: var(--sl-input-font-size-small);
316
+ }
317
+ `]
318
+
319
+ handleTextInput(e: Event) {
320
+ const input = e.target as HTMLInputElement
321
+ e.preventDefault()
322
+ if(!this.multiple && this.fixLength && input.value.length < this.fixLength) {
323
+ input.value = this.value as string
324
+ }
325
+ else if(!this.multiple) {
326
+ this.value = input.value
327
+ }
328
+ else if(input.value === this.separator) {
329
+ input.value = ""
330
+ }
331
+ else {
332
+ this.value = [...this.valueList.slice(0, -1), ...input.value.split(this.separator)]
333
+ input.value = this.valueList.at(-1)!
334
+ }
335
+ this.dispatchInput()
336
+ this.setValidity(this.validity.valid)
337
+ }
338
+
339
+ handleInputKeydown(e: KeyboardEvent) {
340
+ const input = e.target as HTMLInputElement
341
+ if(this.multiple && e.key === "Backspace" && input.selectionStart === 0 && input.selectionEnd === 0) {
342
+ e.preventDefault()
343
+ this.value = this.valueList.slice(0, -1)
344
+ }
345
+ this.requestUpdate()
346
+ }
347
+
348
+ handleOptionClick = (e: PointerEvent) => {
349
+ const option = (e.target as HTMLElement).closest("sl-option") as SlOption | null
350
+ if(option) {
351
+ this.value = option.value
352
+ this.focus()
353
+ this.open = false
354
+ this.dispatchChange()
355
+ this.requestUpdate()
356
+ }
357
+ }
358
+
359
+ dispatchChange() {
360
+ this.dispatchEvent(new CustomEvent("sl-change", {composed: true, bubbles: true}))
361
+ }
362
+
363
+ dispatchInput() {
364
+ this.dispatchEvent(new CustomEvent("sl-input", {composed: true, bubbles: true}))
365
+ }
366
+
367
+ get valueList() {
368
+ return (this.multiple? this.value || []: [this.value]) as string[]
369
+ }
370
+
371
+ get inputSize() {
372
+ return !this.multiple? undefined: Math.min(
373
+ Math.max(this.placeholder?.length ?? 1, this.valueList.at(-1)!?.length ?? 1 - 1),
374
+ 40
375
+ )
376
+ }
377
+
378
+ protected firstUpdated(_changedProperties: PropertyValues): void {
379
+ super.firstUpdated(_changedProperties)
380
+ this.requestUpdate()
381
+ }
382
+
383
+ connectedCallback(): void {
384
+ super.connectedCallback()
385
+ this.requestUpdate()
386
+ }
387
+
388
+ @query("#hide")
389
+ accessor hideEl: HTMLElement
390
+
391
+ @query("input")
392
+ accessor inputEl: HTMLInputElement
393
+
394
+ get inputWidth() {
395
+ return this.hideEl? this.hideEl.offsetWidth: 0
396
+ }
397
+
398
+ get inputValue() {
399
+ const value = this.multiple? this.valueList.at(-1) ?? "": String(this.value ?? "")
400
+ return value !== this.emptyValue? value: ""
401
+ }
402
+
403
+ render() {
404
+ const input = html`
405
+ <input
406
+ part="input"
407
+ type=${this.type}
408
+ size=${ifDefined(this.inputSize)}
409
+ .value=${this.inputValue}
410
+ placeholder=${this.multiple && this.valueList.length > 1? "": this.placeholder}
411
+ ?disabled=${this.disabled || this.inputDisabled}
412
+ @input=${this.handleTextInput}
413
+ @change=${this.dispatchChange}
414
+ @keydown=${this.handleInputKeydown}
415
+ autocomplete=${this.autocomplete as any}
416
+ style=${this.autosize? styleMap({width: `calc(${this.inputWidth}px + 2ch)`}): undefined}
417
+ >
418
+ `
419
+ return html`
420
+ <slot name="label" part="label" @click=${this.focus}>
421
+ <label>${this.label}</label>
422
+ </slot>
423
+ <div part="base" id="anchor" tabindex=${0}>
424
+ <slot name="prefix"></slot>
425
+ ${this.multiple? html`
426
+ <span id="values">
427
+ <slot name="values"></slot>
428
+ ${this.valueList.slice(0, -1).map(v => html`
429
+ <sl-tag size="small" variant="primary">${v}</sl-tag>
430
+ `)}
431
+ ${input}
432
+ </span>
433
+ `: input}
434
+ <div part="suffix">
435
+ <slot name="suffix"></slot>
436
+ <sl-icon-button
437
+ slot="trigger"
438
+ part="trigger"
439
+ id="toggle"
440
+ src=${IconChevronDown}
441
+ @focus=${(e: Event) => e.stopPropagation()}
442
+ @click=${(e: Event) => {this.open = !this.open; e.stopPropagation()}}
443
+ @mousedown=${(e: Event) => {e.stopPropagation(); e.preventDefault()}}
444
+ ></sl-icon-button>
445
+ </div>
446
+ </div>
447
+ <sl-popup anchor="anchor" placement=${this.placement} strategy="fixed" ?active=${this.open} auto-size="vertical" auto-size-padding="10" flip shift>
448
+ <sl-menu id="options" @click=${this.handleOptionClick}>
449
+ <slot></slot>
450
+ </sl-menu>
451
+ </sl-popup>
452
+ <div id="help-text">${this.helpText}</div>
453
+ <span id="hide">${this.inputEl?.value}</span>
454
+ `
455
+ }
456
456
  }