diff --git a/.env b/.env new file mode 100644 index 0000000..b117f22 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +VSCE_PAT=9wucEHBNFD5RTS403Oyoflyi984cAwAfa42FHN7tmqmzfbXsSKP9JQQJ99BKACAAAAAAAAAAAAAGAZDO25Ro \ No newline at end of file diff --git a/Sample/.gitignore b/Sample/.gitignore new file mode 100644 index 0000000..25c8fdb --- /dev/null +++ b/Sample/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json \ No newline at end of file diff --git a/Sample/db/db.json b/Sample/db/db.json new file mode 100644 index 0000000..e69de29 diff --git a/Sample/package.json b/Sample/package.json new file mode 100644 index 0000000..e7632af --- /dev/null +++ b/Sample/package.json @@ -0,0 +1,18 @@ +{ + "name": "Quill Starter", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "node server/index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "chalk": "^4.1.2", + "cors": "^2.8.5", + "express": "^4.18.2", + "moment": "^2.30.1" + } +} diff --git a/Sample/server/db/db.js b/Sample/server/db/db.js new file mode 100644 index 0000000..dc4198b --- /dev/null +++ b/Sample/server/db/db.js @@ -0,0 +1,12 @@ +const fs = require('fs/promises'); + +export default class Database { + + constructor() { + + } + + async saveData() { + + } +} \ No newline at end of file diff --git a/Sample/server/index.js b/Sample/server/index.js new file mode 100644 index 0000000..a44f3e0 --- /dev/null +++ b/Sample/server/index.js @@ -0,0 +1,100 @@ +const express = require('express'); +const cors = require('cors'); +const http = require('http'); +const chalk = require('chalk'); +const moment = require('moment'); +const path = require('path'); + +class Server { + db; + UIPath = path.join(__dirname, '../ui') + + registerRoutes(router) { + router.post('/join', this.join) + router.post('/contact', this.contact) + router.get('/*', this.get) + return router + } + + join = (req, res) => { + + } + + contact = (req, res) => { + + } + + get = async (req, res) => { + console.log(this.UIPath) + let url = req.url + let filePath; + if(url.startsWith("/_")) { + filePath = path.join(this.UIPath, url); + } else if(url.includes("75820185")) { + filePath = path.join(this.UIPath, url.split("75820185")[1]); + } else { + filePath = path.join(this.UIPath, "index.html"); + } + + res.sendFile(filePath); + } + + logRequest(req, res, next) { + const formattedDate = moment().format('M.D'); + const formattedTime = moment().format('h:mma'); + if(req.url.includes("/api/")) { + console.log(chalk.blue(` ${req.method} ${req.url} | ${formattedDate} ${formattedTime}`)); + } else { + if(req.url === "/") + console.log(chalk.gray(` ${req.method} ${req.url} | ${formattedDate} ${formattedTime}`)); + } + next(); + } + + logResponse(req, res, next) { + const originalSend = res.send; + res.send = function (body) { + if(res.statusCode >= 400) { + console.log(chalk.blue( `<-${chalk.red(res.statusCode)}- ${req.method} ${req.url} | ${chalk.red(body)}`)); + } else { + console.log(chalk.blue(`<-${res.statusCode}- ${req.method} ${req.url}`)); + } + originalSend.call(this, body); + }; + next(); + } + + constructor() { + const app = express(); + app.use(cors({ origin: '*' })); + app.use(express.json()); + app.use(express.urlencoded({ extended: true })); + + app.use(this.logRequest); + app.use(this.logResponse); + + let router = express.Router(); + this.registerRoutes(router) + app.use('/', router); + + const server = http.createServer(app); + const PORT = 3004; + server.listen(PORT, () => { + console.log("\n") + console.log(chalk.yellow("*************** Comal YR ***************")) + console.log(chalk.yellowBright(`Server is running on port ${PORT}: http://localhost`)); + console.log(chalk.yellow("***************************************")) + console.log("\n") + }); + + process.on('SIGINT', async () => { + console.log(chalk.red('Closing server...')); + console.log(chalk.green('Database connection closed.')); + process.exit(0); + }); + + Object.preventExtensions(this); + } +} + +const server = new Server() \ No newline at end of file diff --git a/Sample/ui/_/code/quill.js b/Sample/ui/_/code/quill.js new file mode 100644 index 0000000..fcebb29 --- /dev/null +++ b/Sample/ui/_/code/quill.js @@ -0,0 +1,1047 @@ +/* + Sam Russell + Captured Sun + 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 + 11.23.25 - Added onSubmit() event for form submission, added marginHorizontal() and marginVertical() + 11.20.25 - Added "pct" style unit, added alignVertical and alignHorizontal for flex boxes + 11.19.25 - Allowing for "auto" values in otherwise numeric styles, adding vmin and vmax units + 11.17.25.3 - Adding styles() and fixing dynamic function from earlier + 11.17.25.2 - Fixing onNavigate() and onAppear() + 11.17.25 - Added dynamic function to have units in style func parameters. + 11.14.25 - Added onTouch, onTap. Changed style setters to work with Safari. Added center() funcs. + 11.13.25 - changed onFocus() to be a boolean event, added onInput() + 11.9.25 - changed p(innerText) to p(innerHTML), adjusted onNavigate to work for multiple elements and with correct "this" scope + 11.7.25 - changed registerShadow() to register(), changed onClick() to be like onHover() + 11.6.25 - adding default value for "button()" "children" parameter + 10.29.25 - adding "gap()" and "label()" functions +*/ + +/* $ NAVIGATION */ +let oldPushState = history.pushState; +history.pushState = function pushState() { + let ret = oldPushState.apply(this, arguments); + window.dispatchEvent(new Event('pushstate')); + window.dispatchEvent(new Event('navigate')); + return ret; +}; + +window.addEventListener('popstate', () => { + window.dispatchEvent(new Event('navigate')); +}); + +window.setQuery = function(key, value) { + const url = new URL(window.location.href); + const params = url.searchParams; + + if (value === null || value === undefined) { + params.delete(key); + } else { + params.set(key, value); + } + + const newUrl = url.toString(); + history.replaceState(null, "", newUrl); + window.dispatchEvent(new Event('query-changed')); + + return newUrl; +}; + +window.navigateTo = function(url) { + window.dispatchEvent(new Event('navigate')); + window.history.pushState({}, '', url); +} + +/* $ SELECTOR */ + +HTMLElement.prototype.$ = function(selector) { + return window.$(selector, this) +} +DocumentFragment.prototype.$ = function(selector) { + return window.$(selector, this) +} +window.$ = function(selector, el = document) { + return el.querySelector(selector) +} + +window.$$ = function(selector, el = document) { + return Array.from(el.querySelectorAll(selector)) +} + +/* CONSOLE */ + +console.red = function(message) { + this.log(`%c${message}`, "color: rgb(254, 79, 42);"); +}; + +console.green = function(message) { + this.log(`%c${message}`, "color: rgb(79, 254, 42);"); + +} + +/* GET CSS VARIABLES FOR DARK OR LIGHT MODE */ +window.darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; +document.documentElement.classList.add(darkMode ? 'dark' : 'light'); + +window.getColor = function(name) { + const rootStyles = getComputedStyle(document.documentElement); + const color = rootStyles.getPropertyValue(`--${name}`).trim(); + if(!color) { + throw new Error("Color not found") + } + return color +} + +/* MOBILE */ + +window.isMobile = function isMobile() { + return /Android|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i.test(navigator.userAgent); +} + +window.css = function css(cssString) { + let container = document.querySelector("style#pageStyle"); + if(!container) { + container = document.createElement('style'); + container.id = "pageStyle"; + document.head.appendChild(container); + } + + let primarySelector = cssString.substring(0, cssString.indexOf("{")).trim(); + primarySelector = primarySelector.replace(/\*/g, "all"); + primarySelector = primarySelector.replace(/#/g, "id-"); + primarySelector = primarySelector.replace(/,/g, ""); + let stylesheet = container.querySelector(`:scope > style[id='${primarySelector}']`) + if(!stylesheet) { + stylesheet = document.createElement('style'); + stylesheet.id = primarySelector; + stylesheet.appendChild(document.createTextNode(cssString)); + container.appendChild(stylesheet); + } else { + stylesheet.innerText = cssString + } +} + +window.html = function html(elementString) { + let parser = new DOMParser(); + let doc = parser.parseFromString(elementString, 'text/html'); + return doc.body.firstChild; +} + +window.util = {} +window.util.observeClassChange = (el, callback) => { + if (!el || !(el instanceof Element)) { + throw new Error("observeClassChange requires a valid DOM element."); + } + + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type === "attributes" && mutation.attributeName === "class") { + callback(el.classList); + } + } + }); + + observer.observe(el, { + attributes: true, + attributeFilter: ["class"] + }); + + return observer; // Optional: return it so you can disconnect later +} + +/* PAGE SETUP */ + +Object.defineProperty(Array.prototype, 'last', { + get() { + return this[this.length - 1]; + }, + enumerable: false, +}); + +/* QUILL */ + +window.quill = { + rendering: [], + + render: (el) => { + if(el instanceof Shadow) { + let parent = quill.rendering[quill.rendering.length-1] + if(!parent) { + parent = document.body + } + parent.appendChild(el) + } else { + if(!el.render) {el.render = () => {}} + let parent = quill.rendering[quill.rendering.length-1] + if(!parent) throw new Error("Quill: no parent for element") + parent.appendChild(el) + } + + quill.rendering.push(el) + el.render() + quill.rendering.pop(el) + }, + + rerender: (el) => { + Array.from(el.attributes).forEach(attr => el.removeAttribute(attr.name)); + el.innerHTML = "" + el.removeAllListeners() + + quill.rendering.push(el) + el.render() + 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") + }, +} + +window.Shadow = class extends HTMLElement { + constructor() { + super() + } +} +window.register = (el, tagname) => { + if (typeof el.prototype.render !== 'function') { + throw new Error("Element must have a render: " + el.prototype.constructor.name) + } + if(!tagname) { + tagname = el.prototype.constructor.name.toLowerCase() + "-" + } + customElements.define(tagname, el) + if(el.css) { + css(el.css) + } + + window[el.prototype.constructor.name] = function (...params) { + let instance = new el(...params) + quill.render(instance) + return instance + } +} + +HTMLElement.prototype.rerender = function() { + quill.rerender(this) +} + +/* Styling */ + +window.pct = "%" +window.vmin = "vmin" +window.vmax = "vmax" +window.vh = "vh" +window.vw = "vw" +window.px = "px" +window.em = "em" +window.rem = "rem" +window.inches = "in" + +HTMLElement.prototype.addStyle = function(func) { + return func(this) +} + +window.css = function css(cssString) { + let container = document.querySelector("style#pageStyle"); + if(!container) { + container = document.createElement('style'); + container.id = "pageStyle"; + document.head.appendChild(container); + } + + let primarySelector = cssString.substring(0, cssString.indexOf("{")).trim(); + primarySelector = primarySelector.replace(/\*/g, "all"); + primarySelector = primarySelector.replace(/#/g, "id-"); + primarySelector = primarySelector.replace(/,/g, ""); + let stylesheet = container.querySelector(`:scope > style[id='${primarySelector}']`) + if(!stylesheet) { + stylesheet = document.createElement('style'); + stylesheet.id = primarySelector; + stylesheet.appendChild(document.createTextNode(cssString)); + container.appendChild(stylesheet); + } else { + stylesheet.innerText = cssString + } +} + +function extendHTMLElementWithStyleSetters() { + + function cssValueType(prop) { + const div = document.createElement("div"); + const style = div.style; + if (!(prop in style)) return "invalid"; + + switch(prop) { + + case "gap": + case "borderRadius": + case "width": + case "height": + case "maxWidth": + case "maxHeight": + case "minWidth": + case "minHeight": + + case "left": + case "top": + case "bottom": + case "right": + + case "padding": + case "paddingLeft": + case "paddingTop": + case "paddingBottom": + case "paddingRight": + + case "margin": + case "marginLeft": + case "marginTop": + case "marginBottom": + case "marginRight": + + case "textUnderlineOffset": + + return "unit-number" + + default: + + return "string" + } + + } + + let allStyleProps = ["accentColor", "additiveSymbols", "alignContent", "alignItems", "alignSelf", "alignmentBaseline", "all", "anchorName", "anchorScope", "animation", "animationComposition", "animationDelay", "animationDirection", "animationDuration", "animationFillMode", "animationIterationCount", "animationName", "animationPlayState", "animationRange", "animationRangeEnd", "animationRangeStart", "animationTimeline", "animationTimingFunction", "appRegion", "appearance", "ascentOverride", "aspectRatio", "backdropFilter", "backfaceVisibility", "background", "backgroundAttachment", "backgroundBlendMode", "backgroundClip", "backgroundColor", "backgroundImage", "backgroundOrigin", "backgroundPosition", "backgroundPositionX", "backgroundPositionY", "backgroundRepeat", "backgroundSize", "basePalette", "baselineShift", "baselineSource", "blockSize", "border", "borderBlock", "borderBlockColor", "borderBlockEnd", "borderBlockEndColor", "borderBlockEndStyle", "borderBlockEndWidth", "borderBlockStart", "borderBlockStartColor", "borderBlockStartStyle", "borderBlockStartWidth", "borderBlockStyle", "borderBlockWidth", "borderBottom", "borderBottomColor", "borderBottomLeftRadius", "borderBottomRightRadius", "borderBottomStyle", "borderBottomWidth", "borderCollapse", "borderColor", "borderEndEndRadius", "borderEndStartRadius", "borderImage", "borderImageOutset", "borderImageRepeat", "borderImageSlice", "borderImageSource", "borderImageWidth", "borderInline", "borderInlineColor", "borderInlineEnd", "borderInlineEndColor", "borderInlineEndStyle", "borderInlineEndWidth", "borderInlineStart", "borderInlineStartColor", "borderInlineStartStyle", "borderInlineStartWidth", "borderInlineStyle", "borderInlineWidth", "borderLeft", "borderLeftColor", "borderLeftStyle", "borderLeftWidth", "borderRadius", "borderRight", "borderRightColor", "borderRightStyle", "borderRightWidth", "borderSpacing", "borderStartEndRadius", "borderStartStartRadius", "borderStyle", "borderTop", "borderTopColor", "borderTopLeftRadius", "borderTopRightRadius", "borderTopStyle", "borderTopWidth", "borderWidth", "bottom", "boxDecorationBreak", "boxShadow", "boxSizing", "breakAfter", "breakBefore", "breakInside", "bufferedRendering", "captionSide", "caretAnimation", "caretColor", "clear", "clip", "clipPath", "clipRule", "color", "colorInterpolation", "colorInterpolationFilters", "colorRendering", "colorScheme", "columnCount", "columnFill", "columnGap", "columnRule", "columnRuleColor", "columnRuleStyle", "columnRuleWidth", "columnSpan", "columnWidth", "columns", "contain", "containIntrinsicBlockSize", "containIntrinsicHeight", "containIntrinsicInlineSize", "containIntrinsicSize", "containIntrinsicWidth", "container", "containerName", "containerType", "content", "contentVisibility", "cornerBlockEndShape", "cornerBlockStartShape", "cornerBottomLeftShape", "cornerBottomRightShape", "cornerBottomShape", "cornerEndEndShape", "cornerEndStartShape", "cornerInlineEndShape", "cornerInlineStartShape", "cornerLeftShape", "cornerRightShape", "cornerShape", "cornerStartEndShape", "cornerStartStartShape", "cornerTopLeftShape", "cornerTopRightShape", "cornerTopShape", "counterIncrement", "counterReset", "counterSet", "cursor", "cx", "cy", "d", "descentOverride", "direction", "display", "dominantBaseline", "dynamicRangeLimit", "emptyCells", "fallback", "fieldSizing", "fill", "fillOpacity", "fillRule", "filter", "flex", "flexBasis", "flexDirection", "flexFlow", "flexGrow", "flexShrink", "flexWrap", "float", "floodColor", "floodOpacity", "font", "fontDisplay", "fontFamily", "fontFeatureSettings", "fontKerning", "fontOpticalSizing", "fontPalette", "fontSize", "fontSizeAdjust", "fontStretch", "fontStyle", "fontSynthesis", "fontSynthesisSmallCaps", "fontSynthesisStyle", "fontSynthesisWeight", "fontVariant", "fontVariantAlternates", "fontVariantCaps", "fontVariantEastAsian", "fontVariantEmoji", "fontVariantLigatures", "fontVariantNumeric", "fontVariantPosition", "fontVariationSettings", "fontWeight", "forcedColorAdjust", "gap", "grid", "gridArea", "gridAutoColumns", "gridAutoFlow", "gridAutoRows", "gridColumn", "gridColumnEnd", "gridColumnGap", "gridColumnStart", "gridGap", "gridRow", "gridRowEnd", "gridRowGap", "gridRowStart", "gridTemplate", "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows", "height", "hyphenateCharacter", "hyphenateLimitChars", "hyphens", "imageOrientation", "imageRendering", "inherits", "initialLetter", "initialValue", "inlineSize", "inset", "insetBlock", "insetBlockEnd", "insetBlockStart", "insetInline", "insetInlineEnd", "insetInlineStart", "interactivity", "interpolateSize", "isolation", "justifyContent", "justifyItems", "justifySelf", "left", "letterSpacing", "lightingColor", "lineBreak", "lineGapOverride", "lineHeight", "listStyle", "listStyleImage", "listStylePosition", "listStyleType", "margin", "marginBlock", "marginBlockEnd", "marginBlockStart", "marginBottom", "marginInline", "marginInlineEnd", "marginInlineStart", "marginLeft", "marginRight", "marginTop", "marker", "markerEnd", "markerMid", "markerStart", "mask", "maskClip", "maskComposite", "maskImage", "maskMode", "maskOrigin", "maskPosition", "maskRepeat", "maskSize", "maskType", "mathDepth", "mathShift", "mathStyle", "maxBlockSize", "maxHeight", "maxInlineSize", "maxWidth", "minBlockSize", "minHeight", "minInlineSize", "minWidth", "mixBlendMode", "navigation", "negative", "objectFit", "objectPosition", "objectViewBox", "offset", "offsetAnchor", "offsetDistance", "offsetPath", "offsetPosition", "offsetRotate", "opacity", "order", "orphans", "outline", "outlineColor", "outlineOffset", "outlineStyle", "outlineWidth", "overflow", "overflowAnchor", "overflowBlock", "overflowClipMargin", "overflowInline", "overflowWrap", "overflowX", "overflowY", "overlay", "overrideColors", "overscrollBehavior", "overscrollBehaviorBlock", "overscrollBehaviorInline", "overscrollBehaviorX", "overscrollBehaviorY", "pad", "padding", "paddingBlock", "paddingBlockEnd", "paddingBlockStart", "paddingBottom", "paddingInline", "paddingInlineEnd", "paddingInlineStart", "paddingLeft", "paddingRight", "paddingTop", "page", "pageBreakAfter", "pageBreakBefore", "pageBreakInside", "pageOrientation", "paintOrder", "perspective", "perspectiveOrigin", "placeContent", "placeItems", "placeSelf", "pointerEvents", "position", "positionAnchor", "positionArea", "positionTry", "positionTryFallbacks", "positionTryOrder", "positionVisibility", "prefix", "printColorAdjust", "quotes", "r", "range", "readingFlow", "readingOrder", "resize", "result", "right", "rotate", "rowGap", "rubyAlign", "rubyPosition", "rx", "ry", "scale", "scrollBehavior", "scrollInitialTarget", "scrollMargin", "scrollMarginBlock", "scrollMarginBlockEnd", "scrollMarginBlockStart", "scrollMarginBottom", "scrollMarginInline", "scrollMarginInlineEnd", "scrollMarginInlineStart", "scrollMarginLeft", "scrollMarginRight", "scrollMarginTop", "scrollMarkerGroup", "scrollPadding", "scrollPaddingBlock", "scrollPaddingBlockEnd", "scrollPaddingBlockStart", "scrollPaddingBottom", "scrollPaddingInline", "scrollPaddingInlineEnd", "scrollPaddingInlineStart", "scrollPaddingLeft", "scrollPaddingRight", "scrollPaddingTop", "scrollSnapAlign", "scrollSnapStop", "scrollSnapType", "scrollTargetGroup", "scrollTimeline", "scrollTimelineAxis", "scrollTimelineName", "scrollbarColor", "scrollbarGutter", "scrollbarWidth", "shapeImageThreshold", "shapeMargin", "shapeOutside", "shapeRendering", "size", "sizeAdjust", "speak", "speakAs", "src", "stopColor", "stopOpacity", "stroke", "strokeDasharray", "strokeDashoffset", "strokeLinecap", "strokeLinejoin", "strokeMiterlimit", "strokeOpacity", "strokeWidth", "suffix", "symbols", "syntax", "system", "tabSize", "tableLayout", "textAlign", "textAlignLast", "textAnchor", "textAutospace", "textBox", "textBoxEdge", "textBoxTrim", "textCombineUpright", "textDecoration", "textDecorationColor", "textDecorationLine", "textDecorationSkipInk", "textDecorationStyle", "textDecorationThickness", "textEmphasis", "textEmphasisColor", "textEmphasisPosition", "textEmphasisStyle", "textIndent", "textOrientation", "textOverflow", "textRendering", "textShadow", "textSizeAdjust", "textSpacingTrim", "textTransform", "textUnderlineOffset", "textUnderlinePosition", "textWrap", "textWrapMode", "textWrapStyle", "timelineScope", "top", "touchAction", "transform", "transformBox", "transformOrigin", "transformStyle", "transition", "transitionBehavior", "transitionDelay", "transitionDuration", "transitionProperty", "transitionTimingFunction", "translate", "types", "unicodeBidi", "unicodeRange", "userSelect", "vectorEffect", "verticalAlign", "viewTimeline", "viewTimelineAxis", "viewTimelineInset", "viewTimelineName", "viewTransitionClass", "viewTransitionGroup", "viewTransitionName", "visibility", "webkitAlignContent", "webkitAlignItems", "webkitAlignSelf", "webkitAnimation", "webkitAnimationDelay", "webkitAnimationDirection", "webkitAnimationDuration", "webkitAnimationFillMode", "webkitAnimationIterationCount", "webkitAnimationName", "webkitAnimationPlayState", "webkitAnimationTimingFunction", "webkitAppRegion", "webkitAppearance", "webkitBackfaceVisibility", "webkitBackgroundClip", "webkitBackgroundOrigin", "webkitBackgroundSize", "webkitBorderAfter", "webkitBorderAfterColor", "webkitBorderAfterStyle", "webkitBorderAfterWidth", "webkitBorderBefore", "webkitBorderBeforeColor", "webkitBorderBeforeStyle", "webkitBorderBeforeWidth", "webkitBorderBottomLeftRadius", "webkitBorderBottomRightRadius", "webkitBorderEnd", "webkitBorderEndColor", "webkitBorderEndStyle", "webkitBorderEndWidth", "webkitBorderHorizontalSpacing", "webkitBorderImage", "webkitBorderRadius", "webkitBorderStart", "webkitBorderStartColor", "webkitBorderStartStyle", "webkitBorderStartWidth", "webkitBorderTopLeftRadius", "webkitBorderTopRightRadius", "webkitBorderVerticalSpacing", "webkitBoxAlign", "webkitBoxDecorationBreak", "webkitBoxDirection", "webkitBoxFlex", "webkitBoxOrdinalGroup", "webkitBoxOrient", "webkitBoxPack", "webkitBoxReflect", "webkitBoxShadow", "webkitBoxSizing", "webkitClipPath", "webkitColumnBreakAfter", "webkitColumnBreakBefore", "webkitColumnBreakInside", "webkitColumnCount", "webkitColumnGap", "webkitColumnRule", "webkitColumnRuleColor", "webkitColumnRuleStyle", "webkitColumnRuleWidth", "webkitColumnSpan", "webkitColumnWidth", "webkitColumns", "webkitFilter", "webkitFlex", "webkitFlexBasis", "webkitFlexDirection", "webkitFlexFlow", "webkitFlexGrow", "webkitFlexShrink", "webkitFlexWrap", "webkitFontFeatureSettings", "webkitFontSmoothing", "webkitHyphenateCharacter", "webkitJustifyContent", "webkitLineBreak", "webkitLineClamp", "webkitLocale", "webkitLogicalHeight", "webkitLogicalWidth", "webkitMarginAfter", "webkitMarginBefore", "webkitMarginEnd", "webkitMarginStart", "webkitMask", "webkitMaskBoxImage", "webkitMaskBoxImageOutset", "webkitMaskBoxImageRepeat", "webkitMaskBoxImageSlice", "webkitMaskBoxImageSource", "webkitMaskBoxImageWidth", "webkitMaskClip", "webkitMaskComposite", "webkitMaskImage", "webkitMaskOrigin", "webkitMaskPosition", "webkitMaskPositionX", "webkitMaskPositionY", "webkitMaskRepeat", "webkitMaskSize", "webkitMaxLogicalHeight", "webkitMaxLogicalWidth", "webkitMinLogicalHeight", "webkitMinLogicalWidth", "webkitOpacity", "webkitOrder", "webkitPaddingAfter", "webkitPaddingBefore", "webkitPaddingEnd", "webkitPaddingStart", "webkitPerspective", "webkitPerspectiveOrigin", "webkitPerspectiveOriginX", "webkitPerspectiveOriginY", "webkitPrintColorAdjust", "webkitRtlOrdering", "webkitRubyPosition", "webkitShapeImageThreshold", "webkitShapeMargin", "webkitShapeOutside", "webkitTapHighlightColor", "webkitTextCombine", "webkitTextDecorationsInEffect", "webkitTextEmphasis", "webkitTextEmphasisColor", "webkitTextEmphasisPosition", "webkitTextEmphasisStyle", "webkitTextFillColor", "webkitTextOrientation", "webkitTextSecurity", "webkitTextSizeAdjust", "webkitTextStroke", "webkitTextStrokeColor", "webkitTextStrokeWidth", "webkitTransform", "webkitTransformOrigin", "webkitTransformOriginX", "webkitTransformOriginY", "webkitTransformOriginZ", "webkitTransformStyle", "webkitTransition", "webkitTransitionDelay", "webkitTransitionDuration", "webkitTransitionProperty", "webkitTransitionTimingFunction", "webkitUserDrag", "webkitUserModify", "webkitUserSelect", "webkitWritingMode", "whiteSpace", "whiteSpaceCollapse", "widows", "width", "willChange", "wordBreak", "wordSpacing", "wordWrap", "writingMode", "x", "y", "zIndex", "zoom"] + + allStyleProps.forEach(prop => { + if (prop === "translate") return; + + const type = cssValueType(prop); + + 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.`); + } + if(value === "auto") { + this.style[prop] = value + return this + } + this.style[prop] = value + unit; + return this; + }; + break; + + case "string": + HTMLElement.prototype[prop] = function(value) { + this.style[prop] = value; + return this; + }; + break; + } + }); +} + +extendHTMLElementWithStyleSetters(); + +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.`); + 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.`); + 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.`); + 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.`); + 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.`); + + switch(value) { + case "6xl": + value = "3.75"; unit = "rem" + break; + + case "5xl": + value = "3"; unit = "rem" + break; + + case "4xl": + value = "2.25"; unit = "rem" + break; + + case "3xl": + value = "1.875"; unit = "rem" + break; + + case "2xl": + value = "1.5"; unit = "rem" + break; + + case "xl": + value = "1.25"; unit = "rem" + break; + + case "l": + value = "1.125"; unit = "rem" + break; + + case "s": + value = "0.875"; unit = "rem" + break; + + case "xs": + value = "0.75"; unit = "rem" + break; + + default: + break; + } + this.style.fontSize = value + unit + return this +} + +function checkPositionType(el) { + let computed = window.getComputedStyle(el).position + if(!(computed === "absolute" || computed === "fixed")) { + el.style.position = "absolute" + } +} + +HTMLElement.prototype.x = function(value, unit = "px") { + if (typeof value !== 'number' || isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + checkPositionType(this) + this.style.left = value + unit + return this +} + +HTMLElement.prototype.y = function(value, unit = "px") { + if (typeof value !== 'number' || isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + checkPositionType(this) + this.style.top = value + unit + return this +} + +HTMLElement.prototype.xRight = function(value, unit = "px") { + if (typeof value !== 'number' || isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + checkPositionType(this) + this.style.right = value + unit + return this +} + +HTMLElement.prototype.yBottom = function(value, unit = "px") { + if (typeof value !== 'number' || isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + checkPositionType(this) + this.style.bottom = value + unit + return this +} + +HTMLElement.prototype.backgroundImage = function (...values) { + const formatted = values + .map(v => { + if(v.includes("/") && !v.includes("gradient")) { + v = "url(" + v + ")" + } + return String(v).trim(); + }) + .join(", "); + + this.style.backgroundImage = formatted; + return this; +}; + +HTMLElement.prototype.center = function () { + this.style.transform = "translate(-50%, -50%)" + return this; +}; + +HTMLElement.prototype.centerX = function () { + this.style.transform = "translateX(-50%)" + return this; +}; + +HTMLElement.prototype.centerY = function () { + this.style.transform = "translateY(-50%)" + return this; +}; + +HTMLElement.prototype.alignVertical = function (value) { + const direction = getComputedStyle(this).flexDirection; + if(!direction) { + throw new Error("alignVertical can be only be used on HStacks or VStacks!") + } + + if (direction === "column" || direction === "column-reverse") { + this.style.justifyContent = value; + } else { + this.style.alignItems = value; + } + return this +} + +HTMLElement.prototype.alignHorizontal = function (value) { + const direction = getComputedStyle(this).flexDirection; + if(!direction) { + throw new Error("alignHorizontal can be only be used on HStacks or VStacks!") + } + + if (direction === "column" || direction === "column-reverse") { + this.style.alignItems = value; + } else { + this.style.justifyContent = value; + } + return this +} + + +/* Elements */ + +quill.setChildren = function(el, innerContent) { + if(typeof innerContent === "string") { + el.innerText = innerContent + } else if(typeof innerContent === "function") { + el.render = innerContent + } else { + throw new Error("Children of unknown type") + } +} + +window.a = function a( href, inner=href ) { + if(!href) throw new Error("quill a: missing href argument. Function: a( href, inner=href )") + let link = document.createElement("a") + link.setAttribute('href', href); + quill.setChildren(link, inner) + quill.render(link) + return link +} + +window.img = function img(src, width="", height="") { + let image = document.createElement("img") + + if(!src || !(typeof src==="string")) { + throw new Error("img: missing first argument: src | String") + } else { + image.src = src + } + if(width && typeof width === "string") { + image.style.width = width + } else if(width) { + image.style.width = width + "px" + } + if(height && typeof height === "string") { + image.style.height = height + } else if(height) { + image.style.height = height + "px" + } + quill.render(image) + return image +} + +HTMLImageElement.prototype.backgroundColor = function(value) { + if (this.src.endsWith('.svg') || this.src.startsWith('data:image/svg+xml')) { + fetch(this.src).then(response => response.text()) + .then(svgText => { + const modifiedSvg = svgText.replace(/\bfill="[^"]*"/g, `fill="${value}"`); + const blob = new Blob([modifiedSvg], { type: 'image/svg+xml' }); + this.src = URL.createObjectURL(blob); + }).catch(error => { + console.error('Error updating SVG fill:', error); + }); + } else { + this.style.backgroundColor = value; + } + + return this; // Always returns the element itself + }; + +window.p = function p(innerHTML) { + let el = document.createElement("p") + if(typeof innerText === "function") { + el.render = innerHTML + } else { + el.innerHTML = innerHTML + } + el.style.margin = "0"; + quill.render(el) + return el +} + +window.h1 = function h1(innerText) { + let el = document.createElement("h1") + el.innerText = innerText + quill.render(el) + return el +} + +window.h2 = function h2(innerText) { + let el = document.createElement("h2") + el.innerText = innerText + quill.render(el) + return el +} + +window.h3 = function h3(innerText) { + let el = document.createElement("h3") + el.innerText = innerText + quill.render(el) + return el +} + +window.div = function (innerText) { + let el = document.createElement("div") + el.innerText = innerText ?? "" + quill.render(el) + return el +} + +window.span = function (innerText) { + let el = document.createElement("span") + el.innerText = innerText + quill.render(el) + return el +} + +window.button = function (children = "") { + let el = document.createElement("button") + quill.setChildren(el, children) + quill.render(el) + return el +} + +window.form = function(cb) { + let el = document.createElement("form") + el.render = cb + quill.render(el) + return el +} + +window.input = function(placeholder, width, height) { + let el = document.createElement("input") + el.placeholder = placeholder + el.style.width = width + el.style.height = height + quill.render(el) + return el +} + +window.label = function(text) { + let el = document.createElement("label") + el.innerText = text + quill.render(el) + return el +} + +window.textarea = function(placeholder) { + let el = document.createElement("textarea") + el.placeholder = placeholder + quill.render(el) + return el +} + + +/* STACKS */ + +window.VStack = function (cb = () => {}) { + let styles = ` + display: flex; + flex-direction: column; + ` + let nowRendering = quill.rendering[quill.rendering.length-1] + if (nowRendering.innerHTML.trim() === "" && !quill.isStack(nowRendering)) { + nowRendering.style.cssText += styles + nowRendering.classList.add("VStack") + cb() + return nowRendering + } + + let div = document.createElement("div") + div.classList.add("VStack") + div.style.cssText += styles + div.render = cb + quill.render(div) + return div +} + +window.HStack = function (cb = () => {}) { + let styles = ` + display: flex; + flex-direction: row; + `; + let nowRendering = quill.rendering[quill.rendering.length - 1]; + if (nowRendering.innerHTML.trim() === "" && !quill.isStack(nowRendering)) { + nowRendering.style.cssText += styles; + nowRendering.classList.add("HStack") + cb(); + return nowRendering; + } + + let div = document.createElement("div"); + div.classList.add("HStack"); + div.style.cssText += styles; + div.render = cb; + quill.render(div); + return div; +}; + +window.ZStack = function (cb = () => {}) { + let nowRendering = quill.rendering[quill.rendering.length - 1]; + if (nowRendering.innerHTML.trim() === "" && !quill.isStack(nowRendering)) { + nowRendering.classList.add("ZStack") + cb(); + return nowRendering; + } + + let div = document.createElement("div"); + div.classList.add("ZStack"); + div.render = cb; + quill.render(div); + return div; +}; + +/* SHAPES */ + +window.svgMethods = function(svg) { + svg.pulse = function (duration = 600) { + this.style.transition = `transform ${duration}ms ease-in-out` + this.style.transform = "scale(1.2)" + setTimeout(() => { + this.style.transform = "scale(1)" + }, duration / 2) + return this + } + + // Rotate (e.g. loading spinner) + svg.rotate = function (degrees = 360, duration = 1000) { + this.style.transition = `transform ${duration}ms linear` + this.style.transform = `rotate(${degrees}deg)` + return this + } + + // Change color + svg.fill = function (color) { + this.setAttribute("fill", color) + return this + } + + svg.height = function (height) { + this.setAttribute("height", height) + return this + } + + svg.width = function (width) { + this.setAttribute("width", width) + return this + } + + svg.stroke = function (width, color) { + this.setAttribute("stroke", color) + this.setAttribute("stroke-width", width) + return this + } + + // Toggle visibility + svg.toggle = function () { + this.style.display = this.style.display === "none" ? "" : "none" + return this + } +} + +window.Rectangle = function (width = "40px", height = "40px") { + const svgNS = "http://www.w3.org/2000/svg"; + const svgEl = document.createElementNS(svgNS, "svg"); + const rectEl = document.createElementNS(svgNS, "rect"); + + // SVG size + svgEl.setAttribute("width", width); + svgEl.setAttribute("height", height); + svgEl.setAttribute("viewBox", "0 0 100 100"); + svgEl.setAttribute("preserveAspectRatio", "xMidYMid meet"); + + // Rectangle: full size, slightly rounded corners + rectEl.setAttribute("x", "15"); // 15% margin from edges + rectEl.setAttribute("y", "15"); + rectEl.setAttribute("width", "70"); // 70% of viewBox + rectEl.setAttribute("height", "70"); + // rectEl.setAttribute("rx", "8"); // rounded corners (optional) + // rectEl.setAttribute("ry", "8"); + + svgEl.appendChild(rectEl); + svgMethods(svgEl); // assuming you have this + quill.render(svgEl); + return svgEl; +} + +window.Triangle = function (width = "40px", height = "40px") { + const svgNS = "http://www.w3.org/2000/svg" + const svgEl = document.createElementNS(svgNS, "svg") + const pathEl = document.createElementNS(svgNS, "path") + + // SVG size + svgEl.setAttribute("width", width) + svgEl.setAttribute("height", height) + svgEl.setAttribute("viewBox", "0 0 100 100") + svgEl.setAttribute("preserveAspectRatio", "xMidYMid meet") + // Right-pointing triangle (Play icon) + pathEl.setAttribute("d", "M 25 15 L 90 50 L 25 85 Z") // ◄ adjust points if needed + + svgEl.appendChild(pathEl) + svgMethods(svgEl) + quill.render(svgEl) + return svgEl +} + + +/* EVENTS */ + +HTMLElement.prototype.onAppear = function(func) { + func.call(this); + return this; +}; + +HTMLElement.prototype.onClick = function(func) { + const onMouseDown = () => func.call(this, false); + const onMouseUp = () => func.call(this, true); + this._storeListener("mousedown", onMouseDown); + this._storeListener("mouseup", onMouseUp); + return this; +}; + +HTMLElement.prototype.onMouseDown = function(func) { + this._storeListener("mousedown", func); + return this; +}; + +HTMLElement.prototype.onMouseUp = function(func) { + this._storeListener("mouseup", func); + return this; +}; + +HTMLElement.prototype.onRightClick = function(func) { + this._storeListener("contextmenu", func); + return this; +}; + +HTMLElement.prototype.onHover = function(cb) { + const onEnter = (e) => cb.call(this, true, e); + const onLeave = (e) => cb.call(this, false, e); + this._storeListener("mouseover", onEnter); + this._storeListener("mouseleave", onLeave); + return this; +}; + +HTMLElement.prototype.onFocus = function(cb) { + if (!this.matches('input, textarea, select, button')) { + throw new Error("Can't put focus event on non-form element!"); + } + const onFocus = () => cb.call(this, true); + const onBlur = () => cb.call(this, false); + this._storeListener("focus", onFocus); + this._storeListener("blur", onBlur); + return this; +}; + +HTMLElement.prototype.onKeyDown = function(cb) { + this._storeListener("keydown", cb); + return this; +}; + +HTMLElement.prototype.onInput = function(cb) { + if(!this.matches('input, textarea, [contenteditable=""], [contenteditable="true"]')) + throw new Error("Can't put input event on non-input element!") + this._storeListener("input", cb); + return this; +}; + +HTMLElement.prototype.onChange = function(cb) { + if(!this.matches('input, textarea, [contenteditable=""], [contenteditable="true"]')) + throw new Error("Can't put input event on non-input element!") + this._storeListener("change", cb); + return this; +}; + + +HTMLElement.prototype.onSubmit = function(cb) { + if(!this.matches('form')) + throw new Error("Can't put form event on non-form element!") + this._storeListener("submit", cb); + return this; +}; + +HTMLElement.prototype.onTouch = function(cb) { + const onStart = () => cb.call(this, true); + const onEnd = () => cb.call(this, false); + const onCancel = () => cb.call(this, null); + this._storeListener("touchstart", onStart); + this._storeListener("touchend", onEnd); + this._storeListener("touchcancel", onCancel); + return this; +}; + +HTMLElement.prototype.onTap = function(cb) { + this._storeListener("touchend", cb); + return this; +}; + +/* 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. +*/ +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")) + } +}) + +/* +Same principle applies +*/ +queryListeners = [] +HTMLElement.prototype.onQueryChanged = function(cb) { + this._storeListener("query-changed", cb); + + let found = false + for(entry of queryListeners) { + if(entry.cb.toString() === cb.toString() && + this.nodeName === entry.el.nodeName) { + found = true + break; + } + } + if(found === false) { + queryListeners.push({el: this, cb: cb}) + } + + return this; +}; +window.addEventListener("query-changed", () => { + for(entry of queryListeners) { + entry.el.dispatchEvent(new CustomEvent("query-changed")) + } +}) + +HTMLElement.prototype.onEvent = function(name, cb) { + window._storeListener(window, name, cb); + return this; +}; + +HTMLElement.prototype._storeListener = function(type, handler, options) { + window._storeListener(this, type, handler, options) +} + +window.__listeners = [] + +function _storeListener(target, type, handler, options) { + if (!target.__listeners) target.__listeners = []; + + const optionsString = JSON.stringify(options); + + const index = target.__listeners.findIndex(listener => + listener.type === type && + listener.handler.toString() === handler.toString() && + JSON.stringify(listener.options) === optionsString + ); + + if (index === -1) { // Listener is new + target.addEventListener(type, handler, options); + target.__listeners.push({ type, handler, options }); + } else { // Listener is a duplicate, can be replaced + const old = target.__listeners[index]; + target.removeEventListener(old.type, old.handler, old.options); + + // Replace with the new one + target.addEventListener(type, handler, options); + target.__listeners[index] = { type, handler, options }; + } +} + +HTMLElement.prototype.removeAllListeners = function() { + if (!this.__listeners) return; + for (const { type, handler, options } of this.__listeners) { + this.removeEventListener(type, handler, options); + } + this.__listeners = []; + return this; +}; + +/* ATTRIBUTES */ + +HTMLElement.prototype.attr = function(attributes) { + if ( + typeof attributes !== "object" || + attributes === null || + Array.isArray(attributes) + ) { + throw new TypeError("attr() expects an object with key-value pairs"); + } + + for (const [key, value] of Object.entries(attributes)) { + this.setAttribute(key, value); + } + return this; +}; diff --git a/Sample/ui/_/code/shared.css b/Sample/ui/_/code/shared.css new file mode 100644 index 0000000..e69de29 diff --git a/Sample/ui/components/Home.js b/Sample/ui/components/Home.js new file mode 100644 index 0000000..848b517 --- /dev/null +++ b/Sample/ui/components/Home.js @@ -0,0 +1,7 @@ +class Home extends Shadow { + render() { + + } +} + +register(Home) \ No newline at end of file diff --git a/Sample/ui/index.html b/Sample/ui/index.html new file mode 100644 index 0000000..4b63e31 --- /dev/null +++ b/Sample/ui/index.html @@ -0,0 +1,13 @@ + + + + Quill Starter + + + + + + + + + \ No newline at end of file diff --git a/Sample/ui/index.js b/Sample/ui/index.js new file mode 100644 index 0000000..b14af88 --- /dev/null +++ b/Sample/ui/index.js @@ -0,0 +1,2 @@ +import "./components/Home.js" +Home() \ No newline at end of file diff --git a/VSCode/README.md b/VSCode/README.md index 787d9bb..d0b2fe4 100644 --- a/VSCode/README.md +++ b/VSCode/README.md @@ -5,7 +5,7 @@ Forked from this repository: ## 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. +- ```*shadow```: Type in a JS file and select the suggestion to create JS Custom Element boilerplate. ## Functions: Clone this repository into the top level of the project you are working on, so the HTML can find the "quill.js" functions. diff --git a/VSCode/deploy-howto.md b/VSCode/deploy-howto.md new file mode 100644 index 0000000..2320f8a --- /dev/null +++ b/VSCode/deploy-howto.md @@ -0,0 +1,15 @@ +# To Deploy Extension + +```vsce package``` + +```vsce publish``` + +# To Get PAT + +Login to Azure +Search "Devops" +Login to devops (samuel@sun.museum) +Click "User Settings" in the top right > "Personal Access Tokens" +Create one which has "Allow all Organizations" and "Marketplace" > "Manage" selected +Put it in the .env file here +set it in the terminal like `export VSCE_PAT=""` \ No newline at end of file diff --git a/VSCode/package.json b/VSCode/package.json index 0564d2d..d2f5525 100644 --- a/VSCode/package.json +++ b/VSCode/package.json @@ -2,7 +2,7 @@ "name": "quill", "displayName": "Quill", "description": "HTML/CSS Syntax highlighting, best used with the Quill framework", - "version": "1.0.4", + "version": "1.0.6", "publisher": "capturedsun", "icon": "docs/Quill.png", "engines": { diff --git a/VSCode/quill-1.0.5.vsix b/VSCode/quill-1.0.5.vsix new file mode 100644 index 0000000..b2ce258 Binary files /dev/null and b/VSCode/quill-1.0.5.vsix differ diff --git a/VSCode/snippets-html.json b/VSCode/snippets-html.json index e1e54d1..47408a4 100644 --- a/VSCode/snippets-html.json +++ b/VSCode/snippets-html.json @@ -2,46 +2,17 @@ "*page": { "prefix": "*html", "body": [ - "", + "", "", " ", " Quill", - " ", - " ", - " ", - " ", + " ", + " ", + " ", + " ", + " ", " ", - " ", - "", + " ", " ", "" ], diff --git a/VSCode/snippets-js.json b/VSCode/snippets-js.json index 798cc06..ef107f7 100644 --- a/VSCode/snippets-js.json +++ b/VSCode/snippets-js.json @@ -2,7 +2,7 @@ "html": { "prefix": "html", "body": [ - "html(`$1`);" + "html(`$`);" ], "description": "Create a DOM node" }, @@ -15,35 +15,17 @@ "description": "Use the DOM collection of functions" }, - "*element": { - "prefix": "*element", + "*shadow": { + "prefix": "*shadow", "body": [ - "class Index extends HTMLElement {", - "", - " css = /*css*/ `", - " index-el {", - " ", - " }", - " `", - "", - " html = /*html*/ `", - " `", - "", - " constructor() {", - " super();", - " addStyle(this.css);", - " this.innerHTML = this.html;", - " }", - "", - " connectedCallback() {", + "class Index extends Shadow {", + " render() {", " ", " }", - "", "}", - "", - "customElements.define('index-el', Index);", - "export default Index;" + " ", + "register(Index)" ], - "description": "Custom Element Template" + "description": "Custom Shadow Template" } } \ No newline at end of file diff --git a/about.md b/about.md deleted file mode 100644 index a5b7d1c..0000000 --- a/about.md +++ /dev/null @@ -1,26 +0,0 @@ -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 \ No newline at end of file diff --git a/app.js b/app.js deleted file mode 100644 index 2735a2e..0000000 --- a/app.js +++ /dev/null @@ -1,13 +0,0 @@ -/* -Captured Sun -*/ - -"use strict"; - -class Home extends Page { - render = () => { - ("hello world") - } -} - -export default Home \ No newline at end of file diff --git a/deploy-howto.md b/deploy-howto.md deleted file mode 100644 index a817a79..0000000 --- a/deploy-howto.md +++ /dev/null @@ -1,5 +0,0 @@ -# To Deploy Extension - -```vsce package``` - -```vsce publish``` \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index be47f22..0000000 --- a/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - Quill - - - - - - - - - - diff --git a/index.js b/index.js index 6320184..fcebb29 100644 --- a/index.js +++ b/index.js @@ -1,62 +1,59 @@ -/* NAVIGATION */ +/* + Sam Russell + Captured Sun + 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 + 11.23.25 - Added onSubmit() event for form submission, added marginHorizontal() and marginVertical() + 11.20.25 - Added "pct" style unit, added alignVertical and alignHorizontal for flex boxes + 11.19.25 - Allowing for "auto" values in otherwise numeric styles, adding vmin and vmax units + 11.17.25.3 - Adding styles() and fixing dynamic function from earlier + 11.17.25.2 - Fixing onNavigate() and onAppear() + 11.17.25 - Added dynamic function to have units in style func parameters. + 11.14.25 - Added onTouch, onTap. Changed style setters to work with Safari. Added center() funcs. + 11.13.25 - changed onFocus() to be a boolean event, added onInput() + 11.9.25 - changed p(innerText) to p(innerHTML), adjusted onNavigate to work for multiple elements and with correct "this" scope + 11.7.25 - changed registerShadow() to register(), changed onClick() to be like onHover() + 11.6.25 - adding default value for "button()" "children" parameter + 10.29.25 - adding "gap()" and "label()" functions +*/ +/* $ NAVIGATION */ let oldPushState = history.pushState; history.pushState = function pushState() { let ret = oldPushState.apply(this, arguments); window.dispatchEvent(new Event('pushstate')); - window.dispatchEvent(new Event('locationchange')); + window.dispatchEvent(new Event('navigate')); return ret; }; window.addEventListener('popstate', () => { - window.dispatchEvent(new Event('locationchange')); + window.dispatchEvent(new Event('navigate')); }); - -window.addEventListener('locationchange', locationChange); -let urlBeforeChange = window.location.href; + +window.setQuery = function(key, value) { + const url = new URL(window.location.href); + const params = url.searchParams; + + if (value === null || value === undefined) { + params.delete(key); + } else { + params.set(key, value); + } + + const newUrl = url.toString(); + history.replaceState(null, "", newUrl); + window.dispatchEvent(new Event('query-changed')); + + return newUrl; +}; window.navigateTo = function(url) { + window.dispatchEvent(new Event('navigate')); window.history.pushState({}, '', url); } -Object.defineProperty(window, 'routes', { - configurable: true, - enumerable: true, - set: function(newValue) { - Object.defineProperty(window, 'routes', { - value: newValue, - writable: false, - configurable: false, - enumerable: true - }); - - locationChange(); - }, - get: function() { - return window.routes; - } -}); - -function locationChange() { - let URL = window.location.pathname.split("/").filter(d => (d !== 'Web') && (!d.includes('.html'))).join("/") - if(URL === "") URL = "/" - - console.log("Location change: ", URL) - - if(!window.routes[URL]) { - console.error("Quill: no URL for this route: ", URL) - return - } - - let page = new window.routes[URL]() - window.rendering.push(page) - page.render() - window.rendering.pop(page) - - urlBeforeChange = window.location.href; -} - -/* $() */ +/* $ SELECTOR */ HTMLElement.prototype.$ = function(selector) { return window.$(selector, this) @@ -65,11 +62,11 @@ DocumentFragment.prototype.$ = function(selector) { return window.$(selector, this) } window.$ = function(selector, el = document) { - if(selector[0] === "#" || selector.includes("[name")) { - return el.querySelector(selector) - } else { - return el.querySelectorAll(selector); - } + return el.querySelector(selector) +} + +window.$$ = function(selector, el = document) { + return Array.from(el.querySelectorAll(selector)) } /* CONSOLE */ @@ -83,556 +80,484 @@ console.green = function(message) { } -/* HTML */ +/* GET CSS VARIABLES FOR DARK OR LIGHT MODE */ +window.darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; +document.documentElement.classList.add(darkMode ? 'dark' : 'light'); -window.Group = function Group(func) { - let container = document.createElement('div'); - container.render = func - Registry.render(container) - return container +window.getColor = function(name) { + const rootStyles = getComputedStyle(document.documentElement); + const color = rootStyles.getPropertyValue(`--${name}`).trim(); + if(!color) { + throw new Error("Color not found") + } + return color } -window.html = function html(htmlString) { - let container = document.createElement('div'); - container.innerHTML = htmlString; +/* MOBILE */ - if (container.children.length === 1) { - Registry.render(container.children[0]) - return container.children[0]; - } - - let fragment = document.createDocumentFragment(); - while (container.firstChild) { - fragment.appendChild(container.firstChild); - } - - Registry.render(fragment) - return fragment; -}; - -/* COMPATIBILITY */ - -function detectMobile() { - const mobileDeviceRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; - return mobileDeviceRegex.test(navigator.userAgent); +window.isMobile = function isMobile() { + return /Android|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i.test(navigator.userAgent); } + +window.css = function css(cssString) { + let container = document.querySelector("style#pageStyle"); + if(!container) { + container = document.createElement('style'); + container.id = "pageStyle"; + document.head.appendChild(container); + } -function getSafariVersion() { - const userAgent = navigator.userAgent; - const isSafari = userAgent.includes("Safari") && !userAgent.includes("Chrome"); - if (isSafari) { - const safariVersionMatch = userAgent.match(/Version\/(\d+\.\d+)/); - const safariVersion = safariVersionMatch ? parseFloat(safariVersionMatch[1]) : null; - return safariVersion; + let primarySelector = cssString.substring(0, cssString.indexOf("{")).trim(); + primarySelector = primarySelector.replace(/\*/g, "all"); + primarySelector = primarySelector.replace(/#/g, "id-"); + primarySelector = primarySelector.replace(/,/g, ""); + let stylesheet = container.querySelector(`:scope > style[id='${primarySelector}']`) + if(!stylesheet) { + stylesheet = document.createElement('style'); + stylesheet.id = primarySelector; + stylesheet.appendChild(document.createTextNode(cssString)); + container.appendChild(stylesheet); + } else { + stylesheet.innerText = cssString } } -/* REGISTER */ - -class ObservedObject { - - constructor() { - this._observers = {} - } - - static create(obj = {}) { - let instance = new this() - - Object.keys(instance).forEach((key) => { - if(key[0] === "$") { - key = key.slice(1) - instance._observers[key] = new Map() - const backingFieldName = `_${key}`; - instance[backingFieldName] = instance["$" + key] - Object.defineProperty(instance, key, { - set: function(newValue) { - if(Array.isArray(newValue) && newValue.parent === undefined) { - instance[backingFieldName] = new ObservedObject.ObservedArray(newValue, this, key) - } else { - instance[backingFieldName] = newValue; - } - for (let [observer, properties] of instance._observers[key]) { - for (let property of properties) { - if(property === "children") { - Registry.rerender(observer) - } else { - if(Array.isArray(property)) { - observer[property[0]][property[1]] = newValue; - } else { - observer[property] = newValue; - } - } - } - } - }, - get: function() { - if(Registry.lastState) { - Registry.lastState = [...Registry.lastState, key, instance[backingFieldName]] - } - return instance[backingFieldName]; - }, - enumerable: true, - configurable: true - }); - - delete instance["$" + key] - } - - if(obj[key]) { - instance[key] = obj[key] - } else { - if(instance[key] === undefined || instance[key] === null) { - throw new Error(`ObservedObject: Non-default value "${key}" must be initialized!`) - } - } - }) - - return instance - } - - static ObservedArray = class ObservedArray extends Array { - parent; - name; - - constructor(arr = [], parent, name) { - super(); - this.parent = parent - this.name = name - this.push(...arr); - } - - triggerParent() { - this.parent[this.name] = this - } - - push(...args) { - const result = super.push(...args); - this.triggerParent() - return result; - } - - pop() { - const result = super.pop(); - this.triggerParent() - return result; - } - - shift() { - const result = super.shift(); - this.triggerParent() - return result; - } - - unshift(...args) { - const result = super.unshift(...args); - this.triggerParent() - return result; - } - - splice(start, deleteCount, ...items) { - const removedItems = super.splice(start, deleteCount, ...items); - if (items.length > 0) { - console.log(`Inserted ${items.length} items:`, items); - } - if (removedItems.length > 0) { - console.log(`Removed ${removedItems.length} items:`, removedItems); - } - this.triggerParent() - return removedItems; - } - } +window.html = function html(elementString) { + let parser = new DOMParser(); + let doc = parser.parseFromString(elementString, 'text/html'); + return doc.body.firstChild; } -window.Page = class Page { - constructor() { - return new Proxy(this, { - get(target, prop) { - if (prop in target) { - return target[prop]; - } - - if (prop in document.body) { - if (typeof document.body[prop] === 'function') { - return document.body[prop].bind(document.body); - } - return document.body[prop]; - } - - return undefined; - } - }); +window.util = {} +window.util.observeClassChange = (el, callback) => { + if (!el || !(el instanceof Element)) { + throw new Error("observeClassChange requires a valid DOM element."); } -}; -window.Shadow = class Shadow extends HTMLElement { + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type === "attributes" && mutation.attributeName === "class") { + callback(el.classList); + } + } + }); + + observer.observe(el, { + attributes: true, + attributeFilter: ["class"] + }); + + return observer; // Optional: return it so you can disconnect later +} + +/* PAGE SETUP */ + +Object.defineProperty(Array.prototype, 'last', { + get() { + return this[this.length - 1]; + }, + enumerable: false, +}); + +/* QUILL */ + +window.quill = { + rendering: [], + + render: (el) => { + if(el instanceof Shadow) { + let parent = quill.rendering[quill.rendering.length-1] + if(!parent) { + parent = document.body + } + parent.appendChild(el) + } else { + if(!el.render) {el.render = () => {}} + let parent = quill.rendering[quill.rendering.length-1] + if(!parent) throw new Error("Quill: no parent for element") + parent.appendChild(el) + } + + quill.rendering.push(el) + el.render() + quill.rendering.pop(el) + }, + + rerender: (el) => { + Array.from(el.attributes).forEach(attr => el.removeAttribute(attr.name)); + el.innerHTML = "" + el.removeAllListeners() + + quill.rendering.push(el) + el.render() + 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") + }, +} + +window.Shadow = class extends HTMLElement { constructor() { super() } } - -window.Registry = class Registry { - - static style = document.querySelector("style#shadows"); - - /* - State Reactivity [stateName].get(elem).push(attribute) - _observers = { - name: Map( -

: [innerText, background] - ), - } - - OO Reactivity: [objectName][objectField].get(elem).push(attribute) - $$form: extends ObservedObject { - _observers = { - canvasPosition: Map( -

: [position] - ), - path: Map( -

: [innerText] - ) - } - } - */ - - static initReactivity(elem, attr, value) { - if(!Registry.lastState || Registry.lastState.length === 0) { - return - } - let parent = window.rendering.last() - - if(Registry.lastState.length === 3) { - let [objName, objField, fieldValue] = Registry.lastState - if(!objName) return; - - let valueCheck = parent[objName][objField] - if(valueCheck !== undefined && valueCheck === value) { - if(!parent[objName]._observers[objField].get(elem)) { - parent[objName]._observers[objField].set(elem, []) - } - let properties = parent[objName]._observers[objField].get(elem) - if(!properties.includes(attr)) { - properties.push(attr) - } - } - } else { - let [stateUsed, stateValue] = Registry.lastState - if(!stateUsed) return; - - if(parent[stateUsed] === value) { - if(!parent._observers[stateUsed].get(elem)) { - parent._observers[stateUsed].set(elem, []) - } - 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 = [] +window.register = (el, tagname) => { + if (typeof el.prototype.render !== 'function') { + throw new Error("Element must have a render: " + el.prototype.constructor.name) + } + if(!tagname) { + tagname = el.prototype.constructor.name.toLowerCase() + "-" + } + customElements.define(tagname, el) + if(el.css) { + css(el.css) } - static render = (el, parent) => { - let renderParent = window.rendering[window.rendering.length-1] - if(renderParent) { - renderParent.appendChild(el) - if(el._onAppear) { - el._onAppear() - } - } - window.rendering.push(el) - el.render() - window.rendering.pop(el) + window[el.prototype.constructor.name] = function (...params) { + let instance = new el(...params) + quill.render(instance) + return instance } - - static rerender = (el) => { - if(el.parentElement) { - window.rendering.push(el.parentElement) - } - window.rendering.push(el) - el.innerHTML = "" - el.render() - window.rendering.pop() - window.rendering.pop() - } - - static testInitialized(el) { - let fields = Object.keys(el).filter(key => - typeof el[key] !== 'function' && key !== "_observers" - ) - - for(let field of fields) { - if(el[field] === undefined) { - throw new Error(`Quill: field "${field}" must be initialized`) - } - } - } - - static construct = (elem) => { // After default params are set, but before body of constructor - const params = window.Registry.currentParams - const allNames = Object.keys(elem).filter(key => typeof elem[key] !== 'function') - const stateNames = allNames.filter(field => /^[$][^$]/.test(field)).map(str => str.substring(1)); - 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) { - elem._observers = {} - - // State -> Attributes: set each state value as getter and setter - stateNames.forEach(name => { - const backingFieldName = `_${name}`; - elem._observers[name] = new Map() - - Object.defineProperty(elem, name, { - set: function(newValue) { - elem[backingFieldName] = newValue; - elem.setAttribute(name, typeof newValue === "object" ? "{..}" : newValue); - for (let [element, properties] of elem._observers[name]) { - for (let property of properties) { - if(Array.isArray(property)) { - element[property[0]][property[1]] = newValue; - } else { - element[property] = newValue; - } - } - } - }, - get: function() { - let rendering = window.rendering[window.rendering.length - 1] - let value = elem[backingFieldName] - if(!rendering) return value - - Registry.lastState = [name, value] - return value; - }, - enumerable: true, - configurable: true - }); - - if(elem["$" + name] !== undefined) { - elem[name] = elem["$" + name] - } - - delete elem["$" + name] - }); - } - - function makeObservedObjects(elem, objectNames, params) { - objectNames.forEach(name => { - const backingFieldName = `_${name}`; - - Object.defineProperty(elem, name, { - set: function(newValue) { - elem[backingFieldName] = newValue; - }, - get: function() { - Registry.lastState = [name] - return elem[backingFieldName]; - }, - enumerable: true, - configurable: true - }); - - if(elem["$$" + name] !== undefined) { - elem[name] = elem["$$" + name] - } - - delete elem["$$" + name] - }); - } - - makeState(elem, stateNames, params) - makeObservedObjects(elem, observedObjectNames, params) - - for(let name of allNames) { - if(name.replace(/^\$+/, '') in elem.style) { - throw new Error(`Quill: Property name "${name.replace(/^\$+/, '')}" is not valid`) - } - } - - let i = -1 - for (let param of params) { - i++ - - if(i >= allNames.length) { - throw new Error(`${elem.constructor.name}: ${params.length} arguments passed in where ${allNames.length} expected!`) - } - - let bareName = allNames[i].replace(/^(\$\$|\$)/, ''); - - if(elem[bareName] === undefined) { - if(allNames[i].startsWith("$$") && !(param instanceof ObservedObject)) { - throw new Error(`Field ${allNames[i]} must be an Observed Object!`) - } - elem[bareName] = param - } - } - } - - static register = (el, tagname) => { - let stateVariables = this.parseClassFields(el).filter(field => /^[$][^$]/.test(field)).map(str => str.substring(1)); - el = this.parseConstructor(el) - - // Observe attributes - Object.defineProperty(el, 'observedAttributes', { - get: function() { - return stateVariables; - } - }); - - // Attributes -> State - Object.defineProperty(el.prototype, 'attributeChangedCallback', { - value: function(name, oldValue, newValue) { - const fieldName = `${name}`; - let blacklistedValues = ["[object Object]", "{..}", this[fieldName]] - if (stateVariables.includes(fieldName) && !blacklistedValues.includes(newValue)) { - this[fieldName] = newValue; - } - }, - writable: true, - configurable: true - }); - - customElements.define(tagname, el) - this.addStyle(tagname) - - // Actual Constructor - window[el.prototype.constructor.name] = function (...params) { - window.Registry.currentParams = params - let elIncarnate = new el(...params) - Registry.render(elIncarnate) - return elIncarnate - } - } - - static getStyle = function(el) { - let stylesheet = this.styles.querySelector(`:scope > style[id='${el.tagName.toLowerCase()}']`) - return stylesheet?.sheet.cssRules[0]?.cssText - } - - static removeStyle = function(el) { - let stylesheet = document.getElementById(el.tagName.toLowerCase())?.sheet - if(stylesheet.cssRules.length > 0) { - stylesheet.deleteRule(0) - } - } - - static addStyle = function(tag) { - let stylesheet = this.styles.querySelector(`:scope > style[id='${tag}']`) - tag = tag.replace(/\*/g, "all"); - tag = tag.replace(/#/g, "id-"); - tag = tag.replace(/,/g, ""); - if(!stylesheet) { - stylesheet = document.createElement('style'); - stylesheet.id = tag; - this.styles.appendChild(stylesheet); - } - } - - static updateStyle = function(tag, string) { - let sheet = this.styles.querySelector(`:scope > style[id='${tag}']`)?.sheet - if(!sheet) console.error('Quill: could not find stylesheet to update!') - - for (let i = 0; i < sheet.cssRules.length; i++) { - let rule = sheet.cssRules[i]; - - if (rule.selectorText === tag || rule.selectorText === `${tag.toLowerCase()}`) { - sheet.deleteRule(i); - break; - } - } - - sheet.insertRule(`${tag} { ${string} }`, sheet.cssRules.length); - } -} - -if(!Registry.styles) { - Registry.styles = document.createElement('style'); - Registry.styles.id = "shadows"; - document.head.appendChild(Registry.styles); } HTMLElement.prototype.rerender = function() { - Registry.rerender(this) + quill.rerender(this) } -/* DEFAULT WRAPPERS */ +/* Styling */ -let allStyleProps = ["accentColor", "additiveSymbols", "alignContent", "alignItems", "alignSelf", "alignmentBaseline", "all", "animation", "animationComposition", "animationDelay", "animationDirection", "animationDuration", "animationFillMode", "animationIterationCount", "animationName", "animationPlayState", "animationRange", "animationRangeEnd", "animationRangeStart", "animationTimeline", "animationTimingFunction", "appRegion", "appearance", "ascentOverride", "aspectRatio", "backdropFilter", "backfaceVisibility", "background", "backgroundAttachment", "backgroundBlendMode", "backgroundClip", "backgroundColor", "backgroundImage", "backgroundOrigin", "backgroundPosition", "backgroundPositionX", "backgroundPositionY", "backgroundRepeat", "backgroundSize", "basePalette", "baselineShift", "baselineSource", "blockSize", "border", "borderBlock", "borderBlockColor", "borderBlockEnd", "borderBlockEndColor", "borderBlockEndStyle", "borderBlockEndWidth", "borderBlockStart", "borderBlockStartColor", "borderBlockStartStyle", "borderBlockStartWidth", "borderBlockStyle", "borderBlockWidth", "borderBottom", "borderBottomColor", "borderBottomLeftRadius", "borderBottomRightRadius", "borderBottomStyle", "borderBottomWidth", "borderCollapse", "borderColor", "borderEndEndRadius", "borderEndStartRadius", "borderImage", "borderImageOutset", "borderImageRepeat", "borderImageSlice", "borderImageSource", "borderImageWidth", "borderInline", "borderInlineColor", "borderInlineEnd", "borderInlineEndColor", "borderInlineEndStyle", "borderInlineEndWidth", "borderInlineStart", "borderInlineStartColor", "borderInlineStartStyle", "borderInlineStartWidth", "borderInlineStyle", "borderInlineWidth", "borderLeft", "borderLeftColor", "borderLeftStyle", "borderLeftWidth", "borderRadius", "borderRight", "borderRightColor", "borderRightStyle", "borderRightWidth", "borderSpacing", "borderStartEndRadius", "borderStartStartRadius", "borderStyle", "borderTop", "borderTopColor", "borderTopLeftRadius", "borderTopRightRadius", "borderTopStyle", "borderTopWidth", "borderWidth", "bottom", "boxShadow", "boxSizing", "breakAfter", "breakBefore", "breakInside", "bufferedRendering", "captionSide", "caretColor", "clear", "clip", "clipPath", "clipRule", "color", "colorInterpolation", "colorInterpolationFilters", "colorRendering", "colorScheme", "columnCount", "columnFill", "columnGap", "columnRule", "columnRuleColor", "columnRuleStyle", "columnRuleWidth", "columnSpan", "columnWidth", "columns", "contain", "containIntrinsicBlockSize", "containIntrinsicHeight", "containIntrinsicInlineSize", "containIntrinsicSize", "containIntrinsicWidth", "container", "containerName", "containerType", "content", "contentVisibility", "counterIncrement", "counterReset", "counterSet", "cursor", "cx", "cy", "d", "descentOverride", "direction", "display", "dominantBaseline", "emptyCells", "fallback", "fieldSizing", "fill", "fillOpacity", "fillRule", "filter", "flex", "flexBasis", "flexDirection", "flexFlow", "flexGrow", "flexShrink", "flexWrap", "float", "floodColor", "floodOpacity", "font", "fontDisplay", "fontFamily", "fontFeatureSettings", "fontKerning", "fontOpticalSizing", "fontPalette", "fontSize", "fontStretch", "fontStyle", "fontSynthesis", "fontSynthesisSmallCaps", "fontSynthesisStyle", "fontSynthesisWeight", "fontVariant", "fontVariantAlternates", "fontVariantCaps", "fontVariantEastAsian", "fontVariantLigatures", "fontVariantNumeric", "fontVariantPosition", "fontVariationSettings", "fontWeight", "forcedColorAdjust", "gap", "grid", "gridArea", "gridAutoColumns", "gridAutoFlow", "gridAutoRows", "gridColumn", "gridColumnEnd", "gridColumnGap", "gridColumnStart", "gridGap", "gridRow", "gridRowEnd", "gridRowGap", "gridRowStart", "gridTemplate", "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows", "height", "hyphenateCharacter", "hyphenateLimitChars", "hyphens", "imageOrientation", "imageRendering", "inherits", "initialLetter", "initialValue", "inlineSize", "inset", "insetBlock", "insetBlockEnd", "insetBlockStart", "insetInline", "insetInlineEnd", "insetInlineStart", "isolation", "justifyContent", "justifyItems", "justifySelf", "left", "letterSpacing", "lightingColor", "lineBreak", "lineGapOverride", "lineHeight", "listStyle", "listStyleImage", "listStylePosition", "listStyleType", "margin", "marginBlock", "marginBlockEnd", "marginBlockStart", "marginBottom", "marginInline", "marginInlineEnd", "marginInlineStart", "marginLeft", "marginRight", "marginTop", "marker", "markerEnd", "markerMid", "markerStart", "mask", "maskClip", "maskComposite", "maskImage", "maskMode", "maskOrigin", "maskPosition", "maskRepeat", "maskSize", "maskType", "mathDepth", "mathShift", "mathStyle", "maxBlockSize", "maxHeight", "maxInlineSize", "maxWidth", "minBlockSize", "minHeight", "minInlineSize", "minWidth", "mixBlendMode", "negative", "objectFit", "objectPosition", "objectViewBox", "offset", "offsetAnchor", "offsetDistance", "offsetPath", "offsetPosition", "offsetRotate", "opacity", "order", "orphans", "outline", "outlineColor", "outlineOffset", "outlineStyle", "outlineWidth", "overflow", "overflowAnchor", "overflowClipMargin", "overflowWrap", "overflowX", "overflowY", "overlay", "overrideColors", "overscrollBehavior", "overscrollBehaviorBlock", "overscrollBehaviorInline", "overscrollBehaviorX", "overscrollBehaviorY", "pad", "padding", "paddingBlock", "paddingBlockEnd", "paddingBlockStart", "paddingBottom", "paddingInline", "paddingInlineEnd", "paddingInlineStart", "paddingLeft", "paddingRight", "paddingTop", "page", "pageBreakAfter", "pageBreakBefore", "pageBreakInside", "pageOrientation", "paintOrder", "perspective", "perspectiveOrigin", "placeContent", "placeItems", "placeSelf", "pointerEvents", "position", "prefix", "quotes", "r", "range", "resize", "right", "rotate", "rowGap", "rubyPosition", "rx", "ry", "scale", "scrollBehavior", "scrollMargin", "scrollMarginBlock", "scrollMarginBlockEnd", "scrollMarginBlockStart", "scrollMarginBottom", "scrollMarginInline", "scrollMarginInlineEnd", "scrollMarginInlineStart", "scrollMarginLeft", "scrollMarginRight", "scrollMarginTop", "scrollPadding", "scrollPaddingBlock", "scrollPaddingBlockEnd", "scrollPaddingBlockStart", "scrollPaddingBottom", "scrollPaddingInline", "scrollPaddingInlineEnd", "scrollPaddingInlineStart", "scrollPaddingLeft", "scrollPaddingRight", "scrollPaddingTop", "scrollSnapAlign", "scrollSnapStop", "scrollSnapType", "scrollTimeline", "scrollTimelineAxis", "scrollTimelineName", "scrollbarColor", "scrollbarGutter", "scrollbarWidth", "shapeImageThreshold", "shapeMargin", "shapeOutside", "shapeRendering", "size", "sizeAdjust", "speak", "speakAs", "src", "stopColor", "stopOpacity", "stroke", "strokeDasharray", "strokeDashoffset", "strokeLinecap", "strokeLinejoin", "strokeMiterlimit", "strokeOpacity", "strokeWidth", "suffix", "symbols", "syntax", "system", "tabSize", "tableLayout", "textAlign", "textAlignLast", "textAnchor", "textCombineUpright", "textDecoration", "textDecorationColor", "textDecorationLine", "textDecorationSkipInk", "textDecorationStyle", "textDecorationThickness", "textEmphasis", "textEmphasisColor", "textEmphasisPosition", "textEmphasisStyle", "textIndent", "textOrientation", "textOverflow", "textRendering", "textShadow", "textSizeAdjust", "textSpacingTrim", "textTransform", "textUnderlineOffset", "textUnderlinePosition", "textWrap", "timelineScope", "top", "touchAction", "transform", "transformBox", "transformOrigin", "transformStyle", "transition", "transitionBehavior", "transitionDelay", "transitionDuration", "transitionProperty", "transitionTimingFunction", "translate", "unicodeBidi", "unicodeRange", "userSelect", "vectorEffect", "verticalAlign", "viewTimeline", "viewTimelineAxis", "viewTimelineInset", "viewTimelineName", "viewTransitionName", "visibility", "webkitAlignContent", "webkitAlignItems", "webkitAlignSelf", "webkitAnimation", "webkitAnimationDelay", "webkitAnimationDirection", "webkitAnimationDuration", "webkitAnimationFillMode", "webkitAnimationIterationCount", "webkitAnimationName", "webkitAnimationPlayState", "webkitAnimationTimingFunction", "webkitAppRegion", "webkitAppearance", "webkitBackfaceVisibility", "webkitBackgroundClip", "webkitBackgroundOrigin", "webkitBackgroundSize", "webkitBorderAfter", "webkitBorderAfterColor", "webkitBorderAfterStyle", "webkitBorderAfterWidth", "webkitBorderBefore", "webkitBorderBeforeColor", "webkitBorderBeforeStyle", "webkitBorderBeforeWidth", "webkitBorderBottomLeftRadius", "webkitBorderBottomRightRadius", "webkitBorderEnd", "webkitBorderEndColor", "webkitBorderEndStyle", "webkitBorderEndWidth", "webkitBorderHorizontalSpacing", "webkitBorderImage", "webkitBorderRadius", "webkitBorderStart", "webkitBorderStartColor", "webkitBorderStartStyle", "webkitBorderStartWidth", "webkitBorderTopLeftRadius", "webkitBorderTopRightRadius", "webkitBorderVerticalSpacing", "webkitBoxAlign", "webkitBoxDecorationBreak", "webkitBoxDirection", "webkitBoxFlex", "webkitBoxOrdinalGroup", "webkitBoxOrient", "webkitBoxPack", "webkitBoxReflect", "webkitBoxShadow", "webkitBoxSizing", "webkitClipPath", "webkitColumnBreakAfter", "webkitColumnBreakBefore", "webkitColumnBreakInside", "webkitColumnCount", "webkitColumnGap", "webkitColumnRule", "webkitColumnRuleColor", "webkitColumnRuleStyle", "webkitColumnRuleWidth", "webkitColumnSpan", "webkitColumnWidth", "webkitColumns", "webkitFilter", "webkitFlex", "webkitFlexBasis", "webkitFlexDirection", "webkitFlexFlow", "webkitFlexGrow", "webkitFlexShrink", "webkitFlexWrap", "webkitFontFeatureSettings", "webkitFontSmoothing", "webkitHyphenateCharacter", "webkitJustifyContent", "webkitLineBreak", "webkitLineClamp", "webkitLocale", "webkitLogicalHeight", "webkitLogicalWidth", "webkitMarginAfter", "webkitMarginBefore", "webkitMarginEnd", "webkitMarginStart", "webkitMask", "webkitMaskBoxImage", "webkitMaskBoxImageOutset", "webkitMaskBoxImageRepeat", "webkitMaskBoxImageSlice", "webkitMaskBoxImageSource", "webkitMaskBoxImageWidth", "webkitMaskClip", "webkitMaskComposite", "webkitMaskImage", "webkitMaskOrigin", "webkitMaskPosition", "webkitMaskPositionX", "webkitMaskPositionY", "webkitMaskRepeat", "webkitMaskSize", "webkitMaxLogicalHeight", "webkitMaxLogicalWidth", "webkitMinLogicalHeight", "webkitMinLogicalWidth", "webkitOpacity", "webkitOrder", "webkitPaddingAfter", "webkitPaddingBefore", "webkitPaddingEnd", "webkitPaddingStart", "webkitPerspective", "webkitPerspectiveOrigin", "webkitPerspectiveOriginX", "webkitPerspectiveOriginY", "webkitPrintColorAdjust", "webkitRtlOrdering", "webkitRubyPosition", "webkitShapeImageThreshold", "webkitShapeMargin", "webkitShapeOutside", "webkitTapHighlightColor", "webkitTextCombine", "webkitTextDecorationsInEffect", "webkitTextEmphasis", "webkitTextEmphasisColor", "webkitTextEmphasisPosition", "webkitTextEmphasisStyle", "webkitTextFillColor", "webkitTextOrientation", "webkitTextSecurity", "webkitTextSizeAdjust", "webkitTextStroke", "webkitTextStrokeColor", "webkitTextStrokeWidth", "webkitTransform", "webkitTransformOrigin", "webkitTransformOriginX", "webkitTransformOriginY", "webkitTransformOriginZ", "webkitTransformStyle", "webkitTransition", "webkitTransitionDelay", "webkitTransitionDuration", "webkitTransitionProperty", "webkitTransitionTimingFunction", "webkitUserDrag", "webkitUserModify", "webkitUserSelect", "webkitWritingMode", "whiteSpace", "whiteSpaceCollapse", "widows", "width", "willChange", "wordBreak", "wordSpacing", "wordWrap", "writingMode", "x", "y", "zIndex", "zoom"] +window.pct = "%" +window.vmin = "vmin" +window.vmax = "vmax" +window.vh = "vh" +window.vw = "vw" +window.px = "px" +window.em = "em" +window.rem = "rem" +window.inches = "in" -window.a = function a({ href, name=href } = {}) { +HTMLElement.prototype.addStyle = function(func) { + return func(this) +} + +window.css = function css(cssString) { + let container = document.querySelector("style#pageStyle"); + if(!container) { + container = document.createElement('style'); + container.id = "pageStyle"; + document.head.appendChild(container); + } + + let primarySelector = cssString.substring(0, cssString.indexOf("{")).trim(); + primarySelector = primarySelector.replace(/\*/g, "all"); + primarySelector = primarySelector.replace(/#/g, "id-"); + primarySelector = primarySelector.replace(/,/g, ""); + let stylesheet = container.querySelector(`:scope > style[id='${primarySelector}']`) + if(!stylesheet) { + stylesheet = document.createElement('style'); + stylesheet.id = primarySelector; + stylesheet.appendChild(document.createTextNode(cssString)); + container.appendChild(stylesheet); + } else { + stylesheet.innerText = cssString + } +} + +function extendHTMLElementWithStyleSetters() { + + function cssValueType(prop) { + const div = document.createElement("div"); + const style = div.style; + if (!(prop in style)) return "invalid"; + + switch(prop) { + + case "gap": + case "borderRadius": + case "width": + case "height": + case "maxWidth": + case "maxHeight": + case "minWidth": + case "minHeight": + + case "left": + case "top": + case "bottom": + case "right": + + case "padding": + case "paddingLeft": + case "paddingTop": + case "paddingBottom": + case "paddingRight": + + case "margin": + case "marginLeft": + case "marginTop": + case "marginBottom": + case "marginRight": + + case "textUnderlineOffset": + + return "unit-number" + + default: + + return "string" + } + + } + + let allStyleProps = ["accentColor", "additiveSymbols", "alignContent", "alignItems", "alignSelf", "alignmentBaseline", "all", "anchorName", "anchorScope", "animation", "animationComposition", "animationDelay", "animationDirection", "animationDuration", "animationFillMode", "animationIterationCount", "animationName", "animationPlayState", "animationRange", "animationRangeEnd", "animationRangeStart", "animationTimeline", "animationTimingFunction", "appRegion", "appearance", "ascentOverride", "aspectRatio", "backdropFilter", "backfaceVisibility", "background", "backgroundAttachment", "backgroundBlendMode", "backgroundClip", "backgroundColor", "backgroundImage", "backgroundOrigin", "backgroundPosition", "backgroundPositionX", "backgroundPositionY", "backgroundRepeat", "backgroundSize", "basePalette", "baselineShift", "baselineSource", "blockSize", "border", "borderBlock", "borderBlockColor", "borderBlockEnd", "borderBlockEndColor", "borderBlockEndStyle", "borderBlockEndWidth", "borderBlockStart", "borderBlockStartColor", "borderBlockStartStyle", "borderBlockStartWidth", "borderBlockStyle", "borderBlockWidth", "borderBottom", "borderBottomColor", "borderBottomLeftRadius", "borderBottomRightRadius", "borderBottomStyle", "borderBottomWidth", "borderCollapse", "borderColor", "borderEndEndRadius", "borderEndStartRadius", "borderImage", "borderImageOutset", "borderImageRepeat", "borderImageSlice", "borderImageSource", "borderImageWidth", "borderInline", "borderInlineColor", "borderInlineEnd", "borderInlineEndColor", "borderInlineEndStyle", "borderInlineEndWidth", "borderInlineStart", "borderInlineStartColor", "borderInlineStartStyle", "borderInlineStartWidth", "borderInlineStyle", "borderInlineWidth", "borderLeft", "borderLeftColor", "borderLeftStyle", "borderLeftWidth", "borderRadius", "borderRight", "borderRightColor", "borderRightStyle", "borderRightWidth", "borderSpacing", "borderStartEndRadius", "borderStartStartRadius", "borderStyle", "borderTop", "borderTopColor", "borderTopLeftRadius", "borderTopRightRadius", "borderTopStyle", "borderTopWidth", "borderWidth", "bottom", "boxDecorationBreak", "boxShadow", "boxSizing", "breakAfter", "breakBefore", "breakInside", "bufferedRendering", "captionSide", "caretAnimation", "caretColor", "clear", "clip", "clipPath", "clipRule", "color", "colorInterpolation", "colorInterpolationFilters", "colorRendering", "colorScheme", "columnCount", "columnFill", "columnGap", "columnRule", "columnRuleColor", "columnRuleStyle", "columnRuleWidth", "columnSpan", "columnWidth", "columns", "contain", "containIntrinsicBlockSize", "containIntrinsicHeight", "containIntrinsicInlineSize", "containIntrinsicSize", "containIntrinsicWidth", "container", "containerName", "containerType", "content", "contentVisibility", "cornerBlockEndShape", "cornerBlockStartShape", "cornerBottomLeftShape", "cornerBottomRightShape", "cornerBottomShape", "cornerEndEndShape", "cornerEndStartShape", "cornerInlineEndShape", "cornerInlineStartShape", "cornerLeftShape", "cornerRightShape", "cornerShape", "cornerStartEndShape", "cornerStartStartShape", "cornerTopLeftShape", "cornerTopRightShape", "cornerTopShape", "counterIncrement", "counterReset", "counterSet", "cursor", "cx", "cy", "d", "descentOverride", "direction", "display", "dominantBaseline", "dynamicRangeLimit", "emptyCells", "fallback", "fieldSizing", "fill", "fillOpacity", "fillRule", "filter", "flex", "flexBasis", "flexDirection", "flexFlow", "flexGrow", "flexShrink", "flexWrap", "float", "floodColor", "floodOpacity", "font", "fontDisplay", "fontFamily", "fontFeatureSettings", "fontKerning", "fontOpticalSizing", "fontPalette", "fontSize", "fontSizeAdjust", "fontStretch", "fontStyle", "fontSynthesis", "fontSynthesisSmallCaps", "fontSynthesisStyle", "fontSynthesisWeight", "fontVariant", "fontVariantAlternates", "fontVariantCaps", "fontVariantEastAsian", "fontVariantEmoji", "fontVariantLigatures", "fontVariantNumeric", "fontVariantPosition", "fontVariationSettings", "fontWeight", "forcedColorAdjust", "gap", "grid", "gridArea", "gridAutoColumns", "gridAutoFlow", "gridAutoRows", "gridColumn", "gridColumnEnd", "gridColumnGap", "gridColumnStart", "gridGap", "gridRow", "gridRowEnd", "gridRowGap", "gridRowStart", "gridTemplate", "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows", "height", "hyphenateCharacter", "hyphenateLimitChars", "hyphens", "imageOrientation", "imageRendering", "inherits", "initialLetter", "initialValue", "inlineSize", "inset", "insetBlock", "insetBlockEnd", "insetBlockStart", "insetInline", "insetInlineEnd", "insetInlineStart", "interactivity", "interpolateSize", "isolation", "justifyContent", "justifyItems", "justifySelf", "left", "letterSpacing", "lightingColor", "lineBreak", "lineGapOverride", "lineHeight", "listStyle", "listStyleImage", "listStylePosition", "listStyleType", "margin", "marginBlock", "marginBlockEnd", "marginBlockStart", "marginBottom", "marginInline", "marginInlineEnd", "marginInlineStart", "marginLeft", "marginRight", "marginTop", "marker", "markerEnd", "markerMid", "markerStart", "mask", "maskClip", "maskComposite", "maskImage", "maskMode", "maskOrigin", "maskPosition", "maskRepeat", "maskSize", "maskType", "mathDepth", "mathShift", "mathStyle", "maxBlockSize", "maxHeight", "maxInlineSize", "maxWidth", "minBlockSize", "minHeight", "minInlineSize", "minWidth", "mixBlendMode", "navigation", "negative", "objectFit", "objectPosition", "objectViewBox", "offset", "offsetAnchor", "offsetDistance", "offsetPath", "offsetPosition", "offsetRotate", "opacity", "order", "orphans", "outline", "outlineColor", "outlineOffset", "outlineStyle", "outlineWidth", "overflow", "overflowAnchor", "overflowBlock", "overflowClipMargin", "overflowInline", "overflowWrap", "overflowX", "overflowY", "overlay", "overrideColors", "overscrollBehavior", "overscrollBehaviorBlock", "overscrollBehaviorInline", "overscrollBehaviorX", "overscrollBehaviorY", "pad", "padding", "paddingBlock", "paddingBlockEnd", "paddingBlockStart", "paddingBottom", "paddingInline", "paddingInlineEnd", "paddingInlineStart", "paddingLeft", "paddingRight", "paddingTop", "page", "pageBreakAfter", "pageBreakBefore", "pageBreakInside", "pageOrientation", "paintOrder", "perspective", "perspectiveOrigin", "placeContent", "placeItems", "placeSelf", "pointerEvents", "position", "positionAnchor", "positionArea", "positionTry", "positionTryFallbacks", "positionTryOrder", "positionVisibility", "prefix", "printColorAdjust", "quotes", "r", "range", "readingFlow", "readingOrder", "resize", "result", "right", "rotate", "rowGap", "rubyAlign", "rubyPosition", "rx", "ry", "scale", "scrollBehavior", "scrollInitialTarget", "scrollMargin", "scrollMarginBlock", "scrollMarginBlockEnd", "scrollMarginBlockStart", "scrollMarginBottom", "scrollMarginInline", "scrollMarginInlineEnd", "scrollMarginInlineStart", "scrollMarginLeft", "scrollMarginRight", "scrollMarginTop", "scrollMarkerGroup", "scrollPadding", "scrollPaddingBlock", "scrollPaddingBlockEnd", "scrollPaddingBlockStart", "scrollPaddingBottom", "scrollPaddingInline", "scrollPaddingInlineEnd", "scrollPaddingInlineStart", "scrollPaddingLeft", "scrollPaddingRight", "scrollPaddingTop", "scrollSnapAlign", "scrollSnapStop", "scrollSnapType", "scrollTargetGroup", "scrollTimeline", "scrollTimelineAxis", "scrollTimelineName", "scrollbarColor", "scrollbarGutter", "scrollbarWidth", "shapeImageThreshold", "shapeMargin", "shapeOutside", "shapeRendering", "size", "sizeAdjust", "speak", "speakAs", "src", "stopColor", "stopOpacity", "stroke", "strokeDasharray", "strokeDashoffset", "strokeLinecap", "strokeLinejoin", "strokeMiterlimit", "strokeOpacity", "strokeWidth", "suffix", "symbols", "syntax", "system", "tabSize", "tableLayout", "textAlign", "textAlignLast", "textAnchor", "textAutospace", "textBox", "textBoxEdge", "textBoxTrim", "textCombineUpright", "textDecoration", "textDecorationColor", "textDecorationLine", "textDecorationSkipInk", "textDecorationStyle", "textDecorationThickness", "textEmphasis", "textEmphasisColor", "textEmphasisPosition", "textEmphasisStyle", "textIndent", "textOrientation", "textOverflow", "textRendering", "textShadow", "textSizeAdjust", "textSpacingTrim", "textTransform", "textUnderlineOffset", "textUnderlinePosition", "textWrap", "textWrapMode", "textWrapStyle", "timelineScope", "top", "touchAction", "transform", "transformBox", "transformOrigin", "transformStyle", "transition", "transitionBehavior", "transitionDelay", "transitionDuration", "transitionProperty", "transitionTimingFunction", "translate", "types", "unicodeBidi", "unicodeRange", "userSelect", "vectorEffect", "verticalAlign", "viewTimeline", "viewTimelineAxis", "viewTimelineInset", "viewTimelineName", "viewTransitionClass", "viewTransitionGroup", "viewTransitionName", "visibility", "webkitAlignContent", "webkitAlignItems", "webkitAlignSelf", "webkitAnimation", "webkitAnimationDelay", "webkitAnimationDirection", "webkitAnimationDuration", "webkitAnimationFillMode", "webkitAnimationIterationCount", "webkitAnimationName", "webkitAnimationPlayState", "webkitAnimationTimingFunction", "webkitAppRegion", "webkitAppearance", "webkitBackfaceVisibility", "webkitBackgroundClip", "webkitBackgroundOrigin", "webkitBackgroundSize", "webkitBorderAfter", "webkitBorderAfterColor", "webkitBorderAfterStyle", "webkitBorderAfterWidth", "webkitBorderBefore", "webkitBorderBeforeColor", "webkitBorderBeforeStyle", "webkitBorderBeforeWidth", "webkitBorderBottomLeftRadius", "webkitBorderBottomRightRadius", "webkitBorderEnd", "webkitBorderEndColor", "webkitBorderEndStyle", "webkitBorderEndWidth", "webkitBorderHorizontalSpacing", "webkitBorderImage", "webkitBorderRadius", "webkitBorderStart", "webkitBorderStartColor", "webkitBorderStartStyle", "webkitBorderStartWidth", "webkitBorderTopLeftRadius", "webkitBorderTopRightRadius", "webkitBorderVerticalSpacing", "webkitBoxAlign", "webkitBoxDecorationBreak", "webkitBoxDirection", "webkitBoxFlex", "webkitBoxOrdinalGroup", "webkitBoxOrient", "webkitBoxPack", "webkitBoxReflect", "webkitBoxShadow", "webkitBoxSizing", "webkitClipPath", "webkitColumnBreakAfter", "webkitColumnBreakBefore", "webkitColumnBreakInside", "webkitColumnCount", "webkitColumnGap", "webkitColumnRule", "webkitColumnRuleColor", "webkitColumnRuleStyle", "webkitColumnRuleWidth", "webkitColumnSpan", "webkitColumnWidth", "webkitColumns", "webkitFilter", "webkitFlex", "webkitFlexBasis", "webkitFlexDirection", "webkitFlexFlow", "webkitFlexGrow", "webkitFlexShrink", "webkitFlexWrap", "webkitFontFeatureSettings", "webkitFontSmoothing", "webkitHyphenateCharacter", "webkitJustifyContent", "webkitLineBreak", "webkitLineClamp", "webkitLocale", "webkitLogicalHeight", "webkitLogicalWidth", "webkitMarginAfter", "webkitMarginBefore", "webkitMarginEnd", "webkitMarginStart", "webkitMask", "webkitMaskBoxImage", "webkitMaskBoxImageOutset", "webkitMaskBoxImageRepeat", "webkitMaskBoxImageSlice", "webkitMaskBoxImageSource", "webkitMaskBoxImageWidth", "webkitMaskClip", "webkitMaskComposite", "webkitMaskImage", "webkitMaskOrigin", "webkitMaskPosition", "webkitMaskPositionX", "webkitMaskPositionY", "webkitMaskRepeat", "webkitMaskSize", "webkitMaxLogicalHeight", "webkitMaxLogicalWidth", "webkitMinLogicalHeight", "webkitMinLogicalWidth", "webkitOpacity", "webkitOrder", "webkitPaddingAfter", "webkitPaddingBefore", "webkitPaddingEnd", "webkitPaddingStart", "webkitPerspective", "webkitPerspectiveOrigin", "webkitPerspectiveOriginX", "webkitPerspectiveOriginY", "webkitPrintColorAdjust", "webkitRtlOrdering", "webkitRubyPosition", "webkitShapeImageThreshold", "webkitShapeMargin", "webkitShapeOutside", "webkitTapHighlightColor", "webkitTextCombine", "webkitTextDecorationsInEffect", "webkitTextEmphasis", "webkitTextEmphasisColor", "webkitTextEmphasisPosition", "webkitTextEmphasisStyle", "webkitTextFillColor", "webkitTextOrientation", "webkitTextSecurity", "webkitTextSizeAdjust", "webkitTextStroke", "webkitTextStrokeColor", "webkitTextStrokeWidth", "webkitTransform", "webkitTransformOrigin", "webkitTransformOriginX", "webkitTransformOriginY", "webkitTransformOriginZ", "webkitTransformStyle", "webkitTransition", "webkitTransitionDelay", "webkitTransitionDuration", "webkitTransitionProperty", "webkitTransitionTimingFunction", "webkitUserDrag", "webkitUserModify", "webkitUserSelect", "webkitWritingMode", "whiteSpace", "whiteSpaceCollapse", "widows", "width", "willChange", "wordBreak", "wordSpacing", "wordWrap", "writingMode", "x", "y", "zIndex", "zoom"] + + allStyleProps.forEach(prop => { + if (prop === "translate") return; + + const type = cssValueType(prop); + + 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.`); + } + if(value === "auto") { + this.style[prop] = value + return this + } + this.style[prop] = value + unit; + return this; + }; + break; + + case "string": + HTMLElement.prototype[prop] = function(value) { + this.style[prop] = value; + return this; + }; + break; + } + }); +} + +extendHTMLElementWithStyleSetters(); + +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.`); + 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.`); + 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.`); + 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.`); + 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.`); + + switch(value) { + case "6xl": + value = "3.75"; unit = "rem" + break; + + case "5xl": + value = "3"; unit = "rem" + break; + + case "4xl": + value = "2.25"; unit = "rem" + break; + + case "3xl": + value = "1.875"; unit = "rem" + break; + + case "2xl": + value = "1.5"; unit = "rem" + break; + + case "xl": + value = "1.25"; unit = "rem" + break; + + case "l": + value = "1.125"; unit = "rem" + break; + + case "s": + value = "0.875"; unit = "rem" + break; + + case "xs": + value = "0.75"; unit = "rem" + break; + + default: + break; + } + this.style.fontSize = value + unit + return this +} + +function checkPositionType(el) { + let computed = window.getComputedStyle(el).position + if(!(computed === "absolute" || computed === "fixed")) { + el.style.position = "absolute" + } +} + +HTMLElement.prototype.x = function(value, unit = "px") { + if (typeof value !== 'number' || isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + checkPositionType(this) + this.style.left = value + unit + return this +} + +HTMLElement.prototype.y = function(value, unit = "px") { + if (typeof value !== 'number' || isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + checkPositionType(this) + this.style.top = value + unit + return this +} + +HTMLElement.prototype.xRight = function(value, unit = "px") { + if (typeof value !== 'number' || isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + checkPositionType(this) + this.style.right = value + unit + return this +} + +HTMLElement.prototype.yBottom = function(value, unit = "px") { + if (typeof value !== 'number' || isNaN(value)) + throw new Error(`Invalid value: ${value}. Expected a number.`); + checkPositionType(this) + this.style.bottom = value + unit + return this +} + +HTMLElement.prototype.backgroundImage = function (...values) { + const formatted = values + .map(v => { + if(v.includes("/") && !v.includes("gradient")) { + v = "url(" + v + ")" + } + return String(v).trim(); + }) + .join(", "); + + this.style.backgroundImage = formatted; + return this; +}; + +HTMLElement.prototype.center = function () { + this.style.transform = "translate(-50%, -50%)" + return this; +}; + +HTMLElement.prototype.centerX = function () { + this.style.transform = "translateX(-50%)" + return this; +}; + +HTMLElement.prototype.centerY = function () { + this.style.transform = "translateY(-50%)" + return this; +}; + +HTMLElement.prototype.alignVertical = function (value) { + const direction = getComputedStyle(this).flexDirection; + if(!direction) { + throw new Error("alignVertical can be only be used on HStacks or VStacks!") + } + + if (direction === "column" || direction === "column-reverse") { + this.style.justifyContent = value; + } else { + this.style.alignItems = value; + } + return this +} + +HTMLElement.prototype.alignHorizontal = function (value) { + const direction = getComputedStyle(this).flexDirection; + if(!direction) { + throw new Error("alignHorizontal can be only be used on HStacks or VStacks!") + } + + if (direction === "column" || direction === "column-reverse") { + this.style.alignItems = value; + } else { + this.style.justifyContent = value; + } + return this +} + + +/* Elements */ + +quill.setChildren = function(el, innerContent) { + if(typeof innerContent === "string") { + el.innerText = innerContent + } else if(typeof innerContent === "function") { + el.render = innerContent + } else { + throw new Error("Children of unknown type") + } +} + +window.a = function a( href, inner=href ) { + if(!href) throw new Error("quill a: missing href argument. Function: a( href, inner=href )") let link = document.createElement("a") link.setAttribute('href', href); - link.innerText = name - Registry.render(link) + quill.setChildren(link, inner) + quill.render(link) return link } @@ -654,605 +579,469 @@ window.img = function img(src, width="", height="") { } else if(height) { image.style.height = height + "px" } - Registry.render(image) + quill.render(image) return image } -window.p = function p(innerText) { - let para = document.createElement("p") - para.innerText = innerText - Registry.initReactivity(para, "innerText", innerText) - Registry.render(para) - return para +HTMLImageElement.prototype.backgroundColor = function(value) { + if (this.src.endsWith('.svg') || this.src.startsWith('data:image/svg+xml')) { + fetch(this.src).then(response => response.text()) + .then(svgText => { + const modifiedSvg = svgText.replace(/\bfill="[^"]*"/g, `fill="${value}"`); + const blob = new Blob([modifiedSvg], { type: 'image/svg+xml' }); + this.src = URL.createObjectURL(blob); + }).catch(error => { + console.error('Error updating SVG fill:', error); + }); + } else { + this.style.backgroundColor = value; + } + + return this; // Always returns the element itself + }; + +window.p = function p(innerHTML) { + let el = document.createElement("p") + if(typeof innerText === "function") { + el.render = innerHTML + } else { + el.innerHTML = innerHTML + } + el.style.margin = "0"; + quill.render(el) + return el } window.h1 = function h1(innerText) { - let header = document.createElement("h1") - header.innerText = innerText - Registry.initReactivity(header, "innerText", innerText) - Registry.render(header) - return header + let el = document.createElement("h1") + el.innerText = innerText + quill.render(el) + return el } window.h2 = function h2(innerText) { - let header = document.createElement("h2") - header.innerText = innerText - Registry.initReactivity(header, "innerText", innerText) - Registry.render(header) - return header + let el = document.createElement("h2") + el.innerText = innerText + quill.render(el) + return el } window.h3 = function h3(innerText) { - let header = document.createElement("h3") - header.innerText = innerText - Registry.initReactivity(header, "innerText", innerText) - Registry.render(header) - return header + let el = document.createElement("h3") + el.innerText = innerText + quill.render(el) + return el } window.div = function (innerText) { - let div = document.createElement("div") - div.innerText = innerText ?? "" - Registry.render(div) - return div + let el = document.createElement("div") + el.innerText = innerText ?? "" + quill.render(el) + return el } window.span = function (innerText) { - let span = document.createElement("span") - span.innerText = innerText - Registry.render(span) - return span + let el = document.createElement("span") + el.innerText = innerText + quill.render(el) + return el } -/* CUSTOM */ - -window.ForEach = function (arr, cb) { - Registry.initReactivity(window.rendering.last(), "children", arr) - arr.forEach((el, i) => { - cb(el, i) - }) +window.button = function (children = "") { + let el = document.createElement("button") + quill.setChildren(el, children) + quill.render(el) + return el } +window.form = function(cb) { + let el = document.createElement("form") + el.render = cb + quill.render(el) + return el +} + +window.input = function(placeholder, width, height) { + let el = document.createElement("input") + el.placeholder = placeholder + el.style.width = width + el.style.height = height + quill.render(el) + return el +} + +window.label = function(text) { + let el = document.createElement("label") + el.innerText = text + quill.render(el) + return el +} + +window.textarea = function(placeholder) { + let el = document.createElement("textarea") + el.placeholder = placeholder + quill.render(el) + return el +} + + +/* STACKS */ + window.VStack = function (cb = () => {}) { - let nowRendering = window.rendering.last() - if(nowRendering.innerHTML.trim() === "") { - let styles = ` - display: flex; - flex-direction: column; - ` - if(nowRendering instanceof Shadow) { - Registry.updateStyle(nowRendering.tagName.toLowerCase(), styles) - } else { - nowRendering.style.cssText += styles - } + let styles = ` + display: flex; + flex-direction: column; + ` + let nowRendering = quill.rendering[quill.rendering.length-1] + if (nowRendering.innerHTML.trim() === "" && !quill.isStack(nowRendering)) { + nowRendering.style.cssText += styles + nowRendering.classList.add("VStack") cb() return nowRendering - } else { - let div = document.createElement("div") - div.classList.add("VStack") - div.style.display = "flex" - div.style.flexDirection = "column" - div.render = cb - Registry.render(div) - return div } + + let div = document.createElement("div") + div.classList.add("VStack") + div.style.cssText += styles + div.render = cb + quill.render(div) + return div } window.HStack = function (cb = () => {}) { - let nowRendering = window.rendering.last() - if(nowRendering.innerHTML.trim() === "") { - let styles = ` - display: flex; - flex-direction: row; - ` - if(nowRendering instanceof Shadow) { - Registry.updateStyle(nowRendering.tagName.toLowerCase(), styles) - } else { - nowRendering.style.cssText += styles - } - cb() - return nowRendering - } else { - let div = document.createElement("div") - div.classList.add("HStack") - div.style.display = "flex" - div.style.flexDirection = "row" - div.render = cb - Registry.render(div) - return div + let styles = ` + display: flex; + flex-direction: row; + `; + let nowRendering = quill.rendering[quill.rendering.length - 1]; + if (nowRendering.innerHTML.trim() === "" && !quill.isStack(nowRendering)) { + nowRendering.style.cssText += styles; + nowRendering.classList.add("HStack") + cb(); + return nowRendering; } -} + + let div = document.createElement("div"); + div.classList.add("HStack"); + div.style.cssText += styles; + div.render = cb; + quill.render(div); + return div; +}; window.ZStack = function (cb = () => {}) { - let nowRendering = window.rendering.last() - if(nowRendering.innerHTML.trim() === "") { // Parent is Empty, make it a ZStack - cb() - return nowRendering - } else { - let div = document.createElement("div") - div.classList.add("ZStack") - div.render = cb - Registry.render(div) - return div + let nowRendering = quill.rendering[quill.rendering.length - 1]; + if (nowRendering.innerHTML.trim() === "" && !quill.isStack(nowRendering)) { + nowRendering.classList.add("ZStack") + cb(); + return nowRendering; } -} + + let div = document.createElement("div"); + div.classList.add("ZStack"); + div.render = cb; + quill.render(div); + return div; +}; /* SHAPES */ -window.Circle = function(text = "") { - let div = document.createElement("div") - div.innerText = text - div.style.borderRadius = "100%" - div.style.width = "20px" - div.style.height = "20px" - div.style.background = "black" - div.style.color = "white" - div.style.textAlign = "center" - Registry.render(div) - return div -} - -window.Triangle = function() { - let div = document.createElement("div") - div.style.cssText += ` - width: 20px; - height: 17.3px; - clip-path: polygon(50% 0%, 0% 100%, 100% 100%); - background: black; - ` - Registry.render(div) - return div -} - -/* PROTOTYPE FUNCTIONS */ - -Array.prototype.last = function() { - return this[this.length-1] -} -HTMLElement.prototype.addAttribute = function(name) { - this.setAttribute(name, "") -} - -HTMLElement.prototype.ownHTML = function() { - return this.startingTag() + this.endingTag() -} - -HTMLElement.prototype.startingTag = function() { - const tag = this.tagName.toLowerCase(); - let html = `<${tag}`; - - for (const attr of this.attributes) { - html += ` ${attr.name}="${attr.value}"`; - } - - html += `>`; - return html; -} - -HTMLElement.prototype.endingTag = function() { - const tag = this.tagName.toLowerCase(); - return ``; -} - -Element.prototype.render = function (...els) { - if(els.length > 0) { - this.innerHTML = "" - els.forEach((el) => { - this.appendChild(el) - }) - } - return this -} - -HTMLElement.prototype.class = function(classNames) { - this.className = classNames - return this -} - -/* PROTOTYPE STYLING */ - -function extendHTMLElementWithStyleSetters() { - allStyleProps.forEach(prop => { - if(prop === "translate") return - HTMLElement.prototype[prop] = function(value) { - this.style[prop] = value; - Registry.initReactivity(this, ["style", prop], value); - return this; - }; - }); -} -extendHTMLElementWithStyleSetters(); - -HTMLElement.prototype.padding = function(direction, value) { - if(!value) { - this.style.padding = direction; - } - - const directionName = `padding${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.paddingTop = function(value, unit = "px") { - this.style.paddingTop = value + unit - Registry.initReactivity(this, ["style", "paddingTop"], value); +window.svgMethods = function(svg) { + svg.pulse = function (duration = 600) { + this.style.transition = `transform ${duration}ms ease-in-out` + this.style.transform = "scale(1.2)" + setTimeout(() => { + this.style.transform = "scale(1)" + }, duration / 2) return this } - HTMLElement.prototype.paddingLeft = function(value, unit = "px") { - this.style.paddingLeft = value + unit - Registry.initReactivity(this, ["style", "paddingLeft"], value); + // Rotate (e.g. loading spinner) + svg.rotate = function (degrees = 360, duration = 1000) { + this.style.transition = `transform ${duration}ms linear` + this.style.transform = `rotate(${degrees}deg)` return this } - HTMLElement.prototype.paddingBottom = function(value, unit = "px") { - this.style.paddingBottom = value + unit - Registry.initReactivity(this, ["style", "paddingBottom"], value); + // Change color + svg.fill = function (color) { + this.setAttribute("fill", color) return this } - HTMLElement.prototype.paddingRight = function(value, unit = "px") { - this.style.paddingRight = value + unit - Registry.initReactivity(this, ["style", "paddingRight"], value); + svg.height = function (height) { + this.setAttribute("height", height) return this } -HTMLElement.prototype.margin = function(value, unit = "px") { - this.style.margin = value + unit - Registry.initReactivity(this, ["style", "margin"], value); - return this -} - - HTMLElement.prototype.marginTop = function(value, unit = "px") { - this.style.marginTop = value + unit - Registry.initReactivity(this, ["style", "marginTop"], value); + svg.width = function (width) { + this.setAttribute("width", width) return this } - HTMLElement.prototype.marginLeft = function(value, unit = "px") { - this.style.marginLeft = value + unit - Registry.initReactivity(this, ["style", "marginLeft"], value); + svg.stroke = function (width, color) { + this.setAttribute("stroke", color) + this.setAttribute("stroke-width", width) return this } - HTMLElement.prototype.marginBottom = function(value, unit = "px") { - this.style.marginBottom = value + unit - Registry.initReactivity(this, ["style", "marginBottom"], value); + // Toggle visibility + svg.toggle = function () { + this.style.display = this.style.display === "none" ? "" : "none" return this } - - HTMLElement.prototype.marginRight = function(value, unit = "px") { - this.style.marginRight = value + unit - Registry.initReactivity(this, ["style", "marginRight"], value); - return this - } - -HTMLElement.prototype.width = function(value, unit = "px") { - this.style.width = value + unit - Registry.initReactivity(this, ["style", "width"], value); - return this } -HTMLElement.prototype.height = function(value, unit = "px") { - this.style.height = value + unit - Registry.initReactivity(this, ["style", "height"], value); - return this +window.Rectangle = function (width = "40px", height = "40px") { + const svgNS = "http://www.w3.org/2000/svg"; + const svgEl = document.createElementNS(svgNS, "svg"); + const rectEl = document.createElementNS(svgNS, "rect"); + + // SVG size + svgEl.setAttribute("width", width); + svgEl.setAttribute("height", height); + svgEl.setAttribute("viewBox", "0 0 100 100"); + svgEl.setAttribute("preserveAspectRatio", "xMidYMid meet"); + + // Rectangle: full size, slightly rounded corners + rectEl.setAttribute("x", "15"); // 15% margin from edges + rectEl.setAttribute("y", "15"); + rectEl.setAttribute("width", "70"); // 70% of viewBox + rectEl.setAttribute("height", "70"); + // rectEl.setAttribute("rx", "8"); // rounded corners (optional) + // rectEl.setAttribute("ry", "8"); + + svgEl.appendChild(rectEl); + svgMethods(svgEl); // assuming you have this + quill.render(svgEl); + return svgEl; } -HTMLElement.prototype.fontSize = function(value, unit = "px") { - this.style.fontSize = value + unit - Registry.initReactivity(this, ["style", "fontSize"], value); - return this +window.Triangle = function (width = "40px", height = "40px") { + const svgNS = "http://www.w3.org/2000/svg" + const svgEl = document.createElementNS(svgNS, "svg") + const pathEl = document.createElementNS(svgNS, "path") + + // SVG size + svgEl.setAttribute("width", width) + svgEl.setAttribute("height", height) + svgEl.setAttribute("viewBox", "0 0 100 100") + svgEl.setAttribute("preserveAspectRatio", "xMidYMid meet") + // Right-pointing triangle (Play icon) + pathEl.setAttribute("d", "M 25 15 L 90 50 L 25 85 Z") // ◄ adjust points if needed + + svgEl.appendChild(pathEl) + svgMethods(svgEl) + quill.render(svgEl) + return svgEl } -function checkStyle(el) { - let computed = window.getComputedStyle(el).position - if(!(computed === "absolute" || computed === "fixed")) { - el.style.position = "absolute" - } -} -HTMLElement.prototype.x = function(value, unit = "px") { - if (typeof value !== 'number' || isNaN(value)) - throw new Error(`Invalid value: ${value}. Expected a number.`); - checkStyle(this) - this.style.left = value + unit - Registry.initReactivity(this, ["style", "left"], value); - return this -} - -HTMLElement.prototype.y = function(value, unit = "px") { - if (typeof value !== 'number' || isNaN(value)) - throw new Error(`Invalid value: ${value}. Expected a number.`); - checkStyle(this) - this.style.top = value + unit - Registry.initReactivity(this, ["style", "top"], value); - return this -} - -HTMLElement.prototype.xRight = function(value, unit = "px") { - if (typeof value !== 'number' || isNaN(value)) - throw new Error(`Invalid value: ${value}. Expected a number.`); - checkStyle(this) - this.style.right = value + unit - Registry.initReactivity(this, ["style", "right"], value); - return this -} - -HTMLElement.prototype.yBottom = function(value, unit = "px") { - if (typeof value !== 'number' || isNaN(value)) - throw new Error(`Invalid value: ${value}. Expected a number.`); - checkStyle(this) - this.style.bottom = value + unit - Registry.initReactivity(this, ["style", "bottom"], value); - return this -} - -HTMLElement.prototype.positionType = function (value) { - if(!(value === "absolute" || value === "relative" || value === "static" || value === "fixed" || value === "sticky")) { - console.error("HTMLElement.overlflow: must have valid overflow value!") - return; - } - this.style.position = value - Registry.initReactivity(this, ["style", "position"], value); - return this -} - -/* PROTOTYPE EVENTS */ +/* EVENTS */ HTMLElement.prototype.onAppear = function(func) { - func(this) - return this -} + func.call(this); + return this; +}; + HTMLElement.prototype.onClick = function(func) { - this.addEventListener("click", func) - return this -} + const onMouseDown = () => func.call(this, false); + const onMouseUp = () => func.call(this, true); + this._storeListener("mousedown", onMouseDown); + this._storeListener("mouseup", onMouseUp); + return this; +}; + HTMLElement.prototype.onMouseDown = function(func) { - this.addEventListener("mousedown", func) - return this -} + this._storeListener("mousedown", func); + return this; +}; + HTMLElement.prototype.onMouseUp = function(func) { - this.addEventListener("mouseup", func) - return this -} + this._storeListener("mouseup", func); + return this; +}; + HTMLElement.prototype.onRightClick = function(func) { - this.addEventListener("contextmenu", func) - return this -} + this._storeListener("contextmenu", func); + return this; +}; HTMLElement.prototype.onHover = function(cb) { - this.addEventListener("mouseover", () => cb(true)) - this.addEventListener("mouseleave", () => cb(false)) - return this -} + const onEnter = (e) => cb.call(this, true, e); + const onLeave = (e) => cb.call(this, false, e); + this._storeListener("mouseover", onEnter); + this._storeListener("mouseleave", onLeave); + return this; +}; -/* PARSE */ +HTMLElement.prototype.onFocus = function(cb) { + if (!this.matches('input, textarea, select, button')) { + throw new Error("Can't put focus event on non-form element!"); + } + const onFocus = () => cb.call(this, true); + const onBlur = () => cb.call(this, false); + this._storeListener("focus", onFocus); + this._storeListener("blur", onBlur); + return this; +}; -Registry.parseClassFields = function(classObject) { - let str = classObject.toString(); - const lines = str.split('\n'); - const fields = []; - let braceDepth = 0; // Tracks the depth of curly braces to identify when we're inside a function/method +HTMLElement.prototype.onKeyDown = function(cb) { + this._storeListener("keydown", cb); + return this; +}; - for (let line of lines) { - const trimmedLine = line.trim(); +HTMLElement.prototype.onInput = function(cb) { + if(!this.matches('input, textarea, [contenteditable=""], [contenteditable="true"]')) + throw new Error("Can't put input event on non-input element!") + this._storeListener("input", cb); + return this; +}; - // Update braceDepth based on the current line - braceDepth += (trimmedLine.match(/{/g) || []).length; - braceDepth -= (trimmedLine.match(/}/g) || []).length; +HTMLElement.prototype.onChange = function(cb) { + if(!this.matches('input, textarea, [contenteditable=""], [contenteditable="true"]')) + throw new Error("Can't put input event on non-input element!") + this._storeListener("change", cb); + return this; +}; - // Check if the line is outside any function/method (top-level within the class) - if (braceDepth === 1) { - // Attempt to match a class field declaration with or without initialization - const fieldMatch = trimmedLine.match(/^([a-zA-Z_$][0-9a-zA-Z_$]*)\s*(=|;|\n|$)/); - if (fieldMatch) { - fields.push(fieldMatch[1]); - } - } - // If we encounter the constructor, stop the parsing as we're only interested in fields above it - if (trimmedLine.startsWith('constructor')) { +HTMLElement.prototype.onSubmit = function(cb) { + if(!this.matches('form')) + throw new Error("Can't put form event on non-form element!") + this._storeListener("submit", cb); + return this; +}; + +HTMLElement.prototype.onTouch = function(cb) { + const onStart = () => cb.call(this, true); + const onEnd = () => cb.call(this, false); + const onCancel = () => cb.call(this, null); + this._storeListener("touchstart", onStart); + this._storeListener("touchend", onEnd); + this._storeListener("touchcancel", onCancel); + return this; +}; + +HTMLElement.prototype.onTap = function(cb) { + this._storeListener("touchend", cb); + return this; +}; + +/* 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. +*/ +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 fields; + return this; +}; +window.addEventListener("navigate", () => { + for(entry of navigateListeners) { + entry.el.dispatchEvent(new CustomEvent("navigate")) + } +}) + +/* +Same principle applies +*/ +queryListeners = [] +HTMLElement.prototype.onQueryChanged = function(cb) { + this._storeListener("query-changed", cb); + + let found = false + for(entry of queryListeners) { + if(entry.cb.toString() === cb.toString() && + this.nodeName === entry.el.nodeName) { + found = true + break; + } + } + if(found === false) { + queryListeners.push({el: this, cb: cb}) + } + + return this; +}; +window.addEventListener("query-changed", () => { + for(entry of queryListeners) { + entry.el.dispatchEvent(new CustomEvent("query-changed")) + } +}) + +HTMLElement.prototype.onEvent = function(name, cb) { + window._storeListener(window, name, cb); + return this; +}; + +HTMLElement.prototype._storeListener = function(type, handler, options) { + window._storeListener(this, type, handler, options) } -Registry.parseConstructor = function(classObject) { - let str = classObject.toString(); - const lines = str.split('\n'); - let modifiedLines = []; - let braceDepth = 0; - let constructorFound = false - let superCallFound = false; - let constructorEndFound = false; +window.__listeners = [] - for (let i = 0; i < lines.length; i++) { - let line = lines[i]; - const trimmedLine = line.trim(); +function _storeListener(target, type, handler, options) { + if (!target.__listeners) target.__listeners = []; - modifiedLines.push(line); + const optionsString = JSON.stringify(options); - braceDepth += (trimmedLine.match(/{/g) || []).length; - braceDepth -= (trimmedLine.match(/}/g) || []).length; + const index = target.__listeners.findIndex(listener => + listener.type === type && + listener.handler.toString() === handler.toString() && + JSON.stringify(listener.options) === optionsString + ); - if (trimmedLine.startsWith('constructor(')) { - constructorFound = true; - } + if (index === -1) { // Listener is new + target.addEventListener(type, handler, options); + target.__listeners.push({ type, handler, options }); + } else { // Listener is a duplicate, can be replaced + const old = target.__listeners[index]; + target.removeEventListener(old.type, old.handler, old.options); - if (constructorFound && trimmedLine.startsWith('super(') && !superCallFound) { - superCallFound = true; - modifiedLines.push(` window.Registry.construct(this);`); - } - - if (constructorFound && braceDepth === 1 && superCallFound && !constructorEndFound) { - modifiedLines.splice(modifiedLines.length - 1, 0, ' Object.preventExtensions(this);'); - modifiedLines.splice(modifiedLines.length - 1, 0, ' window.Registry.testInitialized(this);'); - constructorEndFound = true - } - } - - if(superCallFound) { - let modifiedStr = modifiedLines.join('\n'); - modifiedStr = '(' + modifiedStr + ')' - modifiedStr += `//# sourceURL=${classObject.prototype.constructor.name}.shadow` - return eval(modifiedStr); - } - - if(constructorFound) { - throw new Error("Quill: Constructor must have super()! " + lines[0]) - } else { - let constructorString = ` - constructor(...params) { - super(...params) - window.Registry.construct(this) - Object.preventExtensions(this); - window.Registry.testInitialized(this); - } - ` - let closingBracket = str.lastIndexOf("}"); - str = str.slice(0, closingBracket - 1) + constructorString + "\n}" - return eval('(' + str + `) //# sourceURL=${classObject.prototype.constructor.name}.shadow`); + // Replace with the new one + target.addEventListener(type, handler, options); + target.__listeners[index] = { type, handler, options }; } } -Registry.getRender = function(classObject) { - const classString = classObject.toString(); - - const regular = /render\s*\(\s*\)\s*\{/; - const arrow = /render\s*=\s*\(\s*\)\s*=>\s*\{/; - const matches = classString.match(regular) || classString.match(arrow); - - if(matches && matches.length === 1) { - return classString.substring(matches.index) - } else { - console.error("Quill: render funcion not defined properly!") +HTMLElement.prototype.removeAllListeners = function() { + if (!this.__listeners) return; + for (const { type, handler, options } of this.__listeners) { + this.removeEventListener(type, handler, options); } -} + this.__listeners = []; + return this; +}; -Registry.parseRender = class ParseRender { - str; - i = 0; - functionStack; - result; +/* ATTRIBUTES */ - /* - [ - [Function scope, value used] - ["VStack.ForEach", "form.children"], - ["VStack.ForEach.SidebarFile", "form.color"] - ["VStack.x", windowState.sidebarOut] - ] - */ - - constructor(classObject) { - this.str = Registry.getRender(classObject.toString()).replace(/\s/g, ""); - this.functionStack = "" - this.result = [] +HTMLElement.prototype.attr = function(attributes) { + if ( + typeof attributes !== "object" || + attributes === null || + Array.isArray(attributes) + ) { + throw new TypeError("attr() expects an object with key-value pairs"); } - parse() { - this.parseFunction() - return this.result + for (const [key, value] of Object.entries(attributes)) { + this.setAttribute(key, value); } - - parseFunction() { - console.log(this.str) - this.copyTo("{") - let firstEl = this.copyTo("(") - console.log(firstEl) - - if(!firstEl) { - console.log("Empty render function") - return - } - - if(firstEl.includes("Stack")) { - this.parseFunction() - } else if(firstEl.includes("ForEach")) { - let array = this.copyTo(",") - if(array.includes("this")) { - console.log(this.result) - this.result.push([this.functionStack + "ForEach", array.replace("this.", "")]) - } - this.parseFunction() - } else if(firstEl === "switch") { - - } else if(firstEl === ("if")) { - console.log("if") - } - } - - copyTo = function(char) { - this.i = this.str.indexOf(char) - let copied = this.str.substring(0, this.str.indexOf(char)); - this.str = this.str.slice(this.i + 1); // Update the string to exclude the copied part and the character - - return copied - } - - // firstParam(str, i, stack, total) { - // console.log(str[i]) - // switch(str[i]) { - // case "(": - // console.log("function") - // break; - // case "\"": - // console.log("string") - // break; - // default: - // if (!isNaN(input)) { - // console.log("Number"); - // } else { - // console.log("Variable"); - // } - // } - // } - -} - -window.register = Registry.register -window.rendering = [] - - -// const usage = []; -// const lines = renderFunctionToClassEnd.split('\n'); - -// let currentFunction = null; -// let currentFunctionChain = []; // To keep track of nested function names - -// for (const line of lines) { -// const functionMatch = line.match(/^\s*([a-zA-Z_$][0-9a-zA-Z_$]*)\s*=\s*\(\s*\)\s*=>\s*{/); -// if (functionMatch) { -// currentFunction = functionMatch[1]; -// currentFunctionChain.push(currentFunction); -// } - -// if (line.includes('this')) { -// if (currentFunction) { -// const thisUsage = line.match(/this\.[a-zA-Z_$][0-9a-zA-Z_$]*(?:\.[a-zA-Z_$][0-9a-zA-Z_$]*)*/g); -// if (thisUsage) { -// for (const usageItem of thisUsage) { -// const propertyChain = usageItem.replace('this.', ''); -// const completeChain = [...currentFunctionChain, propertyChain]; -// usage.push(completeChain); -// } -// } -// } else { -// const thisUsage = line.match(/this\.[a-zA-Z_$][0-9a-zA-Z_$]*/g); -// if (thisUsage) { -// for (const usageItem of thisUsage) { -// const propertyChain = usageItem.replace('this.', ''); -// usage.push([propertyChain]); -// } -// } -// } -// } -// } + return this; +}; diff --git a/notes.txt b/notes.txt deleted file mode 100644 index e598b22..0000000 --- a/notes.txt +++ /dev/null @@ -1,151 +0,0 @@ - - - - -Shadow.File = (param) => new class { - trippy = "asd" - - constructor() { - console.log(param) - } -} - -console.log(Shadow.File("as")) - - -ObservedObject.WindowState = () => ({ - sidebarOut: false, - children: null -}) -let windowState = ObservedObject.WindowState() -console.log(windowState) - - -Shadow(class File { - -}, "file-") - - - -3/16 -const TestClass = () => new class { - constructor() { - console.log("hey") - } -}() - -This works. Could extend shadow. This way the function and the actual code are not separate. - - -3/10 - -Ran into a problem where I can't call the same element within itself. - -There are two problems: - -1. Javascript scoping means that it tries to call the class it is inside of. -2. Quill instantiates the object when registered, to track its state - - -This is what I ended up going with - simply not using the Space() recursively and instead making a child space. - -class Space extends HTMLElement { - form = Forms.observe(window.location.pathname, this) - - contents = [ - ...this.form.children.map(form => { - switch(form.type) { - case "file": - return File() - case "space": - return ChildSpace() - case "link": - return Link() - } - }) - ] - - constructor() { - super() - console.log(this.form.path) - console.log(this.contents) - this.render(...this.contents) - } -} - - - -This was my attempt to see if an anonymous class can be used. The class functions and extends HTMLElement - so problem #2 is solved. -Problem #1 however, scoping, is not solved unless putting "window.Space()" instead of Space() here, so that it does not -attempt to access the named function value. It seems both functions and classes have this problem. Perhaps there is a -different way it could be done in Quill, like so - - - -const el = () => class extends HTMLElement { - ... -} -quill.register(el, "Space", "parchment-space") - - -However, not naming it at the top is less than desirable. - -quill("Space", class extends HTMLElement { - form = Forms.observe(window.location.pathname, this) - - contents = [ - ...this.form.children.map(form => { - switch(form.type) { - case "file": - return File() - case "space": - return ChildSpace() - case "link": - return Link() - } - }) - ] - - constructor() { - super() - console.log(this.form.path) - console.log(this.contents) - this.render(...this.contents) - } -}) - -this would probably work. Still less than ideal but maybe if we used syntax highlighting it could be alright. -How to name something without the class having access? This is the problem. I didn't try it without being an arrow function so -perhaps this would have a chance. - - - -// const Space = () => class extends HTMLElement { -// form = Forms.observe(window.location.pathname, this) - -// contents = [ -// ...this.form.children.map(form => { -// switch(form.type) { -// case "file": -// return File() -// case "space": -// return Space() -// case "link": -// return Link() -// } -// }) -// ] - -// constructor() { -// super() -// console.log(this.form.path) -// console.log(this.contents) -// this.render(...this.contents) -// } -// } - -// let instan = Space() -// customElements.define("space-", instan) -// window.Space = () => "boi" -// console.log(new instan()) - -window.register(Space, "parchment-space") \ No newline at end of file diff --git a/path.js b/path.js deleted file mode 100644 index c652ce3..0000000 --- a/path.js +++ /dev/null @@ -1,151 +0,0 @@ -class PathProcessor { - path; - hasTrailingSlash = false; - node = (typeof module !== 'undefined' && module.exports) ? true : false; - - constructor(path) { - this.path = path; - if(path === undefined) { - this.path = "" - } - } - - #removeTrailingSlash(path) { - if(path === "/") { - return path; - } - return path.endsWith("/") ? path.slice(0, -1) : path - } - - full() { - if(!this.node) return; - let path = this.path; - this.path = "" - this.join(Path.homedir(), path) - return this; - } - - web() { - if(!this.node) return; - const os = require('os') - this.path = this.path.replace(this.#removeTrailingSlash(os.homedir()), "") - if(this.path === "") { - this.path = "/" - } - return this; - } - - encode() { // Uses the built in encodeURI and also encodes certain characters that are't covered by it - this.path = encodeURI(this.path).replace(/[!'()*]/g, function(c) { - return '%' + c.charCodeAt(0).toString(16); - }); - return this; - } - - decoded() { - this.path = decodeURIComponent(this.path); - return this; - } - - parent() { - const parts = this.path.split('/').filter(Boolean); - parts.pop(); - this.path = '/' + parts.join('/'); - return this; - } - - end() { - const parts = this.path.split('/').filter(Boolean); - this.path = parts.pop() || ''; - return this; - } - - trailingSlash() { - this.path = this.path.endsWith("/") ? this.path : this.path+"/"; - this.hasTrailingSlash = true; - return this; - } - - noTrailingSlash() { - this.path = this.#removeTrailingSlash(this.path) - return this; - } - - join(...segments) { - if (this.path) { - segments.unshift(this.path); - } - - this.path = segments - .map((part, index) => { - if (index === 0) { - return part.trim().replace(/[/]*$/g, ''); - } else { - return part.trim().replace(/(^[/]*|[/]*$)/g, ''); - } - }) - .filter(Boolean) // Remove empty segments - .join('/'); - - return this; - } - - components() { - return this.path.split('/').filter(Boolean); - } - - build() { - return this.hasTrailingSlash ? this.path : this.#removeTrailingSlash(this.path) - } -} - -export default class Path { - static full(path) { - return new PathProcessor(path).full(); - } - - static web(path) { - return new PathProcessor(path).web(); - } - - static decoded(path) { - return new PathProcessor(path).decoded() - } - - static encode(path) { - return new PathProcessor(path).encode() - } - - static parent(path) { - return new PathProcessor(path).parent(); - } - - static end(path) { - return new PathProcessor(path).end(); - } - - static trailingSlash(path) { - return new PathProcessor(path).trailingSlash(); - } - - static noTrailingSlash(path) { - return new PathProcessor(path).noTrailingSlash(); - } - - static components(path) { - return new PathProcessor(path).components(); - } - - static join(...segments) { - return new PathProcessor(null).join(...segments); - } - - static homedir() { - if(typeof module === 'undefined' || !module.exports) return; - const os = require('os') - let ret = os.homedir().replace(/\\/g, '/').replace(/^[a-zA-Z]:/, ''); - return ret - } -} - -window.Path = Path; \ No newline at end of file