Skip to content

Instantly share code, notes, and snippets.

@okikio
Created June 10, 2023 01:00
Show Gist options
  • Select an option

  • Save okikio/e3c7f4c979dd7e092980efded1ad7bda to your computer and use it in GitHub Desktop.

Select an option

Save okikio/e3c7f4c979dd7e092980efded1ad7bda to your computer and use it in GitHub Desktop.
dom-expressions.ts
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