Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

light editor improvements #5

Closed
wants to merge 11 commits into from
2 changes: 0 additions & 2 deletions docs/frontend/styles/_normalize.css
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,6 @@ kr-layout::part(skip-link) {
display: inline;
padding: 0.25em;
margin: -0.25em;
line-height: 1.2;
transition: all 0.2s;
}

kr-layout::part(skip-link):is(:hover),
Expand Down
3 changes: 2 additions & 1 deletion docs/frontend/styles/components/_top_nav.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.top-nav {
display: flex;
align-items: center;
max-width: var(--top-nav-max-width);
justify-content: center;
margin: 0 auto;
Expand Down Expand Up @@ -72,7 +73,7 @@
}

.top-nav__github {
padding-top: var(--sl-spacing-2x-small);
padding-bottom: 0;
margin-inline-start: var(--sl-spacing-medium);
margin-inline-end: 2px;
}
Expand Down
19 changes: 19 additions & 0 deletions docs/src/_documentation/components/light-disclosure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: <light-disclosure>
permalink: /components/light-disclosure/
component: light-disclosure
---


## Typical Usage

<light-disclosure style="display: none;"></light-disclosure>

<light-preview inline-preview>
<template slot="code">
<light-disclosure summary="Source Code">
<code style="background-color: rgba(0,0,0,0.1);">export const x = "hi"</code>
</light-disclosure>
</template>
</light-preview>

2 changes: 1 addition & 1 deletion docs/src/_documentation/components/light-pen.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ component: light-pen

## Typical Usage

<light-pen style="padding: 8px; height: 100%;" resize-position="30" open-languages="js,html,css">
<light-pen style="padding: 8px; height: 75vh; max-height: 75vh;" resize-position="30" open-languages="js,html,css">
<template slot="html">
<light-pen>
<template slot="html">
Expand Down
192 changes: 192 additions & 0 deletions exports/light-disclosure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { html, css } from "lit"
import { baseStyles } from "./base-styles.js"
import { BaseElement } from "../internal/base-element.js"

function motionReduced () {
return /** @type {any} */ (window.matchMedia(`(prefers-reduced-motion: reduce)`)) === true || window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;
}

/**
* A `<details>` element packaged nicely to animate like a disclosure.
* @customElement
* @tagName light-disclosure
*/
export class LightDisclosure extends BaseElement {
static baseName = "light-disclosure"
static styles = [
baseStyles,
css`
[part~="content-base"] {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 300ms ease-in-out;
}

[part~="content"] {
overflow: hidden;
}

details[open][expanded] [part~="content-base"] {
grid-template-rows: 1fr;
}

summary {
padding: 0.4em 0.6em;
cursor: pointer;
}

summary:hover {
background-color: rgba(0,0,0,0.05);
}

@media (prefers-reduced-motion: reduce) {
[part~="content-base"] {
transition: none;
}
details[open] [part~="content-base"] {
grid-template-rows: 1fr;
}
}
`
]

static properties = {
summary: {},
open: { type: Boolean }
}

constructor () {
super()
/**
* @type {string}
*/
this.summary = ""

/**
* @type {boolean}
*/
this.open = false

/**
* @internal
* This is used because Safari has strange timing on the "toggle" event. If we don't use this,
* our initial opening of the disclosure gets clipped and is like a normal `<details>`
*/
this._openOnToggle = true
}

// TODO: Add a mutationObserver for when it connects

/**
* @param {import("lit").PropertyValues<this>} changedProperties
*/
willUpdate (changedProperties) {
const details = this.details

if (details && changedProperties.has("open")) {
if (!this.open) {
if (details.hasAttribute("expanded")) {
details.removeAttribute("expanded")
} else {
details.open = this.open
}

// "transitionend" will fire and set "open" on the details element accordingly.
// If motion is reduced, our transition will never fire. so we need to set "open" on the <details> here.
if (motionReduced()) {
details.open = this.open
}

this.dispatchEvent(new Event("light-toggle"))
} else {
details.open = this.open

this._openOnToggle = false

// If you only wait 1 animation frame, we get clipped by `display: none;`
requestAnimationFrame(() => {
requestAnimationFrame(() => {
details.setAttribute("expanded", "")
this._openOnToggle = true
this.dispatchEvent(new Event("light-toggle"))
})
})
}
}
}

render () {
return html`
<details
part="details"
@transitionend=${this.handleTransitionEnd}
@toggle=${this.handleToggle}
>
<summary part="summary" @click=${this.handleSummaryClick}>
<slot name="summary">${this.summary}</slot>
</summary>

<div part="content-base">
<div part="content">
<slot></slot>
</div>
</div>
</details>
`
}

get details () {
return this.shadowRoot?.querySelector("details")
}

/**
* @param {TransitionEvent} e
*/
handleTransitionEnd (e) {
const details = this.details

if (!details) return
if (!(e.propertyName === "grid-template-rows")) return

if (details.open === true) {
if (!(details.hasAttribute("expanded"))) {
details.open = false
}
} else {
details.open = true
}
}

/**
* Toggle fires after the attribute is set / unset, so its useless for expanded. But useful for when users search a page with "ctrl+f"
* @param {Event} _e
*/
handleToggle (_e) {
const details = this.details

if (!details) return

if (details.open && !(details.hasAttribute("expanded")) && this._openOnToggle) {
this.open = details.open
this.dispatchEvent(new Event("light-toggle"))
details.setAttribute("expanded", "")
}
}

/**
* @param {Event} e
*/
handleSummaryClick (e) {
const details = this.details
if (!details) return

e.preventDefault()

if (details.open) {
this.open = false
return false
}

this.open = true
}
}
59 changes: 1 addition & 58 deletions exports/light-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { BaseElement } from "../internal/base-element.js";
import { baseStyles } from "./base-styles.js";
import { styles } from "./light-editor.styles.js";
import { theme } from "./default-theme.styles.js";
import { LightResizeEvent } from "./events/light-resize-event.js"
import { dedent } from "../internal/dedent.js";

HighlightJS.registerLanguage('javascript', JavaScript);
Expand Down Expand Up @@ -64,31 +63,6 @@ export default class LightEditor extends BaseElement {
this.textarea = null
}

/**
* @ignore
* @param {ResizeObserverEntry[]} entries
*/
handleTextAreaResize (entries) {
const { target } = entries[0]
const {
left, right,
top, bottom
} = entries[0].contentRect;
const width = left + right
const height = top + bottom

// @ts-expect-error
target.parentElement.style.setProperty("--textarea-height", `${height}px`)


/**
* Fires whenever the editor resizes
*/
this.dispatchEvent(new LightResizeEvent("light-resize", {height, width}))
// One day we'll allow the textarea to resize the width.
// target.parentElement.style.setProperty("--textarea-width", `${width}px`)
}

render () {
const language = this.language

Expand Down Expand Up @@ -121,15 +95,13 @@ export default class LightEditor extends BaseElement {
this.dispatchEvent(new Event("light-selectionchange", { bubbles: true, composed: true }))
}}
@input=${/** @param {Event} e */ (e) => {
this.syncScroll(e)
this.value = /** @type {HTMLTextAreaElement} */ (e.currentTarget).value
this.dispatchEvent(new Event("light-input", { bubbles: true, composed: true }))
}}
@change=${/** @param {Event} e */ (e) => {
this.value = /** @type {HTMLTextAreaElement} */ (e.currentTarget).value
this.dispatchEvent(new Event("light-change", { bubbles: true, composed: true }))
}}
@scroll=${this.syncScroll}
.value=${this.value}
></textarea>
</div>
Expand All @@ -149,10 +121,6 @@ export default class LightEditor extends BaseElement {

const textarea = element

this.textareaResizeObserver = new ResizeObserver((entries) => this.handleTextAreaResize(entries))

this.textareaResizeObserver.observe(textarea)

this.textareaMutationObserver = new MutationObserver((mutationRecords) => {
// We actually don't care about what the mutation is, just update and move on.
// for (const mutation of mutationRecords) {
Expand Down Expand Up @@ -189,29 +157,7 @@ export default class LightEditor extends BaseElement {
}
}

/**
* @internal
* @param {Event} e
*/
syncScroll (e) {
/**
* @type {null | HTMLTextAreaElement}
*/
// @ts-expect-error
const textarea = e.target

if (textarea == null) return

const pre = this.shadowRoot?.querySelector(`pre`)

if (pre == null) return

pre.scrollTop = textarea.scrollTop;
pre.scrollLeft = textarea.scrollLeft;
}

disconnectedCallback () {
this.textareaResizeObserver?.disconnect()
this.textareaMutationObserver?.disconnect()
super.disconnectedCallback()
}
Expand All @@ -227,7 +173,7 @@ export default class LightEditor extends BaseElement {
// @ts-expect-error
const target = evt.target

// Let's not focus trap. For now.
// Let's not trap focus. For now.
// if ('Tab' === evt.key) {
// evt.preventDefault()
// target.setRangeText('\t', target.selectionStart, target.selectionEnd, 'end')
Expand All @@ -245,8 +191,6 @@ export default class LightEditor extends BaseElement {
let { code, language } = options

code = this.unescapeCharacters(code)
// Dedent is nice, but we don't want to do it on user typed data.
// code = dedent(code)
code = this.injectNewLine(code)

return HighlightJS.highlight(code, {language}).value
Expand Down Expand Up @@ -274,5 +218,4 @@ export default class LightEditor extends BaseElement {

return text
}

}
Loading
Loading