Quill v2
This commit is contained in:
16
README.md
16
README.md
@@ -7,6 +7,20 @@
|
|||||||
|
|
||||||
Usage: Install the VSCode extension "Quill".
|
Usage: Install the VSCode extension "Quill".
|
||||||
|
|
||||||
|
## Rendering Elements:
|
||||||
|
```
|
||||||
|
document.body.append(
|
||||||
|
p("Hi")
|
||||||
|
.padding("top", 12),
|
||||||
|
|
||||||
|
Link({href: "google.com", name: "hey"})
|
||||||
|
.background("green")
|
||||||
|
|
||||||
|
// NavigationBar()
|
||||||
|
// .onClick()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
## 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.
|
||||||
@@ -16,3 +30,5 @@ Clone this repository into the top level of the project you are working on, so t
|
|||||||
Use backticks with both to get HTML and CSS syntax highlighting.
|
Use backticks with both to get HTML and CSS syntax highlighting.
|
||||||
- ```css() or addStyle()```: Adds a style to a Quill style tag in the head.
|
- ```css() or addStyle()```: Adds a style to a Quill style tag in the head.
|
||||||
- ```html()```: Creates a parsed HTML element (which is not yet in the DOM)
|
- ```html()```: Creates a parsed HTML element (which is not yet in the DOM)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
26
about.md
Normal file
26
about.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
Attribute/State Cases:
|
||||||
|
|
||||||
|
Dual Instantiation
|
||||||
|
- HTML-first instantiation from attributes (when first loaded and parsed)
|
||||||
|
observedAttributes will pick this up
|
||||||
|
- JS-first instantiation where attributes are set from constructor (or) from init function (or)
|
||||||
|
press puts attributes on from state before saving
|
||||||
|
init function can set attributes and variables - perhaps state is always required to be passed in
|
||||||
|
|
||||||
|
Usage Flexibility
|
||||||
|
- attributes can have default values
|
||||||
|
$url = "hey"
|
||||||
|
- attributes can be named or unnamed when passed in to constructor functions
|
||||||
|
|
||||||
|
Attribute / State Reflexivity
|
||||||
|
- when attribute is changed, state value is changed
|
||||||
|
modify prototype at runtime? Add overrides for setattr and remove? ||
|
||||||
|
use observedAttributes + attributeChanged (not good) - Forms parent element?
|
||||||
|
- when state is changed, attribute value is changed
|
||||||
|
modify prototype at runtime to add a setter for the state such that when it is set it sets the attribute
|
||||||
|
|
||||||
|
Bindings
|
||||||
|
- should be able to have a child variable be bound to that of a parent
|
||||||
|
|
||||||
|
Binding is denoted by prior to variable
|
||||||
|
State is denoted by "$" prior to variable
|
||||||
207
index.js
207
index.js
@@ -1,3 +1,5 @@
|
|||||||
|
/* $() */
|
||||||
|
|
||||||
HTMLElement.prototype.$ = function(selector) {
|
HTMLElement.prototype.$ = function(selector) {
|
||||||
return window.$(selector, this)
|
return window.$(selector, this)
|
||||||
}
|
}
|
||||||
@@ -11,6 +13,108 @@ window.$ = function(selector, el = document) {
|
|||||||
return el.querySelectorAll(selector);
|
return el.querySelectorAll(selector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* REGISTER */
|
||||||
|
|
||||||
|
window.registerElement = (el, tagname) => {
|
||||||
|
const randomClassName = 'Custom' + Math.random().toString(36).substring(2, 7);
|
||||||
|
const DynamicClass = {[randomClassName]: class extends el {}}[randomClassName];
|
||||||
|
const randomTagName = 'custom-' + Math.random().toString(36).substring(2, 7);
|
||||||
|
customElements.define(randomTagName, DynamicClass);
|
||||||
|
const instance = new DynamicClass();
|
||||||
|
let stateVariables = Object.keys(instance).filter(field => field.startsWith('$'));
|
||||||
|
let stateVariablesWithout$ = stateVariables.map(str => str.substring(1));
|
||||||
|
|
||||||
|
// Observe attributes
|
||||||
|
Object.defineProperty(el, 'observedAttributes', {
|
||||||
|
get: function() {
|
||||||
|
return stateVariablesWithout$;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attributes -> State
|
||||||
|
Object.defineProperty(el.prototype, 'attributeChangedCallback', {
|
||||||
|
value: function(name, oldValue, newValue) {
|
||||||
|
const fieldName = `$${name}`;
|
||||||
|
if (fieldName in this && this[fieldName] !== newValue) {
|
||||||
|
this[fieldName] = newValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
customElements.define(tagname, el)
|
||||||
|
|
||||||
|
// Actual Constructor
|
||||||
|
window[el.prototype.constructor.name] = function (...params) {
|
||||||
|
console.log(params, stateVariables)
|
||||||
|
let elem = new el()
|
||||||
|
|
||||||
|
// State -> Attributes
|
||||||
|
// set each state value as getter and setter
|
||||||
|
stateVariables.forEach(property => {
|
||||||
|
const attrName = property.substring(1); // Assuming 'property' starts with a symbol like '$'
|
||||||
|
const backingFieldName = `_${property}`; // Construct the backing field name
|
||||||
|
|
||||||
|
Object.defineProperty(elem, property, {
|
||||||
|
set: function(newValue) {
|
||||||
|
console.log(`Setting attribute ${attrName} to ${newValue}`);
|
||||||
|
this[backingFieldName] = newValue; // Use the backing field to store the value
|
||||||
|
this.setAttribute(attrName, newValue); // Synchronize with the attribute
|
||||||
|
},
|
||||||
|
get: function() {
|
||||||
|
return this[backingFieldName]; // Provide a getter to access the backing field value
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// if there are more than one, expect them to be named
|
||||||
|
if(stateVariables.length > 1) {
|
||||||
|
if(typeof params[0] !== "object") {
|
||||||
|
console.error(`Quill: elements with multiple state initializers must structure them like {color: "blue", type: 2}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let i = -1
|
||||||
|
for (let param in params[0]) {
|
||||||
|
i++
|
||||||
|
let paramName = "$" + param
|
||||||
|
if(stateVariables[i] !== paramName) {
|
||||||
|
if(!stateVariables[i]) {
|
||||||
|
console.error(`Quill: state ${state} must be initialized`)
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
console.error(`Quill: state initializer ${i} ${paramName} should be ${stateVariables[0]}`)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
elem[paramName] = params[0][param]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(params[0] !== stateVariables[0]) {
|
||||||
|
console.error(`Quill: state initializer $${params[0]} should be ${stateVariables[0]}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
elem[stateVariables[0]] = params[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all state variables are set
|
||||||
|
for(state of stateVariables) {
|
||||||
|
console.log(elem[state])
|
||||||
|
if(!elem[state]) {
|
||||||
|
console.error(`Quill: state ${state} must be initialized`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return elem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PROTOTYPE FUNCTIONS */
|
||||||
|
|
||||||
HTMLElement.prototype.addAttribute = function(name) {
|
HTMLElement.prototype.addAttribute = function(name) {
|
||||||
this.setAttribute(name, "")
|
this.setAttribute(name, "")
|
||||||
}
|
}
|
||||||
@@ -36,6 +140,107 @@ HTMLElement.prototype.endingTag = function() {
|
|||||||
return `</${tag}>`;
|
return `</${tag}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HTMLElement.prototype.render = function (...els) {
|
||||||
|
this.innerHTML = ""
|
||||||
|
if(els) {
|
||||||
|
els.forEach((el) => {
|
||||||
|
this.appendChild(el)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLElement.prototype.class = function(classNames) {
|
||||||
|
this.className = classNames
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PROTOTYPE STYLING */
|
||||||
|
|
||||||
|
HTMLElement.prototype.styleVar = function(name, value) {
|
||||||
|
this.style.setProperty(name, value)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLElement.prototype.background = function(value) {
|
||||||
|
this.style.backgroundColor = value
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLElement.prototype.padding = function(direction, amount) {
|
||||||
|
const directionName = `padding${direction.charAt(0).toUpperCase()}${direction.slice(1)}`;
|
||||||
|
if (typeof amount === 'number') {
|
||||||
|
this.style[directionName] = `${amount}px`;
|
||||||
|
} else {
|
||||||
|
this.style[directionName] = amount;
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLElement.prototype.position = function({x, y} = {}) {
|
||||||
|
if(!x || !y) {
|
||||||
|
console.error("HTMLElement.position: must have valid x and y values!")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(x, y)
|
||||||
|
this.style.left = `${x}%`
|
||||||
|
this.style.top = `${y}%`
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLElement.prototype.overflow = function(value) {
|
||||||
|
if(!(value === "visible" || value === "hidden" || value === "clip" || value === "scroll" || value === "auto")) {
|
||||||
|
console.error("HTMLElement.overlflow: must have valid overflow value!")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.style.overflow = value;
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* PROTOTYPE EVENTS */
|
||||||
|
|
||||||
|
HTMLElement.prototype.onClick = function(func) {
|
||||||
|
this.addEventListener("click", func)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DEFAULT WRAPPERS */
|
||||||
|
|
||||||
|
window.a = function a({ href, name="" } = {}) {
|
||||||
|
let link = document.createElement("a")
|
||||||
|
link.setAttribute('href', href);
|
||||||
|
link.innerText = name
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
|
||||||
|
window.img = function img(width="", height="", src="") {
|
||||||
|
let image = new Image()
|
||||||
|
if(width) image.width = width
|
||||||
|
if(height) image.height = height
|
||||||
|
if(src) image.src = src
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
|
||||||
|
window.p = function p(innerText) {
|
||||||
|
let para = document.createElement("p")
|
||||||
|
para.innerText = innerText
|
||||||
|
return para
|
||||||
|
}
|
||||||
|
|
||||||
|
window.div = function (innerText) {
|
||||||
|
let div = document.createElement("div")
|
||||||
|
div.innerText = innerText
|
||||||
|
return div
|
||||||
|
}
|
||||||
|
|
||||||
|
window.span = function (innerText) {
|
||||||
|
let span = document.createElement("span")
|
||||||
|
span.innerText = innerText
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
/* STRING TRANSLATORS */
|
||||||
|
|
||||||
window.css = function css(cssString) {
|
window.css = function css(cssString) {
|
||||||
let container = document.querySelector("style#quillStyles");
|
let container = document.querySelector("style#quillStyles");
|
||||||
if(!container) {
|
if(!container) {
|
||||||
@@ -77,6 +282,8 @@ window.html = function html(htmlString) {
|
|||||||
return fragment;
|
return fragment;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* COMPATIBILITY */
|
||||||
|
|
||||||
function detectMobile() {
|
function detectMobile() {
|
||||||
const mobileDeviceRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
const mobileDeviceRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
||||||
return mobileDeviceRegex.test(navigator.userAgent);
|
return mobileDeviceRegex.test(navigator.userAgent);
|
||||||
|
|||||||
Reference in New Issue
Block a user