From 02fd1057bb157032a98d619396c587485715dab5 Mon Sep 17 00:00:00 2001 From: Stephen Date: Fri, 26 Jul 2024 18:50:49 +0200 Subject: [PATCH] Adds data property type to element() --- element/README.md | 13 ++++++++++++ element/element.js | 2 +- element/property.js | 5 +++++ modules/template.js | 50 +++++++++++++++++++++++++++++++++------------ 4 files changed, 56 insertions(+), 14 deletions(-) diff --git a/element/README.md b/element/README.md index f1bb9bf..ac60484 100644 --- a/element/README.md +++ b/element/README.md @@ -53,6 +53,19 @@ definitions. - `"tokens"` - defines a tokens attribute (think `class`) and a string setter / TokenList getter property - `"src"` - defines a URL attribute that links to a data property (TODO) - `"module"` - defines a URL attribute that ... (TODO) +- `"data"` - defines a property that exposes Literal's `data` object. This is +useful if you are building a closed system where literal custom elements are +authored inside literal templates, as data can be passed efficiently from +template to custom element. + +```html + +``` + +It is probably less useful for publishing custom elements intended for general +consumption. Changes to properties defined in this way are signalled to Literal's renderer. Literal updates the shadow DOM (not the whole thing, just the parts that need diff --git a/element/element.js b/element/element.js index 8f2652e..cd9f966 100644 --- a/element/element.js +++ b/element/element.js @@ -113,7 +113,7 @@ export default function LiteralElement(tag, lifecycle = {}, properties = {}, par // Create templates. This is a crude way to do it, and we should probably // isolate templates in the shadow from those outside with a separate // template cache (based around shadow.getElementById()?)... but... it'll - // do for now + // do for now... ? if (window.DEBUG) document.head.appendChild(create('comment', ' Templates for ' + name)); document.head.append.apply(document.head, templates); diff --git a/element/property.js b/element/property.js index 73c3c50..4518bd7 100644 --- a/element/property.js +++ b/element/property.js @@ -110,6 +110,11 @@ const types = { .then((object) => this[symbol].value = object) .catch((error) => console.error(error)); } + }), + + data: (name, symbol) => ({ + get: function() { return getInternals(this).renderer.data; }, + set: function(data) { getInternals(this).renderer.push(data); } }) }; diff --git a/modules/template.js b/modules/template.js index f1a0628..357d970 100644 --- a/modules/template.js +++ b/modules/template.js @@ -92,7 +92,7 @@ export class LiteralDOM { const children = content.childNodes; // The first node may change. The last node is always the last node. - this.#data = Signal.of(Data.of(data)); + this.#data = Signal.of(Data.objectOf(data)); this.#first = children[0]; this.#last = children[children.length - 1]; @@ -131,8 +131,8 @@ export class LiteralDOM { } /* - template.firstNode - template.lastNode + .firstNode + .lastNode */ get firstNode() { @@ -148,24 +148,40 @@ export class LiteralDOM { return this.#last; } - /* - .push() - */ + /** + .data + Read-only property exposing (literal's `data` proxy of) the currently + rendered object. This is the same object available as `data` inside a + literal template. Setting properties on this object causes the DOM to update. + **/ + + get data() { + const data = this.#data.value; + return Data.of(data) || data; + } + + /** + .push(object) + Rerenders and binds the DOM to (literal's `data` proxy of) `object`. This is + the same object available as `data` inside the template. + **/ push(object) { if (this.status === 'done') throw new Error('Renderer is done, cannot .push() data'); + // Make sure we have the raw object + object = Data.objectOf(object); + // Dedup - if (this.#data === object) return; + if (this.#data.value === object) return; // If we are coming out of sleep put content back in the DOM - if (this.#data === null && object !== null) { + if (this.#data.value === null && object !== null) { this.lastNode.before(this.content); } - // Causes renderers to .invalidate() because they are dependent on - // this.#data signal - this.#data.value = Data.of(object); + // Causes renderers dependent on this signal to .invalidate() + this.#data.value = object; // If object is null put template to sleep: remove all but the last node // to the content fragment and blank out the last text node, which we @@ -246,8 +262,16 @@ export default class LiteralRenderer extends LiteralDOM { } static of(html) { - const template = create('template', html); - return new LiteralRenderer(template); + return LiteralRenderer.from(create('template', html)); + } + + static from(template, parent) { + const id = identify(template, 'literal-'); + const fragment = template.content; + const compiled = cache[id] + || (cache[id] = LiteralRenderer.compile(fragment, options, '#' + id)); + + return new LiteralDOM(compiled, fragment.cloneNode(true), parent = template.parentElement); } static compile(fragment, options, src) {