@lume/live-code
Version:
A `<live-code>` element for editable code with live output.
849 lines (803 loc) • 37.7 kB
JavaScript
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
var _, done = false;
for (var i = decorators.length - 1; i >= 0; i--) {
var context = {};
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
if (kind === "accessor") {
if (result === void 0) continue;
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
if (_ = accept(result.get)) descriptor.get = _;
if (_ = accept(result.set)) descriptor.set = _;
if (_ = accept(result.init)) initializers.unshift(_);
}
else if (_ = accept(result)) {
if (kind === "field") initializers.unshift(_);
else descriptor[key] = _;
}
}
if (target) Object.defineProperty(target, contextIn.name, descriptor);
done = true;
};
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
var useValue = arguments.length > 2;
for (var i = 0; i < initializers.length; i++) {
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
}
return useValue ? value : void 0;
};
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
};
import { Element, element, numberAttribute, stringAttribute, booleanAttribute, } from '@lume/element';
import { signal, reactive, Effectful } from 'classy-solid';
import { batch, onCleanup } from 'solid-js';
import html from 'solid-js/html';
import { smoothy } from 'thememirror/dist/themes/smoothy.js'; // Needs a fix for selection highlight. https://github.com/vadimdemedes/thememirror
import { CodeMirrorContentchangedEvent } from 'code-mirror-el';
import {} from './OutputView.js';
import { stripIndent } from './stripIndent.js';
export class LiveCodeContentchangedEvent extends CodeMirrorContentchangedEvent {
constructor(view) {
super(view);
}
}
let ID = 0;
let LiveCode = (() => {
let _classDecorators = [element('live-code')];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
let _classSuper = Effectful(Element);
let _content_decorators;
let _content_initializers = [];
let _content_extraInitializers = [];
let _src_decorators;
let _src_initializers = [];
let _src_extraInitializers = [];
let _stripIndent_decorators;
let _stripIndent_initializers = [];
let _stripIndent_extraInitializers = [];
let _trim_decorators;
let _trim_initializers = [];
let _trim_extraInitializers = [];
let _autorun_decorators;
let _autorun_initializers = [];
let _autorun_extraInitializers = [];
let _autorunInView_decorators;
let _autorunInView_initializers = [];
let _autorunInView_extraInitializers = [];
let _mode_decorators;
let _mode_initializers = [];
let _mode_extraInitializers = [];
let _debounce_decorators;
let _debounce_initializers = [];
let _debounce_extraInitializers = [];
var LiveCode = class extends _classSuper {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
_content_decorators = [stringAttribute];
_src_decorators = [stringAttribute];
_stripIndent_decorators = [booleanAttribute];
_trim_decorators = [booleanAttribute];
_autorun_decorators = [booleanAttribute];
_autorunInView_decorators = [booleanAttribute];
_mode_decorators = [stringAttribute];
_debounce_decorators = [numberAttribute];
__esDecorate(null, null, _content_decorators, { kind: "field", name: "content", static: false, private: false, access: { has: obj => "content" in obj, get: obj => obj.content, set: (obj, value) => { obj.content = value; } }, metadata: _metadata }, _content_initializers, _content_extraInitializers);
__esDecorate(null, null, _src_decorators, { kind: "field", name: "src", static: false, private: false, access: { has: obj => "src" in obj, get: obj => obj.src, set: (obj, value) => { obj.src = value; } }, metadata: _metadata }, _src_initializers, _src_extraInitializers);
__esDecorate(null, null, _stripIndent_decorators, { kind: "field", name: "stripIndent", static: false, private: false, access: { has: obj => "stripIndent" in obj, get: obj => obj.stripIndent, set: (obj, value) => { obj.stripIndent = value; } }, metadata: _metadata }, _stripIndent_initializers, _stripIndent_extraInitializers);
__esDecorate(null, null, _trim_decorators, { kind: "field", name: "trim", static: false, private: false, access: { has: obj => "trim" in obj, get: obj => obj.trim, set: (obj, value) => { obj.trim = value; } }, metadata: _metadata }, _trim_initializers, _trim_extraInitializers);
__esDecorate(null, null, _autorun_decorators, { kind: "field", name: "autorun", static: false, private: false, access: { has: obj => "autorun" in obj, get: obj => obj.autorun, set: (obj, value) => { obj.autorun = value; } }, metadata: _metadata }, _autorun_initializers, _autorun_extraInitializers);
__esDecorate(null, null, _autorunInView_decorators, { kind: "field", name: "autorunInView", static: false, private: false, access: { has: obj => "autorunInView" in obj, get: obj => obj.autorunInView, set: (obj, value) => { obj.autorunInView = value; } }, metadata: _metadata }, _autorunInView_initializers, _autorunInView_extraInitializers);
__esDecorate(null, null, _mode_decorators, { kind: "field", name: "mode", static: false, private: false, access: { has: obj => "mode" in obj, get: obj => obj.mode, set: (obj, value) => { obj.mode = value; } }, metadata: _metadata }, _mode_initializers, _mode_extraInitializers);
__esDecorate(null, null, _debounce_decorators, { kind: "field", name: "debounce", static: false, private: false, access: { has: obj => "debounce" in obj, get: obj => obj.debounce, set: (obj, value) => { obj.debounce = value; } }, metadata: _metadata }, _debounce_initializers, _debounce_extraInitializers);
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
LiveCode = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
__runInitializers(_classThis, _classExtraInitializers);
}
/** The text to put in the editor. */
content = __runInitializers(this, _content_initializers, ''
/**
* A file from which to get code for the editor from. If given, takes
* priority over the `code` value.
*/
);
/**
* A file from which to get code for the editor from. If given, takes
* priority over the `code` value.
*/
src = (__runInitializers(this, _content_extraInitializers), __runInitializers(this, _src_initializers, ''
/**
* When true (default) common indentation will be removed. Useful for
* example if the `content` property is being set with a template string and
* the content is indented to make the outer code more readable but the
* indentation is undersired in the result. Set the attribute
* `strip-indent="false"` to disable.
*/
));
/**
* When true (default) common indentation will be removed. Useful for
* example if the `content` property is being set with a template string and
* the content is indented to make the outer code more readable but the
* indentation is undersired in the result. Set the attribute
* `strip-indent="false"` to disable.
*/
stripIndent = (__runInitializers(this, _src_extraInitializers), __runInitializers(this, _stripIndent_initializers, true
/**
* When true (default) leading and trailing whitespace will be trimmed. Set
* the attribute `trim="false"` to disable.
*/
));
/**
* When true (default) leading and trailing whitespace will be trimmed. Set
* the attribute `trim="false"` to disable.
*/
trim = (__runInitializers(this, _stripIndent_extraInitializers), __runInitializers(this, _trim_initializers, true
/**
* By default the live preview will update automatically (debounced) when
* code in the editor is modified. Set to false to prevent automatic
* running, and run only when the rerun button is clicked.
*/
));
/**
* By default the live preview will update automatically (debounced) when
* code in the editor is modified. Set to false to prevent automatic
* running, and run only when the rerun button is clicked.
*/
autorun = (__runInitializers(this, _trim_extraInitializers), __runInitializers(this, _autorun_initializers, true
/**
* Only useful when `autorun` is true. When `autorun` is true, then if this
* is true, the preview will only autorun if it is visible on screen (f.e.
* not scrolled outside of the view). If this is false, then the preview
* will autorun regardless if it is visible or not. If there are a lot of
* examples on the page, running them all even if they are not visible could
* be costly, and you may want to run only the ones that are in view.
*
* When true, any live code previews that go off screen will be discarded,
* and automatically re-ran when they come back into view.
*/
));
/**
* Only useful when `autorun` is true. When `autorun` is true, then if this
* is true, the preview will only autorun if it is visible on screen (f.e.
* not scrolled outside of the view). If this is false, then the preview
* will autorun regardless if it is visible or not. If there are a lot of
* examples on the page, running them all even if they are not visible could
* be costly, and you may want to run only the ones that are in view.
*
* When true, any live code previews that go off screen will be discarded,
* and automatically re-ran when they come back into view.
*/
autorunInView = (__runInitializers(this, _autorun_extraInitializers), __runInitializers(this, _autorunInView_initializers, true
/**
* Specify the editor mode:
* - script
* - script>iframe
* - html>iframe
*/
));
/**
* Specify the editor mode:
* - script
* - script>iframe
* - html>iframe
*/
mode = (__runInitializers(this, _autorunInView_extraInitializers), __runInitializers(this, _mode_initializers, 'html>iframe'
/**
* If `autorun` is true, then autorun is debounced by this amount in
* milliseconds after a user types into the code editor. Defaults to `1000`.
*/
));
/**
* If `autorun` is true, then autorun is debounced by this amount in
* milliseconds after a user types into the code editor. Defaults to `1000`.
*/
debounce = (__runInitializers(this, _mode_extraInitializers), __runInitializers(this, _debounce_initializers, 1000
/////////////////////////
// Private reactive state
));
/////////////////////////
// Private reactive state
#_ = (__runInitializers(this, _debounce_extraInitializers), new ((() => {
let _classDecorators = [reactive];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
let _error_decorators;
let _error_initializers = [];
let _error_extraInitializers = [];
let _initialValue_decorators;
let _initialValue_initializers = [];
let _initialValue_extraInitializers = [];
let _editorValue_decorators;
let _editorValue_initializers = [];
let _editorValue_extraInitializers = [];
let _debouncedEditorValue_decorators;
let _debouncedEditorValue_initializers = [];
let _debouncedEditorValue_extraInitializers = [];
let _smaller_decorators;
let _smaller_initializers = [];
let _smaller_extraInitializers = [];
let _canView_decorators;
let _canView_initializers = [];
let _canView_extraInitializers = [];
var class_1 = class {
static { _classThis = this; }
static { __setFunctionName(_classThis, ""); }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
_error_decorators = [signal];
_initialValue_decorators = [signal];
_editorValue_decorators = [signal];
_debouncedEditorValue_decorators = [signal];
_smaller_decorators = [signal];
_canView_decorators = [signal];
__esDecorate(null, null, _error_decorators, { kind: "field", name: "error", static: false, private: false, access: { has: obj => "error" in obj, get: obj => obj.error, set: (obj, value) => { obj.error = value; } }, metadata: _metadata }, _error_initializers, _error_extraInitializers);
__esDecorate(null, null, _initialValue_decorators, { kind: "field", name: "initialValue", static: false, private: false, access: { has: obj => "initialValue" in obj, get: obj => obj.initialValue, set: (obj, value) => { obj.initialValue = value; } }, metadata: _metadata }, _initialValue_initializers, _initialValue_extraInitializers);
__esDecorate(null, null, _editorValue_decorators, { kind: "field", name: "editorValue", static: false, private: false, access: { has: obj => "editorValue" in obj, get: obj => obj.editorValue, set: (obj, value) => { obj.editorValue = value; } }, metadata: _metadata }, _editorValue_initializers, _editorValue_extraInitializers);
__esDecorate(null, null, _debouncedEditorValue_decorators, { kind: "field", name: "debouncedEditorValue", static: false, private: false, access: { has: obj => "debouncedEditorValue" in obj, get: obj => obj.debouncedEditorValue, set: (obj, value) => { obj.debouncedEditorValue = value; } }, metadata: _metadata }, _debouncedEditorValue_initializers, _debouncedEditorValue_extraInitializers);
__esDecorate(null, null, _smaller_decorators, { kind: "field", name: "smaller", static: false, private: false, access: { has: obj => "smaller" in obj, get: obj => obj.smaller, set: (obj, value) => { obj.smaller = value; } }, metadata: _metadata }, _smaller_initializers, _smaller_extraInitializers);
__esDecorate(null, null, _canView_decorators, { kind: "field", name: "canView", static: false, private: false, access: { has: obj => "canView" in obj, get: obj => obj.canView, set: (obj, value) => { obj.canView = value; } }, metadata: _metadata }, _canView_initializers, _canView_extraInitializers);
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
class_1 = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
__runInitializers(_classThis, _classExtraInitializers);
}
error = __runInitializers(this, _error_initializers, '');
initialValue = (__runInitializers(this, _error_extraInitializers), __runInitializers(this, _initialValue_initializers, ''));
editorValue = (__runInitializers(this, _initialValue_extraInitializers), __runInitializers(this, _editorValue_initializers, ''));
debouncedEditorValue = (__runInitializers(this, _editorValue_extraInitializers), __runInitializers(this, _debouncedEditorValue_initializers, ''));
smaller = (__runInitializers(this, _debouncedEditorValue_extraInitializers), __runInitializers(this, _smaller_initializers, false));
canView = (__runInitializers(this, _smaller_extraInitializers), __runInitializers(this, _canView_initializers, null));
constructor() {
__runInitializers(this, _canView_extraInitializers);
}
};
return class_1 = _classThis;
})())());
#id = ID++;
#codemirror;
#resizeObserver = new ResizeObserver(changes => {
for (const change of changes) {
let width = 0;
// Use the newer API if available.
// NOTE We care about the contentBoxSize (not the
// borderBoxSize) because the content box is the area in
// which we're rendering visuals.
if (change.contentBoxSize) {
// Get the first item, because we're not expecting to
// have a column layout.
const { inlineSize, blockSize } = change.contentBoxSize[0];
const isHorizontal = getComputedStyle(this.#form).writingMode.includes('horizontal');
// If the text writing mode is horizontal, then inlinSize is
// the width, otherwise in vertical writing mode it is the height.
// For more details: https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentBoxSize#Syntax
if (isHorizontal)
width = inlineSize;
else
width = blockSize;
}
// Otherwise use the older API (possibly polyfilled)
else {
width = change.contentRect.width;
}
this.#handleResize(width);
}
});
#form;
#fullviewModalHost;
#dialog;
#toggleFullviewDialog() {
if (this.#dialog.open) {
document.body.removeAttribute('inert');
this.#form.slot = '';
this.#dialog.close();
}
else {
if (!document.body.hasAttribute('inert'))
document.body.toggleAttribute('inert');
this.#form.slot = 'fullscreen';
this.#dialog.showModal();
}
}
connectedCallback() {
super.connectedCallback();
// This is a fallback for browsers that don't have Fullscreen API,
// but have <dialog> support (namely iOS Safari).
if (!this.#fullviewModalHost.shadowRoot) {
const root = this.#fullviewModalHost.attachShadow({ mode: 'open' });
root.append(...html `
<dialog ref=${(e) => (this.#dialog = e)}>
<slot name="fullscreen"></slot>
</dialog>
<slot></slot>
<style>
dialog {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
max-width: unset;
max-height: unset;
border: none;
}
/*
* Make the backdrop bigger so that when iOS viewport
* resizes (when Safari UI auto-hides) we avoid
* revealing content underneath the modal.
*/
::backdrop {
width: 150%;
height: 150%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
}
</style>
`);
this.#dialog.addEventListener('touchmove', event => {
event.preventDefault();
event.stopPropagation();
});
}
this.createEffect(() => {
this.#executeCodeDebounced = debounce(this.#executeCode, this.debounce);
});
this.createEffect(() => {
if (this.src)
this.#loadCodeFromSrc();
else if (this.content)
this.#loadCodeFromContent();
else
this.#loadCodeFromTemplate();
});
this.createEffect(() => {
if (!this.autorun)
return;
if (this.autorunInView) {
const debouncedSetCanView = debounce(() => (this.#_.canView = true), 500);
const observer = new IntersectionObserver(entries => {
debouncedSetCanView.cancel();
for (const entry of entries) {
// Has come into view.
if (entry.isIntersecting) {
// on first run, show immediately if in view
if (this.#_.canView === null)
this.#_.canView = true;
// otherwise debounce to avoid many editors causing a flood of network and CPU load when scrolling past them.
else
debouncedSetCanView();
}
else {
this.#_.canView = false;
}
}
});
observer.observe(this);
onCleanup(() => {
debouncedSetCanView.cancel();
observer.disconnect();
});
}
else {
this.#_.canView = true;
}
this.createEffect(() => {
if (!this.#_.canView)
return;
this.createEffect(() => {
// When initialValue is same as editorValue, it most likely means we
// have started fresh or reset (most likely the user did not restore
// the original code manually, but that could be possible too), so
// start the example right away.
if (this.#_.editorValue === this.#_.initialValue) {
this.#executeCodeDebounced.cancel();
this.#executeCodeQuick();
}
// Otherwise we debounce while modifying code (it does not match
// with initialValue).
else {
this.#executeCodeDebounced();
}
});
onCleanup(() => {
// Empty string causes OutputView to be empty (f.e. removes the iframe)
this.#_.debouncedEditorValue = '';
});
});
});
this.#resizeObserver.observe(this.#form);
}
disconnectedCallback() {
super.disconnectedCallback();
this.stopEffects();
this.#resizeObserver.disconnect();
}
#onClickRerun = (event) => {
// prevent form submission.
event.preventDefault();
this.rerun();
};
rerun = () => {
this.#executeCodeDebounced.cancel();
this.#executeCodeQuick();
};
#onClickReset = (event) => {
// prevent form submission.
event.preventDefault();
this.reset();
};
reset = () => {
batch(() => {
this.#_.initialValue = this.#_.initialValue;
this.#_.editorValue = this.#_.initialValue;
});
};
#onClickCopy = (event) => {
event.preventDefault();
this.copy();
};
copy = () => {
navigator.clipboard.writeText(this.#codemirror?.currentContent ?? '');
};
#onClickToggleFullscreen = (event) => {
event.preventDefault();
this.toggleFullscreen();
};
toggleFullscreen = () => {
if (document.fullscreenElement)
document.exitFullscreen();
else
this.#form.requestFullscreen?.() ?? this.#form.webkitRequestFullscreen?.() ?? this.#toggleFullviewDialog();
};
#handleResize(width) {
if (width < 800)
this.#_.smaller = true;
else
this.#_.smaller = false;
}
#handleError(err) {
// TODO For now we skip harmless ResizeObserver errors. Chrome has a
// bug that causes this error to be emitted when it shouldn't. We
// should remove this block once the bug is fixed. See
// https://github.com/w3c/csswg-drafts/issues/5487
// @ts-expect-error
if (err && err.message && err.message.includes('ResizeObserver loop')) {
console.error(err);
return;
}
// @ts-expect-error
this.error = err && err.message ? (err.stack ? `ERROR: ${err.message}\n\n${err.stack}` : err.message) : err;
setTimeout(() => {
// Throw it in a separate task so that it won't interrupt
// live-code's operation, but the user can view it in console
// if it is of any help.
throw err;
}, 0);
}
#handleChange = debounce((event) => {
this.#_.editorValue = event.content;
this.dispatchEvent(new LiveCodeContentchangedEvent(event.view));
}, 100);
#executeCodeDebounced = debounce(() => { });
#executeCode() {
this.#_.error = '';
this.#_.debouncedEditorValue = this.#_.editorValue;
}
#executeCodeQuick = debounce(() => this.#executeCode(), 0);
async #loadCodeFromSrc() {
let cleaned = false;
onCleanup(() => (cleaned = true));
const relativeUrl = new URL(this.src, window.location.href);
const response = await fetch(relativeUrl.href);
const code = await response.text();
if (cleaned)
return;
this.#applyCode(code);
}
#loadCodeFromContent() {
let content = this.content;
const isSelector = /^[.#]/.test(this.content);
// If code starts with . or #
if (isSelector) {
// consider it a selector from which to get the code from.
const html = document.querySelector(this.content);
if (!html)
throw Error(`${this.content} is not found`);
content = unescape(html.innerHTML);
}
this.#applyCode(content);
}
#loadCodeFromTemplate() {
const template = this.children[0]; // only child must be <template>
if (!(template instanceof HTMLTemplateElement))
return;
const code = template.innerHTML;
this.#applyCode(code);
}
#applyCode(code) {
// for back compat
if (code.includes('${host}')) {
console.warn('<live-code>: ${host} is deprecated, use ${location.origin} instead');
code = code.replaceAll('${host}', location.origin);
}
if (code.includes('href="/"')) {
console.warn('<live-code>: href="/" is deprecated, use a <base> tag instead.');
code = code.replaceAll('href="/"', `href="${location.origin}"`);
}
const keys = [
'hash',
'host',
'hostname',
'href',
'origin',
'pathname',
'port',
'protocol',
'search',
];
for (const key of keys)
code = code.replace('${location.' + key + '}', window.location[key]);
// FIXME we rely on this so that live preview will not run twice
// initially. Instead, we need to update <code-mirror> so that it does
// not emit a contentchanged event when the value is being set, only
// when being modified by something else (like the user typing)
if (this.stripIndent)
code = stripIndent(code);
if (this.trim)
code = code.trim();
if (this.src) {
const dirUrl = new URL(this.src, window.location.href).href;
code = `<base href="${dirUrl}" />\n` + code;
}
batch(() => {
this.#_.initialValue = code;
this.#_.editorValue = code;
});
}
get syntax() {
return this.mode.startsWith('html') ? 'html' : 'js';
}
template = () => html `
<div ref=${(e) => (this.#fullviewModalHost = e)} style="display: contents">
<form
ref=${(e) => (this.#form = e)}
class=${() => 'live-code' + (this.#_.smaller ? ' live-code-smaller' : '')}
>
<input class="live-code-tab-input" type="radio" name="tab" id=${'live-code-tab-1-' + this.#id} />
<input class="live-code-tab-input" type="radio" name="tab" id=${'live-code-tab-2-' + this.#id} checked />
<label class="live-code-tab-label" for=${'live-code-tab-1-' + this.#id}>
<span>CODE</span>
<div class="live-code-edit-area live-code-tab-content">
<div class="live-code-buttons">
<div class="live-code-reset"><button onclick=${this.#onClickReset}>Reset</button></div>
<div class="live-code-copy"><button onclick=${this.#onClickCopy}>Copy</button></div>
</div>
<div class="live-code-editor">
<code-mirror
ref=${(e) => (this.#codemirror = e)}
basic-setup
content=${() => this.#_.initialValue}
language=${() => this.syntax}
strip-indent=${() => this.stripIndent}
trim=${() => this.trim}
on:contentchanged=${(event) => this.#handleChange(event)}
stylesheet=${
/*css*/ `
.cm-focused {
outline: none !important;
}
.cm-activeLine {
/* The color from noctisLilac with an additional 0.4 opacity value */
background-color: rgba(225, 222, 243, 0.4) !important;
}
`}
xtheme=${smoothy}
extensions=${[]}
></code-mirror>
</div>
</div>
</label>
<label class="live-code-tab-label" for=${'live-code-tab-2-' + this.#id}>
<span>RESULT</span>
<div class="live-code-preview-area live-code-tab-content">
<div class="live-code-buttons">
<div class="live-code-rerun"><button onclick=${this.#onClickRerun}>Rerun</button></div>
<div class="live-code-fullscreen">
<button onclick=${this.#onClickToggleFullscreen}>Toggle Fullscreen</button>
</div>
</div>
<div class=${() => 'live-code-error' + (!this.#_.error ? ' hidden' : '')}>
<pre>${() => this.#_.error}</pre>
</div>
<output-view
class=${() => (this.#_.error ? ' hidden' : '')}
value=${() => this.#_.debouncedEditorValue}
mode=${() => this.mode}
on:error=${(e) => this.#handleError(e.error)}
></output-view>
</div>
</label>
</form>
</div>
`;
css = /*css*/ `
:host {
height: 420px;
}
.live-code {
font-family: 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif;
background: rgb(242, 241, 248); /* Background color from noctisLilac theme. */
height: 100%;
width: 100%;
position: relative;
display: flex;
--tab-color: deeppink;
--tab-text-color: white;
}
.live-code-tab-input {
display: none;
}
.live-code-smaller .live-code-tab-label {
display: inline;
width: 50%;
height: 40px;
}
.live-code-smaller .live-code-tab-label > span {
display: block;
text-align: center;
vertical-align: middle;
line-height: 40px;
font-size: 1.5em;
background: rgb(0 0 0 / 0.02)
}
.live-code-smaller .live-code-tab-input:nth-child(1):checked ~ label:nth-child(3),
.live-code-smaller .live-code-tab-input:nth-child(2):checked ~ label:nth-child(4) {
background: var(--tab-color);
}
.live-code-smaller .live-code-tab-input:nth-child(1):checked ~ label:nth-child(3) > span,
.live-code-smaller .live-code-tab-input:nth-child(2):checked ~ label:nth-child(4) > span {
color: var(--tab-text-color);
}
.live-code-tab-content:not(.live-code-smaller *) {
height: 100%;
}
.live-code-smaller .live-code-tab-content {
top: 40px;
left: 0;
right: 0;
bottom: 0;
position: absolute;
}
.live-code-tab-label:not(.live-code-smaller *) {
width: 50%;
}
.live-code-tab-label:not(.live-code-smaller *) > span {
display: none;
}
.live-code-smaller .live-code-tab-input:nth-child(1):not(:checked) ~ label:nth-child(3) > .live-code-tab-content,
.live-code-smaller .live-code-tab-input:nth-child(2):not(:checked) ~ label:nth-child(4) > .live-code-tab-content {
display: none;
}
/* Button row {{{ */
.live-code-edit-area:not(.live-code-smaller *) .live-code-rerun,
.live-code-preview-area:not(.live-code-smaller *) .live-code-reset {
display: none;
}
.live-code-buttons {
flex-grow: 0;
height: 40px;
display: flex;
gap: 5px;
padding: 4px 6px;
justify-content: flex-end;
align-items: center;
}
.live-code-smaller .live-code-buttons {
padding: 6px 9px;
}
.live-code-buttons button {
display: block;
border-radius: 3px;
border: none;
background: rgb(0 0 0 / 0.08);
padding: 5px 10px;
color: black;
font-size: 1em;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
.live-code-smaller .live-code-buttons button {
padding: 5px 10px;
font-size: 1.2em;
}
/* }}} */
/* Main area with editor and preview {{{ */
.live-code-edit-area {
display: flex;
flex-direction: column;
}
.live-code-edit-area:not(.live-code-smaller *) {
padding-right: 10px;
}
.live-code-preview-area {
display: flex;
flex-direction: column;
}
.live-code-preview,
.live-code-error {
box-sizing: border-box;
}
.live-code-editor,
.live-code-preview,
.live-code-error {
flex-grow: 1;
height: 100%;
overflow: auto;
border-radius: 2px;
border-top-left-radius: 0;
border-top-right-radius: 0;
background: #f9f9f9;
}
code-mirror {
height: 100%;
}
.live-code-error {
color: #f66;
padding: 25px 35px;
}
/* }}} */
/* Show only preview */
/* big mode */
:host([editor-hidden]) [for^=live-code-tab-1] {display: none}
:host([editor-hidden]) [for^=live-code-tab-2] {width: 100%}
/* small mode */
:host([editor-hidden]) .live-code-preview-area {display: flex !important; top: 0}
:host([editor-hidden]) .live-code-tab-label > span {display: none}
:host([editor-hidden]) .live-code-tab-label {background: none !important} /*ugh*/
/* Scrollbars */
.live-code ::-webkit-scrollbar-track {
border-radius: 10px;
background-color: #f5f5f5;
}
.live-code ::-webkit-scrollbar {
width: 8px;
height: 8px;
background-color: #f5f5f5;
}
.live-code ::-webkit-scrollbar-thumb {
border-radius: 8px;
background-color: #bbb;
transition: all 0.5s;
}
.live-code ::-webkit-scrollbar-thumb:hover {
border-radius: 8px;
background-color: #777;
}
`;
};
return LiveCode = _classThis;
})();
export { LiveCode };
function debounce(fn, time = 0) {
let timeout = 0;
const debounced = function (...args) {
clearTimeout(timeout);
timeout = window.setTimeout(() => fn.apply(this, args), time);
};
debounced.cancel = () => clearTimeout(timeout);
return debounced;
}
//# sourceMappingURL=LiveCode.js.map