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 - - - - - - - - - - - - - - - -
- - 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

-
-
+