2023-03-16 13:40:25 +08:00
|
|
|
import { boolMap, WC_PART, NO_CHANGE, NOTHING } from './constants.js'
|
2023-03-08 11:50:38 +08:00
|
|
|
|
2023-03-16 13:40:25 +08:00
|
|
|
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 =>
|
2023-03-07 19:15:23 +08:00
|
|
|
value === null || (typeof value != 'object' && typeof value != 'function')
|
2023-03-16 13:40:25 +08:00
|
|
|
const isArray = Array.isArray
|
|
|
|
const isIterable = value =>
|
2023-03-07 19:15:23 +08:00
|
|
|
isArray(value) ||
|
|
|
|
typeof (value === null || value === void 0
|
2023-03-08 11:50:38 +08:00
|
|
|
? false
|
2023-03-07 19:15:23 +08:00
|
|
|
: value[Symbol.iterator]) === 'function'
|
2023-03-16 13:40:25 +08:00
|
|
|
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(
|
2023-03-07 19:15:23 +08:00
|
|
|
`>|${SPACE_CHAR}(?:(${NAME_CHAR}+)(${SPACE_CHAR}*=${SPACE_CHAR}*(?:${ATTR_VALUE_CHAR}|("|')|))|$)`,
|
|
|
|
'g'
|
|
|
|
)
|
2023-03-16 13:40:25 +08:00
|
|
|
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
|
|
|
|
|
|
|
|
const templateCache = new WeakMap()
|
|
|
|
const walker = document.createTreeWalker(document, 129, null, false)
|
|
|
|
|
|
|
|
function getTemplateHtml(strings, type) {
|
|
|
|
let len = strings.length - 1
|
|
|
|
let attrNames = []
|
2023-03-07 19:15:23 +08:00
|
|
|
let html2 = type === SVG_RESULT ? '<svg>' : ''
|
|
|
|
let rawTextEndRegex
|
|
|
|
let regex = textEndRegex
|
2023-03-08 15:13:15 +08:00
|
|
|
for (let i = 0; i < len; i++) {
|
2023-03-16 13:40:25 +08:00
|
|
|
let s = strings[i]
|
2023-03-07 19:15:23 +08:00
|
|
|
let attrNameEndIndex = -1
|
|
|
|
let attrName
|
|
|
|
let lastIndex = 0
|
|
|
|
let match
|
|
|
|
while (lastIndex < s.length) {
|
|
|
|
regex.lastIndex = lastIndex
|
|
|
|
match = regex.exec(s)
|
|
|
|
if (match === null) {
|
2023-03-06 19:20:23 +08:00
|
|
|
break
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
|
|
|
lastIndex = regex.lastIndex
|
|
|
|
if (regex === textEndRegex) {
|
|
|
|
if (match[COMMENT_START] === '!--') {
|
|
|
|
regex = commentEndRegex
|
|
|
|
} else if (match[COMMENT_START] !== void 0) {
|
|
|
|
regex = comment2EndRegex
|
|
|
|
} else if (match[TAG_NAME] !== void 0) {
|
|
|
|
if (rawTextElement.test(match[TAG_NAME])) {
|
|
|
|
rawTextEndRegex = new RegExp(`</${match[TAG_NAME]}`, 'g')
|
|
|
|
}
|
|
|
|
regex = tagEndRegex
|
|
|
|
} else if (match[DYNAMIC_TAG_NAME] !== void 0) {
|
|
|
|
regex = tagEndRegex
|
2023-03-06 19:20:23 +08:00
|
|
|
}
|
2023-03-07 19:15:23 +08:00
|
|
|
} else if (regex === tagEndRegex) {
|
|
|
|
if (match[ENTIRE_MATCH] === '>') {
|
|
|
|
regex =
|
|
|
|
rawTextEndRegex !== null && rawTextEndRegex !== void 0
|
|
|
|
? rawTextEndRegex
|
|
|
|
: textEndRegex
|
|
|
|
attrNameEndIndex = -1
|
|
|
|
} else if (match[ATTRIBUTE_NAME] === void 0) {
|
|
|
|
attrNameEndIndex = -2
|
|
|
|
} else {
|
|
|
|
attrNameEndIndex = regex.lastIndex - match[SPACES_AND_EQUALS].length
|
|
|
|
attrName = match[ATTRIBUTE_NAME]
|
|
|
|
regex =
|
|
|
|
match[QUOTE_CHAR] === void 0
|
|
|
|
? tagEndRegex
|
|
|
|
: match[QUOTE_CHAR] === '"'
|
|
|
|
? doubleQuoteAttrEndRegex
|
|
|
|
: singleQuoteAttrEndRegex
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
regex === doubleQuoteAttrEndRegex ||
|
|
|
|
regex === singleQuoteAttrEndRegex
|
|
|
|
) {
|
|
|
|
regex = tagEndRegex
|
|
|
|
} else if (regex === commentEndRegex || regex === comment2EndRegex) {
|
|
|
|
regex = textEndRegex
|
|
|
|
} else {
|
|
|
|
regex = tagEndRegex
|
|
|
|
rawTextEndRegex = void 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 13:40:25 +08:00
|
|
|
let end =
|
2023-03-07 19:15:23 +08:00
|
|
|
regex === tagEndRegex && strings[i + 1].startsWith('/>') ? ' ' : ''
|
|
|
|
html2 +=
|
|
|
|
regex === textEndRegex
|
|
|
|
? s + nodeMarker
|
|
|
|
: attrNameEndIndex >= 0
|
|
|
|
? (attrNames.push(attrName),
|
|
|
|
s.slice(0, attrNameEndIndex) +
|
|
|
|
boundAttributeSuffix +
|
|
|
|
s.slice(attrNameEndIndex)) +
|
|
|
|
marker +
|
|
|
|
end
|
|
|
|
: s +
|
|
|
|
marker +
|
|
|
|
(attrNameEndIndex === -2 ? (attrNames.push(void 0), i) : end)
|
|
|
|
}
|
2023-03-16 13:40:25 +08:00
|
|
|
let htmlResult =
|
2023-03-08 15:13:15 +08:00
|
|
|
html2 + (strings[len] || '<?>') + (type === SVG_RESULT ? '</svg>' : '')
|
2023-03-07 19:15:23 +08:00
|
|
|
if (!Array.isArray(strings) || !strings.hasOwnProperty('raw')) {
|
|
|
|
let message = 'invalid template strings array'
|
|
|
|
|
|
|
|
throw new Error(message)
|
|
|
|
}
|
|
|
|
return [htmlResult, attrNames]
|
|
|
|
}
|
|
|
|
class Template {
|
2023-03-17 10:59:48 +08:00
|
|
|
constructor({ strings, ['__dom_type__']: type }, options) {
|
2023-03-07 19:15:23 +08:00
|
|
|
this.parts = []
|
|
|
|
let node
|
|
|
|
let nodeIndex = 0
|
|
|
|
let attrNameIndex = 0
|
2023-03-16 13:40:25 +08:00
|
|
|
let partCount = strings.length - 1
|
|
|
|
let parts = this.parts
|
|
|
|
let [html2, attrNames] = getTemplateHtml(strings, type)
|
2023-03-16 23:42:04 +08:00
|
|
|
this.el = Template.createElement(html2)
|
2023-03-07 19:15:23 +08:00
|
|
|
walker.currentNode = this.el.content
|
|
|
|
if (type === SVG_RESULT) {
|
2023-03-16 13:40:25 +08:00
|
|
|
let content = this.el.content
|
|
|
|
let svgElement = content.firstChild
|
2023-03-07 19:15:23 +08:00
|
|
|
svgElement.remove()
|
|
|
|
content.append(...svgElement.childNodes)
|
|
|
|
}
|
2023-03-17 10:18:40 +08:00
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
while ((node = walker.nextNode()) !== null && parts.length < partCount) {
|
|
|
|
if (node.nodeType === 1) {
|
|
|
|
if (node.hasAttributes()) {
|
2023-03-16 13:40:25 +08:00
|
|
|
let attrsToRemove = []
|
2023-03-17 10:18:40 +08:00
|
|
|
|
2023-03-16 13:40:25 +08:00
|
|
|
for (let name of node.getAttributeNames()) {
|
2023-03-07 19:15:23 +08:00
|
|
|
if (
|
|
|
|
name.endsWith(boundAttributeSuffix) ||
|
|
|
|
name.startsWith(marker)
|
|
|
|
) {
|
2023-03-16 13:40:25 +08:00
|
|
|
let realName = attrNames[attrNameIndex++]
|
2023-03-07 19:15:23 +08:00
|
|
|
attrsToRemove.push(name)
|
|
|
|
if (realName !== void 0) {
|
2023-03-16 13:40:25 +08:00
|
|
|
let value = node.getAttribute(
|
2023-03-07 19:15:23 +08:00
|
|
|
realName.toLowerCase() + boundAttributeSuffix
|
|
|
|
)
|
2023-03-16 13:40:25 +08:00
|
|
|
let statics = value.split(marker)
|
|
|
|
let m = /([:@])?(.*)/.exec(realName)
|
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
parts.push({
|
|
|
|
type: ATTRIBUTE_PART,
|
|
|
|
index: nodeIndex,
|
|
|
|
name: m[2],
|
|
|
|
strings: statics,
|
|
|
|
ctor:
|
2023-03-16 13:40:25 +08:00
|
|
|
m[1] === ':'
|
2023-03-07 19:15:23 +08:00
|
|
|
? PropertyPart
|
|
|
|
: m[1] === '@'
|
|
|
|
? EventPart
|
|
|
|
: AttributePart
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
parts.push({
|
|
|
|
type: ELEMENT_PART,
|
|
|
|
index: nodeIndex
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-16 13:40:25 +08:00
|
|
|
for (let name of attrsToRemove) {
|
2023-03-07 19:15:23 +08:00
|
|
|
node.removeAttribute(name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (rawTextElement.test(node.tagName)) {
|
2023-03-16 13:40:25 +08:00
|
|
|
let strings2 = node.textContent.split(marker)
|
|
|
|
let lastIndex = strings2.length - 1
|
2023-03-07 19:15:23 +08:00
|
|
|
if (lastIndex > 0) {
|
|
|
|
node.textContent = ''
|
|
|
|
for (let i = 0; i < lastIndex; i++) {
|
|
|
|
node.append(strings2[i], createMarker())
|
|
|
|
walker.nextNode()
|
|
|
|
parts.push({ type: CHILD_PART, index: ++nodeIndex })
|
|
|
|
}
|
|
|
|
node.append(strings2[lastIndex], createMarker())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (node.nodeType === 8) {
|
2023-03-16 13:40:25 +08:00
|
|
|
let data = node.data
|
2023-03-07 19:15:23 +08:00
|
|
|
if (data === markerMatch) {
|
|
|
|
parts.push({ type: CHILD_PART, index: nodeIndex })
|
|
|
|
} else {
|
|
|
|
let i = -1
|
|
|
|
while ((i = node.data.indexOf(marker, i + 1)) !== -1) {
|
|
|
|
parts.push({ type: COMMENT_PART, index: nodeIndex })
|
|
|
|
i += marker.length - 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
nodeIndex++
|
2023-03-06 19:20:23 +08:00
|
|
|
}
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
2023-03-16 23:42:04 +08:00
|
|
|
static createElement(html2) {
|
2023-03-13 18:37:12 +08:00
|
|
|
let el = document.createElement('template')
|
2023-03-07 19:15:23 +08:00
|
|
|
el.innerHTML = html2
|
|
|
|
return el
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function resolveDirective(part, value, parent = part, attributeIndex) {
|
2023-03-08 15:13:15 +08:00
|
|
|
if (value === NO_CHANGE) {
|
2023-03-07 19:15:23 +08:00
|
|
|
return value
|
|
|
|
}
|
|
|
|
let currentDirective =
|
|
|
|
attributeIndex !== void 0
|
2023-03-08 11:50:38 +08:00
|
|
|
? parent.__directives?.[attributeIndex]
|
2023-03-07 19:15:23 +08:00
|
|
|
: parent.__directive
|
2023-03-08 11:50:38 +08:00
|
|
|
|
2023-03-16 13:40:25 +08:00
|
|
|
let nextDirectiveConstructor = isPrimitive(value)
|
2023-03-07 19:15:23 +08:00
|
|
|
? void 0
|
|
|
|
: value['_$litDirective$']
|
2023-03-08 11:50:38 +08:00
|
|
|
|
|
|
|
if (currentDirective?.constructor !== nextDirectiveConstructor) {
|
|
|
|
currentDirective._$notifyDirectiveConnectionChanged?.call(
|
|
|
|
currentDirective,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
if (nextDirectiveConstructor === void 0) {
|
|
|
|
currentDirective = void 0
|
|
|
|
} else {
|
|
|
|
currentDirective = new nextDirectiveConstructor(part)
|
|
|
|
currentDirective._$initialize(part, parent, attributeIndex)
|
|
|
|
}
|
|
|
|
if (attributeIndex !== void 0) {
|
2023-03-08 11:50:38 +08:00
|
|
|
if (!parent.__directives) {
|
|
|
|
parent.__directives = []
|
|
|
|
}
|
|
|
|
parent.__directives[attributeIndex] = currentDirective
|
2023-03-07 19:15:23 +08:00
|
|
|
} else {
|
|
|
|
parent.__directive = currentDirective
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (currentDirective !== void 0) {
|
|
|
|
value = resolveDirective(
|
|
|
|
part,
|
|
|
|
currentDirective._$resolve(part, value.values),
|
|
|
|
currentDirective,
|
|
|
|
attributeIndex
|
|
|
|
)
|
2023-03-06 19:20:23 +08:00
|
|
|
}
|
2023-03-07 19:15:23 +08:00
|
|
|
return value
|
2023-03-06 19:20:23 +08:00
|
|
|
}
|
2023-03-16 13:40:25 +08:00
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
class TemplateInstance {
|
|
|
|
constructor(template, parent) {
|
|
|
|
this._parts = []
|
|
|
|
this._$disconnectableChildren = void 0
|
|
|
|
this._$template = template
|
|
|
|
this._$parent = parent
|
|
|
|
}
|
|
|
|
get parentNode() {
|
|
|
|
return this._$parent.parentNode
|
|
|
|
}
|
|
|
|
get _$isConnected() {
|
|
|
|
return this._$parent._$isConnected
|
|
|
|
}
|
2023-03-22 18:49:46 +08:00
|
|
|
|
|
|
|
#checkRef(node, walker, options) {
|
|
|
|
do {
|
2023-03-22 18:56:14 +08:00
|
|
|
if (node?.nodeType === 1 && node?.getAttribute('ref')) {
|
2023-03-22 18:49:46 +08:00
|
|
|
options.host.$refs[node.getAttribute('ref')] = node
|
|
|
|
node.removeAttribute('ref')
|
|
|
|
}
|
|
|
|
} while ((node = walker.nextNode()) !== null)
|
|
|
|
}
|
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
_clone(options) {
|
2023-03-13 18:37:12 +08:00
|
|
|
let {
|
2023-03-07 19:15:23 +08:00
|
|
|
el: { content },
|
|
|
|
parts
|
|
|
|
} = this._$template
|
2023-03-13 18:37:12 +08:00
|
|
|
|
|
|
|
let fragment = (options?.creationScope || document).importNode(
|
|
|
|
content,
|
|
|
|
true
|
|
|
|
)
|
2023-03-07 19:15:23 +08:00
|
|
|
walker.currentNode = fragment
|
|
|
|
let node = walker.nextNode()
|
|
|
|
let nodeIndex = 0
|
|
|
|
let partIndex = 0
|
|
|
|
let templatePart = parts[0]
|
2023-03-20 11:29:19 +08:00
|
|
|
// 没有动态绑定时, 查一遍是否有ref属性
|
|
|
|
if (templatePart === void 0) {
|
2023-03-22 18:49:46 +08:00
|
|
|
this.#checkRef(node, walker, options)
|
2023-03-20 11:29:19 +08:00
|
|
|
} else {
|
|
|
|
while (templatePart !== void 0) {
|
|
|
|
if (nodeIndex === templatePart.index) {
|
|
|
|
let part
|
|
|
|
if (templatePart.type === CHILD_PART) {
|
|
|
|
part = new ChildPart(node, node.nextSibling, this, options)
|
|
|
|
} else if (templatePart.type === ATTRIBUTE_PART) {
|
|
|
|
part = new templatePart.ctor(
|
|
|
|
node,
|
|
|
|
templatePart.name,
|
|
|
|
templatePart.strings,
|
|
|
|
this,
|
|
|
|
options
|
|
|
|
)
|
|
|
|
} else if (templatePart.type === ELEMENT_PART) {
|
|
|
|
part = new ElementPart(node, this, options)
|
|
|
|
}
|
|
|
|
if (node.nodeType === 1 && node.getAttribute('ref')) {
|
|
|
|
if (options.host.$refs) {
|
|
|
|
options.host.$refs[node.getAttribute('ref')] = node
|
2023-03-22 18:49:46 +08:00
|
|
|
node.removeAttribute('ref')
|
2023-03-20 11:29:19 +08:00
|
|
|
}
|
2023-03-17 10:59:48 +08:00
|
|
|
}
|
2023-03-20 11:29:19 +08:00
|
|
|
this._parts.push(part)
|
|
|
|
templatePart = parts[++partIndex]
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
nodeIndex !==
|
|
|
|
(templatePart === null || templatePart === void 0
|
|
|
|
? void 0
|
|
|
|
: templatePart.index)
|
|
|
|
) {
|
|
|
|
node = walker.nextNode()
|
|
|
|
nodeIndex++
|
2023-03-17 10:59:48 +08:00
|
|
|
}
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
2023-03-22 18:49:46 +08:00
|
|
|
// 再检查剩下没有动态绑定的节点
|
|
|
|
this.#checkRef(node, walker, options)
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
2023-03-20 11:29:19 +08:00
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
return fragment
|
|
|
|
}
|
|
|
|
_update(values) {
|
|
|
|
let i = 0
|
2023-03-16 13:40:25 +08:00
|
|
|
for (let part of this._parts) {
|
2023-03-07 19:15:23 +08:00
|
|
|
if (part !== void 0) {
|
|
|
|
if (part.strings !== void 0) {
|
|
|
|
part._$setValue(values, part, i)
|
|
|
|
i += part.strings.length - 2
|
|
|
|
} else {
|
|
|
|
part._$setValue(values[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-16 13:40:25 +08:00
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
class ChildPart {
|
|
|
|
constructor(startNode, endNode, parent, options) {
|
|
|
|
this.type = CHILD_PART
|
2023-03-08 15:13:15 +08:00
|
|
|
this._$committedValue = NOTHING
|
2023-03-07 19:15:23 +08:00
|
|
|
this._$disconnectableChildren = void 0
|
|
|
|
this._$startNode = startNode
|
|
|
|
this._$endNode = endNode
|
|
|
|
this._$parent = parent
|
|
|
|
this.options = options
|
2023-03-08 11:50:38 +08:00
|
|
|
this.__isConnected = options?.isConnected || true
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
|
|
|
get _$isConnected() {
|
2023-03-08 11:50:38 +08:00
|
|
|
return this._$parent?._$isConnected || this.__isConnected
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
|
|
|
get parentNode() {
|
2023-03-08 11:11:05 +08:00
|
|
|
let parentNode = this._$startNode.parentNode
|
2023-03-16 13:40:25 +08:00
|
|
|
let parent = this._$parent
|
2023-03-07 19:15:23 +08:00
|
|
|
if (parent !== void 0 && parentNode.nodeType === 11) {
|
|
|
|
parentNode = parent.parentNode
|
|
|
|
}
|
|
|
|
return parentNode
|
|
|
|
}
|
|
|
|
get startNode() {
|
|
|
|
return this._$startNode
|
|
|
|
}
|
|
|
|
get endNode() {
|
|
|
|
return this._$endNode
|
|
|
|
}
|
|
|
|
_$setValue(value, directiveParent = this) {
|
|
|
|
value = resolveDirective(this, value, directiveParent)
|
|
|
|
if (isPrimitive(value)) {
|
2023-03-08 15:13:15 +08:00
|
|
|
if (value === NOTHING || value == null || value === '') {
|
|
|
|
if (this._$committedValue !== NOTHING) {
|
2023-03-16 13:40:25 +08:00
|
|
|
this.#clear()
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
2023-03-08 15:13:15 +08:00
|
|
|
this._$committedValue = NOTHING
|
|
|
|
} else if (value !== this._$committedValue && value !== NO_CHANGE) {
|
2023-03-07 19:15:23 +08:00
|
|
|
this._commitText(value)
|
|
|
|
}
|
|
|
|
} else if (value['__dom_type__'] !== void 0) {
|
|
|
|
this._commitTemplateResult(value)
|
|
|
|
} else if (value.nodeType !== void 0) {
|
|
|
|
this._commitNode(value)
|
|
|
|
} else if (isIterable(value)) {
|
|
|
|
this._commitIterable(value)
|
|
|
|
} else {
|
|
|
|
this._commitText(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_insert(node, ref = this._$endNode) {
|
2023-03-08 11:11:05 +08:00
|
|
|
return this._$startNode.parentNode.insertBefore(node, ref)
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
|
|
|
_commitNode(value) {
|
|
|
|
if (this._$committedValue !== value) {
|
2023-03-16 13:40:25 +08:00
|
|
|
this.#clear()
|
2023-03-07 19:15:23 +08:00
|
|
|
this._$committedValue = this._insert(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_commitText(value) {
|
|
|
|
if (
|
2023-03-08 15:13:15 +08:00
|
|
|
this._$committedValue !== NOTHING &&
|
2023-03-07 19:15:23 +08:00
|
|
|
isPrimitive(this._$committedValue)
|
|
|
|
) {
|
2023-03-16 13:40:25 +08:00
|
|
|
let node = this._$startNode.nextSibling
|
2023-03-08 11:11:05 +08:00
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
node.data = value
|
|
|
|
} else {
|
2023-03-16 13:40:25 +08:00
|
|
|
this._commitNode(document.createTextNode(value))
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
|
|
|
this._$committedValue = value
|
|
|
|
}
|
|
|
|
_commitTemplateResult(result) {
|
2023-03-16 13:40:25 +08:00
|
|
|
let { values, ['__dom_type__']: type } = result
|
|
|
|
let template =
|
2023-03-07 19:15:23 +08:00
|
|
|
typeof type === 'number'
|
2023-03-17 10:18:40 +08:00
|
|
|
? this.#getTemplate(result)
|
2023-03-16 23:42:04 +08:00
|
|
|
: (type.el === void 0 && (type.el = Template.createElement(type.h)),
|
2023-03-07 19:15:23 +08:00
|
|
|
type)
|
2023-03-08 11:50:38 +08:00
|
|
|
|
|
|
|
if (this._$committedValue?._$template === template) {
|
2023-03-07 19:15:23 +08:00
|
|
|
this._$committedValue._update(values)
|
|
|
|
} else {
|
2023-03-16 13:40:25 +08:00
|
|
|
let instance = new TemplateInstance(template, this)
|
|
|
|
let fragment = instance._clone(this.options)
|
2023-03-07 19:15:23 +08:00
|
|
|
|
|
|
|
instance._update(values)
|
2023-03-06 19:20:23 +08:00
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
this._commitNode(fragment)
|
|
|
|
this._$committedValue = instance
|
|
|
|
}
|
|
|
|
}
|
2023-03-17 10:18:40 +08:00
|
|
|
#getTemplate(result) {
|
2023-03-07 19:15:23 +08:00
|
|
|
let template = templateCache.get(result.strings)
|
|
|
|
if (template === void 0) {
|
2023-03-16 23:42:04 +08:00
|
|
|
template = new Template(result, this.options)
|
|
|
|
templateCache.set(result.strings, template)
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
|
|
|
return template
|
|
|
|
}
|
|
|
|
_commitIterable(value) {
|
|
|
|
if (!isArray(this._$committedValue)) {
|
|
|
|
this._$committedValue = []
|
2023-03-16 13:40:25 +08:00
|
|
|
this.#clear()
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
2023-03-16 13:40:25 +08:00
|
|
|
let itemParts = this._$committedValue
|
2023-03-07 19:15:23 +08:00
|
|
|
let partIndex = 0
|
|
|
|
let itemPart
|
2023-03-16 13:40:25 +08:00
|
|
|
for (let item of value) {
|
2023-03-07 19:15:23 +08:00
|
|
|
if (partIndex === itemParts.length) {
|
|
|
|
itemParts.push(
|
|
|
|
(itemPart = new ChildPart(
|
|
|
|
this._insert(createMarker()),
|
|
|
|
this._insert(createMarker()),
|
|
|
|
this,
|
|
|
|
this.options
|
|
|
|
))
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
itemPart = itemParts[partIndex]
|
|
|
|
}
|
|
|
|
itemPart._$setValue(item)
|
|
|
|
partIndex++
|
|
|
|
}
|
|
|
|
if (partIndex < itemParts.length) {
|
2023-03-16 13:40:25 +08:00
|
|
|
this.#clear(itemPart && itemPart._$endNode.nextSibling, partIndex)
|
2023-03-07 19:15:23 +08:00
|
|
|
itemParts.length = partIndex
|
|
|
|
}
|
|
|
|
}
|
2023-03-16 13:40:25 +08:00
|
|
|
|
|
|
|
#clear(start = this._$startNode.nextSibling, from) {
|
2023-03-08 11:50:38 +08:00
|
|
|
this._$notifyConnectionChanged?.call(this, false, true, from)
|
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
while (start && start !== this._$endNode) {
|
2023-03-08 11:50:38 +08:00
|
|
|
let node = start.nextSibling
|
2023-03-08 11:11:05 +08:00
|
|
|
start.remove()
|
2023-03-08 11:50:38 +08:00
|
|
|
start = node
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
setConnected(isConnected) {
|
|
|
|
if (this._$parent === void 0) {
|
|
|
|
this.__isConnected = isConnected
|
2023-03-08 11:50:38 +08:00
|
|
|
this._$notifyConnectionChanged?.call(this, isConnected)
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
|
|
|
}
|
2023-03-06 19:20:23 +08:00
|
|
|
}
|
2023-03-16 13:40:25 +08:00
|
|
|
// 常规属性
|
2023-03-07 19:15:23 +08:00
|
|
|
class AttributePart {
|
|
|
|
constructor(element, name, strings, parent, options) {
|
|
|
|
this.type = ATTRIBUTE_PART
|
2023-03-08 15:13:15 +08:00
|
|
|
this._$committedValue = NOTHING
|
2023-03-07 19:15:23 +08:00
|
|
|
this._$disconnectableChildren = void 0
|
|
|
|
this.element = element
|
|
|
|
this.name = name
|
|
|
|
this._$parent = parent
|
|
|
|
this.options = options
|
|
|
|
if (strings.length > 2 || strings[0] !== '' || strings[1] !== '') {
|
|
|
|
this._$committedValue = new Array(strings.length - 1).fill(new String())
|
|
|
|
this.strings = strings
|
|
|
|
} else {
|
2023-03-08 15:13:15 +08:00
|
|
|
this._$committedValue = NOTHING
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
get tagName() {
|
|
|
|
return this.element.tagName
|
|
|
|
}
|
|
|
|
get _$isConnected() {
|
|
|
|
return this._$parent._$isConnected
|
|
|
|
}
|
2023-03-16 13:40:25 +08:00
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
_$setValue(value, directiveParent = this, valueIndex, noCommit) {
|
2023-03-16 13:40:25 +08:00
|
|
|
let strings = this.strings
|
2023-03-07 19:15:23 +08:00
|
|
|
let change = false
|
|
|
|
if (strings === void 0) {
|
|
|
|
value = resolveDirective(this, value, directiveParent, 0)
|
|
|
|
change =
|
|
|
|
!isPrimitive(value) ||
|
2023-03-08 15:13:15 +08:00
|
|
|
(value !== this._$committedValue && value !== NO_CHANGE)
|
2023-03-07 19:15:23 +08:00
|
|
|
if (change) {
|
|
|
|
this._$committedValue = value
|
|
|
|
}
|
|
|
|
} else {
|
2023-03-16 13:40:25 +08:00
|
|
|
let values = value
|
2023-03-07 19:15:23 +08:00
|
|
|
value = strings[0]
|
2023-03-08 15:13:15 +08:00
|
|
|
|
|
|
|
for (let i = 0; i < strings.length - 1; i++) {
|
|
|
|
let v = resolveDirective(
|
|
|
|
this,
|
|
|
|
values[valueIndex + i],
|
|
|
|
directiveParent,
|
|
|
|
i
|
|
|
|
)
|
|
|
|
if (v === NO_CHANGE) {
|
2023-03-07 19:15:23 +08:00
|
|
|
v = this._$committedValue[i]
|
|
|
|
}
|
|
|
|
change || (change = !isPrimitive(v) || v !== this._$committedValue[i])
|
2023-03-08 15:13:15 +08:00
|
|
|
if (v === NOTHING) {
|
|
|
|
value = NOTHING
|
|
|
|
} else if (value !== NOTHING) {
|
2023-03-07 19:15:23 +08:00
|
|
|
value += (v !== null && v !== void 0 ? v : '') + strings[i + 1]
|
|
|
|
}
|
|
|
|
this._$committedValue[i] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (change && !noCommit) {
|
2023-03-17 19:02:20 +08:00
|
|
|
this.commitValue(value)
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
|
|
|
}
|
2023-03-16 13:40:25 +08:00
|
|
|
|
2023-03-17 19:02:20 +08:00
|
|
|
commitValue(value) {
|
2023-03-16 13:40:25 +08:00
|
|
|
let isBoolAttr = boolMap[this.name]
|
2023-03-17 10:18:40 +08:00
|
|
|
// ref属性不渲染到节点上
|
|
|
|
if (this.name === 'ref') {
|
|
|
|
this.options.host.$refs[value] = this.element
|
|
|
|
return
|
|
|
|
}
|
2023-03-16 13:40:25 +08:00
|
|
|
|
|
|
|
if (isBoolAttr) {
|
|
|
|
this.element[isBoolAttr] = !(value === false || value === null)
|
|
|
|
|
|
|
|
if (this.element[isBoolAttr]) {
|
|
|
|
this.element.setAttribute(this.name, '')
|
|
|
|
} else {
|
|
|
|
this.element.removeAttribute(this.name)
|
|
|
|
}
|
2023-03-07 19:15:23 +08:00
|
|
|
} else {
|
2023-03-16 13:40:25 +08:00
|
|
|
if (value === null || value === void 0) {
|
|
|
|
this.element.removeAttribute(this.name)
|
|
|
|
} else {
|
|
|
|
this.element.setAttribute(this.name, value)
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-16 13:40:25 +08:00
|
|
|
// 赋值属性
|
2023-03-07 19:15:23 +08:00
|
|
|
class PropertyPart extends AttributePart {
|
2023-03-16 13:40:25 +08:00
|
|
|
constructor(...args) {
|
|
|
|
super(...args)
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
2023-03-13 18:37:12 +08:00
|
|
|
|
2023-03-17 19:02:20 +08:00
|
|
|
commitValue(value) {
|
2023-03-16 13:40:25 +08:00
|
|
|
this.element[this.name] = value
|
2023-03-07 19:15:23 +08:00
|
|
|
}
|
|
|
|
}
|
2023-03-13 18:37:12 +08:00
|
|
|
|
2023-03-16 13:40:25 +08:00
|
|
|
// 事件属性
|
2023-03-07 19:15:23 +08:00
|
|
|
class EventPart extends AttributePart {
|
2023-03-16 13:40:25 +08:00
|
|
|
constructor(...args) {
|
|
|
|
super(...args)
|
2023-03-07 19:15:23 +08:00
|
|
|
this.type = EVENT_PART
|
|
|
|
}
|
|
|
|
_$setValue(newListener, directiveParent = this) {
|
|
|
|
newListener =
|
2023-03-08 15:13:15 +08:00
|
|
|
resolveDirective(this, newListener, directiveParent, 0) || NOTHING
|
2023-03-08 11:50:38 +08:00
|
|
|
|
2023-03-08 15:13:15 +08:00
|
|
|
if (newListener === NO_CHANGE) {
|
2023-03-07 19:15:23 +08:00
|
|
|
return
|
|
|
|
}
|
2023-03-16 13:40:25 +08:00
|
|
|
let oldListener = this._$committedValue
|
|
|
|
let shouldRemoveListener =
|
2023-03-08 15:13:15 +08:00
|
|
|
(newListener === NOTHING && oldListener !== NOTHING) ||
|
2023-03-07 19:15:23 +08:00
|
|
|
newListener.capture !== oldListener.capture ||
|
|
|
|
newListener.once !== oldListener.once ||
|
|
|
|
newListener.passive !== oldListener.passive
|
2023-03-16 13:40:25 +08:00
|
|
|
let shouldAddListener =
|
2023-03-08 15:13:15 +08:00
|
|
|
newListener !== NOTHING &&
|
|
|
|
(oldListener === NOTHING || shouldRemoveListener)
|
2023-03-08 11:11:05 +08:00
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
if (shouldRemoveListener) {
|
|
|
|
this.element.removeEventListener(this.name, this, oldListener)
|
|
|
|
}
|
|
|
|
if (shouldAddListener) {
|
|
|
|
this.element.addEventListener(this.name, this, newListener)
|
|
|
|
}
|
|
|
|
this._$committedValue = newListener
|
|
|
|
}
|
|
|
|
handleEvent(event) {
|
|
|
|
if (typeof this._$committedValue === 'function') {
|
2023-03-08 11:50:38 +08:00
|
|
|
this._$committedValue.call(this.options?.host || this.element, event)
|
2023-03-07 19:15:23 +08:00
|
|
|
} else {
|
|
|
|
this._$committedValue.handleEvent(event)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-16 13:40:25 +08:00
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
class ElementPart {
|
|
|
|
constructor(element, parent, options) {
|
|
|
|
this.element = element
|
|
|
|
this.type = ELEMENT_PART
|
|
|
|
this._$disconnectableChildren = void 0
|
|
|
|
this._$parent = parent
|
|
|
|
this.options = options
|
|
|
|
}
|
|
|
|
get _$isConnected() {
|
|
|
|
return this._$parent._$isConnected
|
|
|
|
}
|
|
|
|
_$setValue(value) {
|
|
|
|
resolveDirective(this, value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 23:42:04 +08:00
|
|
|
export function render(value, container, options = {}) {
|
2023-03-13 18:28:03 +08:00
|
|
|
let part = container[WC_PART]
|
2023-03-06 19:20:23 +08:00
|
|
|
|
2023-03-07 19:15:23 +08:00
|
|
|
if (part === void 0) {
|
2023-03-13 18:28:03 +08:00
|
|
|
container[WC_PART] = part = new ChildPart(
|
|
|
|
container.insertBefore(createMarker(), null),
|
|
|
|
null,
|
2023-03-07 19:15:23 +08:00
|
|
|
void 0,
|
2023-03-16 23:42:04 +08:00
|
|
|
options
|
2023-03-07 19:15:23 +08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
part._$setValue(value)
|
|
|
|
|
|
|
|
return part
|
|
|
|
}
|
2023-03-08 15:13:15 +08:00
|
|
|
|
|
|
|
export const html = (strings, ...values) => {
|
|
|
|
return {
|
|
|
|
__dom_type__: HTML_RESULT,
|
|
|
|
strings,
|
|
|
|
values
|
|
|
|
}
|
|
|
|
}
|
|
|
|
export const svg = (strings, ...values) => {
|
|
|
|
return {
|
|
|
|
__dom_type__: SVG_RESULT,
|
|
|
|
strings,
|
|
|
|
values
|
|
|
|
}
|
|
|
|
}
|