Code
A block for showing source code, highlighted with highlight.js for TypeScript, HTML and CSS. Whitespace and line breaks are kept as written.
Examples
@customElement('demo-el')
export class DemoEl extends LitElement {
static styles = css`
:host { color: rgb(var(--accent)); }
`;
render() {
return html`<p>Hello</p>`;
}
}<zakuni-button class="primary">Save</zakuni-button>.card:hover { box-shadow: 0 0 8px var(--sf-color, #f90); }Properties
| Name | Type | Default | Description |
|---|---|---|---|
code | string | '' | Source to display. Highlighted during server-side rendering when set; otherwise slotted text is highlighted after hydration. |
language | 'ts' | 'html' | 'css' | auto | Language to highlight as. When omitted, text starting with < is treated as HTML and everything else as TypeScript. |
CSS variables
| Name | Type | Default | Description |
|---|---|---|---|
--code-bg | <color> | #eee | Block background. |
--code-fg | <color> | #1d0a0b | Plain text colour. |
--code-comment | <color> | #7a7a7a | Comments. |
--code-string | <color> | #2e7d32 | Strings and regular expressions. |
--code-keyword | <color> | #6a1b9a | Keywords, built-ins and literals. |
--code-decorator | <color> | #b26a00 | Decorators and other meta. |
--code-number | <color> | #c62828 | Numbers. |
--code-tag | <color> | #1565c0 | HTML tags and CSS selectors. |
--code-attr | <color> | #00695c | HTML attributes and CSS properties. |
Usage
<zakuni-code>const x = 1;</zakuni-code>
<zakuni-code language="html"><button>Go</button></zakuni-code>The block scrolls horizontally when a line is wider than its container.
Implementation
import { LitElement, css, html, type CSSResultGroup } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import hljs from 'highlight.js/lib/core';
import typescript from 'highlight.js/lib/languages/typescript';
import xml from 'highlight.js/lib/languages/xml';
import cssLang from 'highlight.js/lib/languages/css';
hljs.registerLanguage('ts', typescript);
hljs.registerLanguage('html', xml);
hljs.registerLanguage('css', cssLang);
export type CodeLanguage = 'ts' | 'html' | 'css';
/** Drop a single leading newline and trailing whitespace, as `<pre>` would. */
const normalize = (src: string) => src.replace(/^\n/, '').replace(/\s+$/, '');
@customElement('zakuni-code')
export class ZakuniCode extends LitElement {
/**
* Source to display. When set, it is highlighted inside `render()`, so it
* also works with server-side rendering and no client JavaScript.
* When omitted, slotted text is highlighted on the client after hydration.
*/
@property({ type: String })
code = '';
/** Language to highlight as. Defaults to HTML for text starting with `<`, TypeScript otherwise. */
@property({ type: String })
language?: CodeLanguage;
@state()
private _slotted = '';
static styles: CSSResultGroup = css`
:host {
display: block;
}
div {
background: var(--code-bg, #eee);
border-radius: 3px;
color: var(--code-fg, #1d0a0b);
padding: 0.8em 1em;
overflow-x: auto;
}
code {
font-size: 1.2em;
}
pre {
margin: 0;
}
.hljs-comment, .hljs-quote { color: var(--code-comment, #7a7a7a); font-style: italic; }
.hljs-string, .hljs-regexp { color: var(--code-string, #2e7d32); }
.hljs-keyword, .hljs-built_in, .hljs-literal { color: var(--code-keyword, #6a1b9a); }
.hljs-meta { color: var(--code-decorator, #b26a00); }
.hljs-number { color: var(--code-number, #c62828); }
.hljs-tag, .hljs-name, .hljs-selector-tag, .hljs-selector-class, .hljs-selector-pseudo { color: var(--code-tag, #1565c0); }
.hljs-attr, .hljs-attribute { color: var(--code-attr, #00695c); }
.hljs-title, .hljs-title.class_ { color: var(--code-fg, #1d0a0b); }
`;
private _onSlotChange() {
this._slotted = this.textContent ?? '';
}
render() {
const src = normalize(this.code || this._slotted);
if (!src) {
return html`<div><code><pre><slot @slotchange=${this._onSlotChange}></slot></pre></code></div>`;
}
const language = this.language ?? (/^\s*</.test(src) ? 'html' : 'ts');
const { value } = hljs.highlight(src, { language });
return html`<div><code><pre>${unsafeHTML(value)}</pre></code></div>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"zakuni-code": ZakuniCode;
}
}