UNPKG

@aegisjsproject/core

Version:

A fast, secure, modern, light-weight, and simple JS library for creating web components and more!

790 lines (691 loc) 23.2 kB
'use strict'; var events_js = require('@aegisjsproject/callback-registry/events.js'); var callbackRegistry_js = require('@aegisjsproject/callback-registry/callbackRegistry.js'); var css_js = require('@aegisjsproject/parsers/css.js'); var base_js = require('@aegisjsproject/sanitizer/config/base.js'); var html_js = require('@aegisjsproject/parsers/html.js'); var xml_js = require('@aegisjsproject/parsers/xml.js'); var svg_js = require('@aegisjsproject/parsers/svg.js'); var json_js = require('@aegisjsproject/parsers/json.js'); var math_js = require('@aegisjsproject/parsers/math.js'); var url_js = require('@aegisjsproject/url/url.js'); var state_js = require('@aegisjsproject/state/state.js'); var router_js = require('@aegisjsproject/router/router.js'); var component_js = require('@aegisjsproject/component/component.js'); const lightCSS = css_js.createCSSParser({ media: '(prefers-color-scheme: light)', baseURL: document.baseURI }); const darkCSS = css_js.createCSSParser({ media: '(prefers-color-scheme: dark)', baseURL: document.baseURI }); function styleSheetToFile(styleSheet, filename = 'styles.css') { if (! (styleSheet instanceof CSSStyleSheet)) { throw new TypeError('Not a CSSStyleSheet.'); } else { const css = Array.from(styleSheet.cssRules, rule => rule.cssText); return new File(css, filename, { type: styleSheet.type }); } } function styleSheetToLink(styleSheet) { const file = styleSheetToFile(styleSheet); const link = document.createElement('link'); link.relList.add('stylesheet'); link.disabled = styleSheet.disabled; if (styleSheet.media.length !== 0) { link.media = styleSheet.media.mediaText; } link.href = URL.createObjectURL(file); return link; } const ESCAPED_PATTERN = /&(?![a-zA-Z\d]{2,5};|#\d{1,3};)/g; const escapeAttrVal = str => str.toString() // Do not double-escape .replaceAll(ESCAPED_PATTERN, '&amp;') .replaceAll('"', '&quot;'); function createAttribute(name, value = '', namespace) { const attr = typeof namespace === 'string' ? document.createAttributeNS(namespace, name) : document.createAttribute(name); attr.value = value; return attr; } const stringifyAttr = attr => `${attr.name}="${escapeAttrVal(attr.value)}"`; const escape = str => str.toString() .replaceAll(ESCAPED_PATTERN, '&amp;') .replaceAll('<', '&lt;') .replaceAll('>', '&gt;') .replaceAll('"', '&quot;'); const getUniqueSelector = (prefix = '_aegis-scope') => `${prefix}-${crypto.randomUUID()}`; function replaceStyles(target, ...sheets) { if (! (target instanceof Node)) { throw new TypeError('Expected target to be a Document, DocumentFragment, ShadowRoot, or Element.'); } else if (target instanceof Document || target instanceof ShadowRoot) { target.adoptedStyleSheets = sheets; } else if (! (target instanceof Element || DocumentFragment)) { throw new TypeError('Expected target to be a Document, DocumentFragment, ShadowRoot, or Element.'); } else if (target.shadowRoot instanceof ShadowRoot) { return replaceStyles(target.shadowRoot, ...sheets); } else if (! target.isConnected) { throw new TypeError('Target is not connected to the document yet.'); } else { return replaceStyles(target.getRootNode({ composed: false }), ...sheets); } } function addStyles(target, ...sheets) { if (! (target instanceof Node)) { throw new TypeError('Expected target to be a Document, DocumentFragment, ShadowRoot, or Element.'); } else if (target instanceof Document || target instanceof ShadowRoot) { replaceStyles(target, ...target.adoptedStyleSheets, ...sheets); } else if (target.shadowRoot instanceof ShadowRoot) { return addStyles(target.shadowRoot, ...sheets); } else { return addStyles(target.getRootNode({ composed: false }), ...sheets); } } function appendTo(target, ...items) { if (! (target instanceof Node)) { throw new TypeError('Target must be a Node.'); } else { const styles = items.filter(item => item instanceof CSSStyleSheet); const children = items.filter(item => typeof item === 'string' || item instanceof Node); if (styles.length !== 0) { addStyles(target, ...styles); } if (children.length !== 0) { target.append(...children); } events_js.attachListeners(target instanceof ShadowRoot ? target.host : target); } } function prependTo(target, ...items) { if (! (target instanceof Node)) { throw new TypeError('Target must be a Node.'); } else { const styles = items.filter(item => item instanceof CSSStyleSheet); const children = items.filter(item => typeof item === 'string' || item instanceof Node); if (styles.length !== 0) { addStyles(target, ...styles); } if (children.length !== 0) { target.prepend(...children); } events_js.attachListeners(target instanceof ShadowRoot ? target.host : target); } } function replace(target, ...items) { if (! (target instanceof Node)) { throw new TypeError('Target must be a Node.'); } else { const styles = items.filter(item => item instanceof CSSStyleSheet); const children = items.filter(item => typeof item === 'string' || item instanceof Node); if (styles.length !== 0) { replaceStyles(target, ...styles); } if (children.length !== 0) { target.replaceChildren(...children); } events_js.attachListeners(target instanceof ShadowRoot ? target.host : target); } } const toData = ([name, val]) => ['data-' + name.replaceAll(/[A-Z]/g, c => `-${c.toLowerCase()}`), val]; const attr = attrs => Object.entries(attrs).map(([attr, val]) => { switch(typeof val) { case 'string': return createAttribute(attr, val); case 'number': case 'bigint': return Number.isNaN(val) ? undefined : createAttribute(attr, val.toString()); case 'boolean': return val ? createAttribute(attr) : undefined; case 'undefined': return undefined; case 'function': return callbackRegistry_js.createCallback(val); case 'object': if (val === null) { return undefined; } else if (val instanceof URL) { return createAttribute(attr, val.href); } else if (val instanceof Date) { return createAttribute(attr, val.toISOString()); } else { return createAttribute(attr, val.toString()); } case 'symbol': return createAttribute(attr, val.description); default: return createAttribute(attr, val.toString()); } }).filter(attr => attr instanceof Attr) .map(stringifyAttr) .join(' '); function data(dataObj) { return attr(Object.fromEntries(Object.entries(dataObj).map(toData))); } const DATE_FORMAT = { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', }; const formatDate = (date, { weekday = DATE_FORMAT.weekday, month = DATE_FORMAT.month, day = DATE_FORMAT.day, year = DATE_FORMAT.year, hour = DATE_FORMAT.hour, minute = DATE_FORMAT.minute, } = DATE_FORMAT) => date.toLocaleString(navigator.language, { weekday, month, day, year, hour, minute, }); const formatArray = 'Intl' in globalThis && Intl.ListFormat instanceof Function ? arr => new Intl.ListFormat().format(arr.map(stringify)) : arr => arr.join(', '); const formatNumber = 'Intl' in globalThis && Intl.NumberFormat instanceof Function ? num => new Intl.NumberFormat().format(num) : num => num.toString(); const stringify = thing => { switch(typeof thing) { case 'string': return thing; case 'boolean': return thing ? 'true' : 'false'; case 'symbol': return thing.description; case 'number': case 'bigint': return formatNumber(thing); case 'undefined': return ''; case 'function': return callbackRegistry_js.createCallback(thing); case 'object': if (thing === null) { return ''; } else if (Array.isArray(thing)) { return formatArray(thing); } else if (thing instanceof HTMLTemplateElement) { const el = document.createElement('div'); el.append(thing.content.cloneNode(true)); return el.innerHTML; } else if (thing instanceof Element) { return thing.outerHTML; } else if (thing instanceof DocumentFragment) { const el = document.createElement('div'); el.append(thing.cloneNode(true)); return el.innerHTML; } else if (thing instanceof Blob) { return URL.createObjectURL(thing); } else if(thing instanceof Date) { return formatDate(thing); } else if (thing instanceof CSSStyleSheet) { return styleSheetToLink(thing).outerHTML; } else if (thing instanceof DOMTokenList) { return [...thing].join(' '); } else if (thing instanceof NodeList || thing instanceof HTMLCollection || thing instanceof HTMLFormControlsCollection) { return [...thing].map(el => el.outerHTML).join('\n'); } else if (thing instanceof MediaList) { return thing.mediaText; } else if (thing instanceof Attr) { return stringifyAttr(thing); } else if (thing instanceof NamedNodeMap) { return Array.from(thing, stringifyAttr).join(' '); } else if ('TrustedType' in globalThis && thing instanceof globalThis.TrustedType) { return thing; } else if ('Iterator' in globalThis && thing instanceof globalThis.Iterator) { return stringify([...thing]); } else if (thing instanceof AbortSignal) { return callbackRegistry_js.registerSignal(thing); } else if (thing instanceof AbortController) { return callbackRegistry_js.registerController(thing); } else { return thing.toString(); } default: return thing.toString(); } }; const text = (strings, ...values) => Array.isArray(strings) && Array.isArray(strings.raw) ? String.raw(strings, ...values.map(stringify)) : String.raw({ raw: Array.isArray(strings) ? strings : [strings] }, ...values.map(stringify)); const registry = new Set(); function registerComponent(tag, constructor, opts) { if (registry.has(tag)) { throw new Error(`<${tag}> is already registered.`); } else { customElements.define(tag, constructor, opts); registry.add(tag); return constructor; } } function getRegisteredComponentTags() { return Object.freeze(Array.from(registry)); } function getRegisteredComponents() { return Object.freeze(Array.from(registry, tag => customElements.get(tag))); } const sanitizer = Object.freeze({ ...base_js.sanitizer, get elements() { return [...base_js.sanitizer.elements, ...getRegisteredComponentTags()]; }, get attributes() { return ['theme', ...base_js.sanitizer.attributes]; }, }); const html = html_js.createHTMLParser(sanitizer, { mapper: stringify }); function htmlUnsafe(strings, ...values) { const html = String.raw(strings, ...values.map(stringify)); const frag = document.createDocumentFragment(); const tmp = document.createElement('div'); tmp.setHTMLUnsafe(html); frag.append(...tmp.childNodes); return frag; } function docUnsafe(strings, ...values) { const html = String.raw(strings, ...values.map(stringify)); return Document.parseHTMLUnsafe(html); } function htmlToFile(html, filename = 'document.html', { elements = base_js.sanitizer.elements, attributes = base_js.sanitizer.attributes, comments = base_js.sanitizer.comments, ...rest } = base_js.sanitizer) { const doc = Document.parseHTML(html, { elements, attributes, comments, ...rest }); return new File( [ `<!DOCTYPE ${doc.doctype instanceof Node ? doc.doctype.name : 'html' }>`, doc.documentElement.outerHTML, ], filename, { type: doc.contentType } ); } /** * @copyright 2023-2024 Chris Zuber <admin@kernvalley.us> */ function setProp(el, prop, val, { policy, } = {}) { switch(getPropertyType(el.tagName, prop)) { case 'TrustedScript': el[prop] = createScript(val, { policy }); break; case 'TrustedScriptURL': el[prop] = createScriptURL(val, { policy }); break; case 'TrustedHTML': el[prop] = createHTML(val, { policy }); break; default: el[prop] = val; } } function setAttr(el, attr, val, { elementNs, policy, } = {}) { switch(getAttributeType(el.tagName, attr, elementNs)) { case 'TrustedScriptURL': if (typeof elementNs === 'string') { el.setAttributeNs(elementNs, attr, createScriptURL(val, { policy })); } else { el.setAttribute(attr, createScriptURL(val, { policy })); } break; case 'TrustedScript': if (typeof elementNs === 'string') { el.setAttributeNS(elementNs, attr, createScript(val, { policy })); } else { el.setAttribute(attr, createScript(val, { policy })); } break; case 'TrustedHTML': if (typeof elementNs === 'string') { el.setAttributeNS(elementNs, attr, createHTML(val, { policy })); } else { el.setAttribute(attr, createHTML(val, { policy })); } break; default: if (typeof elementNs === 'string') { el.setAttributeNS(elementNs, attr, val); } else { el.setAttribute(attr, val); } } } function supported() { return 'trustedTypes' in globalThis && trustedTypes.createPolicy instanceof Function; } function isTrustPolicy(policy) { if ('TrustedTypePolicy' in globalThis && policy instanceof TrustedTypePolicy) { return true; } else { return policy != null && policy.createHTML instanceof Function; } } function hasDefaultPolicy() { return supported() && isTrustPolicy(trustedTypes.defaultPolicy); } function getAttributeType(tagName, attribute, elementNs) { if (supported()) { return trustedTypes.getAttributeType(tagName.toLowerCase(), attribute, elementNs); } else { return null; } } function getPropertyType(tagName, property) { if (supported()) { return trustedTypes.getPropertyType(tagName.toLowerCase(), property); } else { return null; } } function isHTML(input) { if (supported()) { return trustedTypes.isHTML(input); } else { return typeof input === 'string'; } } function isScript(input) { if (supported()) { return trustedTypes.isScript(input); } else { return typeof input === 'string'; } } function isScriptURL(input) { if (supported()) { return trustedTypes.isScriptURL(input); } else { return typeof input === 'string' || input instanceof URL; } } function isTrustedType(input) { if (supported()) { return trustedTypes.isHTML(input) || trustedTypes.isScript(input) || trustedTypes.isScriptURL(input); } else { return true; } } function createHTML(input, { policy = getDefaultPolicy() } = {}) { if (isTrustPolicy(policy) && ! isHTML(input)) { return policy.createHTML(input); } else { return input; } } function createScript(input, { policy = getDefaultPolicy() } = {}) { if (isTrustPolicy(policy) && ! isScript(input)) { return policy.createScript(input); } else { return input; } } function createScriptURL(input, { policy = getDefaultPolicy() } = {}) { if (isTrustPolicy(policy) && ! isScriptURL(input)) { return policy.createScriptURL(input); } else { return input; } } function createPolicy(name, { createHTML = () => { throw new TypeError('This policy does not provide `createHTML()`'); }, createScript = () => { throw new TypeError('This policy does not provide `createScript()`'); }, createScriptURL = () => { throw new TypeError('This policy does not provide `createScriptURL()`'); }, }) { if (supported()) { return trustedTypes.createPolicy(name, { createHTML, createScript, createScriptURL }); } else { return Object.freeze({ name, createHTML: (input, ...args) => createHTML(input.toString(), ...args), createScript: (input, ...args) => createScript(input.toString(), ...args), createScriptURL: (input, ...args) => createScriptURL(input.toString(), ...args), }); } } function createSanitizerPolicy(name = 'aegis#html') { return createPolicy(name, { createHTML(input, sanitizer) { const el = document.createElement('div'); el.setHTML(input, { sanitizer }); return el.innerHTML; } }); } function getDefaultPolicy() { return 'trustedTypes' in globalThis ? trustedTypes.defaultPolicy : null; } function clone(el, deep = true) { if (el.shadowRoot instanceof ShadowRoot && el.shadowRoot.clonable) { const clone = el.cloneNode(deep); clone.shadowRoot.adoptedStyleSheets = el.shadowRoot.adoptedStyleSheets; return clone; } else { return el.cloneNode(deep); } } function createComponent({ tag = 'div', template = '<slot></slot>', styles, mode = 'open', delegatesFocus = false, clonable = true, slotAssignment = 'named', exportParts, sanitizer: { elements, attributes, comments = false, dataAttributes = true, ...sanitizer } = {}, ...attrs }) { const el = document.createElement(tag); const shadow = el.attachShadow({ mode, clonable, delegatesFocus, slotAssignment }); if (typeof template === 'string' && template.length !== 0) { const frag = document.createDocumentFragment(); frag.setHTML(template, { elements, attributes, comments, dataAttributes, ...sanitizer }); shadow.append(frag); } else if (! (template instanceof Node)) { throw new TypeError('Missing or invalid template.'); } else if (template instanceof HTMLTemplateElement) { shadow.append(template.content.cloneNode(true)); } else if (template instanceof DocumentFragment) { shadow.append(template.cloneNode(true)); } else { shadow.append(template); } if (Array.isArray(styles)) { Promise.all(styles.map(sheet => typeof sheet === 'string' ? new CSSStyleSheet().replace(sheet) : sheet )).then(sheets => shadow.adoptedStyleSheets = sheets); } else if (typeof styles === 'string' && styles.length !== 0) { new CSSStyleSheet().replace(styles) .then(sheet => shadow.adoptedStyleSheets = [sheet]); } if (typeof exportParts === 'string') { el.setAttribute('exportparts', exportParts); } else if (Array.isArray(exportParts)){ el.setAttribute('exportparts', exportParts.join(', ')); } else if (typeof exportParts === 'object' && exportParts !== null) { el.setAttribute( 'exportparts', Object.entries(exportParts).map(([k, v]) => `${k}:${v}`).join(', ') ); } Object.entries(attrs).forEach(([attr, val]) => el.setAttribute(attr, val)); return el; } Object.defineProperty(exports, "EVENTS", { enumerable: true, get: function () { return events_js.EVENTS; } }); Object.defineProperty(exports, "attachListeners", { enumerable: true, get: function () { return events_js.attachListeners; } }); Object.defineProperty(exports, "disconnectEventsObserver", { enumerable: true, get: function () { return events_js.disconnectEventsObserver; } }); Object.defineProperty(exports, "observeEvents", { enumerable: true, get: function () { return events_js.observeEvents; } }); Object.defineProperty(exports, "registerEventAttribute", { enumerable: true, get: function () { return events_js.registerEventAttribute; } }); Object.defineProperty(exports, "setGlobalErrorHandler", { enumerable: true, get: function () { return events_js.setGlobalErrorHandler; } }); Object.defineProperty(exports, "FUNCS", { enumerable: true, get: function () { return callbackRegistry_js.FUNCS; } }); Object.defineProperty(exports, "callCallback", { enumerable: true, get: function () { return callbackRegistry_js.callCallback; } }); Object.defineProperty(exports, "closeRegistration", { enumerable: true, get: function () { return callbackRegistry_js.closeRegistration; } }); Object.defineProperty(exports, "createCallback", { enumerable: true, get: function () { return callbackRegistry_js.createCallback; } }); Object.defineProperty(exports, "getCallback", { enumerable: true, get: function () { return callbackRegistry_js.getCallback; } }); Object.defineProperty(exports, "getHost", { enumerable: true, get: function () { return callbackRegistry_js.getHost; } }); Object.defineProperty(exports, "hasCallback", { enumerable: true, get: function () { return callbackRegistry_js.hasCallback; } }); Object.defineProperty(exports, "listCallbacks", { enumerable: true, get: function () { return callbackRegistry_js.listCallbacks; } }); Object.defineProperty(exports, "registerCallback", { enumerable: true, get: function () { return callbackRegistry_js.registerCallback; } }); Object.defineProperty(exports, "createCSSParser", { enumerable: true, get: function () { return css_js.createCSSParser; } }); Object.defineProperty(exports, "createStyleSheet", { enumerable: true, get: function () { return css_js.createStyleSheet; } }); Object.defineProperty(exports, "css", { enumerable: true, get: function () { return css_js.css; } }); Object.defineProperty(exports, "createHTMLParser", { enumerable: true, get: function () { return html_js.createHTMLParser; } }); Object.defineProperty(exports, "doc", { enumerable: true, get: function () { return html_js.doc; } }); Object.defineProperty(exports, "xml", { enumerable: true, get: function () { return xml_js.xml; } }); Object.defineProperty(exports, "svg", { enumerable: true, get: function () { return svg_js.svg; } }); Object.defineProperty(exports, "json", { enumerable: true, get: function () { return json_js.json; } }); Object.defineProperty(exports, "math", { enumerable: true, get: function () { return math_js.math; } }); Object.defineProperty(exports, "url", { enumerable: true, get: function () { return url_js.url; } }); exports.DATE_FORMAT = DATE_FORMAT; exports.addStyles = addStyles; exports.appendTo = appendTo; exports.attr = attr; exports.clone = clone; exports.createComponent = createComponent; exports.createHTML = createHTML; exports.createPolicy = createPolicy; exports.createSanitizerPolicy = createSanitizerPolicy; exports.createScript = createScript; exports.createScriptURL = createScriptURL; exports.darkCSS = darkCSS; exports.data = data; exports.docUnsafe = docUnsafe; exports.escape = escape; exports.escapeAttrVal = escapeAttrVal; exports.formatDate = formatDate; exports.getAttributeType = getAttributeType; exports.getDefaultPolicy = getDefaultPolicy; exports.getPropertyType = getPropertyType; exports.getRegisteredComponentTags = getRegisteredComponentTags; exports.getRegisteredComponents = getRegisteredComponents; exports.getUniqueSelector = getUniqueSelector; exports.hasDefaultPolicy = hasDefaultPolicy; exports.html = html; exports.htmlToFile = htmlToFile; exports.htmlUnsafe = htmlUnsafe; exports.isHTML = isHTML; exports.isScript = isScript; exports.isScriptURL = isScriptURL; exports.isTrustPolicy = isTrustPolicy; exports.isTrustedType = isTrustedType; exports.lightCSS = lightCSS; exports.prependTo = prependTo; exports.registerComponent = registerComponent; exports.replace = replace; exports.replaceStyles = replaceStyles; exports.setAttr = setAttr; exports.setProp = setProp; exports.stringify = stringify; exports.styleSheetToFile = styleSheetToFile; exports.styleSheetToLink = styleSheetToLink; exports.text = text; Object.keys(state_js).forEach(function (k) { if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, { enumerable: true, get: function () { return state_js[k]; } }); }); Object.keys(router_js).forEach(function (k) { if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, { enumerable: true, get: function () { return router_js[k]; } }); }); Object.keys(component_js).forEach(function (k) { if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, { enumerable: true, get: function () { return component_js[k]; } }); });