diff --git a/.gitignore b/.gitignore index e93efb0..dadcae3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ !/.github !/.gitignore deno.lock -static/dist/index.js diff --git a/README.md b/README.md index d940ef6..5ad0212 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ deno task serve ## Todo +- `deno bundle` seems to be broken. It works with + `deno upgrade --version 1.29.0`. - More options - Some browser tests - Add markdown's **bold** and _italic_ syntax to the text input. diff --git a/deno.json b/deno.json index ea48206..499c416 100644 --- a/deno.json +++ b/deno.json @@ -7,8 +7,8 @@ "compilerOptions": { "lib": ["esnext", "dom", "dom.iterable", "deno.ns"], "strict": true, - "verbatimModuleSyntax": true, "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, "noImplicitOverride": false, "checkJs": true } diff --git a/index.js b/index.js new file mode 100644 index 0000000..13e75a2 --- /dev/null +++ b/index.js @@ -0,0 +1,1256 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file +// This code was bundled using `deno bundle` and it's not recommended to edit it manually + +function convertDashToCamel(str) { + return str.replace(/-([a-z0-9])/g, (g)=>g[1].toUpperCase()); +} +function convertCamelToDash(str) { + return str.replace(/([a-zA-Z0-9])(?=[A-Z])/g, "$1-").toLowerCase(); +} +function createTemplate(html) { + const template = document.createElement("template"); + template.innerHTML = html; + return template; +} +function stringify(input) { + return typeof input === "string" ? input : typeof input === "number" ? input.toString() : ""; +} +function isString(input) { + return typeof input === "string"; +} +function isNull(input) { + return input === null; +} +function isTrue(input) { + return input === true; +} +function isObject(obj) { + return obj !== null && typeof obj === "object" && Array.isArray(obj) === false; +} +function isHtmlElement(input) { + return input instanceof HTMLElement; +} +class ShadowError extends Error { + constructor(message){ + super(message); + this.name = this.constructor.name; + } +} +class Shadow extends HTMLElement { + _renderCounter = 0; + _waitingList = new Set(); + _accessorsStore = new Map(); + _updateCustomEvent = new CustomEvent("_updated"); + _propertiesAndOptions; + _dynamicCssStore = []; + _isConnected = false; + _isPaused = false; + root; + dom = { + id: {}, + class: {} + }; + initUrl = null; + get _isReady() { + return this._isConnected === true && this._isPaused === false && this._waitingList.size === 0; + } + constructor(options = { + mode: "open" + }){ + super(); + this.root = this.attachShadow(options); + this._propertiesAndOptions = this.__propertiesAndOptions || []; + if (this.firstUpdated) { + this.addEventListener("_updated", this.firstUpdated, { + once: true + }); + } + if (this.updated) { + this.addEventListener("_updated", this.updated); + } + } + connectedCallback() { + this.init(this._propertiesAndOptions); + } + init(propertiesAndOptions) { + propertiesAndOptions.forEach(this._makePropertyAccessible); + this._isConnected = true; + if (isTrue(this._isReady)) { + this._actuallyRender(); + } + } + _makePropertyAccessible = ({ property , reflect =true , render =true , wait =false , assert =false })=>{ + if (isTrue(wait)) { + this._waitingList.add(property); + } else if (isTrue(assert) && !this[property]) { + throw new ShadowError(`The property ${property} must have a truthy value.`); + } + this._accessorsStore.set(property, this[property]); + if (isTrue(reflect)) { + this._updateAttribute(property, this[property]); + } + Object.defineProperty(this, property, { + get: ()=>this._accessorsStore.get(property), + set: (value)=>{ + if (isTrue(assert) && !value) { + throw new ShadowError(`The property '${property}' must have a truthy value.`); + } + this._accessorsStore.set(property, value); + if (isTrue(wait)) { + this._waitingList.delete(property); + } + if (isTrue(reflect)) { + this._updateAttribute(property, value); + } + if (isTrue(render) && isTrue(this._isReady)) { + this._actuallyRender(); + } + } + }); + }; + _updateAttribute(property, value) { + const attributeName = convertCamelToDash(property); + const attributeValue = this.getAttribute(attributeName); + if (attributeValue !== value) { + if (isNull(value)) return this.removeAttribute(attributeName); + else { + if (isString(value)) { + this.setAttribute(attributeName, value); + } else { + const jsonValue = JSON.stringify(value); + if (jsonValue === undefined) { + throw new ShadowError(`Only JSON values can be reflected in attributes but received ` + `the value '${value}' for '${property}'.`); + } + if (attributeValue !== jsonValue) { + this.setAttribute(attributeName, jsonValue); + } + } + } + } + } + attributeChangedCallback(name, oldValue, newValue) { + if (newValue === oldValue) { + return undefined; + } else if (name === "init-url" && isString(newValue)) { + this._isPaused = true; + this.update(name, newValue); + this._fetchJsonAndUpdate(newValue).then(()=>{ + this._isPaused = false; + if (isTrue(this._isReady)) { + this._actuallyRender(); + } + }); + } else { + return this.update(name, newValue); + } + } + _fetchJsonAndUpdate(urlOrPath) { + return fetch(new URL(urlOrPath, location.href).href).then((res)=>{ + if (isTrue(res.ok)) { + return res.json().then((data)=>Object.entries(data).forEach(([property, value])=>this[property] = value)); + } else { + throw new ShadowError(`Received status code ${res.status} instead of 200-299 range.`); + } + }).catch((err)=>{ + throw new ShadowError(err.message); + }); + } + update(name, value) { + const property = convertDashToCamel(name); + if (property in this) { + if (this[property] !== value && JSON.stringify(this[property]) !== value) { + try { + this[property] = isNull(value) ? value : JSON.parse(value); + } catch { + this[property] = value; + } + } + } else { + throw new ShadowError(`The property '${property}' does not exist on '${this.constructor.name}'.`); + } + } + addCss(ruleSet, render = true) { + this._dynamicCssStore.push(createTemplate(``)); + if (isTrue(render) && isTrue(this._isReady)) this._actuallyRender(); + } + _createFragment(...inputArray) { + const documentFragment = document.createDocumentFragment(); + inputArray.flat(2).forEach((input)=>{ + if (isObject(input) && input.element instanceof Element) { + const { element , collection } = input; + documentFragment.appendChild(element); + collection.forEach((item)=>this._processCollection(item)); + } else if (isString(input)) { + documentFragment.appendChild(createTemplate(input).content.cloneNode(true)); + } else { + documentFragment.appendChild(document.createTextNode(stringify(input))); + } + }); + return documentFragment; + } + _processCollection({ target , queries , eventsAndListeners }) { + if (isHtmlElement(target)) { + queries.forEach(({ kind , selector })=>kind === "id" ? this.dom.id[selector] = target : this.dom.class[selector] ? this.dom.class[selector].push(target) : this.dom.class[selector] = [ + target + ]); + } + eventsAndListeners.forEach(({ event , listener })=>target.addEventListener(event, listener.bind(this))); + } + _actuallyRender() { + if (this._renderCounter > 0) { + this.dom.id = {}; + this.dom.class = {}; + } + while(this.root.firstChild){ + this.root.removeChild(this.root.firstChild); + } + this.constructor.styles.forEach((template)=>this.root.append(template.content.cloneNode(true))); + const fragment = this._createFragment(this.render()); + if (this._dynamicCssStore.length > 0) { + this._dynamicCssStore.forEach((styleTemplate)=>this.root.append(styleTemplate.content.cloneNode(true))); + } + this.root.prepend(fragment); + this.dispatchEvent(this._updateCustomEvent); + this._renderCounter++; + } + render() { + return ""; + } + static styles = []; + static is; +} +function __default(n) { + for(var l, e, s = arguments, t = 1, r = "", u = "", a = [ + 0 + ], c = function(n) { + 1 === t && (n || (r = r.replace(/^\s*\n\s*|\s*\n\s*$/g, ""))) ? a.push(n ? s[n] : r) : 3 === t && (n || r) ? (a[1] = n ? s[n] : r, t = 2) : 2 === t && "..." === r && n ? a[2] = Object.assign(a[2] || {}, s[n]) : 2 === t && r && !n ? (a[2] = a[2] || {})[r] = !0 : t >= 5 && (5 === t ? ((a[2] = a[2] || {})[e] = n ? r ? r + s[n] : s[n] : r, t = 6) : (n || r) && (a[2][e] += n ? r + s[n] : r)), r = ""; + }, h = 0; h < n.length; h++){ + h && (1 === t && c(), c(h)); + for(var i = 0; i < n[h].length; i++)l = n[h][i], 1 === t ? "<" === l ? (c(), a = [ + a, + "", + null + ], t = 3) : r += l : 4 === t ? "--" === r && ">" === l ? (t = 1, r = "") : r = l + r[0] : u ? l === u ? u = "" : r += l : '"' === l || "'" === l ? u = l : ">" === l ? (c(), t = 1) : t && ("=" === l ? (t = 5, e = r, r = "") : "/" === l && (t < 5 || ">" === n[h][i + 1]) ? (c(), 3 === t && (a = a[0]), t = a, (a = a[0]).push(this.apply(null, t.slice(1))), t = 0) : " " === l || "\t" === l || "\n" === l || "\r" === l ? (c(), t = 2) : r += l), 3 === t && "!--" === r && (t = 4, a = a[0]); + } + return c(), a.length > 2 ? a.slice(1) : a[1]; +} +const SVG_NS = "http://www.w3.org/2000/svg"; +function isArrayOfListeners(input) { + return input.every((i)=>typeof i === "function"); +} +function isSpecialKey(input) { + return input === "id" || input === "class"; +} +function isHReturn(input) { + return isObject(input) && input.element instanceof Element; +} +function h(type, props, ...children) { + const eventsAndListeners = []; + const queries = []; + const collection = []; + const element = type === "svg" ? document.createElementNS(SVG_NS, "svg") : document.createElement(type); + for(const key in props){ + if (typeof props[key] === "function") { + eventsAndListeners.push({ + event: key, + listener: props[key] + }); + } else if (Array.isArray(props[key]) && isArrayOfListeners(props[key])) { + props[key].forEach((listener)=>eventsAndListeners.push({ + event: key, + listener + })); + } else if (key[0] === "@") { + const idOrClass = key.slice(1); + if (isSpecialKey(idOrClass)) { + queries.push({ + kind: idOrClass, + selector: props[key].replace(/ .*/, "") + }); + element.setAttribute(idOrClass, props[key]); + } + } else if (props[key] === true) { + element.setAttribute(key, ""); + } else if (typeof props[key] === "object" && props[key] !== null) { + element.setAttribute(key, JSON.stringify(props[key])); + } else if (typeof props[key] === "string") { + element.setAttribute(key, props[key]); + } else if (props[key] === null || props[key] === false || props[key] === undefined) { + element.removeAttribute(key); + } + } + if (type === "svg") { + element.innerHTML = children.flat(2).reduce((acc, child)=>{ + return acc + (isHReturn(child) ? child.element.outerHTML : stringify(child)); + }, ""); + } else { + for (const child of children.flat(2)){ + if (isHReturn(child)) { + collection.push(...child.collection); + element.appendChild(child.element); + } else { + const str = stringify(child); + if (str) element.appendChild(document.createTextNode(str)); + } + } + } + if (queries.length || eventsAndListeners.length) { + collection.push({ + target: element, + queries, + eventsAndListeners + }); + } + return { + element, + collection + }; +} +const html = __default.bind(h); +function css(strings, ...values) { + const cssTemplates = []; + cssTemplates.push(createTemplate(``)); + return cssTemplates; +} +function customElement(tagName) { + return (clazz)=>{ + Object.defineProperty(clazz, "is", { + value: tagName + }); + window.customElements.define(tagName, clazz); + return clazz; + }; +} +function property({ reflect =true , render =true , wait =false , assert =false } = {}) { + return (protoOrDescriptor, name)=>{ + if (protoOrDescriptor.constructor.observedAttributes === undefined) { + protoOrDescriptor.constructor.observedAttributes = []; + } + if (reflect === true) { + protoOrDescriptor.constructor.observedAttributes.push(convertCamelToDash(name)); + } + if (protoOrDescriptor.__propertiesAndOptions === undefined) { + Object.defineProperty(protoOrDescriptor, "__propertiesAndOptions", { + enumerable: false, + configurable: true, + writable: false, + value: [] + }); + } + protoOrDescriptor.__propertiesAndOptions.push({ + property: name, + reflect, + render, + wait, + assert + }); + }; +} +function changeInlineStyles(element, [property, value]) { + if (property.slice(0, 2) === "--" && element.style.getPropertyValue(property) !== value) { + element.style.setProperty(property, value); + } else if (element.style[property] !== value) { + element.style[property] = value; + } +} +function changeCss(styles, ...elements) { + Object.entries(styles).forEach((entry)=>elements.forEach((element)=>changeInlineStyles(element, entry))); +} +function dispatchCustomEvent(eventName, element, { bubbles =true , composed =true , detail =null } = {}) { + return element.dispatchEvent(new CustomEvent(eventName, { + bubbles, + composed, + detail: detail === null ? { + id: element.id + } : detail + })); +} +var __decorate = this && this.__decorate || function(decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +const readingMethods = new Set([ + "readAsBinaryString", + "readAsDataURL", + "readAsText" +]); +function changeSelectColor(event) { + changeCss({ + color: "inherit" + }, event.target); +} +function validFileType(file, fileType) { + return fileType.split(",").map((s)=>s.trim()).includes(file.type); +} +let LabeledControls = class LabeledControls extends Shadow { + inputFile = null; + items = []; + static styles = css` + :host { + display: block; + box-sizing: border-box; + color: var(--labeledControlsColor, var(--neutralVeryDark, #425466)); + --labeledControlsInputBackground: #f6f9fc; + --labeledControlsPlaceholderColor: #8898aa; + line-height: 25px; + font-size: 17.5px; + } + *, + *::before, + *::after { + box-sizing: inherit; + } + + .group { + display: flex; + flex-direction: column; + } + .group ~ .group { + margin-top: 2px; + } + .group:not(:last-of-type) { + margin-bottom: 2px; + } + + @media (max-width: 640px) { + .group:first-of-type label { + padding-top: 0; + } + } + + .multi { + flex-direction: row; + flex-wrap: wrap; + } + .multi label { + width: 100%; + } + + .multi input { + max-width: 48.5%; + } + .multi input:last-of-type { + margin-left: 3%; + } + + .colorInherit { + color: inherit !important; + } + + label { + display: block; + padding-top: 8px; + color: var(--labeledControlsLabelColor, inherit); + font-size: var(--labeledControlsFontSize, 14px); + font-weight: var(--labeledControlsLabelFontWeight, 500); + text-align: start; + } + + input, + textarea, + select { + display: block; + font: inherit; + font-size: var(--labeledControlsFontSize, inherit); + font-weight: var(--labeledControlsInputFontWeight, 400); + color: var(--labeledControlsInputColor, inherit); + padding: var(--labeledControlsInputPadding, 5px 20px 8px 13px); + outline: none; + box-shadow: var(--labeledControlsInputBoxShadow, none); + border: none; + border-radius: 6px; + margin-left: auto; + margin-right: 0; + width: var(--labeledControlsInputWidthS, 100%); + max-width: var(--labeledControlsInputMaxWidthS, 100%); + height: var(--labeledControlsInputHeightS, auto); + transition: background-color 0.1s ease-in, color 0.1s ease-in; + background: var(--labeledControlsInputBackground); + /** remove the blue background button on click */ + -webkit-tap-highlight-color: transparent; + } + + input:focus-visible, + textarea:focus-visible, + select:focus-visible { + box-shadow: var( + --labeledControlsFocusVisibleBoxShadow, + 0 0 0 1px #e4effa + ); + background: var(--labeledControlsFocusVisibleBackground, transparent); + } + input:focus-visible, + textarea:focus-visible { + color: var( + --labeledControlsFocusVisibleColor, + var(--labeledControlsPlaceholderColor) + ); + } + + input { + accent-color: var(--labeledControlsInputAccentColor, var(--accentColor)); + } + + textarea { + min-height: 90px; + } + + option { + padding: 0; + margin: 0; + width: 100%; + } + + select { + appearance: none; + -webkit-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='%23424770' fill-rule='evenodd' d='M573.888889,46.3409091 C573.444444,45.8863636 572.777778,45.8863636 572.333333,46.3409091 C571.888889,46.7954545 571.888889,47.4772727 572.333333,47.9318182 L575.333333,51 L572.333333,54.0681818 C571.888889,54.5227273 571.888889,55.2045455 572.333333,55.6590909 C572.555556,55.8863636 572.888889,56 573.111111,56 C573.444444,56 573.666667,55.8863636 573.888889,55.6590909 L577.666667,51.7954545 C578.111111,51.3409091 578.111111,50.6590909 577.666667,50.2045455 L573.888889,46.3409091 Z' transform='rotate(90 314 -258)'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-size: 10px 6px; + background-position-x: calc(100% - 22px); + background-position-y: 16px; + color: var(--selectFirstColor, var(--labeledControlsPlaceholderColor)); + } + + input::placeholder, + textarea::placeholder { + color: var(--labeledControlsPlaceholderColor); + /** https://stackoverflow.com/questions/19621306/css-placeholder-text-color-on-firefox */ + opacity: 1; + } + + .group *:disabled { + cursor: not-allowed; + } + + input[type="button"] { + cursor: pointer; + } + + input[type="checkbox"] { + width: 26px; + height: 24px; + cursor: pointer; + } + + input[type="date"] { + color: var(--labeledControlsPlaceholderColor); + } + + input::-webkit-calendar-picker-indicator { + background-image: url('data:image/svg+xml;utf8,'); + } + + input[type="color"] { + cursor: pointer; + } + input::-webkit-color-swatch-wrapper { + padding: 0; + } + input::-webkit-color-swatch { + border: none; + box-shadow: 0px 0px 0px 1px #adbdcc; + border-radius: 6px; + } + input::-moz-color-swatch { + border: none; + box-shadow: 0px 0px 0px 1px #adbdcc; + border-radius: 6px; + } + + input[type="file"] { + cursor: pointer; + } + + input::file-selector-button { + display: none; + } + + .visuallyHidden { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + } + + @media (min-width: 640px) { + .group { + flex-direction: row; + } + .group ~ .group { + margin-top: 8px; + } + .multi { + flex-wrap: nowrap; + } + .multi label { + width: auto; + } + .multi input, + .multi select { + max-width: 33.8%; + } + .multi input:last-of-type, + .multi select:last-of-type { + margin-left: 1.4%; + } + label { + margin-right: 16px; + padding: var(--labeledControlsLabelPadding, 5px 0 8px); + font-size: inherit; + } + input, + textarea, + select { + max-width: var(--labeledControlsInputMaxWidthM, 69%); + width: var(--labeledControlsInputWidthM, 100%); + } + textarea { + min-height: 140px; + } + } + `; + createLabeledControls({ kind , id , label , attr ={} , options =[] , isVisuallyHidden , hasFirstSelectedDisabled }) { + return kind === "input" ? html` + ` : kind === "textarea" ? html` +