@google/model-viewer
Version:
Easily display interactive 3D models on the web and in AR!
140 lines (112 loc) • 4.31 kB
text/typescript
import 'prismjs';
import {property} from 'lit-element';
import {UpdatingElement} from 'lit-element/lib/updating-element.js';
// Silence tsc since prismjs isn't a proper module
declare var Prism: any;
const EMPTY_ATTRIBUTE_RE = /([\w-]+)=\"\"/g;
export type RootNode = Document|ShadowRoot;
/**
* ExampleSnippet is a custom element that enables documentation authors to
* craft a single code snippet that is used for both human-readable
* documentation and running a live demo.
*
* An example usage looks like this:
*
* <div id="demo-container"></div>
* <example-snippet stamp-to="demo-container" highlight-as="html">
* <template>
* <script>
* console.log('This is how you log things to the console!');
* </script>
* </template>
* </example-snippet>
*
* The above example will create a <pre><code></code></pre> inside of itself
* containing syntax-highlightable markup of the code in the template. It will
* also create the content of the template as DOM and insert it into the node
* indicated by the "stamp-to" attribute. The "stamp-to" attribute references
* another node by its ID.
*
* Add the "lazy" boolean attribute if you want to stamp the example manually
* by invoking its "stamp" method. Note that this will prevent the snippet from
* stamping itself automatically upon being connected to the DOM.
*
* ExampleSnippet is a simplified alternative to Polymer Project's DemoSnippet.
* The key differences are:
*
* - ExampleSnippet does not use ShadowDOM by default
* - ExampleSnippet supports stamping demos to elements other than itself
*
* @see https://github.com/PolymerElements/iron-demo-helpers/blob/master/demo-snippet.js
*/
export class ExampleSnippet extends UpdatingElement {
({type: Boolean, attribute: 'use-shadow-root'})
useShadowRoot: boolean = false;
({type: String, attribute: 'stamp-to'}) stampTo: string|null = null;
({type: Object}) template: HTMLTemplateElement|null = null;
({type: String, attribute: 'highlight-as'})
highlightAs: string = 'markup';
({type: Boolean, attribute: 'lazy'}) lazy: boolean = false;
({type: Boolean}) preserveWhitespace: boolean = false;
({type: Boolean, attribute: 'inert-script'})
inertScript: boolean = false;
readonly stamped: boolean = false;
public stamp() {
if (this.stampTo == null || this.template == null) {
return;
}
const root = this.getRootNode()!;
const stampTarget = (root as RootNode).getElementById(this.stampTo);
if (stampTarget != null) {
let parentNode;
if (this.useShadowRoot) {
if (stampTarget.shadowRoot == null) {
stampTarget.attachShadow({mode: 'open'});
}
parentNode = stampTarget.shadowRoot!;
} else {
parentNode = stampTarget;
}
const {template, highlightAs} = this;
const content = template.content.cloneNode(true) as DocumentFragment;
if (this.inertScript) {
const scripts = Array.from(content.querySelectorAll('script'));
for (const script of scripts) {
script.parentNode!.removeChild(script);
}
}
parentNode.appendChild(content);
const pre = document.createElement('pre');
const code = document.createElement('code');
pre.appendChild(code);
let snippet = template.innerHTML;
if (!this.preserveWhitespace) {
snippet = snippet.trim();
}
if (highlightAs === 'html') {
snippet = snippet.replace(EMPTY_ATTRIBUTE_RE, '$1');
}
const highlighted =
Prism.highlight(snippet, Prism.languages[highlightAs], highlightAs);
code.innerHTML = highlighted;
this.appendChild(pre);
}
}
connectedCallback() {
super.connectedCallback && super.connectedCallback();
this.template = this.querySelector('template');
}
createRenderRoot() {
return this;
}
updated(changedProperties: Map<string, any>) {
super.updated(changedProperties);
if (!this.stamped && !this.lazy &&
(changedProperties.has('stamp-to') ||
changedProperties.has('template')) &&
this.template != null && this.stampTo != null) {
this.stamp();
}
}
}
customElements.define('example-snippet', ExampleSnippet);