12.16, 12.17 changes

This commit is contained in:
metacryst
2025-12-25 06:41:43 -06:00
parent ed6d885557
commit b08e2767f6

239
index.js
View File

@@ -1,6 +1,8 @@
/* /*
Sam Russell Sam Russell
Captured Sun Captured Sun
12.17.25 - [Hyperia] - adding width, height functions. adding "e" to onClick. adding the non-window $$ funcs.
12.16.25 - [comalyr] - State
11.25.25.1 - Added minHeight and minWidth to be counted as numerical styles 11.25.25.1 - Added minHeight and minWidth to be counted as numerical styles
11.25.25 - Added onChange for check boxes, added setQuery / onQueryChanged for easy filtering 11.25.25 - Added onChange for check boxes, added setQuery / onQueryChanged for easy filtering
11.24.25 - Fixing onClick because it was reversed, adding event to onHover params 11.24.25 - Fixing onClick because it was reversed, adding event to onHover params
@@ -68,6 +70,12 @@ window.$ = function(selector, el = document) {
window.$$ = function(selector, el = document) { window.$$ = function(selector, el = document) {
return Array.from(el.querySelectorAll(selector)) return Array.from(el.querySelectorAll(selector))
} }
HTMLElement.prototype.$$ = function(selector) {
return window.$$(selector, this)
}
DocumentFragment.prototype.$$ = function(selector) {
return window.$$(selector, this)
}
/* CONSOLE */ /* CONSOLE */
@@ -163,6 +171,7 @@ Object.defineProperty(Array.prototype, 'last', {
window.quill = { window.quill = {
rendering: [], rendering: [],
lastState: null,
render: (el) => { render: (el) => {
if(el instanceof Shadow) { if(el instanceof Shadow) {
@@ -193,19 +202,6 @@ window.quill = {
quill.rendering.pop() quill.rendering.pop()
}, },
loadPage: () => {
let URL = window.location.pathname
if(!window.routes[URL]) {
throw new Error("No URL for this route: ", URL)
}
let pageClass = window[routes[URL]]
document.title = pageClass.title ?? document.title
document.body.innerHTML = ""
let page = new pageClass()
quill.render(page)
},
isStack: (el) => { isStack: (el) => {
return el.classList.contains("HStack") || el.classList.contains("ZStack") || el.classList.contains("VStack") return el.classList.contains("HStack") || el.classList.contains("ZStack") || el.classList.contains("VStack")
}, },
@@ -216,6 +212,7 @@ window.Shadow = class extends HTMLElement {
super() super()
} }
} }
window.register = (el, tagname) => { window.register = (el, tagname) => {
if (typeof el.prototype.render !== 'function') { if (typeof el.prototype.render !== 'function') {
throw new Error("Element must have a render: " + el.prototype.constructor.name) throw new Error("Element must have a render: " + el.prototype.constructor.name)
@@ -230,6 +227,42 @@ window.register = (el, tagname) => {
window[el.prototype.constructor.name] = function (...params) { window[el.prototype.constructor.name] = function (...params) {
let instance = new el(...params) let instance = new el(...params)
if(instance.state) {
let proxy = new Proxy(instance.state, {
get(target, prop, receiver) {
if (typeof prop === "symbol") { // Ignore internal / symbol accesses
return Reflect.get(target, prop, receiver);
}
quill.lastLastState = quill.lastState
quill.lastState = prop;
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
const oldValue = target[prop];
if (oldValue === value) return true;
const result = Reflect.set(target, prop, value, receiver);
instance.stateWatchers[prop].forEach((cb) => cb())
return result;
}
});
Object.defineProperty(instance, "state", {
value: proxy,
writable: false,
configurable: false,
enumerable: true
});
let stateWatchers = {}
Object.keys(instance.state).forEach((key) => stateWatchers[key] = [])
Object.defineProperty(instance, "stateWatchers", {
value: stateWatchers,
writable: false,
configurable: false,
enumerable: true
});
}
quill.render(instance) quill.render(instance)
return instance return instance
} }
@@ -314,6 +347,7 @@ function extendHTMLElementWithStyleSetters() {
case "marginRight": case "marginRight":
case "textUnderlineOffset": case "textUnderlineOffset":
case "letterSpacing":
return "unit-number" return "unit-number"
@@ -333,24 +367,27 @@ function extendHTMLElementWithStyleSetters() {
switch (type) { switch (type) {
case "unit-number": case "unit-number":
HTMLElement.prototype[prop] = function(value, unit = "px") { HTMLElement.prototype[prop] = StyleFunction(function(value, unit = "px") {
if ((typeof value !== "number" || isNaN(value)) && value !== "auto") {
throw new Error(`Invalid value for ${prop}: ${value}. Expected a number.`);
}
if(value === "auto") { if(value === "auto") {
this.style[prop] = value this.style[prop] = value
return this return this
} }
this.style[prop] = value + unit; this.style[prop] = value + unit;
if (value !== "" && this.style[prop] === "") {
throw new Error(`Invalid CSS value for ${prop}: ` + value + unit);
}
return this; return this;
}; });
break; break;
case "string": case "string":
HTMLElement.prototype[prop] = function(value) { HTMLElement.prototype[prop] = StyleFunction(function(value) {
this.style[prop] = value; this.style[prop] = value;
if (value !== "" && this.style[prop] === "") {
throw new Error(`Invalid CSS value for ${prop}: ` + value);
}
return this; return this;
}; });
break; break;
} }
}); });
@@ -358,46 +395,86 @@ function extendHTMLElementWithStyleSetters() {
extendHTMLElementWithStyleSetters(); extendHTMLElementWithStyleSetters();
HTMLElement.prototype.addStateWatcher = function(field, cb) {
let parent = this
while(!(parent instanceof Shadow)) {
parent = parent.parentNode
}
parent.stateWatchers[field].push(cb)
}
// Currently only works for one state variable in the function
// Could probably be fixed by just making lastState an array and clearing it out every function call?
HTMLElement.prototype.setUpState = function(styleFunc, cb) {
let format = (value) => {return Array.isArray(value) ? value : [value]}
// 1. Run the callback to get the style argument and also update lastState
let styleArgs = format(cb())
// 2. Check if lastState has really been updated. If not, the user-provided cb did not access valid state
if(!quill.lastState) {
throw new Error("Quill: style state function does not access valid state")
}
// 3. Construct function to run when state changes
let onStateChange = () => {
styleFunc.call(this, ...format(cb()))
}
// 4. Now listen for the state to change
this.addStateWatcher(quill.lastState, onStateChange)
// 5. Run the original function again, this time with the actual arguments
quill.lastState = null
styleFunc.call(this, ...styleArgs)
}
function StyleFunction(func) {
let styleFunction = function(value, unit = "px") {
if(typeof value === 'function') {
this.setUpState(styleFunction, value)
return this
} else {
func.call(this, value, unit); // ".call" ensures that "this" is correct
return this
}
}
return styleFunction
}
HTMLElement.prototype.styles = function(cb) { HTMLElement.prototype.styles = function(cb) {
cb.call(this, this) cb.call(this, this)
return this return this
} }
HTMLElement.prototype.paddingVertical = function(value, unit = "px") { /* Type 1 */
if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value))
throw new Error(`Invalid value: ${value}. Expected a number.`); HTMLElement.prototype.paddingVertical = StyleFunction(function(value, unit = "px") {
this.style.paddingTop = value + unit this.style.paddingTop = value + unit
this.style.paddingBottom = value + unit this.style.paddingBottom = value + unit
return this return this
} })
HTMLElement.prototype.paddingHorizontal = function(value, unit = "px") { HTMLElement.prototype.paddingHorizontal = StyleFunction(function(value, unit = "px") {
if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value))
throw new Error(`Invalid value: ${value}. Expected a number.`);
this.style.paddingRight = value + unit this.style.paddingRight = value + unit
this.style.paddingLeft = value + unit this.style.paddingLeft = value + unit
return this return this
} })
HTMLElement.prototype.marginVertical = function(value, unit = "px") { HTMLElement.prototype.marginVertical = StyleFunction(function(value, unit = "px") {
if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value))
throw new Error(`Invalid value: ${value}. Expected a number.`);
this.style.marginTop = value + unit this.style.marginTop = value + unit
this.style.marginBottom = value + unit this.style.marginBottom = value + unit
return this return this
} })
HTMLElement.prototype.marginHorizontal = function(value, unit = "px") { HTMLElement.prototype.marginHorizontal = StyleFunction(function(value, unit = "px") {
if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value))
throw new Error(`Invalid value: ${value}. Expected a number.`);
this.style.marginRight = value + unit this.style.marginRight = value + unit
this.style.marginLeft = value + unit this.style.marginLeft = value + unit
return this return this
} })
HTMLElement.prototype.fontSize = function(value, unit = "px") { HTMLElement.prototype.fontSize = StyleFunction(function(value, unit = "px") {
if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value))
throw new Error(`Invalid value: ${value}. Expected a number.`);
switch(value) { switch(value) {
case "6xl": case "6xl":
@@ -441,7 +518,7 @@ HTMLElement.prototype.fontSize = function(value, unit = "px") {
} }
this.style.fontSize = value + unit this.style.fontSize = value + unit
return this return this
} })
function checkPositionType(el) { function checkPositionType(el) {
let computed = window.getComputedStyle(el).position let computed = window.getComputedStyle(el).position
@@ -450,6 +527,26 @@ function checkPositionType(el) {
} }
} }
HTMLElement.prototype.width = function(value, unit = "px") {
if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value))
throw new Error(`Invalid value: ${value}. Expected a number.`);
this.style.width = value + unit
if(window.getComputedStyle(this).display === "inline") {
this.style.display = "block"
}
return this
}
HTMLElement.prototype.height = function(value, unit = "px") {
if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value))
throw new Error(`Invalid value: ${value}. Expected a number.`);
this.style.height = value + unit
if(window.getComputedStyle(this).display === "inline") {
this.style.display = "block"
}
return this
}
HTMLElement.prototype.x = function(value, unit = "px") { HTMLElement.prototype.x = function(value, unit = "px") {
if (typeof value !== 'number' || isNaN(value)) if (typeof value !== 'number' || isNaN(value))
throw new Error(`Invalid value: ${value}. Expected a number.`); throw new Error(`Invalid value: ${value}. Expected a number.`);
@@ -661,7 +758,7 @@ window.form = function(cb) {
return el return el
} }
window.input = function(placeholder, width, height) { window.input = function(placeholder = "", width, height) {
let el = document.createElement("input") let el = document.createElement("input")
el.placeholder = placeholder el.placeholder = placeholder
el.style.width = width el.style.width = width
@@ -670,14 +767,18 @@ window.input = function(placeholder, width, height) {
return el return el
} }
window.label = function(text) { window.label = function(inside) {
let el = document.createElement("label") let el = document.createElement("label")
el.innerText = text if(typeof inside === "string") {
el.innerText = inside
} else {
el.render = inside
}
quill.render(el) quill.render(el)
return el return el
} }
window.textarea = function(placeholder) { window.textarea = function(placeholder = "") {
let el = document.createElement("textarea") let el = document.createElement("textarea")
el.placeholder = placeholder el.placeholder = placeholder
quill.render(el) quill.render(el)
@@ -845,8 +946,8 @@ HTMLElement.prototype.onAppear = function(func) {
}; };
HTMLElement.prototype.onClick = function(func) { HTMLElement.prototype.onClick = function(func) {
const onMouseDown = () => func.call(this, false); const onMouseDown = (e) => func.call(this, false, e);
const onMouseUp = () => func.call(this, true); const onMouseUp = (e) => func.call(this, true, e);
this._storeListener("mousedown", onMouseDown); this._storeListener("mousedown", onMouseDown);
this._storeListener("mouseup", onMouseUp); this._storeListener("mouseup", onMouseUp);
return this; return this;
@@ -929,34 +1030,38 @@ HTMLElement.prototype.onTap = function(cb) {
}; };
/* WHY THIS LISTENER IS THE WAY IT IS: /* WHY THIS LISTENER IS THE WAY IT IS:
- If we dispatch the "navigate" event on the window (as one would expect for a "navigate" event), a listener placed on the element will not pick it up. - We can't just put a listener on the element, because a window "navigate" event won't trigger it
- However, if we add the listener on the window, it won't have the "this" scope that a callback normally would. Which makes it much less useful. - We can't just put a listener on the window, because the "this" variable will only refer to the window
- Then, if we try to add that scope using bind(), it makes the function.toString() unreadable, which means we cannot detect duplicate listeners. - And, if we try to re-add that scope using bind(), it makes the return value of .toString() unreadable, which means we cannot detect duplicate listeners.
- Therefore, we just have to attach the navigate event to the element, and manually trigger that when the window listener fires. - Therefore, we attach a global navigate event to the window, and each navigate event in this array, and manually trigger each event when the global one fires.
*/ */
navigateListeners = [] navigateListeners = []
HTMLElement.prototype.onNavigate = function(cb) {
this._storeListener("navigate", cb);
let found = false
for(entry of navigateListeners) {
if(entry.cb.toString() === cb.toString() &&
this.nodeName === entry.el.nodeName) {
found = true
break;
}
}
if(found === false) {
navigateListeners.push({el: this, cb: cb})
}
return this;
};
window.addEventListener("navigate", () => { window.addEventListener("navigate", () => {
for(entry of navigateListeners) { for(entry of navigateListeners) {
entry.el.dispatchEvent(new CustomEvent("navigate")) entry.el.dispatchEvent(new CustomEvent("navigate"))
} }
}) })
HTMLElement.prototype.onNavigate = function(cb) {
this._storeListener("navigate", cb);
let found = false
let elementIndex = Array.from(this.parentNode.children).indexOf(this)
for(entry of navigateListeners) {
if(
entry.cb.toString() === cb.toString()
&& entry.index === elementIndex
&& this.nodeName === entry.el.nodeName
) {
found = true
break;
}
}
if(found === false) {
navigateListeners.push({el: this, cb: cb, index: elementIndex})
}
return this;
};
/* /*
Same principle applies Same principle applies