12.16, 12.17 changes
This commit is contained in:
239
index.js
239
index.js
@@ -1,6 +1,8 @@
|
||||
/*
|
||||
Sam Russell
|
||||
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 - 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
|
||||
@@ -68,6 +70,12 @@ window.$ = function(selector, el = document) {
|
||||
window.$$ = function(selector, el = document) {
|
||||
return Array.from(el.querySelectorAll(selector))
|
||||
}
|
||||
HTMLElement.prototype.$$ = function(selector) {
|
||||
return window.$$(selector, this)
|
||||
}
|
||||
DocumentFragment.prototype.$$ = function(selector) {
|
||||
return window.$$(selector, this)
|
||||
}
|
||||
|
||||
/* CONSOLE */
|
||||
|
||||
@@ -163,6 +171,7 @@ Object.defineProperty(Array.prototype, 'last', {
|
||||
|
||||
window.quill = {
|
||||
rendering: [],
|
||||
lastState: null,
|
||||
|
||||
render: (el) => {
|
||||
if(el instanceof Shadow) {
|
||||
@@ -193,19 +202,6 @@ window.quill = {
|
||||
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) => {
|
||||
return el.classList.contains("HStack") || el.classList.contains("ZStack") || el.classList.contains("VStack")
|
||||
},
|
||||
@@ -216,6 +212,7 @@ window.Shadow = class extends HTMLElement {
|
||||
super()
|
||||
}
|
||||
}
|
||||
|
||||
window.register = (el, tagname) => {
|
||||
if (typeof el.prototype.render !== 'function') {
|
||||
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) {
|
||||
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)
|
||||
return instance
|
||||
}
|
||||
@@ -314,6 +347,7 @@ function extendHTMLElementWithStyleSetters() {
|
||||
case "marginRight":
|
||||
|
||||
case "textUnderlineOffset":
|
||||
case "letterSpacing":
|
||||
|
||||
return "unit-number"
|
||||
|
||||
@@ -333,24 +367,27 @@ function extendHTMLElementWithStyleSetters() {
|
||||
|
||||
switch (type) {
|
||||
case "unit-number":
|
||||
HTMLElement.prototype[prop] = function(value, unit = "px") {
|
||||
if ((typeof value !== "number" || isNaN(value)) && value !== "auto") {
|
||||
throw new Error(`Invalid value for ${prop}: ${value}. Expected a number.`);
|
||||
}
|
||||
HTMLElement.prototype[prop] = StyleFunction(function(value, unit = "px") {
|
||||
if(value === "auto") {
|
||||
this.style[prop] = value
|
||||
return this
|
||||
}
|
||||
this.style[prop] = value + unit;
|
||||
if (value !== "" && this.style[prop] === "") {
|
||||
throw new Error(`Invalid CSS value for ${prop}: ` + value + unit);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
});
|
||||
break;
|
||||
|
||||
case "string":
|
||||
HTMLElement.prototype[prop] = function(value) {
|
||||
HTMLElement.prototype[prop] = StyleFunction(function(value) {
|
||||
this.style[prop] = value;
|
||||
if (value !== "" && this.style[prop] === "") {
|
||||
throw new Error(`Invalid CSS value for ${prop}: ` + value);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -358,46 +395,86 @@ function 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) {
|
||||
cb.call(this, this)
|
||||
return this
|
||||
}
|
||||
|
||||
HTMLElement.prototype.paddingVertical = function(value, unit = "px") {
|
||||
if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value))
|
||||
throw new Error(`Invalid value: ${value}. Expected a number.`);
|
||||
/* Type 1 */
|
||||
|
||||
HTMLElement.prototype.paddingVertical = StyleFunction(function(value, unit = "px") {
|
||||
this.style.paddingTop = value + unit
|
||||
this.style.paddingBottom = value + unit
|
||||
return this
|
||||
}
|
||||
})
|
||||
|
||||
HTMLElement.prototype.paddingHorizontal = function(value, unit = "px") {
|
||||
if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value))
|
||||
throw new Error(`Invalid value: ${value}. Expected a number.`);
|
||||
HTMLElement.prototype.paddingHorizontal = StyleFunction(function(value, unit = "px") {
|
||||
this.style.paddingRight = value + unit
|
||||
this.style.paddingLeft = value + unit
|
||||
return this
|
||||
}
|
||||
})
|
||||
|
||||
HTMLElement.prototype.marginVertical = function(value, unit = "px") {
|
||||
if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value))
|
||||
throw new Error(`Invalid value: ${value}. Expected a number.`);
|
||||
HTMLElement.prototype.marginVertical = StyleFunction(function(value, unit = "px") {
|
||||
this.style.marginTop = value + unit
|
||||
this.style.marginBottom = value + unit
|
||||
return this
|
||||
}
|
||||
})
|
||||
|
||||
HTMLElement.prototype.marginHorizontal = function(value, unit = "px") {
|
||||
if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value))
|
||||
throw new Error(`Invalid value: ${value}. Expected a number.`);
|
||||
HTMLElement.prototype.marginHorizontal = StyleFunction(function(value, unit = "px") {
|
||||
this.style.marginRight = value + unit
|
||||
this.style.marginLeft = value + unit
|
||||
return this
|
||||
}
|
||||
})
|
||||
|
||||
HTMLElement.prototype.fontSize = function(value, unit = "px") {
|
||||
if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value))
|
||||
throw new Error(`Invalid value: ${value}. Expected a number.`);
|
||||
HTMLElement.prototype.fontSize = StyleFunction(function(value, unit = "px") {
|
||||
|
||||
switch(value) {
|
||||
case "6xl":
|
||||
@@ -441,7 +518,7 @@ HTMLElement.prototype.fontSize = function(value, unit = "px") {
|
||||
}
|
||||
this.style.fontSize = value + unit
|
||||
return this
|
||||
}
|
||||
})
|
||||
|
||||
function checkPositionType(el) {
|
||||
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") {
|
||||
if (typeof value !== 'number' || isNaN(value))
|
||||
throw new Error(`Invalid value: ${value}. Expected a number.`);
|
||||
@@ -661,7 +758,7 @@ window.form = function(cb) {
|
||||
return el
|
||||
}
|
||||
|
||||
window.input = function(placeholder, width, height) {
|
||||
window.input = function(placeholder = "", width, height) {
|
||||
let el = document.createElement("input")
|
||||
el.placeholder = placeholder
|
||||
el.style.width = width
|
||||
@@ -670,14 +767,18 @@ window.input = function(placeholder, width, height) {
|
||||
return el
|
||||
}
|
||||
|
||||
window.label = function(text) {
|
||||
window.label = function(inside) {
|
||||
let el = document.createElement("label")
|
||||
el.innerText = text
|
||||
if(typeof inside === "string") {
|
||||
el.innerText = inside
|
||||
} else {
|
||||
el.render = inside
|
||||
}
|
||||
quill.render(el)
|
||||
return el
|
||||
}
|
||||
|
||||
window.textarea = function(placeholder) {
|
||||
window.textarea = function(placeholder = "") {
|
||||
let el = document.createElement("textarea")
|
||||
el.placeholder = placeholder
|
||||
quill.render(el)
|
||||
@@ -845,8 +946,8 @@ HTMLElement.prototype.onAppear = function(func) {
|
||||
};
|
||||
|
||||
HTMLElement.prototype.onClick = function(func) {
|
||||
const onMouseDown = () => func.call(this, false);
|
||||
const onMouseUp = () => func.call(this, true);
|
||||
const onMouseDown = (e) => func.call(this, false, e);
|
||||
const onMouseUp = (e) => func.call(this, true, e);
|
||||
this._storeListener("mousedown", onMouseDown);
|
||||
this._storeListener("mouseup", onMouseUp);
|
||||
return this;
|
||||
@@ -929,34 +1030,38 @@ HTMLElement.prototype.onTap = function(cb) {
|
||||
};
|
||||
|
||||
/* 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.
|
||||
- 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.
|
||||
- Then, if we try to add that scope using bind(), it makes the function.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.
|
||||
- We can't just put a listener on the element, because a window "navigate" event won't trigger it
|
||||
- We can't just put a listener on the window, because the "this" variable will only refer to the window
|
||||
- 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 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 = []
|
||||
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", () => {
|
||||
for(entry of navigateListeners) {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user