Created
June 10, 2023 01:00
-
-
Save okikio/e3c7f4c979dd7e092980efded1ad7bda to your computer and use it in GitHub Desktop.
dom-expressions.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { signal, effect } from 'https://esm.sh/usignal'; | |
| // Function to check if an expression is iterable | |
| function isIterable(expression) { | |
| return Symbol.iterator in Object(expression); | |
| } | |
| // Function to create a new DOM range for an expression | |
| function createDOMRange(domElement, expression) { | |
| const textNode = document.createTextNode(evaluateExpression(expression.expr)); | |
| domElement.appendChild(textNode); | |
| // Append a zero-width space to ensure range isn't collapsed | |
| const spacer = document.createTextNode('\u200B'); | |
| domElement.appendChild(spacer); | |
| const range = document.createRange(); | |
| range.setStart(textNode, 0); | |
| range.setEnd(textNode, textNode.length); | |
| return { | |
| id: expression.id, | |
| expression: expression.expr, | |
| range, | |
| spacer, | |
| textNode | |
| }; | |
| } | |
| // Function to evaluate an expression | |
| function evaluateExpression(expression) { | |
| return expression(); | |
| } | |
| // Create a signal for the expressions array | |
| const expressionsSignal = signal([ | |
| { id: '1', expr: () => Math.random() }, | |
| [ | |
| { id: '2', expr: () => 'nested expression 1' }, | |
| [ | |
| { id: '5', expr: () => 'level 3 nested expression' }, | |
| { id: '6', expr: () => new Date().toLocaleTimeString() }, | |
| ], | |
| { id: '3', expr: () => new Date().toLocaleTimeString() }, | |
| ], | |
| { id: '4', expr: () => 'constant' }, | |
| ]); | |
| // Initialize the DOM Ranges map | |
| let domRanges = new Map(); | |
| // Function to map expressions to DOM ranges and update the DOM | |
| function mapExpressionsToDOM() { | |
| const domElement = document.getElementById('myDomElementId'); | |
| let expressions = expressionsSignal.value; | |
| // Clear the DOM Element | |
| domElement.innerHTML = ''; | |
| // Reset the DOM Ranges map | |
| domRanges.clear(); | |
| function processExpressions(expressions) { | |
| for (let expression of expressions) { | |
| if (isIterable(expression)) { | |
| // if expression is an iterable, recursively process each item | |
| processExpressions(expression); | |
| } else { | |
| // create DOM range for non-iterable expressions | |
| const domRange = createDOMRange(domElement, expression); | |
| domRanges.set(expression.id, domRange); | |
| } | |
| } | |
| } | |
| processExpressions(expressions); | |
| // Update the DOM | |
| domRanges.forEach(({ expression, range, textNode }) => { | |
| const newContent = evaluateExpression(expression); | |
| textNode.textContent = newContent; | |
| }); | |
| } | |
| // Create an effect to react to changes in the expressions array | |
| effect(mapExpressionsToDOM); | |
| // Delete an expression | |
| function deleteExpression(id) { | |
| const domRange = domRanges.get(id); | |
| if (domRange) { | |
| domRange.range.deleteContents(); | |
| domRange.spacer.remove(); | |
| domRanges.delete(id); | |
| } | |
| } | |
| let idCounter = 7; // Start from the next available ID number | |
| // Add an expression | |
| function addExpression() { | |
| const newExpression = { id: `${idCounter++}`, expr: () => Math.random() }; | |
| const currentExpressions = [...expressionsSignal.value]; | |
| currentExpressions.push(newExpression); | |
| expressionsSignal.value = currentExpressions; // trigger reactive update | |
| } | |
| // Function to recursively delete expression from the list | |
| function deleteNestedExpression(exprs, id) { | |
| return exprs.reduce((res, expr) => { | |
| if (isIterable(expr)) { | |
| const newNestedExpr = deleteNestedExpression(expr, id); | |
| newNestedExpr.length > 0 && res.push(newNestedExpr); | |
| } else if (expr.id !== id) { | |
| res.push(expr); | |
| } | |
| return res; | |
| }, []); | |
| } | |
| // Function to recursively find all non-iterable expressions | |
| function findNestedExpressions(expressions) { | |
| const nestedExpressions = []; | |
| for (let expression of expressions) { | |
| if (isIterable(expression)) { | |
| // if expression is an iterable, recursively find all non-iterable expressions | |
| nestedExpressions.push(...findNestedExpressions(expression)); | |
| } else { | |
| // add the non-iterable expression to the list | |
| nestedExpressions.push(expression); | |
| } | |
| } | |
| return nestedExpressions; | |
| } | |
| // Delete a random nested expression | |
| function deleteRandomNestedExpression() { | |
| let currentExpressions = [...expressionsSignal.value]; | |
| const nestedExpressions = findNestedExpressions(currentExpressions); | |
| if (nestedExpressions.length > 0) { | |
| const randomIndex = Math.floor(Math.random() * nestedExpressions.length); | |
| const deletedExpression = nestedExpressions[randomIndex]; | |
| deleteExpression(deletedExpression.id); | |
| // remove the deleted expression from currentExpressions | |
| currentExpressions = deleteNestedExpression(currentExpressions, deletedExpression.id); | |
| expressionsSignal.value = currentExpressions; // trigger reactive update | |
| } | |
| } | |
| // Attach event listeners to buttons | |
| const addButton = document.getElementById('addButton'); | |
| addButton.addEventListener('click', addExpression); | |
| const deleteButton = document.getElementById('deleteButton'); | |
| deleteButton.addEventListener('click', deleteRandomNestedExpression); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment