diff --git a/package.json b/package.json index 7ac78a4..2cb0d58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bd/core", - "version": "1.3.0", + "version": "1.4.0", "type": "module", "description": "百搭UI组件库的核心", "main": "dist/index.js", diff --git a/src/constants.js b/src/constants.js index 8fd992c..3ecec6f 100644 --- a/src/constants.js +++ b/src/constants.js @@ -27,7 +27,8 @@ const boolMap = Object.create(null) 'noShade', 'open', 'readOnly', - 'selected' + 'selected', + 'loading' ].forEach(function (name) { boolMap[name.toLowerCase()] = name }) diff --git a/src/html.js b/src/html.js index e1d8c00..45ed0a9 100644 --- a/src/html.js +++ b/src/html.js @@ -1,80 +1,57 @@ -import { WC_PART, NO_CHANGE, NOTHING } from './constants.js' +import { boolMap, WC_PART, NO_CHANGE, NOTHING } from './constants.js' -var ENABLE_EXTRA_SECURITY_HOOKS = true - -var identityFunction = value => value -var noopSanitizer = (_node, _name, _type) => identityFunction -var setSanitizer = newSanitizer => { - if (!ENABLE_EXTRA_SECURITY_HOOKS) { - return - } - if (sanitizerFactoryInternal !== noopSanitizer) { - throw new Error( - `Attempted to overwrite existing lit-html security policy. setSanitizeDOMValueFactory should be called at most once.` - ) - } - sanitizerFactoryInternal = newSanitizer -} -var _testOnlyClearSanitizerFactoryDoNotCallOrElse = () => { - sanitizerFactoryInternal = noopSanitizer -} -var createSanitizer = (node, name, type) => { - return sanitizerFactoryInternal(node, name, type) -} -var boundAttributeSuffix = '$wc$' -var marker = `wc$${String(Math.random()).slice(9)}$` -var markerMatch = '?' + marker -var nodeMarker = `<${markerMatch}>` -var createMarker = (v = '') => document.createComment(v) -var isPrimitive = value => +const boundAttributeSuffix = '$wc$' +const marker = `wc$${String(Math.random()).slice(9)}$` +const markerMatch = '?' + marker +const nodeMarker = `<${markerMatch}>` +const createMarker = (v = '') => document.createComment(v) +const isPrimitive = value => value === null || (typeof value != 'object' && typeof value != 'function') -var isArray = Array.isArray -var isIterable = value => +const isArray = Array.isArray +const isIterable = value => isArray(value) || typeof (value === null || value === void 0 ? false : value[Symbol.iterator]) === 'function' -var SPACE_CHAR = `[ \n\f\r]` -var ATTR_VALUE_CHAR = `[^ \n\f\r"'\`<>=]` -var NAME_CHAR = `[^\\s"'>=/]` -var textEndRegex = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g -var COMMENT_START = 1 -var TAG_NAME = 2 -var DYNAMIC_TAG_NAME = 3 -var commentEndRegex = /-->/g -var comment2EndRegex = />/g -var tagEndRegex = new RegExp( +const SPACE_CHAR = `[ \n\f\r]` +const ATTR_VALUE_CHAR = `[^ \n\f\r"'\`<>=]` +const NAME_CHAR = `[^\\s"'>=/]` +const textEndRegex = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g +const COMMENT_START = 1 +const TAG_NAME = 2 +const DYNAMIC_TAG_NAME = 3 +const commentEndRegex = /-->/g +const comment2EndRegex = />/g +const tagEndRegex = new RegExp( `>|${SPACE_CHAR}(?:(${NAME_CHAR}+)(${SPACE_CHAR}*=${SPACE_CHAR}*(?:${ATTR_VALUE_CHAR}|("|')|))|$)`, 'g' ) -var ENTIRE_MATCH = 0 -var ATTRIBUTE_NAME = 1 -var SPACES_AND_EQUALS = 2 -var QUOTE_CHAR = 3 -var singleQuoteAttrEndRegex = /'/g -var doubleQuoteAttrEndRegex = /"/g -var rawTextElement = /^(?:script|style|textarea|title)$/i -var HTML_RESULT = 1 -var SVG_RESULT = 2 -var ATTRIBUTE_PART = 1 -var CHILD_PART = 2 -var PROPERTY_PART = 3 -var BOOLEAN_ATTRIBUTE_PART = 4 -var EVENT_PART = 5 -var ELEMENT_PART = 6 -var COMMENT_PART = 7 +const ENTIRE_MATCH = 0 +const ATTRIBUTE_NAME = 1 +const SPACES_AND_EQUALS = 2 +const QUOTE_CHAR = 3 +const singleQuoteAttrEndRegex = /'/g +const doubleQuoteAttrEndRegex = /"/g +const rawTextElement = /^(?:script|style|textarea|title)$/i +const HTML_RESULT = 1 +const SVG_RESULT = 2 +const ATTRIBUTE_PART = 1 +const CHILD_PART = 2 +const EVENT_PART = 5 +const ELEMENT_PART = 6 +const COMMENT_PART = 7 -var templateCache = new WeakMap() -var walker = document.createTreeWalker(document, 129, null, false) -var sanitizerFactoryInternal = noopSanitizer -var getTemplateHtml = (strings, type) => { - const len = strings.length - 1 - const attrNames = [] +const templateCache = new WeakMap() +const walker = document.createTreeWalker(document, 129, null, false) + +function getTemplateHtml(strings, type) { + let len = strings.length - 1 + let attrNames = [] let html2 = type === SVG_RESULT ? '' : '' let rawTextEndRegex let regex = textEndRegex for (let i = 0; i < len; i++) { - const s = strings[i] + let s = strings[i] let attrNameEndIndex = -1 let attrName let lastIndex = 0 @@ -131,7 +108,7 @@ var getTemplateHtml = (strings, type) => { } } - const end = + let end = regex === tagEndRegex && strings[i + 1].startsWith('/>') ? ' ' : '' html2 += regex === textEndRegex @@ -147,7 +124,7 @@ var getTemplateHtml = (strings, type) => { marker + (attrNameEndIndex === -2 ? (attrNames.push(void 0), i) : end) } - const htmlResult = + let htmlResult = html2 + (strings[len] || '') + (type === SVG_RESULT ? '' : '') if (!Array.isArray(strings) || !strings.hasOwnProperty('raw')) { let message = 'invalid template strings array' @@ -162,44 +139,43 @@ class Template { let node let nodeIndex = 0 let attrNameIndex = 0 - const partCount = strings.length - 1 - const parts = this.parts - const [html2, attrNames] = getTemplateHtml(strings, type) + let partCount = strings.length - 1 + let parts = this.parts + let [html2, attrNames] = getTemplateHtml(strings, type) this.el = Template.createElement(html2, options) walker.currentNode = this.el.content if (type === SVG_RESULT) { - const content = this.el.content - const svgElement = content.firstChild + let content = this.el.content + let svgElement = content.firstChild svgElement.remove() content.append(...svgElement.childNodes) } while ((node = walker.nextNode()) !== null && parts.length < partCount) { if (node.nodeType === 1) { if (node.hasAttributes()) { - const attrsToRemove = [] - for (const name of node.getAttributeNames()) { + let attrsToRemove = [] + for (let name of node.getAttributeNames()) { if ( name.endsWith(boundAttributeSuffix) || name.startsWith(marker) ) { - const realName = attrNames[attrNameIndex++] + let realName = attrNames[attrNameIndex++] attrsToRemove.push(name) if (realName !== void 0) { - const value = node.getAttribute( + let value = node.getAttribute( realName.toLowerCase() + boundAttributeSuffix ) - const statics = value.split(marker) - const m = /([.?@])?(.*)/.exec(realName) + let statics = value.split(marker) + let m = /([:@])?(.*)/.exec(realName) + parts.push({ type: ATTRIBUTE_PART, index: nodeIndex, name: m[2], strings: statics, ctor: - m[1] === '.' + m[1] === ':' ? PropertyPart - : m[1] === '?' - ? BooleanAttributePart : m[1] === '@' ? EventPart : AttributePart @@ -212,13 +188,13 @@ class Template { } } } - for (const name of attrsToRemove) { + for (let name of attrsToRemove) { node.removeAttribute(name) } } if (rawTextElement.test(node.tagName)) { - const strings2 = node.textContent.split(marker) - const lastIndex = strings2.length - 1 + let strings2 = node.textContent.split(marker) + let lastIndex = strings2.length - 1 if (lastIndex > 0) { node.textContent = '' for (let i = 0; i < lastIndex; i++) { @@ -230,7 +206,7 @@ class Template { } } } else if (node.nodeType === 8) { - const data = node.data + let data = node.data if (data === markerMatch) { parts.push({ type: CHILD_PART, index: nodeIndex }) } else { @@ -259,7 +235,7 @@ function resolveDirective(part, value, parent = part, attributeIndex) { ? parent.__directives?.[attributeIndex] : parent.__directive - const nextDirectiveConstructor = isPrimitive(value) + let nextDirectiveConstructor = isPrimitive(value) ? void 0 : value['_$litDirective$'] @@ -294,6 +270,7 @@ function resolveDirective(part, value, parent = part, attributeIndex) { } return value } + class TemplateInstance { constructor(template, parent) { this._parts = [] @@ -356,7 +333,7 @@ class TemplateInstance { } _update(values) { let i = 0 - for (const part of this._parts) { + for (let part of this._parts) { if (part !== void 0) { if (part.strings !== void 0) { part._$setValue(values, part, i) @@ -369,6 +346,7 @@ class TemplateInstance { } } } + class ChildPart { constructor(startNode, endNode, parent, options) { this.type = CHILD_PART @@ -379,16 +357,13 @@ class ChildPart { this._$parent = parent this.options = options this.__isConnected = options?.isConnected || true - if (ENABLE_EXTRA_SECURITY_HOOKS) { - this._textSanitizer = void 0 - } } get _$isConnected() { return this._$parent?._$isConnected || this.__isConnected } get parentNode() { let parentNode = this._$startNode.parentNode - const parent = this._$parent + let parent = this._$parent if (parent !== void 0 && parentNode.nodeType === 11) { parentNode = parent.parentNode } @@ -405,7 +380,7 @@ class ChildPart { if (isPrimitive(value)) { if (value === NOTHING || value == null || value === '') { if (this._$committedValue !== NOTHING) { - this._$clear() + this.#clear() } this._$committedValue = NOTHING } else if (value !== this._$committedValue && value !== NO_CHANGE) { @@ -426,17 +401,7 @@ class ChildPart { } _commitNode(value) { if (this._$committedValue !== value) { - this._$clear() - if ( - ENABLE_EXTRA_SECURITY_HOOKS && - sanitizerFactoryInternal !== noopSanitizer - ) { - const parentNodeName = this._$startNode.parentNode?.nodeName - - if (parentNodeName === 'STYLE' || parentNodeName === 'SCRIPT') { - throw new Error('Forbidden') - } - } + this.#clear() this._$committedValue = this._insert(value) } @@ -446,34 +411,17 @@ class ChildPart { this._$committedValue !== NOTHING && isPrimitive(this._$committedValue) ) { - const node = this._$startNode.nextSibling - if (ENABLE_EXTRA_SECURITY_HOOKS) { - if (this._textSanitizer === void 0) { - this._textSanitizer = createSanitizer(node, 'data', 'property') - } - value = this._textSanitizer(value) - } + let node = this._$startNode.nextSibling node.data = value } else { - if (ENABLE_EXTRA_SECURITY_HOOKS) { - const textNode = document.createTextNode('') - this._commitNode(textNode) - if (this._textSanitizer === void 0) { - this._textSanitizer = createSanitizer(textNode, 'data', 'property') - } - value = this._textSanitizer(value) - - textNode.data = value - } else { - this._commitNode(document.createTextNode(value)) - } + this._commitNode(document.createTextNode(value)) } this._$committedValue = value } _commitTemplateResult(result) { - const { values, ['__dom_type__']: type } = result - const template = + let { values, ['__dom_type__']: type } = result + let template = typeof type === 'number' ? this._$getTemplate(result) : (type.el === void 0 && @@ -483,8 +431,8 @@ class ChildPart { if (this._$committedValue?._$template === template) { this._$committedValue._update(values) } else { - const instance = new TemplateInstance(template, this) - const fragment = instance._clone(this.options) + let instance = new TemplateInstance(template, this) + let fragment = instance._clone(this.options) instance._update(values) @@ -502,12 +450,12 @@ class ChildPart { _commitIterable(value) { if (!isArray(this._$committedValue)) { this._$committedValue = [] - this._$clear() + this.#clear() } - const itemParts = this._$committedValue + let itemParts = this._$committedValue let partIndex = 0 let itemPart - for (const item of value) { + for (let item of value) { if (partIndex === itemParts.length) { itemParts.push( (itemPart = new ChildPart( @@ -524,11 +472,12 @@ class ChildPart { partIndex++ } if (partIndex < itemParts.length) { - this._$clear(itemPart && itemPart._$endNode.nextSibling, partIndex) + this.#clear(itemPart && itemPart._$endNode.nextSibling, partIndex) itemParts.length = partIndex } } - _$clear(start = this._$startNode.nextSibling, from) { + + #clear(start = this._$startNode.nextSibling, from) { this._$notifyConnectionChanged?.call(this, false, true, from) while (start && start !== this._$endNode) { @@ -544,6 +493,7 @@ class ChildPart { } } } +// 常规属性 class AttributePart { constructor(element, name, strings, parent, options) { this.type = ATTRIBUTE_PART @@ -559,9 +509,6 @@ class AttributePart { } else { this._$committedValue = NOTHING } - if (ENABLE_EXTRA_SECURITY_HOOKS) { - this._sanitizer = void 0 - } } get tagName() { return this.element.tagName @@ -569,8 +516,9 @@ class AttributePart { get _$isConnected() { return this._$parent._$isConnected } + _$setValue(value, directiveParent = this, valueIndex, noCommit) { - const strings = this.strings + let strings = this.strings let change = false if (strings === void 0) { value = resolveDirective(this, value, directiveParent, 0) @@ -581,7 +529,7 @@ class AttributePart { this._$committedValue = value } } else { - const values = value + let values = value value = strings[0] for (let i = 0; i < strings.length - 1; i++) { @@ -604,70 +552,45 @@ class AttributePart { } } if (change && !noCommit) { - this._commitValue(value) + this.#commitValue(value) } } - _commitValue(value) { - if (value === NOTHING) { - this.element.removeAttribute(this.name) - } else { - if (ENABLE_EXTRA_SECURITY_HOOKS) { - if (this._sanitizer === void 0) { - this._sanitizer = sanitizerFactoryInternal( - this.element, - this.name, - 'attribute' - ) - } - value = this._sanitizer(value !== null && value !== void 0 ? value : '') - } - this.element.setAttribute( - this.name, - value !== null && value !== void 0 ? value : '' - ) + #commitValue(value) { + let isBoolAttr = boolMap[this.name] + + if (isBoolAttr) { + this.element[isBoolAttr] = !(value === false || value === null) + + if (this.element[isBoolAttr]) { + this.element.setAttribute(this.name, '') + } else { + this.element.removeAttribute(this.name) + } + } else { + if (value === null || value === void 0) { + this.element.removeAttribute(this.name) + } else { + this.element.setAttribute(this.name, value) + } } } } +// 赋值属性 class PropertyPart extends AttributePart { - constructor() { - super(...arguments) - this.type = PROPERTY_PART + constructor(...args) { + super(...args) } - _commitValue(value) { - if (ENABLE_EXTRA_SECURITY_HOOKS) { - if (this._sanitizer === void 0) { - this._sanitizer = sanitizerFactoryInternal( - this.element, - this.name, - 'property' - ) - } - value = this._sanitizer(value) - } - this.element[this.name] = value === NOTHING ? void 0 : value - } -} - -class BooleanAttributePart extends AttributePart { - constructor() { - super(...arguments) - this.type = BOOLEAN_ATTRIBUTE_PART - } - _commitValue(value) { - if (value && value !== NOTHING) { - // 布尔属性,值固定为空 - this.element.setAttribute(this.name, '') - } else { - this.element.removeAttribute(this.name) - } + #commitValue(value) { + this.element[this.name] = value } } +// 事件属性 class EventPart extends AttributePart { - constructor(element, name, strings, parent, options) { - super(element, name, strings, parent, options) + constructor(...args) { + super(...args) this.type = EVENT_PART } _$setValue(newListener, directiveParent = this) { @@ -677,13 +600,13 @@ class EventPart extends AttributePart { if (newListener === NO_CHANGE) { return } - const oldListener = this._$committedValue - const shouldRemoveListener = + let oldListener = this._$committedValue + let shouldRemoveListener = (newListener === NOTHING && oldListener !== NOTHING) || newListener.capture !== oldListener.capture || newListener.once !== oldListener.once || newListener.passive !== oldListener.passive - const shouldAddListener = + let shouldAddListener = newListener !== NOTHING && (oldListener === NOTHING || shouldRemoveListener) @@ -703,6 +626,7 @@ class EventPart extends AttributePart { } } } + class ElementPart { constructor(element, parent, options) { this.element = element @@ -749,8 +673,3 @@ export const svg = (strings, ...values) => { values } } - -if (ENABLE_EXTRA_SECURITY_HOOKS) { - render.setSanitizer = setSanitizer - render.createSanitizer = createSanitizer -} diff --git a/src/index.js b/src/index.js index 06a3cdd..03908c0 100644 --- a/src/index.js +++ b/src/index.js @@ -235,12 +235,12 @@ export class Component extends HTMLElement { unmounted() {} updated() {} - $on(type, callback) { - return bind(this, type, callback) + $on(type, callback, options) { + return bind(this, type, callback, options) } - $off(type, callback) { - unbind(this, type, callback) + $off(type, callback, options) { + unbind(this, type, callback, options) } $emit(type, data = {}) { diff --git a/src/utils.js b/src/utils.js index ab4e2c9..d24a5f7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -13,9 +13,9 @@ export function $(selector, container) { export function $$(selector, container) { if (container) { - return container.querySelectorsAll(selector) + return container.querySelectorAll(selector) } - return document.body.querySelectorsAll(selector) + return document.body.querySelectorAll(selector) } export const nextTick = (function () {