diff --git a/src/directives/reconcile.md b/src/directives/reconcile.md
new file mode 100644
index 0000000..3f5239c
--- /dev/null
+++ b/src/directives/reconcile.md
@@ -0,0 +1,33 @@
+# Algorithme de reconciliation
+
+- tableaux
+ - prev [a, b, c, d, e, f, g]
+ - next [c, d, f, g, e, h, i]
+ - map next reversed index
+
+# Analyse
+
+<< in | >> out
+
+a != c & a >> next (delete) p=1 n=0
+b != c & b >> next (delete) p=2 n=0
+c == c & c << next (static) p=3 n=1 (ignore)
+d == d & d << next (static) p=4 n=2 (ignore)
+v
+prev [a, b, c, d, e, f, g]
+v
+next [c, d, g, f, e, h, i]
+
+e != g & e << next (move 5) move > index p=5 n=2 (avoir un hidden index pour calculer la différence)
+v
+prev [a, b, c, d, e, f, g]
+v
+next [c, d, g, f, e, h, i]
+
+f != g & f << next (move 6) move > index p=6 n=2
+v--max = create all
+prev [a, b, c, d, e, f, g]
+v v
+next [c, d, g, X, X, h, i]
+
+g == g & c << next (static) p=3 n=1 (ignore)
diff --git a/src/directives/template.js b/src/directives/template.js
index 9f45a44..a066f76 100644
--- a/src/directives/template.js
+++ b/src/directives/template.js
@@ -14,30 +14,31 @@
import { xAbstractElement } from "../controllers/abstract.js";
import { signal } from "../reactivity/signal.js";
-import { xcomment } from "../templates/html.js";
+import { cleanupTemplateFragment, xcomment } from "../templates/html.js";
import { childrenOf, elementCloneNode } from "../utils/shortcuts.js";
import { VIF } from "../utils/types.js";
import { forDirective } from "./for.js";
import { ifDirective } from "./if.js";
import { routeDirective } from "./route.js";
-/** @returns {Comment} */
-export const createFlag = () => elementCloneNode(xcomment);
-
/**
* create an abstract DOM part to manipulate a fragment
+ * @param {DocumentFragment} fragment
* @param {VIF.Element.Datas} context
* @param {number} index
* @param {string} key
* @param {any} value
* @returns {VIF.Part}
*/
-export const createPart = (context, index, key, value) => {
+export const createPart = (fragment, context, index, key, value) => {
+ /** @type {NodeList} */
+ const nodes = fragment.childNodes;
+
/**
- * create a flag to identify the head and tail of the fragment
+ * get the comment flag to identify the head and tail of the fragment
* @type {Comment}
*/
- const flag = createFlag();
+ const flag = nodes[nodes.length - 1];
/**
* create a signal for the current array value
@@ -62,15 +63,23 @@ export const createPart = (context, index, key, value) => {
return { flag, abstractElement, property };
};
-export const addPart = (element, context, index, key, value) => {
+/**
+ * create an abstract DOM part and add it into the DOM
+ * @param {HTMLTemplateElement} template
+ * @param {VIF.Element.Datas} context
+ * @param {number} index
+ * @param {string} key
+ * @param {any} value
+ */
+export const addPart = (template, context, index, key, value) => {
/** @type {Array} */
- const parts = element.templateParts;
+ const parts = template.templateParts;
/** @type {VIF.Part} */
let part = parts[index + 1];
/** @type {DocumentFragment} */
- const fragment = elementCloneNode(element.content, true);
+ const fragment = elementCloneNode(template.content, true);
// we create the part corresponding to the fragment
// the part will be stored into an array of parts
@@ -81,33 +90,45 @@ export const addPart = (element, context, index, key, value) => {
if (part) {
part.property && part.property(value);
} else {
- part = parts[index + 1] = createPart(context, index, key, value);
+ part = parts[index + 1] = createPart(
+ fragment,
+ context,
+ index,
+ key,
+ value
+ );
}
// if there is a cached schema, hydrate the fragment
- element.immutableSchema &&
+ template.immutableSchema &&
part.abstractElement.hydrate(
childrenOf(fragment),
- element.immutableSchema
+ template.immutableSchema
);
// replace the current flag by himself plus fragment
- parts[index].flag.replaceWith(parts[index].flag, fragment, part.flag);
+ parts[index].flag.replaceWith(parts[index].flag, fragment);
};
-export const updatePart = (element, index, value) => {
+/**
+ * update a DOM part property value
+ * @param {HTMLTemplateElement} template
+ * @param {number} index
+ * @param {string} value
+ */
+export const updatePart = (template, index, value) => {
// find the part matching to the index and update the signal value
- element.templateParts[index + 1].property(value);
+ template.templateParts[index + 1].property(value);
};
/**
* remove a DOM part from the DOM and disconnect it
- * @param {Array} parts
+ * @param {HTMLTemplateElement} template
* @param {number} index
*/
-export const removePart = (element, index) => {
+export const removePart = (template, index) => {
/** @type {Array} */
- const parts = element.templateParts;
+ const parts = template.templateParts;
/**
* get the head and tail flags
@@ -134,20 +155,30 @@ export const removePart = (element, index) => {
}
};
-// setup template directives basics as element properties
-export const setupTemplateDirective = (element) => {
+/**
+ * setup template directives basics as template properties
+ * @param {HTMLTemplateElement} template
+ */
+export const setupTemplateDirective = (template) => {
+ // cleanup template
+ cleanupTemplateFragment(template);
+
/** @type {Array} */
- element.templateParts = [{ flag: createFlag() }];
+ template.templateParts = [{ flag: elementCloneNode(xcomment) }];
+
+ // append a flag at the end of the template
+ // so we don't have to clone it manually anymore
+ template.content.append(elementCloneNode(xcomment));
/** @type {VIF.Element.DisconnectCallback} */
- element.disconnectCallback = () => {
- for (let x = 1; x < element.templateParts.length; x++) {
- element.templateParts[x].abstractElement.disconnectCallback();
+ template.disconnectCallback = () => {
+ for (let x = 1; x < template.templateParts.length; x++) {
+ template.templateParts[x].abstractElement.disconnectCallback();
}
};
- // replace the current element by the main flag
- element.replaceWith(element.templateParts[0].flag);
+ // replace the current template by the main flag
+ template.replaceWith(template.templateParts[0].flag);
};
/**
diff --git a/src/templates/html.js b/src/templates/html.js
index 3fc9450..ce34e22 100644
--- a/src/templates/html.js
+++ b/src/templates/html.js
@@ -8,6 +8,27 @@ export let xcomment = dom.createComment("");
export let xtemplate = documentCreateElement("template");
export let xfragment = dom.createDocumentFragment();
+/**
+ * Remove all non ELEMENT_NODE from NodeList
+ * @param {HTMLTemplateElement} template
+ */
+export const cleanupTemplateFragment = (template) => {
+ /** @type {NodeList} */
+ const nodes = template.content.childNodes;
+
+ /** @type {number} */
+ let index = nodes.length;
+
+ // explore childNodes
+ while (index--) {
+ /** @type {Node} */
+ const node = nodes[index];
+
+ // remove node if it's not ELEMENT_NODE
+ node.nodeType !== 1 && node.remove();
+ }
+};
+
/**
* Create a DocumentFragment from a string literal
* @param {string} string
@@ -15,9 +36,13 @@ export let xfragment = dom.createDocumentFragment();
*/
export const createTemplateFragmentFromString = (string) => {
// clone the template Element and inject innerHTML (for parsing)
+ /** @type {HTMLTemplateElement} */
let template = elementCloneNode(xtemplate);
template.innerHTML = string;
+ // cleanup template
+ cleanupTemplateFragment(template);
+
// return the template DocumentFragment
return template.content;
};
@@ -29,9 +54,14 @@ export const createTemplateFragmentFromString = (string) => {
*/
export const createTemplateFragmentFromNodeList = (nodeList) => {
// clone the DocumentFragment and append the NodeList
+ /** @type {HTMLTemplateElement} */
let template = elementCloneNode(xfragment);
template.append(...nodeList);
+ // we don't need to cleanup template here because nodeLists are
+ // always different, so cleanup process represent a huge overload
+ // compare to node replacement
+
// return the DocumentFragment
return template;
};
diff --git a/tests/garbage/components.js b/tests/garbage/components.js
index aedf590..9b94726 100644
--- a/tests/garbage/components.js
+++ b/tests/garbage/components.js
@@ -1,79 +1,92 @@
-import {
- useDefine,
- useI18n,
- useNavigate,
- useSignal,
-} from "../../dist/esm/vif.js";
+import { useDefine, useSignal } from "../../src/bundle.js";
-const firstTranslationsObject = {
- "en-EN.js": {
- default: {
- button: "This is a button",
- },
- },
- "fr-FR.js": {
- default: {
- button: "Voici le bouton",
- },
- },
-};
+let id = 0;
-const secondTranslationsObject = {
- "en-EN.js": {
- default: {
- p: "This is a paragraph",
- },
- },
- "fr-FR.js": {
- default: {
- p: "Voici le paragraphe",
- },
- },
-};
-
-function fakeImport(obj, path) {
- return new Promise((resolve) => setTimeout(() => resolve(obj[path]), 1000));
+function _random(max) {
+ return Math.round(Math.random() * 1000) % max;
}
-const firstT = useI18n({
- en: {
- EN: () => fakeImport(firstTranslationsObject, "en-EN.js"),
- default: "EN",
- },
- fr: {
- FR: () => fakeImport(firstTranslationsObject, "fr-FR.js"),
- default: "FR",
- },
- default: "EN",
-});
-
-const secondT = useI18n({
- en: {
- EN: () => fakeImport(secondTranslationsObject, "en-EN.js"),
- default: "EN",
- },
- fr: {
- FR: () => fakeImport(secondTranslationsObject, "fr-FR.js"),
- default: "FR",
- },
- default: "EN",
-});
-
-function First({ props }) {
- props.t = firstT;
- return this.children;
+function buildData(count = 1000) {
+ var adjectives = [
+ "pretty",
+ "large",
+ "big",
+ "small",
+ "tall",
+ "short",
+ "long",
+ "handsome",
+ "plain",
+ "quaint",
+ "clean",
+ "elegant",
+ "easy",
+ "angry",
+ "crazy",
+ "helpful",
+ "mushy",
+ "odd",
+ "unsightly",
+ "adorable",
+ "important",
+ "inexpensive",
+ "cheap",
+ "expensive",
+ "fancy",
+ ];
+ var colours = [
+ "red",
+ "yellow",
+ "blue",
+ "green",
+ "pink",
+ "brown",
+ "purple",
+ "brown",
+ "white",
+ "black",
+ "orange",
+ ];
+ var nouns = [
+ "table",
+ "chair",
+ "house",
+ "bbq",
+ "desk",
+ "car",
+ "pony",
+ "cookie",
+ "sandwich",
+ "burger",
+ "pizza",
+ "mouse",
+ "keyboard",
+ ];
+ var data = [];
+ for (var i = 0; i < count; i++)
+ data.push({
+ id: id++,
+ label:
+ adjectives[_random(adjectives.length)] +
+ " " +
+ colours[_random(colours.length)] +
+ " " +
+ nouns[_random(nouns.length)],
+ });
+ return data;
}
-function Second({ props }) {
- props.t = secondT;
- return this.children;
+function App({ props }) {
+ props.array = useSignal([]);
+ setTimeout(() => {
+ props.array(props.array.value.length ? [] : buildData(10));
+ }, 1000);
+ setTimeout(() => {
+ props.array(props.array.value.length ? [] : buildData(10));
+ }, 2000);
+ setTimeout(() => {
+ props.array(props.array.value.length ? [] : buildData(10));
+ }, 3000);
}
-setTimeout(() => {
- firstT.onload(() => useDefine("first", First));
-}, 500);
-secondT.onload(() => useDefine("second", Second));
-
-setTimeout(() => {
- useI18n.locale("en-GB");
-}, 4000);
+useDefine("app", App);
diff --git a/tests/garbage/delete-after-testing.md b/tests/garbage/delete-after-testing.md
deleted file mode 100644
index 0ee0225..0000000
--- a/tests/garbage/delete-after-testing.md
+++ /dev/null
@@ -1 +0,0 @@
-All the tests in garbage folder must be deleted after primary testing
diff --git a/tests/garbage/en.js b/tests/garbage/en.js
deleted file mode 100644
index 6ac45d6..0000000
--- a/tests/garbage/en.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export default {
- global: {
- name: "Name",
- },
-};
diff --git a/tests/garbage/example.html b/tests/garbage/example.html
deleted file mode 100644
index c16a1e6..0000000
--- a/tests/garbage/example.html
+++ /dev/null
@@ -1,111 +0,0 @@
-
-
-
-
-
- Document
-
-
-
-
- Bienvenue sur votre magasin
- boutique
-
- Retrouvez la liste de vos produits ci dessous
-
-
- Merci de garder le SEO tout en permettant d'utiliser des
- variables globales
-
-
-
-
- Lien vers l'identifiant
-
-
-
-
- Lien vers l'identifiant
-
-
-
-
- Lien vers l'identifiant
-
-
-
-
- identifiant id
- Liste des produits les plus demandés
-
-
-
-
-
- identifiant id
- Liste des produits les plus demandés
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/garbage/fr.bis.js b/tests/garbage/fr.bis.js
deleted file mode 100644
index 409af0e..0000000
--- a/tests/garbage/fr.bis.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export default {
- global: {
- name: "Nom Bis",
- },
-};
diff --git a/tests/garbage/fr.js b/tests/garbage/fr.js
deleted file mode 100644
index d4f85e1..0000000
--- a/tests/garbage/fr.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export default {
- global: {
- name: "Nom",
- },
-};
diff --git a/tests/garbage/index.html b/tests/garbage/index.html
index 2e662f6..5194caf 100644
--- a/tests/garbage/index.html
+++ b/tests/garbage/index.html
@@ -14,25 +14,13 @@ Primary tests here
Here we do the primary testings, all files in this folder must be
deleted after theses tests.
-
-
-
-
-
- PARAGRAPHE
-
-
+