import { LitElement, html, css } from 'lit' import { DarkMode } from '../shared/theme.js' /** A crossed-out eye, drawn rather than fetched: it is the same picture on every spoiler there is. */ const EYE_OFF_SVG = html` ` /** * Block Spoiler */ export class BlockSpoilerElement extends LitElement { /** * Metadata for the admin area and the editor's block picker. Collected at build time into * `compiled/blocks.manifest.json`, which the server reads to register the block. Values must be * plain literals. See `props` in `block-index` for what the picker does with that list. */ static definition = { block: 'spoiler', name: 'Spoiler', description: 'Hides content behind a cover until it is clicked.', icon: 'visualy-impaired', template: 'The content to hide.', props: [ { name: 'label', type: 'string', label: 'Label', hint: 'Heading on the cover.', default: 'Spoiler' }, { name: 'hint', type: 'string', label: 'Hint', hint: 'Line under the label.', default: 'Click to show content' } ] } static get styles() { return css` :host { display: block; } /* The content is laid out either way and only hidden from view, so the box is exactly as tall covered as it is revealed and nothing below it moves when a reader opens it. Hiding it by visibility is what does that: display:none would collapse the box, and a blur or a mask leaves the text on screen for anyone who looks closely enough at the pixels. */ .spoiler { position: relative; margin-bottom: 16px; min-height: 76px; padding: 16px 20px; border: 1px solid var(--spoiler-border); border-radius: 6px; background-color: var(--spoiler-bg); } .spoiler.is-covered .content { visibility: hidden; } .cover { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; width: 100%; padding: 8px; border: 0; border-radius: 5px; background-color: transparent; color: var(--spoiler-fg); font: inherit; text-align: center; cursor: pointer; transition: background-color 0.15s ease; } .cover:hover { background-color: var(--spoiler-hover); } .cover:focus-visible { outline: 2px solid var(--q-primary, #1976d2); outline-offset: -4px; } .label { font-weight: 500; letter-spacing: 0.02em; } .hint { font-size: 0.8em; opacity: 0.75; } :host { --spoiler-border: #e0e0e0; --spoiler-bg: #f5f5f5; --spoiler-fg: #424242; --spoiler-hover: rgba(0, 0, 0, 0.04); } :host([dark]) { --spoiler-border: rgba(255, 255, 255, 0.15); --spoiler-bg: #161b22; --spoiler-fg: rgba(255, 255, 255, 0.75); --spoiler-hover: rgba(255, 255, 255, 0.06); } ` } static get properties() { return { /** * Heading on the cover * @type {string} */ label: { type: String }, /** * Line under the label * @type {string} */ hint: { type: String }, // Internal Properties _covered: { state: true } } } constructor() { super() this.label = 'Spoiler' this.hint = 'Click to show content' this._covered = true // -> Puts `dark` on this element for the styles above to key off this._darkMode = new DarkMode(this) } /** * Drop the outermost margins of the content, the way `block-tabs` does: the box supplies the * padding, and a heading adding its own on top of it would push the cover's text off centre. */ _trimEdgeMargins() { this.firstElementChild?.style.setProperty('margin-top', '0') this.lastElementChild?.style.setProperty('margin-bottom', '0') } connectedCallback() { super.connectedCallback() this._trimEdgeMargins() } render() { return html`
${this._covered ? html` ` : null}
` } } window.customElements.define('block-spoiler', BlockSpoilerElement)