UNPKG

@aegisjsproject/core

Version:

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

135 lines (113 loc) 4.01 kB
import { stringify } from '../stringify.js'; import { elements, attributes } from '@aegisjsproject/sanitizer/config/base.js'; import { getRegisteredComponentTags } from '../componentRegistry.js'; import { createHTMLParser, doc } from '@aegisjsproject/parsers/html.js'; import { isTrustPolicy } from '../trust.js'; import { observeEvents } from '@aegisjsproject/callback-registry'; const sanitizerConfig = Object.freeze({ comments: true, dataAttributes: true, get elements() { return [...elements, ...getRegisteredComponentTags()]; }, get attributes() { return ['theme', ...attributes]; }, }); export const html = createHTMLParser(sanitizerConfig, { mapper: stringify }); export const el = (...args) => html.apply(null, args)?.firstElementChild; export function createShadowParser({ tagName = 'div', mode = 'open', clonable = true, serializable = true, delegatesFocus = false, slotAssignment = 'named', sanitizer = sanitizerConfig, exportParts, callback = ({ shadowRoot }) => observeEvents(shadowRoot), } = {}) { const parser = createHTMLParser(sanitizer, { mapper: stringify }); if (Array.isArray(exportParts)) { exportParts = exportParts.join(', '); } else if (typeof exportParts === 'object') { exportParts = Object.entries(exportParts).map(([k, v]) => `${k}: ${v}`).join(', '); } return (...args) => { const host = document.createElement(tagName); const shadowRoot = host.attachShadow({ mode, clonable, serializable, delegatesFocus, slotAssignment }); const template = parser.apply(parser, args); shadowRoot.adoptedStyleSheets = Array.from( template.querySelectorAll('style'), style => { const sheet = new CSSStyleSheet({ media: style.media, disabled: style.hasAttribute('disabled'), // `style.disabled` != `<style disabled>` baseURL: style.dataset.baseUrl, // Nothing maps to `baseURL`, so use `data-base-url` }); sheet.replaceSync(style.textContent); style.remove(); return sheet; } ); shadowRoot.append(template); if (typeof exportParts === 'string') { host.setAttribute('exportparts', exportParts); } if (callback instanceof Function) { callback.call(host, { shadowRoot, host }); } return host; }; } export const shadow = createShadowParser(); export const styledShadow = createShadowParser({ mode: 'closed', clonable: false, sanitizer: { elements: [...sanitizerConfig.elements, 'style'] }, }); export function createTrustedHTMLTemplate(policy) { if (isTrustPolicy(policy)) { return (strings, ...values) => policy.createHTML(String.raw(strings, ...values.map(stringify))); } else { throw new TypeError('Not a Trusted Types Policy.'); } } export function trustedHTML(strings, ...values) { if (isTrustPolicy(trustedTypes?.defaultPolicy)) { return trustedTypes.defaultPolicy.createHTML(String.raw(strings, ...values.map(stringify))); } else if (! ('trustedTypes' in globalThis)) { throw new Error('Trusted Types is not supported.'); } else { throw new TypeError('No default Trusted Types Policy is available.'); } } export 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; } export function docUnsafe(strings, ...values) { const html = String.raw(strings, ...values.map(stringify)); return Document.parseHTMLUnsafe(html); } export function htmlToFile(html, filename = 'document.html', { elements = sanitizerConfig.elements, attributes = sanitizerConfig.attributes, comments = sanitizerConfig.comments, ...rest } = sanitizerConfig) { 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 } ); } export { createHTMLParser, doc };