The problem is that the for Element when it re renders and new dom element is created that means if an element in the for element it unable to do transition when the state changes
import { mergeStringsWithSeparator,stopUnwanted ,stringSplitter} from './JSML/element.js';
import {removeJSMLWrapper,inheritParentStyles} from './JSML/utils.js';
const JSML = (call) => {
let listeners = new Set();
let templates = new Map();
let keysMaps=new Map()
let componentElement = ['IF', 'ELSEIF', 'CLONEABLE', 'CLONE', 'FOR', 'AWAIT'];
const createDeepProxy = (target, callback) => {
return new Proxy(target, {
get(target, property) {
const value = target[property];
if (typeof value === 'object' && value !== null) {
return createDeepProxy(value, callback);
}
return value;
},
set(target, property, value) {
target[property] = value;
callback();
return true;
},
deleteProperty(target, property) {
delete target[property];
callback();
return true;
}
});
};
const state = createDeepProxy(call(), () => {
listeners.forEach(listener => listener());
});
const parsePlaceholders = (template, context) => {
return template.replace(/{{(.*?)}}/g, (_, expression) => {
try {
return new Function(...Object.keys(context), `return ${expression}`)(...Object.values(context));
} catch {
return ''; // Return empty string for invalid placeholders
}
});
};
const isValidPlaceholder = (value) => value !== undefined && value !== null && value !== '';
const directives = {
bind : (el, expression,nu ,context = {}) => {
const evaluate = () => {
const processedExp = parsePlaceholders(expression, context);
// Check if the processed expression resolves in the current context
try {
const value = new Function("state", `return state.${processedExp}`)(state);
el.textContent = value; // Update the DOM with the current value
} catch (error) {
console.warn(`Failed to evaluate bind expression: ${expression}`, error);
}
};
//console.log(context,nu)
// Add a watcher to evaluate changes dynamically
const addDynamicBinding = () => {
const processedExp = parsePlaceholders(expression, context);
let value = new Function("state", `return state.${processedExp}`)(state);
Object.defineProperty(state, processedExp, {
set(newValue) {
value = newValue;
el.textContent = newValue; // Reflect changes in DOM
},
get() {
return value;
},
});
};
listeners.add(evaluate);
evaluate();
addDynamicBinding();
},
click: (el, expression, context = {}) => {
el.addEventListener('click', () => {
let processedExp = expression;
Object.entries(context).forEach(([key, value]) => {
const replacementValue = typeof value === 'number' ? value : `'${value}'`;
processedExp = processedExp.replace(new RegExp(`{{${key}}}`, 'g'), replacementValue);
});
new Function("state", `state.${processedExp}`)(state);
});
},
xclass: (el, exp, context) => {
const evaluate = () => {
// Parse the expression into an object
//console.log(exp)
const classMap = new Function("state", ` { return ${exp}; }`)(state);
//console.log(classMap)
// return
if (typeof classMap !== "object" || classMap === null) {
console.warn(`Invalid xclass value: Expected an object, got ${typeof classMap}`);
return;
}
// Loop through the classMap object
for (const [className, condition] of Object.entries(classMap)) {
if (condition) {
let newClass=stringSplitter.byMultipleDelimiters(className.toString(),[','])
newClass.forEach( nclass=> {
el.classList.add(nclass)
})
//console.log(e)
} else {
let newClass = stringSplitter.byMultipleDelimiters(className.toString(), [','])
newClass.forEach(nclass => {
el.classList.remove(nclass)
})
}
}
};
// Re-evaluate whenever the state changes
listeners.add(evaluate);
evaluate();
},
xstyle: (el, expression, context = {}) => {
const evaluate = () => {
// Replace placeholders in the expression
let processedExp = parsePlaceholders(expression, context);
try {
// Evaluate the processed expression
const styles = new Function("state", `return ${processedExp}`)(state);
// Apply the evaluated styles to the element
Object.entries(styles).forEach(([key, value]) => {
el.style[key] = value;
});
} catch (error) {
console.error(`Error in xstyle directive: ${error.message}`);
}
};
evaluate();
listeners.add(evaluate);
},
model: (el, expression, context = {}) => {
const updateState = (value) => {
//console.log(value)
const processedExp = parsePlaceholders(expression, context);
//
if (Object.keys(context).length >0) {
const split=stringSplitter.byMultipleDelimiters(processedExp,['.']);
new Function("state", `return state.${context.arrayName}[${context.outerIndex}].${split[split.length -1]}=${value}`)(state)
//console.log(processedExp)
} else {
new Function("state", `state.${processedExp} = ${value}`)(state);
}
};
if (el.type === 'radio') {
el.addEventListener('click', () => {
const processedExp = parsePlaceholders(expression, context);
const split=stringSplitter.byMultipleDelimiters(processedExp,['.']);
const splitLength=split.length
// console.log(processedExp,context)
let currentValue
if (Object.keys(context).length > 0) {
currentValue= new Function("state", `return state.${context.arrayName}[${context.outerIndex}].${split[splitLength -1]}`)(state)
} else {
currentValue=new Function("state", `return state.${processedExp}`)(state);
}
//console.log(currentValue)
updateState(!currentValue); // Toggle the value
});
} else {
el.addEventListener('input', (e) => {
const value = el.type === 'number' ? parseFloat(e.target.value) : e.target.value;
updateState(`'${value}'`);
});
}
const evaluate = () => {
const split=stringSplitter.byMultipleDelimiters(expression,['.']);
const splitLength=split.length
const processedExp = parsePlaceholders(expression, context);
const merged = mergeStringsWithSeparator('.',`${context.arrayName}[${context.index}]`)
const select=stopUnwanted(merged,/undefined[undefined]/i)
if (select && Object.keys(context).length > 0) {
return
}
let value;
if (Object.keys(context).length > 0) {
value=new Function("state", `return state.${context.arrayName}[${context.outerIndex}].${split[splitLength -1]}`)(state)
} else {
if (split.length > 1) {
return
}
value = new Function("state", `return state.${processedExp}`)(state);
}
if (el.type === 'radio') {
el.checked = !!value; // Update the "checked" state
//el.style.backgroundColor = value ? 'blue' : 'red'; // Change style based on state
} else {
el.value = value !== undefined ? value : '';
}
};
evaluate();
listeners.add(evaluate);
}
};
const ElementCom = {
if: (el, context) => {
const expression = el.getAttribute('x');
const evaluate = () => {
const processedExp = parsePlaceholders(expression, context);
try {
const condition = context ? new Function(...Object.keys(context), `return ${processedExp}`)(...Object.values(context)) : new Function('state', `return ${processedExp}`)(state);
Array.from(el.children).forEach(child => {
child.style.display = condition ? '' : 'none';
});
// Handle ELSE sibling
const sibling = el.nextElementSibling;
if (sibling && sibling.tagName === 'ELSE') {
Array.from(sibling.children).forEach(child => {
child.style.display = condition ? 'none' : '';
});
}
} catch (error) {
console.error(`Error in if directive: ${error.message}`);
}
};
evaluate();
listeners.add(evaluate);
},
for: (el, context) => {
if (!templates.has(el)) {
templates.set(el, el.innerHTML);
}
if (!keysMaps.has(el.getAttribute('key'))) {
if(el.getAttribute('key')) {
keysMaps.set(el.getAttribute('key'),el)
}
} else {
console.log(keysMaps.get(el.getAttribute('key')))
console.log('esits')
forLoopUpdater(el,keysMaps)
}
//console.log(el)
const template = templates.get(el);
const [itemNames, , arrayName] = el.getAttribute('x').split(' ');
let splitloopIndex=stringSplitter.byMultipleDelimiters(itemNames,[','])
const itemName=splitloopIndex[0]
const hasInvalidPattern = (str) => {
// Check for the exact pattern '.[].'
const invalidPattern = /[]./;
if (invalidPattern.test(str)) {
return true;
}
return false;
};
const evaluate = () => {
const processedExp = parsePlaceholders(arrayName, context);
if (hasInvalidPattern(processedExp)) {
return;
}
const array = new Function("state", `return state.${processedExp}`)(state);
if (!Array.isArray(array)) return;
el.innerHTML = '';
array.forEach((item, outerIndex) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = template;
const processNode = (node) => {
if (node.nodeType === 3) { // Text node
const text = node.textContent;
const newText = text.replace(
new RegExp(`{{${itemName}}}`, 'g'),
item
).replace(
new RegExp(`{{${itemName}\.(.+?)}}`, 'g'),
(_, prop) => item[prop]
).replace(
new RegExp(`{{index}}`, 'g'),
outerIndex // Outer index for the current loop
).replace(
new RegExp(`{{${splitloopIndex[1]}}}`, 'g'),
outerIndex // Outer index for the current loop
).replace(
new RegExp(`{{outerIndex}}`, 'g'),
outerIndex // Alias for outer index
);
node.textContent = newText;
} else if (node.attributes) {
Array.from(node.attributes).forEach(attr => {
const newValue = attr.value
.replace(new RegExp(`{{${itemName}}}`, 'g'), item)
.replace(new RegExp(`{{${itemName}\.(.+?)}}`, 'g'),
(_, prop) => item[prop])
.replace(new RegExp(`{{index}}`, 'g'), outerIndex).replace(
new RegExp(`{{${splitloopIndex[1]}}}`, 'g'),
outerIndex // Outer index for the current loop
).replace(new RegExp(`{{outerIndex}}`, 'g'), outerIndex);
attr.value = newValue;
});
}
Array.from(node.childNodes).forEach(processNode);
};
Array.from(wrapper.childNodes).forEach(processNode);
const children = Array.from(wrapper.children);
children.forEach(child => {
const childContext = {
...context,
[itemName]: item,
outerIndex,
arrayName// Alias for outer index
};
if (child.tagName === 'FOR') {
const innerExp = child.getAttribute('x');
const [innerItemName, , innerArrayName] = innerExp.split(' ');
childContext[`${itemName}_index`] = outerIndex; // Preserve outer loop index
applyElement(child, 'for', childContext);
} else if (child.tagName === 'IF') {
const exp = child.getAttribute('x');
applyElement(child, 'if', childContext, exp);
} else {
attr.forEach(directive => {
const exp = child.getAttribute(directive);
if (exp) {
apply(child, directive, exp, childContext);
}
});
}
const nextAttriToAllChildren=Array.from(child.querySelectorAll('*'))
nextAttriToAllChildren.forEach(nextedChild =>{
if (nextedChild.tagName === 'IF') {
return
} else if (nextedChild.tagName === 'FOR') {
}
attr.forEach(directive => {
const exp = nextedChild.getAttribute(directive);
if (exp) {
apply(nextedChild, directive, exp, childContext);
}
});
})
});
while (wrapper.firstChild) {
el.appendChild(wrapper.firstChild);
}
});
};
listeners.add(evaluate);
evaluate();
},
};
const forLoopUpdater=(el,mapskey) => {
const node=mapskey.get(el.getAttribute('key'))
const processNode = (node) => {
if (node.nodeType === 3) { // Text node
const text = node.textContent;
const newText = text.replace(
new RegExp(`{{${itemName}}}`, 'g'),
item
).replace(
new RegExp(`{{${itemName}\.(.+?)}}`, 'g'),
(_, prop) => item[prop]
).replace(
new RegExp(`{{index}}`, 'g'),
outerIndex // Outer index for the current loop
).replace(
new RegExp(`{{${splitloopIndex[1]}}}`, 'g'),
outerIndex // Outer index for the current loop
).replace(
new RegExp(`{{outerIndex}}`, 'g'),
outerIndex // Alias for outer index
);
node.textContent = newText;
} else if (node.attributes) {
Array.from(node.attributes).forEach(attr => {
const newValue = attr.value
.replace(new RegExp(`{{${itemName}}}`, 'g'), item)
.replace(new RegExp(`{{${itemName}\.(.+?)}}`, 'g'),
(_, prop) => item[prop])
.replace(new RegExp(`{{index}}`, 'g'), outerIndex).replace(
new RegExp(`{{${splitloopIndex[1]}}}`, 'g'),
outerIndex // Outer index for the current loop
).replace(new RegExp(`{{outerIndex}}`, 'g'), outerIndex);
attr.value = newValue;
});
}
Array.from(node.childNodes).forEach(processNode);
};
}
const applyElement = (el, Elements, context, expression) => {
if (ElementCom[Elements]) {
ElementCom[Elements](el, context, expression);
}
};
const attr = Object.keys(directives);
const apply = (el, directive, expression, context = {}) => {
if (directives[directive]) {
directives[directive](el, expression, context);
}
};
const render = () => {
const jhtmlElements = document.querySelectorAll('*');
jhtmlElements.forEach(el => {
const elements = Array.from(el.children);
elements.forEach(element => {
if (componentElement.includes(element.tagName)) {
componentElement.forEach(com => {
const AllComElement = document.querySelectorAll(com.toLowerCase());
AllComElement.forEach(el => {
applyElement(el, com.toLowerCase());
});
});
return;
}
attr.forEach(directive => {
const expression = element.getAttribute(directive);
if (expression) {
apply(element, directive, expression);
}
});
});
});
componentElement.forEach(jml => {
const jsml=document.querySelectorAll(jml)
jsml.forEach(el => {
inheritParentStyles(el)
})
})
};
render();
return {
state,
render
};
};
export default JSML;
This my library called JSML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>JSML</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div
class="h-[35vh] transition-all
transition duration-300 ease-in-out overflow-hidden bg-red-600 flex items-center justify-center"
xstyle="{
width: state.itemIndex === 0 ? '100vw' : '0px',
transform: state.itemIndex === 0 ? 'scale(1)' : 'scale(0)',
backgroundColor: state.itemIndex === 0 ? 'blue' : 'red'
}"
>
</div>
<div class="flex w-screen h-[35vh]">
<div class=" w-screen h-[35vh]">
<for x="item,i in items" parent="class" key="forloop">
<div
class="h-[35vh] transition-all
transition duration-300 ease-in-out overflow-hidden bg-red-600 flex items-center justify-center"
xstyle="{
width: state.itemIndex === {{i}} ? '100vw' : '0px',
transform: state.itemIndex === {{i}} ? 'scale(1)' : 'scale(0)',
backgroundColor: state.itemIndex === {{i}} ? 'blue' : 'red'
}"
>
<div>
<span>{{item}}</span>
<div>
<span bind="itemIndex"></span>
</div>
<div>
<if x="state.isTrue">
<span>istrue</span>
</if>
</div>
</div>
</for>
</div>
</div>
<script src="./text.js" type="module"></script>
</body>
</html>
That is the html code
import JSML from './index.js';
const app=JSML(()=> ({
items:['1','2','3'],
itemIndex:0,
isTrue:false,
data:true
}))
const carousel= () => {
return setInterval(() => {
app.state.itemIndex++
if (app.state.itemIndex >= 3) {
app.state.itemIndex = 0
clearInterval(carousel)
}
},4000)
}
carousel()
This is the text.js code
From the above please help me make the library for loop not to re-render but in stead update the for element because when ever it re-render it set in new dom and by so transition does not work so help for that in my library
I am expecting the for loop to like a robust type like react or vue and