Compare commits
No commits in common. "master" and "1.5.10" have entirely different histories.
|
@ -1,9 +1,12 @@
|
||||||
*.min.js
|
*.min.js
|
||||||
.httpserver
|
.httpserver
|
||||||
|
index.html
|
||||||
|
test.js
|
||||||
.vscode
|
.vscode
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
test/
|
|
35
Readme.md
35
Readme.md
|
@ -1,30 +1,12 @@
|
||||||
## Wkit
|
## 9号UI组件库的核心
|
||||||
> 一个简单易用、功能完善的用于开发`web components`的轻量级开发库。模板解析基于`lit-html`二次开发
|
|
||||||
|
|
||||||
![downloads](https://img.shields.io/npm/dt/wkit.svg)
|
|
||||||
![version](https://img.shields.io/npm/v/wkit.svg)
|
|
||||||
|
|
||||||
### 开发文档
|
### 开发文档
|
||||||
[开发文档](https://git.wkit.fun/bytedo/wkit/wiki)
|
[开发文档](https://github.com/bd-js/core/wiki)
|
||||||
|
|
||||||
|
|
||||||
### 我们的特色
|
|
||||||
|
|
||||||
- `@xxx=yyy` 事件绑定, 可以任意节点上直接使用类似`vue事件绑定`的这种写法, 就可实现事件绑定。组件被移出文档时, 事件会**自动销毁**, 无须手动销毁。
|
|
||||||
- `@click.stop=xxx` 事件绑定支持修饰符
|
|
||||||
- `:xxx=yyy`, 属性绑定, 类似`vue`,在属性前加上`:`, 该属性不再使用`setAttribute()`, 而是直接赋值, 可满足你需要传递复杂数据结构的需求。
|
|
||||||
- `#animation={type}`, 开箱即用的动画配置, 内置6种动画`fade(默认), scale, bounce, micro-bounce, rotate, slide`
|
|
||||||
- `ref=xxx`, 类似`vue`的节点标识, 可方便的在`mounted()`之后通过 `this.$refs.xxx` 来访问该节点
|
|
||||||
- 内置特色的双向绑定方法`live()`
|
|
||||||
- 用内置的`bind()`方法来给当前组件绑定事件时, 组件移除时事件也会自动销毁,无需手动销毁。
|
|
||||||
- 灵活的`props`定义
|
|
||||||
|
|
||||||
|
|
||||||
### 示例
|
### 示例
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// alias wkit='//jscdn.ink/wkit/latest/index.js'
|
import { css, html, Component } from '//jscdn.ink/@bd/core/latest/index.js'
|
||||||
import { css, html, Component } from 'wkit'
|
|
||||||
|
|
||||||
class Hello extends Component {
|
class Hello extends Component {
|
||||||
|
|
||||||
|
@ -38,7 +20,7 @@ class Hello extends Component {
|
||||||
bar: String // 简写
|
bar: String // 简写
|
||||||
}
|
}
|
||||||
|
|
||||||
// 若需要支持 scss语法, 则需要使用构建工具编译。
|
// 若需要支持 scss, 则需要使用 @bd/wcui-cli,预处理。
|
||||||
// 可支持数组
|
// 可支持数组
|
||||||
static styles = css`
|
static styles = css`
|
||||||
button { color: #09f; }
|
button { color: #09f; }
|
||||||
|
@ -66,12 +48,7 @@ class Hello extends Component {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!customElements.get('wc-hello')) {
|
|
||||||
customElements.define('wc-hello', Hello)
|
customElements.define('wc-hello', Hello)
|
||||||
}
|
|
||||||
// 和上面的写法等价
|
|
||||||
// 如果不希望修改前缀`wc-`的话, 可以直接调用内置的reg方法注册组件即可。
|
|
||||||
Hello.reg('hello')
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -85,7 +62,3 @@ Hello.reg('hello')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 开源协议
|
|
||||||
|
|
||||||
- BSD 3-Clause License (Lit框架的html模板解析的源码)
|
|
||||||
- MIT (除Lit代码之外的所有代码)
|
|
18
package.json
18
package.json
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "wkit",
|
"name": "@bd/core",
|
||||||
"version": "1.12.0",
|
"version": "1.5.10",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "A library for building fast, lightweight web components.",
|
"description": "百搭UI组件库的核心",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"files": [
|
"files": [
|
||||||
"dist/*"
|
"dist/*"
|
||||||
|
@ -14,16 +14,14 @@
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.wkit.fun/bytedo/wkit.git"
|
"url": "git+https://github.com/bd-js/core.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"wkit",
|
"bd.js",
|
||||||
|
"bdjs",
|
||||||
|
"bd-ui",
|
||||||
"wcui",
|
"wcui",
|
||||||
"web-components",
|
"bd-core"
|
||||||
"native",
|
|
||||||
"cross-framework",
|
|
||||||
"html",
|
|
||||||
"css"
|
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "^0.17.11"
|
"esbuild": "^0.17.11"
|
||||||
|
|
48
src/anim.js
48
src/anim.js
|
@ -1,48 +0,0 @@
|
||||||
/**
|
|
||||||
* {}
|
|
||||||
* @author yutent<yutent.io@gmail.com>
|
|
||||||
* @date 2023/03/29 10:28:16
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function animate(duration = 200, fromto = [], out = false) {
|
|
||||||
if (out === false && this.style.display === 'none') {
|
|
||||||
this.style.display = ''
|
|
||||||
}
|
|
||||||
let res = this.animate(fromto, {
|
|
||||||
duration,
|
|
||||||
direction: out ? 'reverse' : 'normal',
|
|
||||||
fill: 'forwards'
|
|
||||||
})
|
|
||||||
return new Promise(resolve => {
|
|
||||||
res.addEventListener(
|
|
||||||
'finish',
|
|
||||||
_ => {
|
|
||||||
if (out) {
|
|
||||||
this.style.display = 'none'
|
|
||||||
}
|
|
||||||
res.cancel()
|
|
||||||
resolve()
|
|
||||||
},
|
|
||||||
{ once: true }
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
export const MODES = {
|
|
||||||
fade: [{ opacity: 0 }, { opacity: 1 }],
|
|
||||||
scale: [{ transform: 'scale(0)' }, { transform: 'scale(1)' }],
|
|
||||||
'micro-bounce': [
|
|
||||||
{ opacity: 0, transform: 'scale(0.8)' },
|
|
||||||
{ transform: 'scale(1.05)', opacity: 1 },
|
|
||||||
{ transform: 'scale(1)' }
|
|
||||||
],
|
|
||||||
slide: [
|
|
||||||
{ opacity: 0, transform: 'translateY(-50px)' },
|
|
||||||
{ opacity: 1, transform: 'translateY(0)' }
|
|
||||||
],
|
|
||||||
bounce: [
|
|
||||||
{ transform: 'scale(0)' },
|
|
||||||
{ transform: 'scale(1.2)' },
|
|
||||||
{ transform: 'scale(1)' }
|
|
||||||
],
|
|
||||||
rotate: [{ transform: 'rotate(0)' }, { transform: 'rotate(360deg)' }]
|
|
||||||
}
|
|
|
@ -35,17 +35,25 @@ const boolMap = Object.create(null)
|
||||||
|
|
||||||
export { boolMap }
|
export { boolMap }
|
||||||
|
|
||||||
export const KEEP_ALIVE = 'keep-alive' // hyphen
|
|
||||||
export const KEEP_ALIVE_C = 'keepAlive' // camelize
|
|
||||||
export const WC_PART = Symbol('wc_path')
|
export const WC_PART = Symbol('wc_path')
|
||||||
|
export const NO_CHANGE = Symbol('wc-noChange')
|
||||||
export const NOTHING = Symbol('wc-nothing')
|
export const NOTHING = Symbol('wc-nothing')
|
||||||
export const __finalized__ = Symbol('finalized')
|
export const __finalized__ = Symbol('finalized')
|
||||||
|
export const __render__ = Symbol('render')
|
||||||
|
export const __init__ = Symbol('init')
|
||||||
export const __props__ = Symbol('props')
|
export const __props__ = Symbol('props')
|
||||||
export const __changed_props__ = Symbol('changed_props')
|
export const __changed_props__ = Symbol('changed_props')
|
||||||
export const __mounted__ = Symbol('mounted')
|
export const __mounted__ = Symbol('mounted')
|
||||||
|
export const __feedback__ = Symbol('feedback')
|
||||||
export const __pending__ = Symbol('pending')
|
export const __pending__ = Symbol('pending')
|
||||||
|
export const __prop2attr__ = Symbol('prop2attr')
|
||||||
|
export const __attr2prop__ = Symbol('attr2prop')
|
||||||
|
export const __clear_update__ = Symbol('clearupdate')
|
||||||
|
export const __children__ = Symbol('children')
|
||||||
|
export const __updated__ = Symbol('updated')
|
||||||
|
export const __in_tree__ = Symbol('intree')
|
||||||
|
|
||||||
export const RESET_CSS_STYLE = `* {box-sizing: border-box;margin: 0;padding: 0;}::before,::after {box-sizing: border-box;}::selection {background: var(--selection-background, var(--color-plain-a));color: var(--selection-color, inherit);}`
|
export const RESET_CSS_STYLE = `* {box-sizing: border-box;margin: 0;padding: 0;}::before,::after {box-sizing: border-box;}`
|
||||||
|
|
||||||
function getDefaultValue(type) {
|
function getDefaultValue(type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -79,26 +87,8 @@ function getType(v) {
|
||||||
? { type: Array, default: v }
|
? { type: Array, default: v }
|
||||||
: { type: Object, default: v }
|
: { type: Object, default: v }
|
||||||
|
|
||||||
case 'string':
|
|
||||||
let type = String
|
|
||||||
let attribute = true
|
|
||||||
if (v.includes('!')) {
|
|
||||||
if (v.startsWith('str!')) {
|
|
||||||
v = v.slice(4)
|
|
||||||
} else if (v.startsWith('num!')) {
|
|
||||||
type = Number
|
|
||||||
v = +v.slice(4) || 0
|
|
||||||
} else if (v.startsWith('bool!')) {
|
|
||||||
type = Boolean
|
|
||||||
v = v.slice(5)
|
|
||||||
v = v !== 'false' && v !== ''
|
|
||||||
}
|
|
||||||
attribute = false
|
|
||||||
}
|
|
||||||
return { type, default: v, attribute }
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return { type: null, default: '' }
|
return { type: String, default: v + '' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,10 +118,6 @@ export function parsePropsDeclaration(options) {
|
||||||
options = { type: Array }
|
options = { type: Array }
|
||||||
break
|
break
|
||||||
|
|
||||||
case String:
|
|
||||||
options = { type: String }
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
options = getType(options)
|
options = getType(options)
|
||||||
break
|
break
|
||||||
|
@ -139,9 +125,7 @@ export function parsePropsDeclaration(options) {
|
||||||
options.default = options.hasOwnProperty('default')
|
options.default = options.hasOwnProperty('default')
|
||||||
? options.default
|
? options.default
|
||||||
: getDefaultValue(options.type)
|
: getDefaultValue(options.type)
|
||||||
options.attribute = options.hasOwnProperty('attribute')
|
options.attribute = true
|
||||||
? options.attribute
|
|
||||||
: true
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,9 +159,6 @@ export function fixedValue(value, options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case null:
|
|
||||||
return value
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return value === null || value === void 0 ? null : value + ''
|
return value === null || value === void 0 ? null : value + ''
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ export function adoptStyles(root, styles = '') {
|
||||||
styles = (RESET_CSS_STYLE + styles.join(' ')).trim()
|
styles = (RESET_CSS_STYLE + styles.join(' ')).trim()
|
||||||
sheet.replaceSync(styles)
|
sheet.replaceSync(styles)
|
||||||
if (root.adoptedStyleSheets) {
|
if (root.adoptedStyleSheets) {
|
||||||
root.adoptedStyleSheets = [sheet]
|
root.adoptedStyleSheets.push(sheet)
|
||||||
} else {
|
} else {
|
||||||
root.appendChild(sheet.elem)
|
root.appendChild(sheet.elem)
|
||||||
}
|
}
|
||||||
|
|
632
src/html.js
632
src/html.js
|
@ -1,35 +1,28 @@
|
||||||
/**
|
import { boolMap, WC_PART, NO_CHANGE, NOTHING } from './constants.js'
|
||||||
* {模板解析, 基于 lit-html 二次开发}
|
|
||||||
* @author yutent<yutent.io@gmail.com>
|
|
||||||
* @date 2023/03/7 16:36:59
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { boolMap, WC_PART, NOTHING } from './constants.js'
|
const boundAttributeSuffix = '$wc$'
|
||||||
import { animate, MODES } from './anim.js'
|
const marker = `wc$${String(Math.random()).slice(9)}$`
|
||||||
import { nextTick, noop } from './utils.js'
|
const markerMatch = '?' + marker
|
||||||
|
const nodeMarker = `<${markerMatch}>`
|
||||||
const BIND_ATTR_SUFFIX = '{{$wkit$}}'
|
const createMarker = (v = '') => document.createComment(v)
|
||||||
const MARKER = `{{^wkit${String(Math.random()).slice(-8)}^}}`
|
|
||||||
const MARKER_MATCH = '?' + MARKER
|
|
||||||
const NODE_MARKER = `<${MARKER_MATCH}>`
|
|
||||||
|
|
||||||
// 是否原始值
|
|
||||||
const isPrimitive = value =>
|
const isPrimitive = value =>
|
||||||
value === null || (typeof value !== 'object' && typeof value !== 'function')
|
value === null || (typeof value != 'object' && typeof value != 'function')
|
||||||
const isArray = Array.isArray
|
const isArray = Array.isArray
|
||||||
const isIterable = value =>
|
const isIterable = value =>
|
||||||
value ? isArray(value) || typeof value[Symbol.iterator] === 'function' : false
|
isArray(value) ||
|
||||||
|
typeof (value === null || value === void 0
|
||||||
|
? false
|
||||||
|
: value[Symbol.iterator]) === 'function'
|
||||||
const SPACE_CHAR = `[ \n\f\r]`
|
const SPACE_CHAR = `[ \n\f\r]`
|
||||||
const ATTR_VALUE_CHAR = `[^ \n\f\r"'\`<>=]`
|
const ATTR_VALUE_CHAR = `[^ \n\f\r"'\`<>=]`
|
||||||
const NAME_CHAR = `[^\\s"'>=/]`
|
const NAME_CHAR = `[^\\s"'>=/]`
|
||||||
const TEXT_END_REGEX = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g
|
const textEndRegex = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g
|
||||||
const COMMENT_START = 1
|
const COMMENT_START = 1
|
||||||
const TAG_NAME = 2
|
const TAG_NAME = 2
|
||||||
const DYNAMIC_TAG_NAME = 3
|
const DYNAMIC_TAG_NAME = 3
|
||||||
const COMMENT_END_REGEXP = /-->/g
|
const commentEndRegex = /-->/g
|
||||||
const COMMENT_END_REGEXP_2 = />/g
|
const comment2EndRegex = />/g
|
||||||
const TAG_END_REGEXP = new RegExp(
|
const tagEndRegex = new RegExp(
|
||||||
`>|${SPACE_CHAR}(?:(${NAME_CHAR}+)(${SPACE_CHAR}*=${SPACE_CHAR}*(?:${ATTR_VALUE_CHAR}|("|')|))|$)`,
|
`>|${SPACE_CHAR}(?:(${NAME_CHAR}+)(${SPACE_CHAR}*=${SPACE_CHAR}*(?:${ATTR_VALUE_CHAR}|("|')|))|$)`,
|
||||||
'g'
|
'g'
|
||||||
)
|
)
|
||||||
|
@ -37,9 +30,9 @@ const ENTIRE_MATCH = 0
|
||||||
const ATTRIBUTE_NAME = 1
|
const ATTRIBUTE_NAME = 1
|
||||||
const SPACES_AND_EQUALS = 2
|
const SPACES_AND_EQUALS = 2
|
||||||
const QUOTE_CHAR = 3
|
const QUOTE_CHAR = 3
|
||||||
const SINGLE_QUOTE_REGEXP = /'/g
|
const singleQuoteAttrEndRegex = /'/g
|
||||||
const DOUBLE_QUOTE_REGEXP = /"/g
|
const doubleQuoteAttrEndRegex = /"/g
|
||||||
const RAW_TEXT_ELEM_REGEXP = /^(?:script|style|textarea|title)$/i
|
const rawTextElement = /^(?:script|style|textarea|title)$/i
|
||||||
const HTML_RESULT = 1
|
const HTML_RESULT = 1
|
||||||
const SVG_RESULT = 2
|
const SVG_RESULT = 2
|
||||||
const ATTRIBUTE_PART = 1
|
const ATTRIBUTE_PART = 1
|
||||||
|
@ -48,7 +41,7 @@ const EVENT_PART = 5
|
||||||
const ELEMENT_PART = 6
|
const ELEMENT_PART = 6
|
||||||
const COMMENT_PART = 7
|
const COMMENT_PART = 7
|
||||||
|
|
||||||
const TEMPLATE_CACHE = new Map()
|
const templateCache = new WeakMap()
|
||||||
const walker = document.createTreeWalker(document, 129, null, false)
|
const walker = document.createTreeWalker(document, 129, null, false)
|
||||||
|
|
||||||
function getTemplateHtml(strings, type) {
|
function getTemplateHtml(strings, type) {
|
||||||
|
@ -56,7 +49,7 @@ function getTemplateHtml(strings, type) {
|
||||||
let attrNames = []
|
let attrNames = []
|
||||||
let html2 = type === SVG_RESULT ? '<svg>' : ''
|
let html2 = type === SVG_RESULT ? '<svg>' : ''
|
||||||
let rawTextEndRegex
|
let rawTextEndRegex
|
||||||
let regex = TEXT_END_REGEX
|
let regex = textEndRegex
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
let s = strings[i]
|
let s = strings[i]
|
||||||
let attrNameEndIndex = -1
|
let attrNameEndIndex = -1
|
||||||
|
@ -70,26 +63,25 @@ function getTemplateHtml(strings, type) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
lastIndex = regex.lastIndex
|
lastIndex = regex.lastIndex
|
||||||
|
if (regex === textEndRegex) {
|
||||||
if (regex === TEXT_END_REGEX) {
|
|
||||||
if (match[COMMENT_START] === '!--') {
|
if (match[COMMENT_START] === '!--') {
|
||||||
regex = COMMENT_END_REGEXP
|
regex = commentEndRegex
|
||||||
} else if (match[COMMENT_START] !== void 0) {
|
} else if (match[COMMENT_START] !== void 0) {
|
||||||
regex = COMMENT_END_REGEXP_2
|
regex = comment2EndRegex
|
||||||
} else if (match[TAG_NAME] !== void 0) {
|
} else if (match[TAG_NAME] !== void 0) {
|
||||||
if (RAW_TEXT_ELEM_REGEXP.test(match[TAG_NAME])) {
|
if (rawTextElement.test(match[TAG_NAME])) {
|
||||||
rawTextEndRegex = new RegExp(`</${match[TAG_NAME]}`, 'g')
|
rawTextEndRegex = new RegExp(`</${match[TAG_NAME]}`, 'g')
|
||||||
}
|
}
|
||||||
regex = TAG_END_REGEXP
|
regex = tagEndRegex
|
||||||
} else if (match[DYNAMIC_TAG_NAME] !== void 0) {
|
} else if (match[DYNAMIC_TAG_NAME] !== void 0) {
|
||||||
regex = TAG_END_REGEXP
|
regex = tagEndRegex
|
||||||
}
|
}
|
||||||
} else if (regex === TAG_END_REGEXP) {
|
} else if (regex === tagEndRegex) {
|
||||||
if (match[ENTIRE_MATCH] === '>') {
|
if (match[ENTIRE_MATCH] === '>') {
|
||||||
regex =
|
regex =
|
||||||
rawTextEndRegex !== null && rawTextEndRegex !== void 0
|
rawTextEndRegex !== null && rawTextEndRegex !== void 0
|
||||||
? rawTextEndRegex
|
? rawTextEndRegex
|
||||||
: TEXT_END_REGEX
|
: textEndRegex
|
||||||
attrNameEndIndex = -1
|
attrNameEndIndex = -1
|
||||||
} else if (match[ATTRIBUTE_NAME] === void 0) {
|
} else if (match[ATTRIBUTE_NAME] === void 0) {
|
||||||
attrNameEndIndex = -2
|
attrNameEndIndex = -2
|
||||||
|
@ -98,76 +90,60 @@ function getTemplateHtml(strings, type) {
|
||||||
attrName = match[ATTRIBUTE_NAME]
|
attrName = match[ATTRIBUTE_NAME]
|
||||||
regex =
|
regex =
|
||||||
match[QUOTE_CHAR] === void 0
|
match[QUOTE_CHAR] === void 0
|
||||||
? TAG_END_REGEXP
|
? tagEndRegex
|
||||||
: match[QUOTE_CHAR] === '"'
|
: match[QUOTE_CHAR] === '"'
|
||||||
? DOUBLE_QUOTE_REGEXP
|
? doubleQuoteAttrEndRegex
|
||||||
: SINGLE_QUOTE_REGEXP
|
: singleQuoteAttrEndRegex
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
regex === DOUBLE_QUOTE_REGEXP ||
|
regex === doubleQuoteAttrEndRegex ||
|
||||||
regex === SINGLE_QUOTE_REGEXP
|
regex === singleQuoteAttrEndRegex
|
||||||
) {
|
) {
|
||||||
regex = TAG_END_REGEXP
|
regex = tagEndRegex
|
||||||
} else if (
|
} else if (regex === commentEndRegex || regex === comment2EndRegex) {
|
||||||
regex === COMMENT_END_REGEXP ||
|
regex = textEndRegex
|
||||||
regex === COMMENT_END_REGEXP_2
|
|
||||||
) {
|
|
||||||
regex = TEXT_END_REGEX
|
|
||||||
} else {
|
} else {
|
||||||
regex = TAG_END_REGEXP
|
regex = tagEndRegex
|
||||||
rawTextEndRegex = void 0
|
rawTextEndRegex = void 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let end =
|
let end =
|
||||||
regex === TAG_END_REGEXP && strings[i + 1].startsWith('/>') ? ' ' : ''
|
regex === tagEndRegex && strings[i + 1].startsWith('/>') ? ' ' : ''
|
||||||
html2 +=
|
html2 +=
|
||||||
regex === TEXT_END_REGEX
|
regex === textEndRegex
|
||||||
? s + NODE_MARKER
|
? s + nodeMarker
|
||||||
: attrNameEndIndex >= 0
|
: attrNameEndIndex >= 0
|
||||||
? (attrNames.push(attrName),
|
? (attrNames.push(attrName),
|
||||||
s.slice(0, attrNameEndIndex) +
|
s.slice(0, attrNameEndIndex) +
|
||||||
BIND_ATTR_SUFFIX +
|
boundAttributeSuffix +
|
||||||
s.slice(attrNameEndIndex)) +
|
s.slice(attrNameEndIndex)) +
|
||||||
MARKER +
|
marker +
|
||||||
end
|
end
|
||||||
: s +
|
: s +
|
||||||
MARKER +
|
marker +
|
||||||
(attrNameEndIndex === -2 ? (attrNames.push(void 0), i) : end)
|
(attrNameEndIndex === -2 ? (attrNames.push(void 0), i) : end)
|
||||||
}
|
}
|
||||||
let htmlResult =
|
let htmlResult =
|
||||||
html2 + (strings[len] || '<?>') + (type === SVG_RESULT ? '</svg>' : '')
|
html2 + (strings[len] || '<?>') + (type === SVG_RESULT ? '</svg>' : '')
|
||||||
if (!isArray(strings) || !strings.hasOwnProperty('raw')) {
|
if (!Array.isArray(strings) || !strings.hasOwnProperty('raw')) {
|
||||||
throw new Error('invalid html ast')
|
let message = 'invalid template strings array'
|
||||||
|
|
||||||
|
throw new Error(message)
|
||||||
}
|
}
|
||||||
return [htmlResult, attrNames]
|
return [htmlResult, attrNames]
|
||||||
}
|
}
|
||||||
|
|
||||||
function createElement(v = '') {
|
|
||||||
let el = document.createElement('template')
|
|
||||||
el.innerHTML = v
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMarker(v = '') {
|
|
||||||
return document.createComment(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Template {
|
class Template {
|
||||||
parts = []
|
constructor({ strings, ['__dom_type__']: type }, options) {
|
||||||
|
this.parts = []
|
||||||
constructor({ strings, values, __dom_type__: type }, options) {
|
|
||||||
let node
|
let node
|
||||||
let nodeIndex = 0
|
let nodeIndex = 0
|
||||||
let attrNameIndex = 0
|
let attrNameIndex = 0
|
||||||
let partCount = strings.length - 1
|
let partCount = strings.length - 1
|
||||||
let parts = this.parts
|
let parts = this.parts
|
||||||
let [html2, attrNames] = getTemplateHtml(strings, type)
|
let [html2, attrNames] = getTemplateHtml(strings, type)
|
||||||
|
this.el = Template.createElement(html2)
|
||||||
this.el = createElement(html2)
|
|
||||||
|
|
||||||
walker.currentNode = this.el.content
|
walker.currentNode = this.el.content
|
||||||
|
|
||||||
if (type === SVG_RESULT) {
|
if (type === SVG_RESULT) {
|
||||||
let content = this.el.content
|
let content = this.el.content
|
||||||
let svgElement = content.firstChild
|
let svgElement = content.firstChild
|
||||||
|
@ -181,33 +157,27 @@ class Template {
|
||||||
let attrsToRemove = []
|
let attrsToRemove = []
|
||||||
|
|
||||||
for (let name of node.getAttributeNames()) {
|
for (let name of node.getAttributeNames()) {
|
||||||
if (name.endsWith(BIND_ATTR_SUFFIX) || name.startsWith(MARKER)) {
|
if (
|
||||||
|
name.endsWith(boundAttributeSuffix) ||
|
||||||
|
name.startsWith(marker)
|
||||||
|
) {
|
||||||
let realName = attrNames[attrNameIndex++]
|
let realName = attrNames[attrNameIndex++]
|
||||||
attrsToRemove.push(name)
|
attrsToRemove.push(name)
|
||||||
if (realName !== void 0) {
|
if (realName !== void 0) {
|
||||||
let value = node.getAttribute(
|
let value = node.getAttribute(
|
||||||
realName.toLowerCase() + BIND_ATTR_SUFFIX
|
realName.toLowerCase() + boundAttributeSuffix
|
||||||
)
|
)
|
||||||
let statics = value.split(MARKER)
|
let statics = value.split(marker)
|
||||||
let m = /([#:@])?(.*)/.exec(realName)
|
let m = /([:@])?(.*)/.exec(realName)
|
||||||
let decorates = []
|
|
||||||
|
|
||||||
if (m[1] === '@' && m[2].includes('.')) {
|
|
||||||
decorates = m[2].split('.')
|
|
||||||
m[2] = decorates.shift()
|
|
||||||
}
|
|
||||||
|
|
||||||
parts.push({
|
parts.push({
|
||||||
type: ATTRIBUTE_PART,
|
type: ATTRIBUTE_PART,
|
||||||
index: nodeIndex,
|
index: nodeIndex,
|
||||||
name: m[2],
|
name: m[2],
|
||||||
strings: statics,
|
strings: statics,
|
||||||
decorates,
|
|
||||||
ctor:
|
ctor:
|
||||||
m[1] === ':'
|
m[1] === ':'
|
||||||
? PropertyPart
|
? PropertyPart
|
||||||
: m[1] === '#' && m[2] === 'animation'
|
|
||||||
? AnimPart
|
|
||||||
: m[1] === '@'
|
: m[1] === '@'
|
||||||
? EventPart
|
? EventPart
|
||||||
: AttributePart
|
: AttributePart
|
||||||
|
@ -224,8 +194,8 @@ class Template {
|
||||||
node.removeAttribute(name)
|
node.removeAttribute(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (RAW_TEXT_ELEM_REGEXP.test(node.tagName)) {
|
if (rawTextElement.test(node.tagName)) {
|
||||||
let strings2 = node.textContent.split(MARKER)
|
let strings2 = node.textContent.split(marker)
|
||||||
let lastIndex = strings2.length - 1
|
let lastIndex = strings2.length - 1
|
||||||
if (lastIndex > 0) {
|
if (lastIndex > 0) {
|
||||||
node.textContent = ''
|
node.textContent = ''
|
||||||
|
@ -239,66 +209,113 @@ class Template {
|
||||||
}
|
}
|
||||||
} else if (node.nodeType === 8) {
|
} else if (node.nodeType === 8) {
|
||||||
let data = node.data
|
let data = node.data
|
||||||
if (data === MARKER_MATCH) {
|
if (data === markerMatch) {
|
||||||
parts.push({ type: CHILD_PART, index: nodeIndex })
|
parts.push({ type: CHILD_PART, index: nodeIndex })
|
||||||
} else {
|
} else {
|
||||||
let i = -1
|
let i = -1
|
||||||
while ((i = node.data.indexOf(MARKER, i + 1)) !== -1) {
|
while ((i = node.data.indexOf(marker, i + 1)) !== -1) {
|
||||||
parts.push({ type: COMMENT_PART, index: nodeIndex })
|
parts.push({ type: COMMENT_PART, index: nodeIndex })
|
||||||
i += MARKER.length - 1
|
i += marker.length - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodeIndex++
|
nodeIndex++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static createElement(html2) {
|
||||||
|
let el = document.createElement('template')
|
||||||
|
el.innerHTML = html2
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function resolveDirective(part, value, parent = part, attributeIndex) {
|
||||||
|
if (value === NO_CHANGE) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
let currentDirective =
|
||||||
|
attributeIndex !== void 0
|
||||||
|
? parent.__directives?.[attributeIndex]
|
||||||
|
: parent.__directive
|
||||||
|
|
||||||
|
let nextDirectiveConstructor = isPrimitive(value)
|
||||||
|
? void 0
|
||||||
|
: value['_$litDirective$']
|
||||||
|
|
||||||
|
if (currentDirective?.constructor !== nextDirectiveConstructor) {
|
||||||
|
currentDirective._$notifyDirectiveConnectionChanged?.call(
|
||||||
|
currentDirective,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
if (nextDirectiveConstructor === void 0) {
|
||||||
|
currentDirective = void 0
|
||||||
|
} else {
|
||||||
|
currentDirective = new nextDirectiveConstructor(part)
|
||||||
|
currentDirective._$initialize(part, parent, attributeIndex)
|
||||||
|
}
|
||||||
|
if (attributeIndex !== void 0) {
|
||||||
|
if (!parent.__directives) {
|
||||||
|
parent.__directives = []
|
||||||
|
}
|
||||||
|
parent.__directives[attributeIndex] = currentDirective
|
||||||
|
} else {
|
||||||
|
parent.__directive = currentDirective
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentDirective !== void 0) {
|
||||||
|
value = resolveDirective(
|
||||||
|
part,
|
||||||
|
currentDirective._$resolve(part, value.values),
|
||||||
|
currentDirective,
|
||||||
|
attributeIndex
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
class TemplateInstance {
|
class TemplateInstance {
|
||||||
constructor(template, parent) {
|
constructor(template, parent) {
|
||||||
this.$parts = []
|
this._parts = []
|
||||||
this.$template = template
|
this._$disconnectableChildren = void 0
|
||||||
this.$parent = parent
|
this._$template = template
|
||||||
|
this._$parent = parent
|
||||||
}
|
}
|
||||||
get parentNode() {
|
get parentNode() {
|
||||||
return this.$parent.parentNode
|
return this._$parent.parentNode
|
||||||
|
}
|
||||||
|
get _$isConnected() {
|
||||||
|
return this._$parent._$isConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
#checkRef(node, walker, options) {
|
#checkRef(node, walker, options) {
|
||||||
do {
|
do {
|
||||||
if (node && node.nodeType === 1 && node.getAttribute('ref')) {
|
if (node.nodeType === 1 && node.getAttribute('ref')) {
|
||||||
options.host.$refs[node.getAttribute('ref')] = node
|
options.host.$refs[node.getAttribute('ref')] = node
|
||||||
node.removeAttribute('ref')
|
node.removeAttribute('ref')
|
||||||
}
|
}
|
||||||
} while ((node = walker.nextNode()) !== null)
|
} while ((node = walker.nextNode()) !== null)
|
||||||
}
|
}
|
||||||
|
|
||||||
clone(options) {
|
_clone(options) {
|
||||||
let {
|
let {
|
||||||
el: { content },
|
el: { content },
|
||||||
parts
|
parts
|
||||||
} = this.$template
|
} = this._$template
|
||||||
|
|
||||||
let fragment = document.importNode(content, true)
|
let fragment = (options?.creationScope || document).importNode(
|
||||||
|
content,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
walker.currentNode = fragment
|
||||||
|
let node = walker.nextNode()
|
||||||
let nodeIndex = 0
|
let nodeIndex = 0
|
||||||
let partIndex = 0
|
let partIndex = 0
|
||||||
let templatePart = parts[0]
|
let templatePart = parts[0]
|
||||||
let node = null
|
|
||||||
|
|
||||||
walker.currentNode = fragment
|
|
||||||
node = walker.nextNode()
|
|
||||||
|
|
||||||
// 没有动态绑定时, 查一遍是否有ref属性
|
// 没有动态绑定时, 查一遍是否有ref属性
|
||||||
if (templatePart === void 0) {
|
if (templatePart === void 0) {
|
||||||
this.#checkRef(node, walker, options)
|
this.#checkRef(node, walker, options)
|
||||||
} else {
|
} else {
|
||||||
while (templatePart !== void 0) {
|
while (templatePart !== void 0) {
|
||||||
if (node.nodeType === 1 && node.getAttribute('ref')) {
|
|
||||||
if (options.host.$refs) {
|
|
||||||
options.host.$refs[node.getAttribute('ref')] = node
|
|
||||||
node.removeAttribute('ref')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nodeIndex === templatePart.index) {
|
if (nodeIndex === templatePart.index) {
|
||||||
let part
|
let part
|
||||||
if (templatePart.type === CHILD_PART) {
|
if (templatePart.type === CHILD_PART) {
|
||||||
|
@ -308,14 +325,19 @@ class TemplateInstance {
|
||||||
node,
|
node,
|
||||||
templatePart.name,
|
templatePart.name,
|
||||||
templatePart.strings,
|
templatePart.strings,
|
||||||
templatePart.decorates,
|
|
||||||
this,
|
this,
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
} else if (templatePart.type === ELEMENT_PART) {
|
} else if (templatePart.type === ELEMENT_PART) {
|
||||||
part = new ElementPart(node, this, options)
|
part = new ElementPart(node, this, options)
|
||||||
}
|
}
|
||||||
this.$parts.push(part)
|
if (node.nodeType === 1 && node.getAttribute('ref')) {
|
||||||
|
if (options.host.$refs) {
|
||||||
|
options.host.$refs[node.getAttribute('ref')] = node
|
||||||
|
node.removeAttribute('ref')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._parts.push(part)
|
||||||
templatePart = parts[++partIndex]
|
templatePart = parts[++partIndex]
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
@ -334,16 +356,15 @@ class TemplateInstance {
|
||||||
|
|
||||||
return fragment
|
return fragment
|
||||||
}
|
}
|
||||||
|
_update(values) {
|
||||||
update(values) {
|
|
||||||
let i = 0
|
let i = 0
|
||||||
for (let part of this.$parts) {
|
for (let part of this._parts) {
|
||||||
if (part !== void 0) {
|
if (part !== void 0) {
|
||||||
if (part.strings !== void 0) {
|
if (part.strings !== void 0) {
|
||||||
part.$setValue(values, i)
|
part._$setValue(values, part, i)
|
||||||
i += part.strings.length - 2
|
i += part.strings.length - 2
|
||||||
} else {
|
} else {
|
||||||
part.$setValue(values[i])
|
part._$setValue(values[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
|
@ -352,116 +373,118 @@ class TemplateInstance {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChildPart {
|
class ChildPart {
|
||||||
type = CHILD_PART
|
|
||||||
#value = NOTHING
|
|
||||||
|
|
||||||
constructor(startNode, endNode, parent, options) {
|
constructor(startNode, endNode, parent, options) {
|
||||||
this.startNode = startNode
|
this.type = CHILD_PART
|
||||||
this.endNode = endNode
|
this._$committedValue = NOTHING
|
||||||
this.$parent = parent
|
this._$disconnectableChildren = void 0
|
||||||
|
this._$startNode = startNode
|
||||||
|
this._$endNode = endNode
|
||||||
|
this._$parent = parent
|
||||||
this.options = options
|
this.options = options
|
||||||
|
this.__isConnected = options?.isConnected || true
|
||||||
|
}
|
||||||
|
get _$isConnected() {
|
||||||
|
return this._$parent?._$isConnected || this.__isConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
get parentNode() {
|
get parentNode() {
|
||||||
let parentNode = this.startNode.parentNode
|
let parentNode = this._$startNode.parentNode
|
||||||
let parent = this.$parent
|
let parent = this._$parent
|
||||||
if (parent !== void 0 && parentNode.nodeType === 11) {
|
if (parent !== void 0 && parentNode.nodeType === 11) {
|
||||||
parentNode = parent.parentNode
|
parentNode = parent.parentNode
|
||||||
}
|
}
|
||||||
return parentNode
|
return parentNode
|
||||||
}
|
}
|
||||||
|
get startNode() {
|
||||||
$setValue(value, clear) {
|
return this._$startNode
|
||||||
|
}
|
||||||
|
get endNode() {
|
||||||
|
return this._$endNode
|
||||||
|
}
|
||||||
|
_$setValue(value, directiveParent = this) {
|
||||||
|
value = resolveDirective(this, value, directiveParent)
|
||||||
if (isPrimitive(value)) {
|
if (isPrimitive(value)) {
|
||||||
if (value === NOTHING || value == null || value === '') {
|
if (value === NOTHING || value == null || value === '') {
|
||||||
if (this.#value !== NOTHING) {
|
if (this._$committedValue !== NOTHING) {
|
||||||
this.#clear()
|
this.#clear()
|
||||||
}
|
}
|
||||||
this.#value = NOTHING
|
this._$committedValue = NOTHING
|
||||||
} else if (value !== this.#value) {
|
} else if (value !== this._$committedValue && value !== NO_CHANGE) {
|
||||||
this.#commitText(value)
|
this._commitText(value)
|
||||||
}
|
}
|
||||||
} else if (value.__dom_type__ !== void 0) {
|
} else if (value['__dom_type__'] !== void 0) {
|
||||||
this.#commitTemplateResult(value)
|
this._commitTemplateResult(value)
|
||||||
} else if (value.nodeType !== void 0) {
|
} else if (value.nodeType !== void 0) {
|
||||||
this.#commitNode(value)
|
this._commitNode(value)
|
||||||
} else if (isIterable(value)) {
|
} else if (isIterable(value)) {
|
||||||
this.#commitIterable(value)
|
this._commitIterable(value)
|
||||||
} else {
|
} else {
|
||||||
this.#commitText(value)
|
this._commitText(value)
|
||||||
}
|
|
||||||
if (clear) {
|
|
||||||
nextTick(_ => this.#clearBindings())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_insert(node, ref = this._$endNode) {
|
||||||
#insert(node, target = this.endNode) {
|
return this._$startNode.parentNode.insertBefore(node, ref)
|
||||||
return this.startNode.parentNode.insertBefore(node, target)
|
|
||||||
}
|
}
|
||||||
|
_commitNode(value) {
|
||||||
#commitNode(value) {
|
if (this._$committedValue !== value) {
|
||||||
if (this.#value !== value) {
|
|
||||||
this.#clear()
|
this.#clear()
|
||||||
this.#value = this.#insert(value)
|
this._$committedValue = this._insert(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_commitText(value) {
|
||||||
#commitText(value) {
|
if (
|
||||||
if (this.#value !== NOTHING && isPrimitive(this.#value)) {
|
this._$committedValue !== NOTHING &&
|
||||||
let node = this.startNode.nextSibling
|
isPrimitive(this._$committedValue)
|
||||||
|
) {
|
||||||
|
let node = this._$startNode.nextSibling
|
||||||
|
|
||||||
node.data = value
|
node.data = value
|
||||||
} else {
|
} else {
|
||||||
this.#commitNode(document.createTextNode(value))
|
this._commitNode(document.createTextNode(value))
|
||||||
}
|
}
|
||||||
this.#value = value
|
this._$committedValue = value
|
||||||
}
|
}
|
||||||
|
_commitTemplateResult(result) {
|
||||||
#commitTemplateResult(result) {
|
let { values, ['__dom_type__']: type } = result
|
||||||
let { values, __dom_type__: type } = result
|
|
||||||
let template =
|
let template =
|
||||||
typeof type === 'number'
|
typeof type === 'number'
|
||||||
? this.#getTemplate(result)
|
? this.#getTemplate(result)
|
||||||
: (type.el === void 0 && (type.el = createElement(type.h)), type)
|
: (type.el === void 0 && (type.el = Template.createElement(type.h)),
|
||||||
|
type)
|
||||||
|
|
||||||
if (this.#value?.$template === template) {
|
if (this._$committedValue?._$template === template) {
|
||||||
this.#value.update(values)
|
this._$committedValue._update(values)
|
||||||
} else {
|
} else {
|
||||||
let instance = new TemplateInstance(template, this)
|
let instance = new TemplateInstance(template, this)
|
||||||
let fragment = instance.clone(this.options)
|
let fragment = instance._clone(this.options)
|
||||||
|
|
||||||
instance.update(values)
|
instance._update(values)
|
||||||
|
|
||||||
this.#commitNode(fragment)
|
this._commitNode(fragment)
|
||||||
this.#value = instance
|
this._$committedValue = instance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#getTemplate(result) {
|
#getTemplate(result) {
|
||||||
let key = result.strings.join()
|
let template = templateCache.get(result.strings)
|
||||||
let template = TEMPLATE_CACHE.get(key)
|
|
||||||
|
|
||||||
if (template === void 0) {
|
if (template === void 0) {
|
||||||
template = new Template(result, this.options)
|
template = new Template(result, this.options)
|
||||||
TEMPLATE_CACHE.set(key, template)
|
templateCache.set(result.strings, template)
|
||||||
}
|
}
|
||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
_commitIterable(value) {
|
||||||
#commitIterable(value) {
|
if (!isArray(this._$committedValue)) {
|
||||||
if (!isArray(this.#value)) {
|
this._$committedValue = []
|
||||||
this.#value = []
|
|
||||||
this.#clear()
|
this.#clear()
|
||||||
}
|
}
|
||||||
let itemParts = this.#value
|
let itemParts = this._$committedValue
|
||||||
let partIndex = 0
|
let partIndex = 0
|
||||||
let itemPart
|
let itemPart
|
||||||
for (let item of value) {
|
for (let item of value) {
|
||||||
if (partIndex === itemParts.length) {
|
if (partIndex === itemParts.length) {
|
||||||
itemParts.push(
|
itemParts.push(
|
||||||
(itemPart = new ChildPart(
|
(itemPart = new ChildPart(
|
||||||
this.#insert(createMarker()),
|
this._insert(createMarker()),
|
||||||
this.#insert(createMarker()),
|
this._insert(createMarker()),
|
||||||
this,
|
this,
|
||||||
this.options
|
this.options
|
||||||
))
|
))
|
||||||
|
@ -469,125 +492,115 @@ class ChildPart {
|
||||||
} else {
|
} else {
|
||||||
itemPart = itemParts[partIndex]
|
itemPart = itemParts[partIndex]
|
||||||
}
|
}
|
||||||
itemPart.$setValue(item)
|
itemPart._$setValue(item)
|
||||||
partIndex++
|
partIndex++
|
||||||
}
|
}
|
||||||
if (partIndex < itemParts.length) {
|
if (partIndex < itemParts.length) {
|
||||||
this.#clear(itemPart && itemPart.endNode.nextSibling, partIndex)
|
this.#clear(itemPart && itemPart._$endNode.nextSibling, partIndex)
|
||||||
itemParts.length = partIndex
|
itemParts.length = partIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#clear(start = this.startNode.nextSibling, from) {
|
#clear(start = this._$startNode.nextSibling, from) {
|
||||||
while (start && start !== this.endNode) {
|
this._$notifyConnectionChanged?.call(this, false, true, from)
|
||||||
|
|
||||||
|
while (start && start !== this._$endNode) {
|
||||||
let node = start.nextSibling
|
let node = start.nextSibling
|
||||||
start.remove()
|
start.remove()
|
||||||
start = node
|
start = node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setConnected(isConnected) {
|
||||||
/**
|
if (this._$parent === void 0) {
|
||||||
* 清除已被移除的节点事件
|
this.__isConnected = isConnected
|
||||||
*/
|
this._$notifyConnectionChanged?.call(this, isConnected)
|
||||||
#clearBindings() {
|
|
||||||
let host = this.options.host
|
|
||||||
let events = host.$events
|
|
||||||
|
|
||||||
for (let ev in events) {
|
|
||||||
for (let i = -1, it; (it = events[ev][++i]); ) {
|
|
||||||
if (!host.root.contains(it.el)) {
|
|
||||||
it.el.removeEventListener(this.name, it.listener, it.options)
|
|
||||||
events[ev][i] = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
events[ev] = events[ev].filter(e => e !== null)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 常规属性
|
// 常规属性
|
||||||
class AttributePart {
|
class AttributePart {
|
||||||
type = ATTRIBUTE_PART
|
constructor(element, name, strings, parent, options) {
|
||||||
|
this.type = ATTRIBUTE_PART
|
||||||
#value = NOTHING
|
this._$committedValue = NOTHING
|
||||||
|
this._$disconnectableChildren = void 0
|
||||||
constructor(element, name, strings, decorates, parent, options = {}) {
|
|
||||||
this.element = element
|
this.element = element
|
||||||
this.name = name
|
this.name = name
|
||||||
this.decorates = decorates
|
this._$parent = parent
|
||||||
this.$parent = parent
|
|
||||||
this.options = options
|
this.options = options
|
||||||
if (strings.length > 2 || strings[0] !== '' || strings[1] !== '') {
|
if (strings.length > 2 || strings[0] !== '' || strings[1] !== '') {
|
||||||
this.#value = Array(strings.length - 1).fill(null)
|
this._$committedValue = new Array(strings.length - 1).fill(new String())
|
||||||
this.strings = strings
|
this.strings = strings
|
||||||
|
} else {
|
||||||
|
this._$committedValue = NOTHING
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
get tagName() {
|
get tagName() {
|
||||||
return this.element.tagName
|
return this.element.tagName
|
||||||
}
|
}
|
||||||
|
get _$isConnected() {
|
||||||
$setValue(value, valueIndex) {
|
return this._$parent._$isConnected
|
||||||
let strings = this.strings
|
|
||||||
let changed = false
|
|
||||||
|
|
||||||
if (typeof value === 'function') {
|
|
||||||
value = value(this.element)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_$setValue(value, directiveParent = this, valueIndex, noCommit) {
|
||||||
|
let strings = this.strings
|
||||||
|
let change = false
|
||||||
if (strings === void 0) {
|
if (strings === void 0) {
|
||||||
changed = !isPrimitive(value) || value !== this.#value
|
value = resolveDirective(this, value, directiveParent, 0)
|
||||||
if (changed) {
|
change =
|
||||||
this.#value = value
|
!isPrimitive(value) ||
|
||||||
|
(value !== this._$committedValue && value !== NO_CHANGE)
|
||||||
|
if (change) {
|
||||||
|
this._$committedValue = value
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let values = value
|
let values = value
|
||||||
value = strings[0]
|
value = strings[0]
|
||||||
|
|
||||||
for (let i = 0; i < strings.length - 1; i++) {
|
for (let i = 0; i < strings.length - 1; i++) {
|
||||||
let v = values[valueIndex + i]
|
let v = resolveDirective(
|
||||||
|
this,
|
||||||
changed || (changed = !isPrimitive(v) || v !== this.#value[i])
|
values[valueIndex + i],
|
||||||
|
directiveParent,
|
||||||
|
i
|
||||||
|
)
|
||||||
|
if (v === NO_CHANGE) {
|
||||||
|
v = this._$committedValue[i]
|
||||||
|
}
|
||||||
|
change || (change = !isPrimitive(v) || v !== this._$committedValue[i])
|
||||||
if (v === NOTHING) {
|
if (v === NOTHING) {
|
||||||
value = NOTHING
|
value = NOTHING
|
||||||
} else if (value !== NOTHING) {
|
} else if (value !== NOTHING) {
|
||||||
value += (v !== null && v !== void 0 ? v : '') + strings[i + 1]
|
value += (v !== null && v !== void 0 ? v : '') + strings[i + 1]
|
||||||
}
|
}
|
||||||
this.#value[i] = v
|
this._$committedValue[i] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// value属性做特殊处理, 解决输入框无法清空的问题
|
if (change && !noCommit) {
|
||||||
if (changed || this.name === 'value') {
|
|
||||||
this.commitValue(value)
|
this.commitValue(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commitValue(value) {
|
commitValue(value) {
|
||||||
let elem = this.element
|
let isBoolAttr = boolMap[this.name]
|
||||||
let attr = this.name
|
|
||||||
let isBoolAttr = boolMap[attr]
|
|
||||||
// ref属性不渲染到节点上
|
// ref属性不渲染到节点上
|
||||||
if (attr === 'ref') {
|
if (this.name === 'ref') {
|
||||||
this.options.host.$refs[value] = elem
|
this.options.host.$refs[value] = this.element
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isBoolAttr) {
|
if (isBoolAttr) {
|
||||||
elem[isBoolAttr] = !(value === false || value === null)
|
this.element[isBoolAttr] = !(value === false || value === null)
|
||||||
|
|
||||||
if (elem[isBoolAttr]) {
|
if (this.element[isBoolAttr]) {
|
||||||
elem.setAttribute(attr, '')
|
this.element.setAttribute(this.name, '')
|
||||||
} else {
|
} else {
|
||||||
elem.removeAttribute(attr)
|
this.element.removeAttribute(this.name)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// value 绑定, 特殊处理
|
|
||||||
if (attr === 'value') {
|
|
||||||
elem.value = value
|
|
||||||
} else {
|
} else {
|
||||||
if (value === null || value === void 0) {
|
if (value === null || value === void 0) {
|
||||||
elem.removeAttribute(attr)
|
this.element.removeAttribute(this.name)
|
||||||
} else {
|
} else {
|
||||||
elem.setAttribute(attr, value)
|
this.element.setAttribute(this.name, value)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -602,150 +615,87 @@ class PropertyPart extends AttributePart {
|
||||||
this.element[this.name] = value
|
this.element[this.name] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 动画属性
|
|
||||||
class AnimPart extends AttributePart {
|
|
||||||
constructor(...args) {
|
|
||||||
super(...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
commitValue({ type = 'fade', duration, custom, immediate = false } = {}) {
|
|
||||||
let elem = this.element
|
|
||||||
let fromto = MODES[type] || MODES.fade
|
|
||||||
|
|
||||||
if (custom) {
|
|
||||||
fromto = custom
|
|
||||||
}
|
|
||||||
elem.$animate = function (out = false) {
|
|
||||||
return animate.call(this, duration, fromto, out)
|
|
||||||
}
|
|
||||||
elem.$animate.immediate = immediate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 事件属性
|
// 事件属性
|
||||||
class EventPart extends AttributePart {
|
class EventPart extends AttributePart {
|
||||||
type = EVENT_PART
|
|
||||||
|
|
||||||
#listener = null
|
|
||||||
|
|
||||||
#prevent = noop
|
|
||||||
#stop = noop
|
|
||||||
#checkSelf = noop
|
|
||||||
|
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args)
|
super(...args)
|
||||||
|
this.type = EVENT_PART
|
||||||
}
|
}
|
||||||
|
_$setValue(newListener, directiveParent = this) {
|
||||||
|
newListener =
|
||||||
|
resolveDirective(this, newListener, directiveParent, 0) || NOTHING
|
||||||
|
|
||||||
$setValue(listener) {
|
if (newListener === NO_CHANGE) {
|
||||||
let host = this.options.host
|
|
||||||
let elem = this.element
|
|
||||||
let name = this.name
|
|
||||||
let options = {}
|
|
||||||
let events = host.$events[name] || []
|
|
||||||
|
|
||||||
if (this.decorates.length) {
|
|
||||||
for (let it of this.decorates) {
|
|
||||||
switch (it) {
|
|
||||||
case 'stop':
|
|
||||||
this.#stop = ev => ev.stopPropagation()
|
|
||||||
break
|
|
||||||
case 'prevent':
|
|
||||||
this.#prevent = ev => ev.preventDefault()
|
|
||||||
break
|
|
||||||
case 'self':
|
|
||||||
this.#checkSelf = ev => ev.target === elem
|
|
||||||
break
|
|
||||||
case 'capture':
|
|
||||||
case 'once':
|
|
||||||
case 'passive':
|
|
||||||
options[it] = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let shouldRemove = listener !== this.#listener
|
|
||||||
|
|
||||||
if (this.#listener) {
|
|
||||||
for (let i = -1, it; (it = events[++i]); ) {
|
|
||||||
if (it.el === elem) {
|
|
||||||
shouldRemove =
|
|
||||||
shouldRemove ||
|
|
||||||
options.capture !== it.capture ||
|
|
||||||
options.once !== it.once ||
|
|
||||||
options.passive !== it.passive
|
|
||||||
if (shouldRemove) {
|
|
||||||
it.el.removeEventListener(name, it.listener, it.options)
|
|
||||||
events[i] = null
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
events = events.filter(it => it !== null)
|
|
||||||
host.$events[name] = events
|
|
||||||
|
|
||||||
if (listener && shouldRemove) {
|
|
||||||
elem.addEventListener(name, this, options)
|
|
||||||
this.#listener = listener
|
|
||||||
|
|
||||||
events.push({ el: elem, listener: this, options })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEvent(ev) {
|
|
||||||
this.#stop(ev)
|
|
||||||
this.#prevent(ev)
|
|
||||||
if (this.#checkSelf(ev) === false) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.#listener.call(this.options.host, ev)
|
let oldListener = this._$committedValue
|
||||||
|
let shouldRemoveListener =
|
||||||
|
(newListener === NOTHING && oldListener !== NOTHING) ||
|
||||||
|
newListener.capture !== oldListener.capture ||
|
||||||
|
newListener.once !== oldListener.once ||
|
||||||
|
newListener.passive !== oldListener.passive
|
||||||
|
let shouldAddListener =
|
||||||
|
newListener !== NOTHING &&
|
||||||
|
(oldListener === NOTHING || shouldRemoveListener)
|
||||||
|
|
||||||
|
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') {
|
||||||
|
this._$committedValue.call(this.options?.host || this.element, event)
|
||||||
|
} else {
|
||||||
|
this._$committedValue.handleEvent(event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ElementPart {
|
class ElementPart {
|
||||||
type = ELEMENT_PART
|
|
||||||
constructor(element, parent, options) {
|
constructor(element, parent, options) {
|
||||||
this.element = element
|
this.element = element
|
||||||
this.$parent = parent
|
this.type = ELEMENT_PART
|
||||||
|
this._$disconnectableChildren = void 0
|
||||||
|
this._$parent = parent
|
||||||
this.options = options
|
this.options = options
|
||||||
}
|
}
|
||||||
|
get _$isConnected() {
|
||||||
$setValue() {}
|
return this._$parent._$isConnected
|
||||||
|
}
|
||||||
|
_$setValue(value) {
|
||||||
|
resolveDirective(this, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function render(value, container, options = {}) {
|
export function render(value, container, options = {}) {
|
||||||
let part = container[WC_PART]
|
let part = container[WC_PART]
|
||||||
|
|
||||||
if (part === void 0) {
|
if (part === void 0) {
|
||||||
part = new ChildPart(
|
container[WC_PART] = part = new ChildPart(
|
||||||
container.insertBefore(createMarker(), null),
|
container.insertBefore(createMarker(), null),
|
||||||
null,
|
null,
|
||||||
void 0,
|
void 0,
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
container[WC_PART] = part
|
|
||||||
}
|
}
|
||||||
part.$setValue(value, true)
|
part._$setValue(value)
|
||||||
|
|
||||||
return part
|
return part
|
||||||
}
|
}
|
||||||
|
|
||||||
export function html(strings, ...values) {
|
export const html = (strings, ...values) => {
|
||||||
return {
|
return {
|
||||||
__dom_type__: HTML_RESULT,
|
__dom_type__: HTML_RESULT,
|
||||||
strings,
|
strings,
|
||||||
values
|
values
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export const svg = (strings, ...values) => {
|
||||||
export function raw(str, values = []) {
|
|
||||||
let strings = values.length ? str.split('%s') : [str]
|
|
||||||
strings.raw = true
|
|
||||||
return html(strings, ...values)
|
|
||||||
}
|
|
||||||
export function svg(strings, ...values) {
|
|
||||||
return {
|
return {
|
||||||
__dom_type__: SVG_RESULT,
|
__dom_type__: SVG_RESULT,
|
||||||
strings,
|
strings,
|
||||||
|
|
395
src/index.js
395
src/index.js
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* { wkit library }
|
* {wcui的核心库, 基于lit-core二次开发}
|
||||||
* @author yutent<yutent.io@gmail.com>
|
* @author yutent<yutent.io@gmail.com>
|
||||||
* @date 2023/03/07 18:10:43
|
* @date 2023/03/07 18:10:43
|
||||||
*/
|
*/
|
||||||
|
@ -7,93 +7,26 @@ import {
|
||||||
fixedValue,
|
fixedValue,
|
||||||
parsePropsDeclaration,
|
parsePropsDeclaration,
|
||||||
boolMap,
|
boolMap,
|
||||||
KEEP_ALIVE,
|
|
||||||
KEEP_ALIVE_C,
|
|
||||||
__finalized__,
|
__finalized__,
|
||||||
|
__render__,
|
||||||
|
__init__,
|
||||||
__props__,
|
__props__,
|
||||||
__changed_props__,
|
__changed_props__,
|
||||||
__mounted__,
|
__mounted__,
|
||||||
__pending__
|
__feedback__,
|
||||||
|
__pending__,
|
||||||
|
__prop2attr__,
|
||||||
|
__attr2prop__,
|
||||||
|
__clear_update__,
|
||||||
|
__children__,
|
||||||
|
__updated__,
|
||||||
|
__in_tree__
|
||||||
} from './constants.js'
|
} from './constants.js'
|
||||||
import { css, adoptStyles } from './css.js'
|
import { css, adoptStyles } from './css.js'
|
||||||
import { render, html, svg, raw } from './html.js'
|
import { render, html, svg } from './html.js'
|
||||||
import { animate, MODES } from './anim.js'
|
import { nextTick, fire, bind, unbind } from './utils.js'
|
||||||
import { nextTick, fire, bind, unbind, hyphen, camelize } from './utils.js'
|
export { $, $$, offset, outsideClick, clearOutsideClick } from './utils.js'
|
||||||
|
export { html, css, svg, bind, unbind, nextTick }
|
||||||
export {
|
|
||||||
$,
|
|
||||||
$$,
|
|
||||||
range,
|
|
||||||
offset,
|
|
||||||
outsideClick,
|
|
||||||
clearOutsideClick
|
|
||||||
} from './utils.js'
|
|
||||||
export { html, raw, css, svg, bind, unbind, nextTick, fire, hyphen, camelize }
|
|
||||||
|
|
||||||
function safely(callback, ...args) {
|
|
||||||
try {
|
|
||||||
callback && callback.apply(this, args)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 简单的类名解析
|
|
||||||
export function classMap(data = {}) {
|
|
||||||
let output = ''
|
|
||||||
for (let k in data) {
|
|
||||||
if (data[k]) {
|
|
||||||
output += ' ' + k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output.slice(1)
|
|
||||||
}
|
|
||||||
// 简单的样式解析
|
|
||||||
export function styleMap(data = {}) {
|
|
||||||
let output = ''
|
|
||||||
for (let k in data) {
|
|
||||||
if (data[k] || data[k] === 0) {
|
|
||||||
output += hyphen(k) + ':' + data[k] + ';'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
// 三元运算符的函数封装(只为了省个参数)
|
|
||||||
export function when(condition, trueCase = '', falseCase = '') {
|
|
||||||
return condition ? trueCase : falseCase
|
|
||||||
}
|
|
||||||
|
|
||||||
// swicth语句的封装
|
|
||||||
export function which(target, list = [], defaultCase = '') {
|
|
||||||
for (let [name, content] of list) {
|
|
||||||
if (target === name) {
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultCase
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 双向绑定
|
|
||||||
* @param key<String> 绑定的字段
|
|
||||||
* @param el 当前对象, 无需传递, 由框架内部处理
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export function live(key, el) {
|
|
||||||
let callback = ev => {
|
|
||||||
Function('o', 'v', `o.${key} = v`)(this, ev.target.value)
|
|
||||||
this.$requestUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (el && !el.__live__) {
|
|
||||||
bind(el, 'input', callback)
|
|
||||||
this.$events.input ??= []
|
|
||||||
this.$events.input.push({ el, listener: callback, options: false })
|
|
||||||
el.__live__ = true
|
|
||||||
}
|
|
||||||
return Function('o', `return o.${key}`)(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Component extends HTMLElement {
|
export class Component extends HTMLElement {
|
||||||
/**
|
/**
|
||||||
|
@ -104,36 +37,37 @@ export class Component extends HTMLElement {
|
||||||
let list = []
|
let list = []
|
||||||
|
|
||||||
this.finalize()
|
this.finalize()
|
||||||
this.parseAnim()
|
|
||||||
|
|
||||||
this[__props__].forEach((options, prop) => {
|
this[__props__].forEach((options, prop) => {
|
||||||
list.push(options.attrName)
|
list.push(prop.toLowerCase())
|
||||||
})
|
})
|
||||||
return list.concat(this.watches || [], KEEP_ALIVE)
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
static parseAnim() {
|
static createProperty(name, options) {
|
||||||
if (this.hasOwnProperty('animation')) {
|
let key = Symbol(name)
|
||||||
let {
|
let descriptor = {
|
||||||
type = 'fade',
|
get() {
|
||||||
duration,
|
return this[key]
|
||||||
custom,
|
},
|
||||||
immediate = false
|
set(value) {
|
||||||
} = this.animation
|
let oldValue = this[key]
|
||||||
let fromto = MODES[type] || MODES.fade
|
value = fixedValue(value, options)
|
||||||
if (custom) {
|
if (oldValue === value) {
|
||||||
fromto = custom
|
return
|
||||||
}
|
}
|
||||||
Object.defineProperty(this.prototype, '$animate', {
|
this[key] = value
|
||||||
value(out) {
|
this.#requestUpdate(name, oldValue)
|
||||||
if (this[__mounted__]) {
|
if (options.observer) {
|
||||||
return animate.call(this, duration, fromto, out)
|
options.observer.call(this, value, oldValue)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
enumerable: false
|
||||||
}
|
}
|
||||||
})
|
|
||||||
this.prototype.$animate.immediate = immediate
|
this[__props__].set(name, options)
|
||||||
delete this.animation
|
|
||||||
}
|
Object.defineProperty(this.prototype, name, descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理静态声明
|
// 处理静态声明
|
||||||
|
@ -147,22 +81,11 @@ export class Component extends HTMLElement {
|
||||||
|
|
||||||
if (this.hasOwnProperty('props')) {
|
if (this.hasOwnProperty('props')) {
|
||||||
for (let k in this.props) {
|
for (let k in this.props) {
|
||||||
// 保留关键字, 直接跳过
|
|
||||||
if (k === KEEP_ALIVE || k === KEEP_ALIVE_C) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
let options = parsePropsDeclaration(this.props[k])
|
let options = parsePropsDeclaration(this.props[k])
|
||||||
let attrName = k.toLowerCase()
|
if (boolMap[k] && k !== boolMap[k]) {
|
||||||
|
k = boolMap[k]
|
||||||
options.attrName = attrName
|
|
||||||
|
|
||||||
if (boolMap[attrName]) {
|
|
||||||
k = boolMap[attrName]
|
|
||||||
} else {
|
|
||||||
options.attrName = hyphen(k)
|
|
||||||
k = camelize(k)
|
|
||||||
}
|
}
|
||||||
this[__props__].set(k, options)
|
this.createProperty(k, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,106 +99,37 @@ export class Component extends HTMLElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keepAlive = false
|
|
||||||
|
|
||||||
removed = false
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this[__pending__] = false
|
this[__pending__] = false
|
||||||
this[__mounted__] = false
|
this[__mounted__] = false
|
||||||
|
this[__in_tree__] = false
|
||||||
// 这里提前定义一次, 是为了在connectedCallback之前, 就已有赋值时报错的bug
|
// 这里提前定义一次, 是为了在connectedCallback之前, 就已有赋值时报错的bug
|
||||||
this[__changed_props__] = new Map() // 记录一次渲染周期内变化的属性
|
this[__changed_props__] = new Map() // 记录一次渲染周期内变化的属性
|
||||||
this.host = this
|
// this[__init__]()
|
||||||
this.root = this.shadowRoot || this.attachShadow({ mode: 'open' })
|
this.root = this.shadowRoot || this.attachShadow({ mode: 'open' })
|
||||||
this.root.ownHost = this
|
|
||||||
|
|
||||||
Object.defineProperty(this, '$refs', {
|
Object.defineProperty(this, '$refs', {
|
||||||
value: Object.create(null)
|
value: Object.create(null),
|
||||||
})
|
enumerable: false
|
||||||
Object.defineProperty(this, '$events', {
|
|
||||||
value: Object.create(null)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
for (let [prop, options] of this.constructor[__props__]) {
|
this.created()
|
||||||
this.#createProperty(prop, options)
|
|
||||||
// 按W3C规范, 在构造函数内不可设置节点的属性
|
|
||||||
// 所以这里只记录需要初始化的属性, 在#init()回调中才去修改
|
|
||||||
this[__changed_props__].set(prop, this[prop])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(_ => this.created())
|
[__init__]() {
|
||||||
}
|
// 这里重新赋值一次, 用于清除掉因为 observer 修正产生的变化
|
||||||
|
this[__changed_props__] = new Map()
|
||||||
#createProperty(name, options) {
|
// 初始化 props
|
||||||
let key = Symbol(name)
|
this.constructor[__props__].forEach((options, prop) => {
|
||||||
let descriptor
|
this[prop] = this[prop] === void 0 ? options.default : this[prop]
|
||||||
|
|
||||||
if (options.type === Array || options.type === Object) {
|
|
||||||
let proxyValue = this.#createProxy(null, options, name)
|
|
||||||
descriptor = {
|
|
||||||
get() {
|
|
||||||
return proxyValue
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
proxyValue = this.#createProxy(
|
|
||||||
fixedValue(value, options),
|
|
||||||
options,
|
|
||||||
name
|
|
||||||
)
|
|
||||||
this.$requestUpdate(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
descriptor = {
|
|
||||||
get() {
|
|
||||||
return this[key]
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
let oldValue = this[key]
|
|
||||||
value = fixedValue(value, options)
|
|
||||||
if (oldValue === value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this[key] = value
|
|
||||||
this.$requestUpdate(name)
|
|
||||||
safely.call(this, options.observer, value, oldValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this[key] = options.default
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperty(this, name, descriptor)
|
|
||||||
}
|
|
||||||
|
|
||||||
#createProxy(obj, options = {}, key) {
|
|
||||||
return new Proxy(obj || options.default, {
|
|
||||||
get: (target, prop, receiver) => {
|
|
||||||
let value = Reflect.get(target, prop, receiver)
|
|
||||||
// 当访问的值是对象时,需要对这个对象也进行代理
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
return this.#createProxy(value, {}, key)
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
},
|
|
||||||
set: (target, prop, value, receiver) => {
|
|
||||||
let oldValue = target[prop]
|
|
||||||
Reflect.set(target, prop, value, receiver)
|
|
||||||
|
|
||||||
this.$requestUpdate(key)
|
|
||||||
safely.call(this, options.observer, value, oldValue)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
#init() {
|
// 若无定义props时, 手动执行一次渲染
|
||||||
for (let [prop, value] of this[__changed_props__]) {
|
if (this[__pending__] === false) {
|
||||||
this.#prop2attr(prop, value)
|
this[__pending__] = true
|
||||||
|
nextTick(_ => this[__updated__]())
|
||||||
}
|
}
|
||||||
// 这里要清除初始化产生的记录
|
|
||||||
this[__changed_props__].clear()
|
|
||||||
this.$requestUpdate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#getPropOptions(name) {
|
#getPropOptions(name) {
|
||||||
|
@ -283,42 +137,22 @@ export class Component extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
if (this.$animate) {
|
this[__init__]()
|
||||||
this.style.display = 'none'
|
|
||||||
}
|
|
||||||
this.removed = false
|
|
||||||
this.#init()
|
|
||||||
adoptStyles(this.root, this.constructor.styles)
|
adoptStyles(this.root, this.constructor.styles)
|
||||||
|
|
||||||
|
this[__children__]?.setConnected(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
if (this.keepAlive) {
|
this[__children__]?.setConnected(false)
|
||||||
nextTick(_ => this.deactivated())
|
this.unmounted()
|
||||||
} else {
|
|
||||||
if (!document.body?.contains(this)) {
|
|
||||||
let $events = this.$events
|
|
||||||
if ($events) {
|
|
||||||
for (let name in $events) {
|
|
||||||
for (let it of $events[name]) {
|
|
||||||
unbind(it.el, name, it.listener, it.options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.removed = true
|
|
||||||
}
|
|
||||||
nextTick(_ => this.unmounted())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 监听属性变化
|
// 监听属性变化
|
||||||
attributeChangedCallback(name, old, val) {
|
attributeChangedCallback(name, old, val) {
|
||||||
if (old === val) {
|
if (old === val) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (name === KEEP_ALIVE) {
|
this[__attr2prop__](name, val, old)
|
||||||
this.keepAlive = val !== null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.#attr2prop(name, val, old)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -327,12 +161,11 @@ export class Component extends HTMLElement {
|
||||||
* @param name<String>
|
* @param name<String>
|
||||||
* @param value<String|Boolean|Number>
|
* @param value<String|Boolean|Number>
|
||||||
*/
|
*/
|
||||||
#prop2attr(name, value) {
|
[__prop2attr__](name, value) {
|
||||||
let options = this.#getPropOptions(name) || {}
|
let options = this.#getPropOptions(name)
|
||||||
let attrName = options.attrName
|
|
||||||
|
|
||||||
if (options.attribute === false || options.type === null) {
|
if (options.attribute === false) {
|
||||||
this.removeAttribute(attrName)
|
this.removeAttribute(name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,17 +173,17 @@ export class Component extends HTMLElement {
|
||||||
case Number:
|
case Number:
|
||||||
case String:
|
case String:
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
this.removeAttribute(attrName)
|
this.removeAttribute(name)
|
||||||
} else {
|
} else {
|
||||||
this.setAttribute(attrName, value)
|
this.setAttribute(name, value)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case Boolean:
|
case Boolean:
|
||||||
if (value === null || value === false) {
|
if (value === null || value === false) {
|
||||||
this.removeAttribute(attrName)
|
this.removeAttribute(name)
|
||||||
} else {
|
} else {
|
||||||
this.setAttribute(attrName, '')
|
this.setAttribute(name, '')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -361,97 +194,71 @@ export class Component extends HTMLElement {
|
||||||
* @param name<String>
|
* @param name<String>
|
||||||
* @param value<String|Boolean|Number>
|
* @param value<String|Boolean|Number>
|
||||||
*/
|
*/
|
||||||
#attr2prop(name, value, old) {
|
[__attr2prop__](name, value, old) {
|
||||||
let _name = boolMap[name]
|
let _name = boolMap[name],
|
||||||
let options, propName
|
options
|
||||||
|
|
||||||
if (_name) {
|
if (_name) {
|
||||||
name = _name
|
name = _name
|
||||||
}
|
}
|
||||||
propName = camelize(name)
|
|
||||||
|
|
||||||
options = this.#getPropOptions(propName)
|
options = this.#getPropOptions(name)
|
||||||
|
this[name] = fixedValue(value, options)
|
||||||
if (options) {
|
|
||||||
value = fixedValue(value, options)
|
|
||||||
|
|
||||||
if (options.attribute === false || options.type === null) {
|
|
||||||
if (value === null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (value === this[propName]) {
|
|
||||||
this.removeAttribute(name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this[propName] = value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求更新
|
// 请求更新
|
||||||
$requestUpdate(name) {
|
#requestUpdate(name, oldValue) {
|
||||||
if (name !== void 0) {
|
|
||||||
this[__changed_props__].set(name, this[name])
|
this[__changed_props__].set(name, this[name])
|
||||||
this.#prop2attr(name, this[name])
|
this[__prop2attr__](name, this[name])
|
||||||
}
|
|
||||||
|
|
||||||
if (this[__pending__] === false) {
|
if (this[__pending__] === false) {
|
||||||
this[__pending__] = true
|
this[__pending__] = true
|
||||||
nextTick(_ => this.#confirmUpdate())
|
nextTick(_ => this[__updated__]())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确认更新到视图
|
// 确认更新到视图
|
||||||
#confirmUpdate() {
|
[__updated__]() {
|
||||||
|
if (this[__pending__]) {
|
||||||
let props = this[__changed_props__]
|
let props = this[__changed_props__]
|
||||||
this.#render()
|
this[__render__]()
|
||||||
this.#feedback(props)
|
this[__feedback__](props)
|
||||||
this.#clearUpdate()
|
this[__clear_update__]()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新回调反馈
|
// 更新回调反馈
|
||||||
#feedback(props) {
|
[__feedback__](props) {
|
||||||
|
if (this[__in_tree__] === false) {
|
||||||
|
this[__in_tree__] = document.body.contains(this)
|
||||||
|
}
|
||||||
|
if (this[__in_tree__]) {
|
||||||
// 初始化时不触发updated回调
|
// 初始化时不触发updated回调
|
||||||
if (this[__mounted__] === false) {
|
if (this[__mounted__] === false) {
|
||||||
this[__mounted__] = true
|
this[__mounted__] = true
|
||||||
if (this.$animate?.immediate) {
|
|
||||||
this.$animate()
|
|
||||||
}
|
|
||||||
nextTick(_ => {
|
|
||||||
if (this.keepAlive) {
|
|
||||||
this.activated()
|
|
||||||
}
|
|
||||||
this.mounted()
|
this.mounted()
|
||||||
this.$requestUpdate()
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
nextTick(_ => this.updated(props))
|
this.updated(props)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#clearUpdate() {
|
[__clear_update__]() {
|
||||||
this[__changed_props__].clear()
|
this[__changed_props__] = new Map()
|
||||||
this[__pending__] = false
|
this[__pending__] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染视图
|
// 渲染视图
|
||||||
#render() {
|
[__render__]() {
|
||||||
try {
|
|
||||||
let ast = this.render()
|
let ast = this.render()
|
||||||
render(ast, this.root, {
|
|
||||||
host: this
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this[__children__] = render(ast, this.root, {
|
||||||
|
host: this,
|
||||||
|
isConnected: !this[__mounted__] && this.isConnected
|
||||||
|
})
|
||||||
|
}
|
||||||
// 几个生命周期回调
|
// 几个生命周期回调
|
||||||
created() {}
|
created() {}
|
||||||
mounted() {}
|
mounted() {}
|
||||||
activated() {}
|
|
||||||
deactivated() {}
|
|
||||||
unmounted() {}
|
unmounted() {}
|
||||||
updated() {}
|
updated() {}
|
||||||
render() {
|
render() {
|
||||||
|
@ -466,7 +273,7 @@ export class Component extends HTMLElement {
|
||||||
unbind(this, type, callback, options)
|
unbind(this, type, callback, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
$emit(type, data = {}, stop = false) {
|
$emit(type, data = {}) {
|
||||||
return fire(this, type, data, stop)
|
return fire(this, type, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
126
src/utils.js
126
src/utils.js
|
@ -4,18 +4,18 @@
|
||||||
* @date 2023/03/07 22:11:30
|
* @date 2023/03/07 22:11:30
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function noop() {}
|
export function $(selector, container) {
|
||||||
|
|
||||||
export function $(selector, container, multi) {
|
|
||||||
let fn = multi ? 'querySelectorAll' : 'querySelector'
|
|
||||||
if (container) {
|
if (container) {
|
||||||
return container[fn](selector)
|
return container.querySelector(selector)
|
||||||
}
|
}
|
||||||
return document.body[fn](selector)
|
return document.body.querySelector(selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $$(selector, container) {
|
export function $$(selector, container) {
|
||||||
return $(selector, container, true)
|
if (container) {
|
||||||
|
return container.querySelectorAll(selector)
|
||||||
|
}
|
||||||
|
return document.body.querySelectorAll(selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const nextTick = (function () {
|
export const nextTick = (function () {
|
||||||
|
@ -24,14 +24,11 @@ export const nextTick = (function () {
|
||||||
let bool = false
|
let bool = false
|
||||||
|
|
||||||
function callback() {
|
function callback() {
|
||||||
while (queue.length > 0) {
|
let n = queue.length
|
||||||
let fn = queue.shift()
|
for (let i = 0; i < n; i++) {
|
||||||
try {
|
queue[i]()
|
||||||
fn()
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
queue = queue.slice(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
new MutationObserver(callback).observe(node, { characterData: true })
|
new MutationObserver(callback).observe(node, { characterData: true })
|
||||||
|
@ -43,46 +40,6 @@ export const nextTick = (function () {
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
export function range(...args) {
|
|
||||||
let start = 0,
|
|
||||||
end = 0,
|
|
||||||
step = 1,
|
|
||||||
out = []
|
|
||||||
switch (args.length) {
|
|
||||||
case 1:
|
|
||||||
end = args[0]
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
case 3:
|
|
||||||
;[start, end, step = 1] = args
|
|
||||||
step = Math.abs(step) || 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start > end) {
|
|
||||||
;[start, end] = [end, start]
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = start; i < end; i += step) {
|
|
||||||
out.push(i)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
//驼峰转换为连字符线风格
|
|
||||||
export function hyphen(target) {
|
|
||||||
return target.replace(/([a-z\d])([A-Z]+)/g, '$1-$2').toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
//连字符转换为驼峰风格
|
|
||||||
export function camelize(target) {
|
|
||||||
//提前判断,提高效率
|
|
||||||
if (target.indexOf('-') < 0) {
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
return target.replace(/\-([a-z])/g, (m, s) => s.toUpperCase())
|
|
||||||
}
|
|
||||||
|
|
||||||
//取得距离页面左上角的坐标
|
//取得距离页面左上角的坐标
|
||||||
export function offset(node) {
|
export function offset(node) {
|
||||||
try {
|
try {
|
||||||
|
@ -108,20 +65,12 @@ export function offset(node) {
|
||||||
/**
|
/**
|
||||||
* 事件绑定
|
* 事件绑定
|
||||||
*/
|
*/
|
||||||
export function bind(dom, type = '', selector, fn, phase = false) {
|
export function bind(dom, type, selector, fn, phase = true) {
|
||||||
let events = type.split(',')
|
let events = type.split(',')
|
||||||
let callback
|
let callback
|
||||||
let isWc = dom && dom.host === dom
|
|
||||||
let host = isWc ? dom : null
|
|
||||||
|
|
||||||
if (!dom || !type) {
|
|
||||||
return console.error(
|
|
||||||
"Argument Error: function bind's arg 1 must be a document obejct"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof selector === 'function') {
|
if (typeof selector === 'function') {
|
||||||
phase = fn || false
|
phase = fn
|
||||||
fn = selector
|
fn = selector
|
||||||
selector = null
|
selector = null
|
||||||
} else {
|
} else {
|
||||||
|
@ -131,25 +80,13 @@ export function bind(dom, type = '', selector, fn, phase = false) {
|
||||||
fn = fn || noop
|
fn = fn || noop
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isWc === false) {
|
|
||||||
let node = dom
|
|
||||||
while (node && node !== document.body && node !== document) {
|
|
||||||
if (node.ownHost) {
|
|
||||||
isWc = true
|
|
||||||
host = node.ownHost
|
|
||||||
break
|
|
||||||
}
|
|
||||||
node = node.parentNode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selector) {
|
if (selector) {
|
||||||
callback = function (ev) {
|
callback = function (ev) {
|
||||||
let agents = $(selector, dom)
|
let agents = $(selector, ev.currentTarget)
|
||||||
let elem = ev.target
|
let elem = ev.target
|
||||||
if (agents) {
|
if (agents) {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (elem === dom) {
|
if (elem === ev.currentTarget) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (agents.contains(elem)) {
|
if (agents.contains(elem)) {
|
||||||
|
@ -166,25 +103,7 @@ export function bind(dom, type = '', selector, fn, phase = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
events.forEach(function (t) {
|
events.forEach(function (t) {
|
||||||
t = t.trim()
|
dom.addEventListener(t.trim(), callback, phase)
|
||||||
if (isWc) {
|
|
||||||
host.$events[t] ??= []
|
|
||||||
|
|
||||||
let _list = host.$events[t]
|
|
||||||
if (_list.length) {
|
|
||||||
let idx = _list.findIndex(
|
|
||||||
it =>
|
|
||||||
it.el === dom && it.listener === callback && it.options === phase
|
|
||||||
)
|
|
||||||
if (idx > -1) {
|
|
||||||
let item = _list[idx]
|
|
||||||
_list.splice(idx, 1)
|
|
||||||
dom.removeEventListener(t, item.listener, item.options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_list.push({ el: dom, listener: callback, options: phase })
|
|
||||||
}
|
|
||||||
dom.addEventListener(t, callback, phase)
|
|
||||||
})
|
})
|
||||||
return callback
|
return callback
|
||||||
}
|
}
|
||||||
|
@ -192,7 +111,7 @@ export function bind(dom, type = '', selector, fn, phase = false) {
|
||||||
/**
|
/**
|
||||||
* 解除事件绑定
|
* 解除事件绑定
|
||||||
*/
|
*/
|
||||||
export function unbind(dom, type = '', fn = noop, phase = false) {
|
export function unbind(dom, type, fn = noop, phase = false) {
|
||||||
let events = type.split(',')
|
let events = type.split(',')
|
||||||
events.forEach(function (t) {
|
events.forEach(function (t) {
|
||||||
dom.removeEventListener(t.trim(), fn, phase)
|
dom.removeEventListener(t.trim(), fn, phase)
|
||||||
|
@ -228,14 +147,9 @@ export function clearOutsideClick(fn = noop) {
|
||||||
unbind(document, 'mousedown', fn)
|
unbind(document, 'mousedown', fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function fire(el, name = 'click', data = {}) {
|
||||||
* @param el <DOM> 节点
|
let ev = document.createEvent('Events')
|
||||||
* @param name <String> 自定义的事件名
|
ev.initEvent(name, true, true)
|
||||||
* @param data <Object> 要合并进event对象的参数
|
|
||||||
* @param stop <Boolean> 是否禁止事件冒泡
|
|
||||||
*/
|
|
||||||
export function fire(el, name = 'click', data = {}, stop) {
|
|
||||||
let ev = new Event(name, { bubbles: !stop, cancelable: true })
|
|
||||||
Object.assign(ev, data)
|
Object.assign(ev, data)
|
||||||
el.dispatchEvent(ev)
|
el.dispatchEvent(ev)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
|
||||||
<title>wkit test page</title>
|
|
||||||
<style>
|
|
||||||
body {line-height: 1.5;font-size:14px;background:var(--page-color);color:var(--text-color);}
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
border-radius: 10px;
|
|
||||||
background: var(--summary-bg-color);
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
border-radius: 3px;
|
|
||||||
background: var(--border-color-dark);
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--summary-color);
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 480px) {
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script type="importmap">
|
|
||||||
{
|
|
||||||
"imports":{
|
|
||||||
"es.shim":"//jscdn.ink/es.shim/latest/index.js",
|
|
||||||
"wkit":"//127.0.0.1:9999/src/index.js",
|
|
||||||
"wkitd":"//jscdn.ink/wkitd/1.3.10/index.js",
|
|
||||||
"fetch":"//jscdn.ink/@bytedo/fetch/latest/next.js",
|
|
||||||
"@bd/ui/":"//jscdn.ink/@bd/ui/0.1.16/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script type="module" src="./test.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<wc-app></wc-app>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
31
test/test.js
31
test/test.js
|
@ -1,31 +0,0 @@
|
||||||
import { html, Component, live } from 'wkit'
|
|
||||||
|
|
||||||
class App extends Component {
|
|
||||||
foo = ''
|
|
||||||
|
|
||||||
bar = { a: 333 }
|
|
||||||
|
|
||||||
created() {
|
|
||||||
// live = live.bind(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.foo = ''
|
|
||||||
this.bar.a = '666'
|
|
||||||
this.$requestUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return html`
|
|
||||||
<p>${this.foo}</p>
|
|
||||||
<p>${this.bar.a}</p>
|
|
||||||
<input value=${live.bind(this, 'bar.a')} />
|
|
||||||
<input value=${this.foo} />
|
|
||||||
<input value=${live.bind(this, 'foo')} />
|
|
||||||
|
|
||||||
<button @click=${this.reset}>666</button>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
App.reg('app')
|
|
Loading…
Reference in New Issue