diff --git a/packages/@hec.js/ui/example/lazy/include.html b/packages/@hec.js/ui/example/lazy/include.html
new file mode 100644
index 0000000..9b06149
--- /dev/null
+++ b/packages/@hec.js/ui/example/lazy/include.html
@@ -0,0 +1 @@
+include {{ name }}
\ No newline at end of file
diff --git a/packages/@hec.js/ui/example/lazy/index.html b/packages/@hec.js/ui/example/lazy/index.html
new file mode 100644
index 0000000..672642e
--- /dev/null
+++ b/packages/@hec.js/ui/example/lazy/index.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+ hec.js :: Plugins
+
+
+
+
+
+ The parts will lazy load...
+
+
+
+
+
Inline 2 {{ name }}
+
+
+
Inline 3 {{ name }}
+
+
+ Inline 4 {{ name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/@hec.js/ui/example/lazy/lazy.js b/packages/@hec.js/ui/example/lazy/lazy.js
new file mode 100644
index 0000000..517a3d7
--- /dev/null
+++ b/packages/@hec.js/ui/example/lazy/lazy.js
@@ -0,0 +1,24 @@
+import { component, signal, templateByNode, templateByString } from '../../lib/index.js';
+
+const p = {
+ name: 'Lazy...'
+}
+
+const lazy = async () => {
+ let i = 1;
+
+ for (const lazy of document.querySelectorAll('[hidden]')) {
+ await new Promise(r => setTimeout(r, 1000));
+
+ p.name += ` -> ${i++}`;
+
+ lazy.removeAttribute('hidden');
+ }
+
+}
+
+lazy();
+
+templateByNode(document.body, p);
+
+component('my-test', {}, () => templateByString(new Date().toJSON()));
\ No newline at end of file
diff --git a/packages/@hec.js/ui/lib/src/component.js b/packages/@hec.js/ui/lib/src/component.js
index 66ff145..bb43472 100644
--- a/packages/@hec.js/ui/lib/src/component.js
+++ b/packages/@hec.js/ui/lib/src/component.js
@@ -1,3 +1,4 @@
+import { notifyVisible } from "./notify/visible.js";
import { f, nodeProps, prop, propsOf } from "./props.js";
import { isSignal, signal } from "./signal.js";
@@ -26,7 +27,13 @@ export function component(name, props, fn) {
/** @type {{ [key: string]: AbortController }} */
#aborts = {};
- connectedCallback() {
+ async connectedCallback() {
+ const hidden = this.closest('[hidden]');
+
+ if (this.hasAttribute('data-lazy') && hidden) {
+ await notifyVisible(this, hidden);
+ }
+
const shadow = this.attachShadow({ mode: 'open' }),
node = fn(this.#signals);
@@ -53,7 +60,7 @@ export function component(name, props, fn) {
this.dispatchEvent(new CustomEvent('::mount'));
}
- node instanceof Promise ? node.then(append) : append(node);
+ append(node instanceof Promise ? await node : node);
}
disconnectedCallback() {
diff --git a/packages/@hec.js/ui/lib/src/plugins.js b/packages/@hec.js/ui/lib/src/plugins.js
index 1529be8..b000045 100644
--- a/packages/@hec.js/ui/lib/src/plugins.js
+++ b/packages/@hec.js/ui/lib/src/plugins.js
@@ -2,6 +2,7 @@ import { dataBindPlugin } from "./plugins/data-bind.js";
import { dataForPlugin } from "./plugins/data-for.js";
import { dataIfPlugin } from "./plugins/data-if.js";
import { dataIncludePlugin } from "./plugins/data-include.js";
+import { dataLazyPlugin } from "./plugins/data-lazy.js";
import { dataOnPlugin } from "./plugins/data-on.js";
import { dataPreloadPlugin } from "./plugins/data-preload.js";
import { dataRoutePlugin } from "./plugins/data-route.js";
@@ -9,9 +10,10 @@ import { dataRoutePlugin } from "./plugins/data-route.js";
/**
* @typedef {{
* select: string,
- * run: (arg0: HTMLElement, arg1: {[key: string]: any}) => void
+ * run: (node: HTMLElement, props: {[key: string]: any}, stopTemplate: () => void) => void
* }} Plugin
*
+ *
/** @type { Plugin[] } */
export const plugins = [
dataForPlugin,
@@ -20,5 +22,6 @@ export const plugins = [
dataIncludePlugin,
dataPreloadPlugin,
dataOnPlugin,
- dataBindPlugin
+ dataBindPlugin,
+ dataLazyPlugin,
];
\ No newline at end of file
diff --git a/packages/@hec.js/ui/lib/src/plugins/data-for.js b/packages/@hec.js/ui/lib/src/plugins/data-for.js
index df6424b..4871450 100644
--- a/packages/@hec.js/ui/lib/src/plugins/data-for.js
+++ b/packages/@hec.js/ui/lib/src/plugins/data-for.js
@@ -10,7 +10,7 @@ const done = new WeakSet();
export const dataForPlugin = {
select: '[data-for]',
- run: (node, props) => {
+ run: (node, props, stopTemplate) => {
if (done.has(node)) {
return;
@@ -62,5 +62,7 @@ export const dataForPlugin = {
if (isSignal(list)) {
list.subscribe({next: update});
}
+
+ stopTemplate();
}
}
\ No newline at end of file
diff --git a/packages/@hec.js/ui/lib/src/plugins/data-include.js b/packages/@hec.js/ui/lib/src/plugins/data-include.js
index b9c17e4..0639daf 100644
--- a/packages/@hec.js/ui/lib/src/plugins/data-include.js
+++ b/packages/@hec.js/ui/lib/src/plugins/data-include.js
@@ -17,7 +17,7 @@ export const dataIncludePlugin = {
loaded.add(node);
- const hidden = node.closest('[hidden]');
+ const hidden = node.hasAttribute('data-lazy') && node.closest('[hidden]');
const execute = async () => {
const response = await fetch(node.dataset.include, {
diff --git a/packages/@hec.js/ui/lib/src/plugins/data-lazy.js b/packages/@hec.js/ui/lib/src/plugins/data-lazy.js
new file mode 100644
index 0000000..0c6d77d
--- /dev/null
+++ b/packages/@hec.js/ui/lib/src/plugins/data-lazy.js
@@ -0,0 +1,33 @@
+import { notifyVisible } from '../notify/visible.js';
+import { templateByNode } from '../template.js';
+
+const loaded = new WeakSet();
+
+/**
+ * @type { import("../plugins.js").Plugin }
+ */
+export const dataLazyPlugin = {
+ select: '[data-lazy]',
+
+ run: (node, props, stopTemplate) => {
+
+ if (loaded.has(node) || !node.childNodes.length) {
+ return;
+ }
+
+ loaded.add(node);
+
+ const hidden = node.closest('[hidden]');
+
+ const execute = () => {
+ for (const child of node.childNodes) {
+ templateByNode(child, props);
+ }
+ }
+
+ if (hidden) {
+ notifyVisible(node, hidden).then(execute);
+ stopTemplate();
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/@hec.js/ui/lib/src/plugins/data-route.js b/packages/@hec.js/ui/lib/src/plugins/data-route.js
index b2c1657..2e2f813 100644
--- a/packages/@hec.js/ui/lib/src/plugins/data-route.js
+++ b/packages/@hec.js/ui/lib/src/plugins/data-route.js
@@ -13,7 +13,7 @@ const joinsRoutes = (node) => {
}
}
- return route
+ return route.replaceAll(/\/+/g, '/');
}
/**
diff --git a/packages/@hec.js/ui/lib/src/template.js b/packages/@hec.js/ui/lib/src/template.js
index 40cca8c..4f94bf0 100644
--- a/packages/@hec.js/ui/lib/src/template.js
+++ b/packages/@hec.js/ui/lib/src/template.js
@@ -19,7 +19,7 @@ export function templateByName(name, props = {}) {
}
/** @type { HTMLTemplateElement } */
- let tmpl = document.querySelector(`template[data-name="${name}"]`);
+ let tmpl = document.querySelector(`template[id="${name}"]`);
if (!tmpl) {
/** @type { HTMLMetaElement } */
@@ -28,7 +28,7 @@ export function templateByName(name, props = {}) {
templatesLoading[name] ??= new Promise(async (resolve) => {
tmpl = document.createElement('template');
- tmpl.dataset.name = name.toString();
+ tmpl.setAttribute('id', name.toString());
tmpl.innerHTML = await fetch(
meta?.content?.replaceAll('[name]', name.toString()) ?? name
).then((r) => r.text());
@@ -139,7 +139,7 @@ export function templateByNode(template, props = {}) {
/** @param { Node } node */
const findExpression = (node) => {
- const parentNode = node.parentNode;
+ let stopFlag = false;
if (node.nodeName == '#document-fragment') {
nodeProps.set(node, props);
@@ -151,25 +151,27 @@ export function templateByNode(template, props = {}) {
for (const plugin of plugins) {
if (node.matches(plugin.select)) {
- plugin.run(node, props);
+ plugin.run(node, props, () => stopFlag = true);
}
}
- if (parentNode === node.parentNode) {
- const attributeNames = node.getAttributeNames();
+ if (stopFlag) {
+ return;
+ }
+
+ const attributeNames = node.getAttributeNames();
- for (const attributeName of attributeNames) {
- const attribute = node.getAttribute(attributeName);
-
- if (attribute.includes('{{')) {
-
- bindExpressions(attribute, (text) => {
- node.setAttribute(attributeName, text.trim().replace(/ +/, ' '))
- });
+ for (const attributeName of attributeNames) {
+ const attribute = node.getAttribute(attributeName);
- } else if (node.localName.includes('-') && props[attribute]) {
- node.setAttribute(attributeName, `@parent.${attribute}`);
- }
+ if (attribute.includes('{{')) {
+
+ bindExpressions(attribute, (text) => {
+ node.setAttribute(attributeName, text.trim().replace(/ +/, ' '))
+ });
+
+ } else if (node.localName.includes('-') && props[attribute]) {
+ node.setAttribute(attributeName, `@parent.${attribute}`);
}
}
@@ -177,10 +179,8 @@ export function templateByNode(template, props = {}) {
bindExpressions(node.textContent, (text) => (node.textContent = text));
}
- if (parentNode === node.parentNode) {
- for (const child of node.childNodes) {
- findExpression(child);
- }
+ for (const child of node.childNodes) {
+ findExpression(child);
}
};