import {LitElement, html, css, RenderOptions} from "lit" import {LitElementWw} from "@webwriter/lit" import {customElement, query} from "lit/decorators.js" import SlIconButton from "@shoelace-style/shoelace/dist/components/icon-button/icon-button.component.js" import SlOption from "@shoelace-style/shoelace/dist/components/option/option.component.js" import IconPlus from "bootstrap-icons/icons/plus.svg" import IconSlashCircle from "bootstrap-icons/icons/slash-circle.svg" import IconCheckCircle from "bootstrap-icons/icons/check-circle.svg" import IconX from "bootstrap-icons/icons/x.svg" import { Combobox } from "../lib/combobox" import { property } from "lit/decorators/property.js" import { queryAsync } from "lit/decorators/query-async.js" import "@shoelace-style/shoelace/dist/themes/light.css" import LOCALIZE from "../../localization/generated" import {msg} from "@lit/localize" declare global {interface HTMLElementTagNameMap { "webwriter-cloze-gap": WebwriterClozeGap; }} @customElement("webwriter-cloze-gap") export class WebwriterClozeGap extends LitElementWw { localize = LOCALIZE @property({type: Array, attribute: true, reflect: true}) accessor solution: string[] @property({type: String, attribute: true, reflect: true}) accessor value: string @property({type: Array, attribute: true, reflect: true}) accessor distraction: string[] = [] @property({type: Boolean, attribute: true, reflect: true}) accessor showOptions = false static scopedElements = { "ww-combobox": Combobox, "sl-icon-button": SlIconButton, "sl-option": SlOption } static styles = css` :host { display: inline-block !important; vertical-align: top; margin: 0 1ch; max-width: calc(100% - 2ch); max-height: 1lh; } :host(::after) { content: " "; display: block; } sl-icon-button[data-hidden] { visibility: hidden; } sl-icon-button::part(base) { padding: 0; } :host(:not(:focus-within)) #add { visibility: hidden; } sl-icon-button { overflow: visible; margin-right: 0; } ww-combobox::part(base) { min-height: unset; padding: 2px 2px; } :host(:is([contenteditable=true], [contenteditable=""])) ww-combobox::part(input) { color: var(--sl-color-success-700); } sl-option::part(base) { padding: 0; font-size: var(--sl-input-font-size-small); } sl-option::part(label) { padding: var(--sl-spacing-2x-small) 0; } sl-option::part(label):hover { color: var(--sl-color-primary-600); } sl-option::part(checked-icon) { display: none; } .remove::part(base):hover { color: var(--sl-color-danger-600) !important; } .remove::part(base):active { color: var(--sl-color-danger-800) !important; } :is(.toggle, .remove)::part(base) { padding: var(--sl-spacing-2x-small); } .solution::part(base) { color: var(--sl-color-success-700); } .toggle { color: inherit; } ` @query("ww-combobox") accessor input: Combobox async focus() { (await this.input).focus() } handleChange = (e: CustomEvent) => { e.preventDefault() if(!this.isContentEditable) { this.value = this.input.value as string } } addSolution(value: string) { this.solution = Array.from(new Set([...(this.solution ?? []), value])) } toggleOptionType(value: string) { if(this.solution.includes(value)) { this.solution = this.solution.filter(v => v !== value) this.distraction = Array.from(new Set([...(this.distraction ?? []), value])) } else if(this.distraction.includes(value)) { this.distraction = this.distraction.filter(v => v !== value) this.solution = Array.from(new Set([...(this.solution ?? []), value])) } } removeOption(value: string) { this.solution = this.solution.filter(v => v !== value) this.distraction = this.distraction.filter(v => v !== value) if(this.allOptions.length === 0) { this.isAdding = false } } handleAddClick = (e: MouseEvent) => { e.stopImmediatePropagation() e.preventDefault() this.addSolution(this.input.value as string) this.isAdding = true this.input.value = "" this.input.open = true } handleToggleClick = (e: MouseEvent, value: string) => { e.stopImmediatePropagation() e.preventDefault() this.toggleOptionType(value) } handleRemoveClick = (e: MouseEvent, value: string) => { e.stopImmediatePropagation() e.preventDefault() this.removeOption(value) } handleKeydown = (e: KeyboardEvent) => { if(e.key === "Enter") { this.handleAddClick(e as any) } } handleBlur = (e: FocusEvent) => { this.isAdding = false if(this.solution.length === 1) { this.input.value = this.solution[0] } } get allOptions() { return [...(this.solution ?? []), ...this.distraction] } @property({type: Boolean, attribute: false}) accessor isAdding = false render() { return html` 1 || this.isAdding || this.distraction.length > 0} size="small" autosize .value=${this.value} @sl-change=${this.handleChange} @sl-input=${() => this.requestUpdate()} @keydown=${this.handleKeydown} > {e.stopPropagation(); e.preventDefault()}} @focus=${e => e.stopPropagation()} > ${this.allOptions.sort().map(value => html` this.handleToggleClick(e, value)} @mousedown=${(e: Event) => {e.stopPropagation(); e.preventDefault()}}> ${value} this.handleRemoveClick(e, value)} @mousedown=${(e: Event) => {e.stopPropagation(); e.preventDefault()}}> `)} ` } }