ternaries almost done, adding margin function

This commit is contained in:
metacryst
2024-05-17 12:47:17 -05:00
parent 8b2f9e2b77
commit 41ec6b7dd3
8 changed files with 350 additions and 189 deletions

View File

@@ -25,6 +25,9 @@ document.body.append(
Ternaries within render() Ternaries within render()
Other statements within render() Other statements within render()
## Limitations:
While rendering is underway, an element's state can only be accessed from within that element
## Boilerplate: ## Boilerplate:
- ```*html```: Type in an HTML file and select the suggestion to create HTML boilerplate. - ```*html```: Type in an HTML file and select the suggestion to create HTML boilerplate.
- ```*element```: Type in a JS file and select the suggestion to create JS Custom Element boilerplate. - ```*element```: Type in a JS file and select the suggestion to create JS Custom Element boilerplate.

View File

@@ -41,6 +41,91 @@ window.testSuites.push( class testObservedObject {
// throw some sort of warning if a global OO is accessed without "this" // throw some sort of warning if a global OO is accessed without "this"
DefaultObservedObject() {
window.Form = class Form extends ObservedObject {
id
path
$canvasPosition
}
class File extends Shadow {
$$form = Form.create({id: "123", path: "/", canvasPosition: "25|25"})
render = () => {
p(this.form.path)
}
}
window.register(File, "file-1")
let file = window.File()
if(file.firstChild?.innerText !== "/") {
return "Path is not inside of paragraph tag"
}
}
ObservedObject() {
let Form = class Form extends ObservedObject {
id
$path
$canvasPosition
}
let object = Form.create({id: "123", path: "/", canvasPosition: "25|25"});
register(class File extends Shadow {
$$form
render = () => {
p(this.form.path)
}
}, randomName("file"))
let file = File(object)
if(file.firstChild?.innerText !== "/") {
return "Path is not inside of paragraph tag"
}
object.path = "/asd"
if(file.form.path !== "/asd") {
return "Path did not change when changing original object"
}
if(file.firstChild?.innerText !== "/asd") {
return "Observed Object did not cause a reactive change"
}
}
ObservedObjectWithArray() {
let Form = class Form extends ObservedObject {
id
$children
}
let object = Form.create({id: "123", children: [{path: "berry"}, {path: "blue"}]});
register(class File extends Shadow {
$$form
render = () => {
ForEach(this.form.children, (child) => {
p(child.path)
})
}
}, randomName("file"))
let file = File(object)
if(file.firstChild?.innerText !== "berry" || file.children[1].innerText !== "blue") {
return "Paths did not render correctly in children"
}
file.form.children.push({path: "hello"})
if(file.children.length !== 3) {
return "No reactivity for adding children"
}
}
NotExtensible() { NotExtensible() {
return "not done" return "not done"
} }

View File

@@ -1,13 +1,19 @@
/*
"(" is preceding character: el, el.attr, if, switch
el: the el is window.rendering
el.attr:
find the function and attr in the string
if there are multiple instances of being used with this el, add to a list (and if no list then make it and we are first)
if: the el is window.rendering. rerender el
switch: the el is window.rendering. rerender el
*/
window.testSuites.push( window.testSuites.push(
class ParseRender { class ParseRender {
// CopyTo() {
// let str = "render=()=>{VStack(()=>{"
// let ret = str.copyTo("{")
// if(ret !== "render=()=>") return "Copy 1 failed!"
// }
ParseRender() { ParseRender() {
class Sidebar extends Shadow { class Sidebar extends Shadow {

View File

@@ -1,159 +0,0 @@
window.testSuites.push( class testRender {
SimpleParagraphWithState() {
class File extends Shadow {
$form
render = () => {
p(this.form.data)
}
constructor() {
super()
}
}
window.register(File, randomName("file"))
let form = {data: "asdf"}
const el = window.File(form)
if(!(el.firstChild?.matches("p"))) {
return `Child paragraph not rendered`
}
if(!(el.firstChild.innerText === "asdf")) {
return "Child para does not have inner text"
}
}
ParagraphConstructorChangeState() {
register(class File extends Shadow {
$name
render = () => {
p(this.name)
}
constructor() {
super()
}
}, randomName("file"))
let name = "asdf"
const file = File(name)
file.name = "hey123"
if(file.firstChild.innerText !== "hey123") {
return "Paragraph did not react to change!"
}
}
LiteralDoesNotCreateFalseReactivity() {
register(class File extends Shadow {
$name = "asd"
render = () => {
p(this.name)
p("asd")
}
constructor() {
super()
}
}, randomName("file"))
const file = File()
file.name = "hey123"
if(file.children[1].innerText === "hey123") {
return "Paragraph innertext falsely changed"
}
}
DefaultObservedObject() {
window.Form = class Form extends ObservedObject {
id
path
$canvasPosition
}
class File extends Shadow {
$$form = Form.create({id: "123", path: "/", canvasPosition: "25|25"})
render = () => {
p(this.form.path)
}
}
window.register(File, "file-1")
let file = window.File()
if(file.firstChild?.innerText !== "/") {
return "Path is not inside of paragraph tag"
}
}
ObservedObject() {
let Form = class Form extends ObservedObject {
id
$path
$canvasPosition
}
let object = Form.create({id: "123", path: "/", canvasPosition: "25|25"});
register(class File extends Shadow {
$$form
render = () => {
p(this.form.path)
}
}, randomName("file"))
let file = File(object)
if(file.firstChild?.innerText !== "/") {
return "Path is not inside of paragraph tag"
}
object.path = "/asd"
if(file.form.path !== "/asd") {
return "Path did not change when changing original object"
}
if(file.firstChild?.innerText !== "/asd") {
return "Observed Object did not cause a reactive change"
}
}
ObservedObjectWithArray() {
let Form = class Form extends ObservedObject {
id
$children
}
let object = Form.create({id: "123", children: [{path: "berry"}, {path: "blue"}]});
register(class File extends Shadow {
$$form
render = () => {
ForEach(this.form.children, (child) => {
p(child.path)
})
}
}, randomName("file"))
let file = File(object)
if(file.firstChild?.innerText !== "berry" || file.children[1].innerText !== "blue") {
return "Paths did not render correctly in children"
}
file.form.children.push({path: "hello"})
if(file.children.length !== 3) {
return "No reactivity for adding children"
}
}
})

41
Test/stacks.test.js Normal file
View File

@@ -0,0 +1,41 @@
window.testSuites.push( class testStacks {
NestedChildren() {
register(class File extends Shadow {
$name
render = () => {
p(this.name)
VStack(() => {
p("Learn to code inside of a startup")
p("➵")
.position("absolute")
.fontSize(30)
.left("50%")
.top("50%")
.transformOrigin("center")
.transform("translate(-50%, -50%) rotate(90deg)")
.transition("all 1s")
.userSelect("none")
.onAppear((self) => {
setTimeout(() => {
self.style.top = "88%"
}, 100)
})
})
}
constructor() {
super()
}
}, randomName("file"))
const file = File("asdf")
if(file.querySelector(".VStack")?.children.length !== 2) {
return "Incorrect amount of children inside vstack!"
}
}
})

102
Test/state.test.js Normal file
View File

@@ -0,0 +1,102 @@
window.testSuites.push( class testState {
SimpleParagraphWithState() {
class File extends Shadow {
$form
render = () => {
p(this.form.data)
}
constructor() {
super()
}
}
window.register(File, randomName("file"))
let form = {data: "asdf"}
const el = window.File(form)
if(!(el.firstChild?.matches("p"))) {
return `Child paragraph not rendered`
}
if(!(el.firstChild.innerText === "asdf")) {
return "Child para does not have inner text"
}
}
ParagraphConstructorChangeState() {
register(class File extends Shadow {
$name
render = () => {
p(this.name)
}
constructor() {
super()
}
}, randomName("file"))
let name = "asdf"
const file = File(name)
file.name = "hey123"
if(file.firstChild.innerText !== "hey123") {
return "Paragraph did not react to change!"
}
}
LiteralDoesNotCreateFalseReactivity() {
register(class File extends Shadow {
$name = "asd"
render = () => {
p(this.name)
p("asd")
}
constructor() {
super()
}
}, randomName("file"))
const file = File()
file.name = "hey123"
if(file.children[1].innerText === "hey123") {
return "Paragraph innertext falsely changed"
}
}
/*
State itself should check if the reactivity is based on an element or a standalone expression
If standalone, handle it
If element, push the info for initReactivity to handle it
*/
TernaryInState() {
register(class File extends Shadow {
$name
render = () => {
p(this.name)
.fontSize(this.name === "asdf" ? 16 : 32)
}
constructor() {
super()
}
}, randomName("file"))
let name = "asdf"
const file = File(name)
if(file.style.fontSize !== "16px") {
return "fail"
}
}
})

View File

@@ -3,9 +3,10 @@ window.testSuites = [];
await import ("./parse.test.js") await import ("./parse.test.js")
await import ("./init.test.js") await import ("./init.test.js")
await import ("./render.test.js")
await import ("./observedobject.test.js") await import ("./observedobject.test.js")
await import ("./parserender.test.js") await import ("./parserender.test.js")
await import ("./stacks.test.js")
await import ("./state.test.js")
window.randomName = function randomName(prefix) { window.randomName = function randomName(prefix) {
const sanitizedPrefix = prefix.toLowerCase().replace(/[^a-z0-9]/g, ''); const sanitizedPrefix = prefix.toLowerCase().replace(/[^a-z0-9]/g, '');

128
index.js
View File

@@ -287,11 +287,11 @@ window.Registry = class Registry {
} }
*/ */
static initReactivity(elem, name, value) { static initReactivity(elem, attr, value) {
let parent = window.rendering.last() if(!Registry.lastState || Registry.lastState.length === 0) {
if(!Registry.lastState) {
return return
} }
let parent = window.rendering.last()
if(Registry.lastState.length === 3) { if(Registry.lastState.length === 3) {
let [objName, objField, fieldValue] = Registry.lastState let [objName, objField, fieldValue] = Registry.lastState
@@ -303,19 +303,26 @@ window.Registry = class Registry {
parent[objName]._observers[objField].set(elem, []) parent[objName]._observers[objField].set(elem, [])
} }
let properties = parent[objName]._observers[objField].get(elem) let properties = parent[objName]._observers[objField].get(elem)
if(!properties.includes(name)) { if(!properties.includes(attr)) {
properties.push(name) properties.push(attr)
} }
} }
} else { } else {
let [stateUsed, stateValue] = Registry.lastState let [stateUsed, stateValue] = Registry.lastState
if(!stateUsed) return; if(!stateUsed) return;
if(stateUsed && parent[stateUsed] === value) { if(parent[stateUsed] === value) {
if(!parent._observers[stateUsed].get(elem)) { if(!parent._observers[stateUsed].get(elem)) {
parent._observers[stateUsed].set(elem, []) parent._observers[stateUsed].set(elem, [])
} }
parent._observers[stateUsed].get(elem).push(name) parent._observers[stateUsed].get(elem).push(attr)
} else {
// TODO: Enable this code to get the dynamic value to be called when state changes
// console.log('not equal to the value')
// const dynamicFunction = new Function(expressionString);
// parent.getValue = dynamicFunction;
// const boundFunction = parent.getValue.bind(parent);
// let value = parent.getValue()
} }
} }
Registry.lastState = [] Registry.lastState = []
@@ -363,6 +370,71 @@ window.Registry = class Registry {
const stateNames = allNames.filter(field => /^[$][^$]/.test(field)).map(str => str.substring(1)); const stateNames = allNames.filter(field => /^[$][^$]/.test(field)).map(str => str.substring(1));
const observedObjectNames = allNames.filter(field => /^[$][$][^$]/.test(field)).map(str => str.substring(2)); const observedObjectNames = allNames.filter(field => /^[$][$][^$]/.test(field)).map(str => str.substring(2));
function catalogReactivity(elem, stateNames, observedObjectNames) {
const renderString = elem.render.toString().replace(/\s/g, "");
const regex = /this\.([a-zA-Z0-9_$]+)/g;
let match;
let matches = [];
while ((match = regex.exec(renderString)) !== null) {
matches.push({index: match.index, identifier: match[1]});
}
let foundReactives = []
matches.forEach(match => {
let {index: statePos, identifier} = match;
if (stateNames.includes(identifier) || observedObjectNames.includes(identifier)) {
let charBefore = renderString[statePos - 1];
if (charBefore === "(") {
function getIdentifier() {
let startIndex = statePos - 2;
let identifier = '';
while (startIndex >= 0 && /^[a-zA-Z0-9_$]$/.test(renderString[startIndex])) {
identifier = renderString[startIndex] + identifier;
startIndex--;
}
return identifier
}
function getExpression(renderString, startPos) {
let endIndex = startPos;
while (endIndex < renderString.length && ![')', ','].includes(renderString[endIndex])) {
endIndex++;
}
return renderString.substring(startPos, endIndex).trim();
}
function containsOperators(expression) {
const operatorRegex = /[+\-*/%=&|<>!^]/;
return operatorRegex.test(expression);
}
let identifier = getIdentifier()
let expression = getExpression(renderString, statePos)
let operators = containsOperators(expression)
foundReactives.push([identifier, expression, operators])
} else {
console.log("Variable or other usage at position:", statePos);
}
if (observedObjectNames.includes(identifier)) {
} else if (stateNames.includes(identifier)) {
}
}
});
elem.reactives = foundReactives
}
catalogReactivity(elem, stateNames, observedObjectNames)
function makeState(elem, stateNames, params) { function makeState(elem, stateNames, params) {
elem._observers = {} elem._observers = {}
@@ -370,7 +442,7 @@ window.Registry = class Registry {
stateNames.forEach(name => { stateNames.forEach(name => {
const backingFieldName = `_${name}`; const backingFieldName = `_${name}`;
elem._observers[name] = new Map() elem._observers[name] = new Map()
Object.defineProperty(elem, name, { Object.defineProperty(elem, name, {
set: function(newValue) { set: function(newValue) {
elem[backingFieldName] = newValue; elem[backingFieldName] = newValue;
@@ -386,8 +458,12 @@ window.Registry = class Registry {
} }
}, },
get: function() { get: function() {
Registry.lastState = [name, elem[backingFieldName]] let rendering = window.rendering[window.rendering.length - 1]
return elem[backingFieldName]; let value = elem[backingFieldName]
if(!rendering) return value
Registry.lastState = [name, value]
return value;
}, },
enumerable: true, enumerable: true,
configurable: true configurable: true
@@ -588,8 +664,8 @@ window.VStack = function (cb = () => {}) {
div.classList.add("VStack") div.classList.add("VStack")
div.style.display = "flex" div.style.display = "flex"
div.style.flexDirection = "column" div.style.flexDirection = "column"
div.render = cb
Registry.render(div) Registry.render(div)
cb()
return div return div
} }
} }
@@ -716,6 +792,23 @@ HTMLElement.prototype.padding = function(direction, value) {
return this return this
} }
HTMLElement.prototype.margin = function(direction, value) {
if(!value) {
this.style.margin = direction;
Registry.initReactivity(this, ["style", "margin"], value);
return this
}
const directionName = `margin${direction.charAt(0).toUpperCase()}${direction.slice(1)}`;
if (typeof value === 'number') {
this.style[directionName] = `${value}px`;
} else {
this.style[directionName] = value;
}
Registry.initReactivity(this, ["style", directionName], value);
return this
}
HTMLElement.prototype.width = function(value, unit = "px") { HTMLElement.prototype.width = function(value, unit = "px") {
this.style.width = value + unit this.style.width = value + unit
Registry.initReactivity(this, ["style", "width"], value); Registry.initReactivity(this, ["style", "width"], value);
@@ -769,17 +862,6 @@ HTMLElement.prototype.yBottom = function(value, unit = "px") {
return this return this
} }
HTMLElement.prototype.margin = function(direction, value) {
const directionName = `margin${direction.charAt(0).toUpperCase()}${direction.slice(1)}`;
if (typeof value === 'number') {
this.style[directionName] = `${value}px`;
} else {
this.style[directionName] = value;
}
Registry.initReactivity(this, ["style", directionName], value);
return this
}
HTMLElement.prototype.positionType = function (value) { HTMLElement.prototype.positionType = function (value) {
if(!(value === "absolute" || value === "relative" || value === "static" || value === "fixed" || value === "sticky")) { if(!(value === "absolute" || value === "relative" || value === "static" || value === "fixed" || value === "sticky")) {
console.error("HTMLElement.overlflow: must have valid overflow value!") console.error("HTMLElement.overlflow: must have valid overflow value!")