Compare commits
104 Commits
Author | SHA1 | Date |
---|---|---|
yutent | f70f4a54fb | |
yutent | dde82e9860 | |
yutent | fc585abcdf | |
yutent | 0f09245657 | |
yutent | 065271f2f6 | |
yutent | c56013c9b4 | |
yutent | db7098c4f8 | |
yutent | ea6010c782 | |
yutent | b4e7f5dfae | |
yutent | bbe04eae83 | |
yutent | 47090149f8 | |
yutent | afac6fddb2 | |
yutent | 0043895a66 | |
yutent | 0be2bf7971 | |
yutent | ff51e09e39 | |
yutent | 0f33586ff4 | |
yutent | 12b4417a94 | |
yutent | 4ebf74f40f | |
yutent | fd154f1bcf | |
yutent | 3908bf7fe6 | |
yutent | c5333cbc2a | |
yutent | ce4bf92ce2 | |
yutent | fb5068b040 | |
yutent | 4f4e8970ca | |
yutent | f54a5a3653 | |
yutent | cf344bcd4b | |
yutent | eebe3c63fc | |
yutent | 0483d6efc3 | |
yutent | 2aa9ab5af0 | |
yutent | 87496a1487 | |
yutent | 4c3d3eae21 | |
yutent | 0a70cb1b2e | |
yutent | 8c1b1350b0 | |
yutent | f53cb98bc4 | |
yutent | c0eb316f87 | |
yutent | 079589febb | |
yutent | 6f3ea0f086 | |
yutent | 5748e6a760 | |
yutent | 289c7142ef | |
yutent | e9d5add0d0 | |
yutent | 9227233d7a | |
yutent | aca6d75553 | |
yutent | f9e766ab4a | |
yutent | da056ae47f | |
yutent | 408ba6ca2f | |
yutent | 1a12b21cc2 | |
yutent | 3256999b09 | |
yutent | 05ec00658c | |
yutent | 69fae35b2f | |
yutent | 5ddb00e6b6 | |
yutent | da5e5fd4a4 | |
yutent | ecd46ba23d | |
yutent | 134e465dd0 | |
yutent | 5e1a5d7ebd | |
yutent | 714d9ceb3b | |
yutent | a456df0b53 | |
yutent | c9b21cd790 | |
yutent | 5326af856c | |
yutent | 948c4e08c6 | |
yutent | 7adf718231 | |
yutent | 878e469a1a | |
yutent | bd36c96292 | |
yutent | 87c79f56ec | |
yutent | 7ee22886cb | |
yutent | 0ee7ddc6a1 | |
yutent | 724b3732a0 | |
yutent | d7918768b9 | |
yutent | b36bb36fda | |
yutent | 363a2ce623 | |
yutent | 91ba64722e | |
yutent | 21a565e04c | |
yutent | 8c033984d4 | |
yutent | f8f749d32e | |
yutent | 8770c32e5b | |
yutent | 7568e701cf | |
yutent | f7e0ce08c3 | |
yutent | d2ccaa5c3d | |
yutent | 9641f5b82a | |
yutent | 9b76cfa141 | |
yutent | f397c57c37 | |
yutent | c416622f72 | |
yutent | 7669b3b713 | |
yutent | 81f8fd45eb | |
yutent | 886829f44d | |
yutent | 310e2bff1e | |
yutent | 836d566f55 | |
yutent | 439287da9c | |
yutent | 406f581ecf | |
yutent | 0eecaa22dc | |
yutent | 58d4277964 | |
yutent | 6ca5045bd0 | |
yutent | 304d320cb3 | |
yutent | d0af1270dc | |
yutent | 8cebdd1994 | |
yutent | 195f3b1828 | |
yutent | a6efaaabf0 | |
yutent | 3bc02e2e13 | |
yutent | a57016e14e | |
yutent | c632f41e75 | |
yutent | 1f8f541d7f | |
yutent | 9e307aabf2 | |
yutent | f1830910aa | |
yutent | 4463d0bc5e | |
yutent | efd6e0862d |
|
@ -1,12 +1,9 @@
|
||||||
*.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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
test/
|
98
Readme.md
98
Readme.md
|
@ -1,72 +1,55 @@
|
||||||
## 9号UI组件库的核心
|
## Wkit
|
||||||
|
> 一个简单易用、功能完善的用于开发`web components`的轻量级开发库。模板解析基于`lit-html`二次开发
|
||||||
|
|
||||||
|
![downloads](https://img.shields.io/npm/dt/wkit.svg)
|
||||||
|
![version](https://img.shields.io/npm/v/wkit.svg)
|
||||||
|
|
||||||
### 开发文档
|
### 开发文档
|
||||||
[开发文档](https://github.com/9th-js/core/wiki)
|
[开发文档](https://git.wkit.fun/bytedo/wkit/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
|
||||||
import { css, html, Component } from '//jscdn.ink/@ninejs/core/latest/index.js'
|
// alias wkit='//jscdn.ink/wkit/latest/index.js'
|
||||||
|
import { css, html, Component } from 'wkit'
|
||||||
|
|
||||||
class Hello extends Component {
|
class Hello extends Component {
|
||||||
|
|
||||||
static props = {
|
static props = {
|
||||||
count: {
|
count: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0,
|
||||||
}
|
attribute: true // 是否显式表现为html属性
|
||||||
|
},
|
||||||
|
foo: 0, // 简写
|
||||||
|
bar: String // 简写
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 若需要支持 scss语法, 则需要使用构建工具编译。
|
||||||
|
// 可支持数组
|
||||||
static styles = css`
|
static styles = css`
|
||||||
button { color: #09f; }
|
button { color: #09f; }
|
||||||
span { color: #f30; }
|
span { color: #f30; }
|
||||||
`
|
`
|
||||||
|
|
||||||
render(){
|
// 支持多个
|
||||||
return html`
|
static styles = [
|
||||||
<div>
|
css`...`,
|
||||||
<button @click="${this.increase}">点击我</button>
|
css`...`
|
||||||
</div>
|
]
|
||||||
<div>所有点击数为: <span>${this.count}</span></div>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
increase(){
|
|
||||||
this.count++
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define('wc-hello', Hello)
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
<!-- 在html中,便可以直接使用 -->
|
|
||||||
<wc-hello></wc-hello>
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
若需要支持 scss, 则需要使用构建工具,预处理。
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { css, html, Component } from '@ninejs/core'
|
|
||||||
|
|
||||||
@customElement('wc-hello')
|
|
||||||
class Hello extends Component {
|
|
||||||
|
|
||||||
static props = {
|
|
||||||
count: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 这个scss会被解析进来
|
|
||||||
@extendStyle('hello.scss')
|
|
||||||
static styles
|
|
||||||
|
|
||||||
render(){
|
render(){
|
||||||
return html`
|
return html`
|
||||||
|
@ -83,8 +66,12 @@ class Hello extends Component {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 这个可以省略
|
if (!customElements.get('wc-hello')) {
|
||||||
// customElements.define('wc-hello', Hello)
|
customElements.define('wc-hello', Hello)
|
||||||
|
}
|
||||||
|
// 和上面的写法等价
|
||||||
|
// 如果不希望修改前缀`wc-`的话, 可以直接调用内置的reg方法注册组件即可。
|
||||||
|
Hello.reg('hello')
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -95,3 +82,10 @@ class Hello extends Component {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 开源协议
|
||||||
|
|
||||||
|
- BSD 3-Clause License (Lit框架的html模板解析的源码)
|
||||||
|
- MIT (除Lit代码之外的所有代码)
|
22
package.json
22
package.json
|
@ -1,25 +1,29 @@
|
||||||
{
|
{
|
||||||
"name": "@bd/core",
|
"name": "wkit",
|
||||||
"version": "1.0.2",
|
"version": "1.12.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "百搭UI组件库的核心",
|
"description": "A library for building fast, lightweight web components.",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"files": [
|
"files": [
|
||||||
"dist/*"
|
"dist/*"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "esbuild src/index.js --minify --bundle --format=esm --target=es2017 --outfile=dist/index.js"
|
"build:next": "esbuild src/index.js --minify --bundle --format=esm --target=esnext --outfile=dist/index.js",
|
||||||
|
"build:es6": "esbuild src/index.js --minify --bundle --format=esm --target=es6 --outfile=dist/index.es6.js",
|
||||||
|
"build": "npm run build:next && npm run build:es6"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/bd-js/core.git"
|
"url": "https://git.wkit.fun/bytedo/wkit.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bd.js",
|
"wkit",
|
||||||
"bdjs",
|
|
||||||
"bd-ui",
|
|
||||||
"wcui",
|
"wcui",
|
||||||
"bd-core"
|
"web-components",
|
||||||
|
"native",
|
||||||
|
"cross-framework",
|
||||||
|
"html",
|
||||||
|
"css"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "^0.17.11"
|
"esbuild": "^0.17.11"
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
* {}
|
||||||
|
* @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)' }]
|
||||||
|
}
|
|
@ -27,31 +27,25 @@ const boolMap = Object.create(null)
|
||||||
'noShade',
|
'noShade',
|
||||||
'open',
|
'open',
|
||||||
'readOnly',
|
'readOnly',
|
||||||
'selected'
|
'selected',
|
||||||
|
'loading'
|
||||||
].forEach(function (name) {
|
].forEach(function (name) {
|
||||||
boolMap[name.toLowerCase()] = name
|
boolMap[name.toLowerCase()] = name
|
||||||
})
|
})
|
||||||
|
|
||||||
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 RESET_CSS_STYLE = `* {box-sizing: border-box;margin: 0;padding: 0;}::before,::after {box-sizing: border-box;}`
|
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);}`
|
||||||
|
|
||||||
function getDefaultValue(type) {
|
function getDefaultValue(type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -85,8 +79,26 @@ 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: String, default: v + '' }
|
return { type: null, default: '' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,24 +128,30 @@ 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
|
||||||
}
|
}
|
||||||
options.default = options.default || getDefaultValue(options.type)
|
options.default = options.hasOwnProperty('default')
|
||||||
options.attribute = true
|
? options.default
|
||||||
|
: getDefaultValue(options.type)
|
||||||
|
options.attribute = options.hasOwnProperty('attribute')
|
||||||
|
? options.attribute
|
||||||
|
: true
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fixedValue(value, options) {
|
export function fixedValue(value, options) {
|
||||||
switch (options.type) {
|
switch (options.type) {
|
||||||
case Number:
|
case Number:
|
||||||
return +value || 0
|
return value === null ? null : +value || 0
|
||||||
break
|
|
||||||
|
|
||||||
case Boolean:
|
case Boolean:
|
||||||
return value === false ? false : value !== null
|
return value === false ? false : value !== null
|
||||||
break
|
|
||||||
|
|
||||||
case Object:
|
case Object:
|
||||||
if (typeof value === 'object') {
|
if (typeof value === 'object') {
|
||||||
|
@ -145,7 +163,6 @@ export function fixedValue(value, options) {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
|
||||||
|
|
||||||
case Array:
|
case Array:
|
||||||
if (typeof value === 'object') {
|
if (typeof value === 'object') {
|
||||||
|
@ -157,10 +174,11 @@ export function fixedValue(value, options) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
|
||||||
|
case null:
|
||||||
|
return value
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return value + ''
|
return value === null || value === void 0 ? null : value + ''
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
src/css.js
20
src/css.js
|
@ -6,6 +6,18 @@
|
||||||
|
|
||||||
import { RESET_CSS_STYLE } from './constants.js'
|
import { RESET_CSS_STYLE } from './constants.js'
|
||||||
|
|
||||||
|
let MyCSSStyleSheet = CSSStyleSheet
|
||||||
|
|
||||||
|
if (!document.adoptedStyleSheets) {
|
||||||
|
MyCSSStyleSheet = class {
|
||||||
|
elem = document.createElement('style')
|
||||||
|
|
||||||
|
replaceSync(css) {
|
||||||
|
this.elem.textContent = css.replace(/\s+/g, ' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function css(strs, ...args) {
|
export function css(strs, ...args) {
|
||||||
let output = ''
|
let output = ''
|
||||||
let tmp = Array.from(strs)
|
let tmp = Array.from(strs)
|
||||||
|
@ -16,7 +28,7 @@ export function css(strs, ...args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function adoptStyles(root, styles = '') {
|
export function adoptStyles(root, styles = '') {
|
||||||
let sheet = new CSSStyleSheet()
|
let sheet = new MyCSSStyleSheet()
|
||||||
if (typeof styles === 'string') {
|
if (typeof styles === 'string') {
|
||||||
styles = [styles]
|
styles = [styles]
|
||||||
} else {
|
} else {
|
||||||
|
@ -24,5 +36,9 @@ 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)
|
||||||
root.adoptedStyleSheets.push(sheet)
|
if (root.adoptedStyleSheets) {
|
||||||
|
root.adoptedStyleSheets = [sheet]
|
||||||
|
} else {
|
||||||
|
root.appendChild(sheet.elem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
944
src/html.js
944
src/html.js
File diff suppressed because it is too large
Load Diff
430
src/index.js
430
src/index.js
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* {wcui的核心库, 基于lit-core二次开发}
|
* { wkit library }
|
||||||
* @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,25 +7,93 @@ import {
|
||||||
fixedValue,
|
fixedValue,
|
||||||
parsePropsDeclaration,
|
parsePropsDeclaration,
|
||||||
boolMap,
|
boolMap,
|
||||||
|
KEEP_ALIVE,
|
||||||
|
KEEP_ALIVE_C,
|
||||||
__finalized__,
|
__finalized__,
|
||||||
__render__,
|
|
||||||
__init__,
|
|
||||||
__props__,
|
__props__,
|
||||||
__changed_props__,
|
__changed_props__,
|
||||||
__mounted__,
|
__mounted__,
|
||||||
__feedback__,
|
__pending__
|
||||||
__pending__,
|
|
||||||
__prop2attr__,
|
|
||||||
__attr2prop__,
|
|
||||||
__clear_update__,
|
|
||||||
__children__,
|
|
||||||
__updated__
|
|
||||||
} from './constants.js'
|
} from './constants.js'
|
||||||
import { css, adoptStyles } from './css.js'
|
import { css, adoptStyles } from './css.js'
|
||||||
import { render, html, svg } from './html.js'
|
import { render, html, svg, raw } from './html.js'
|
||||||
import { nextTick, fire, bind, unbind } from './utils.js'
|
import { animate, MODES } from './anim.js'
|
||||||
export { $, $$, offset, outsideClick, clearOutsideClick } from './utils.js'
|
import { nextTick, fire, bind, unbind, hyphen, camelize } 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 {
|
||||||
/**
|
/**
|
||||||
|
@ -36,33 +104,36 @@ 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) => {
|
||||||
options.attribute && list.push(prop.toLowerCase())
|
list.push(options.attrName)
|
||||||
})
|
})
|
||||||
return list
|
return list.concat(this.watches || [], KEEP_ALIVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
static createProperty(name, options) {
|
static parseAnim() {
|
||||||
let key = Symbol(name)
|
if (this.hasOwnProperty('animation')) {
|
||||||
let descriptor = {
|
let {
|
||||||
get() {
|
type = 'fade',
|
||||||
return this[key]
|
duration,
|
||||||
},
|
custom,
|
||||||
set(value) {
|
immediate = false
|
||||||
let oldValue = this[key]
|
} = this.animation
|
||||||
value = fixedValue(value, options)
|
let fromto = MODES[type] || MODES.fade
|
||||||
if (oldValue === value) {
|
if (custom) {
|
||||||
return
|
fromto = custom
|
||||||
|
}
|
||||||
|
Object.defineProperty(this.prototype, '$animate', {
|
||||||
|
value(out) {
|
||||||
|
if (this[__mounted__]) {
|
||||||
|
return animate.call(this, duration, fromto, out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this[key] = value
|
})
|
||||||
this.#requestUpdate(name, oldValue)
|
this.prototype.$animate.immediate = immediate
|
||||||
},
|
delete this.animation
|
||||||
enumerable: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this[__props__].set(name, options)
|
|
||||||
Object.defineProperty(this.prototype, name, descriptor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理静态声明
|
// 处理静态声明
|
||||||
|
@ -76,38 +147,135 @@ 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) {
|
||||||
let options = parsePropsDeclaration(this.props[k])
|
// 保留关键字, 直接跳过
|
||||||
if (boolMap[k] && k !== boolMap[k]) {
|
if (k === KEEP_ALIVE || k === KEEP_ALIVE_C) {
|
||||||
k = boolMap[k]
|
continue
|
||||||
}
|
}
|
||||||
this.createProperty(k, options)
|
let options = parsePropsDeclaration(this.props[k])
|
||||||
|
let attrName = k.toLowerCase()
|
||||||
|
|
||||||
|
options.attrName = attrName
|
||||||
|
|
||||||
|
if (boolMap[attrName]) {
|
||||||
|
k = boolMap[attrName]
|
||||||
|
} else {
|
||||||
|
options.attrName = hyphen(k)
|
||||||
|
k = camelize(k)
|
||||||
|
}
|
||||||
|
this[__props__].set(k, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete this.props
|
delete this.props
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static reg(name = '') {
|
||||||
|
name = 'wc-' + name
|
||||||
|
if (!customElements.get(name)) {
|
||||||
|
customElements.define(name, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keepAlive = false
|
||||||
|
|
||||||
|
removed = false
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this[__pending__] = false
|
this[__pending__] = false
|
||||||
this[__mounted__] = false
|
this[__mounted__] = false
|
||||||
this[__init__]()
|
// 这里提前定义一次, 是为了在connectedCallback之前, 就已有赋值时报错的bug
|
||||||
this.created()
|
this[__changed_props__] = new Map() // 记录一次渲染周期内变化的属性
|
||||||
|
this.host = this
|
||||||
|
this.root = this.shadowRoot || this.attachShadow({ mode: 'open' })
|
||||||
|
this.root.ownHost = this
|
||||||
|
|
||||||
|
Object.defineProperty(this, '$refs', {
|
||||||
|
value: Object.create(null)
|
||||||
|
})
|
||||||
|
Object.defineProperty(this, '$events', {
|
||||||
|
value: Object.create(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (let [prop, options] of this.constructor[__props__]) {
|
||||||
|
this.#createProperty(prop, options)
|
||||||
|
// 按W3C规范, 在构造函数内不可设置节点的属性
|
||||||
|
// 所以这里只记录需要初始化的属性, 在#init()回调中才去修改
|
||||||
|
this[__changed_props__].set(prop, this[prop])
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(_ => this.created())
|
||||||
}
|
}
|
||||||
|
|
||||||
[__init__]() {
|
#createProperty(name, options) {
|
||||||
this.root = this.shadowRoot || this.attachShadow({ mode: 'open' })
|
let key = Symbol(name)
|
||||||
|
let descriptor
|
||||||
|
|
||||||
this[__changed_props__] = new Map() // 记录本次变化的属性
|
if (options.type === Array || options.type === Object) {
|
||||||
// 初始化 props
|
let proxyValue = this.#createProxy(null, options, name)
|
||||||
this.constructor[__props__].forEach((options, prop) => {
|
descriptor = {
|
||||||
this[prop] = options.default
|
get() {
|
||||||
})
|
return proxyValue
|
||||||
// 若无定义props时, 手动执行一次渲染
|
},
|
||||||
if (this[__pending__] === false) {
|
set(value) {
|
||||||
this[__pending__] = true
|
proxyValue = this.#createProxy(
|
||||||
this.performUpdate()
|
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() {
|
||||||
|
for (let [prop, value] of this[__changed_props__]) {
|
||||||
|
this.#prop2attr(prop, value)
|
||||||
|
}
|
||||||
|
// 这里要清除初始化产生的记录
|
||||||
|
this[__changed_props__].clear()
|
||||||
|
this.$requestUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
#getPropOptions(name) {
|
#getPropOptions(name) {
|
||||||
|
@ -115,21 +283,42 @@ export class Component extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
if (this.$animate) {
|
||||||
|
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() {
|
||||||
this[__children__]?.setConnected(false)
|
if (this.keepAlive) {
|
||||||
this.unmounted()
|
nextTick(_ => this.deactivated())
|
||||||
|
} 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
|
||||||
}
|
}
|
||||||
this[__attr2prop__](name, val)
|
if (name === KEEP_ALIVE) {
|
||||||
|
this.keepAlive = val !== null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.#attr2prop(name, val, old)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -138,10 +327,12 @@ 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) {
|
if (options.attribute === false || options.type === null) {
|
||||||
|
this.removeAttribute(attrName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,17 +340,17 @@ export class Component extends HTMLElement {
|
||||||
case Number:
|
case Number:
|
||||||
case String:
|
case String:
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
this.removeAttribute(name)
|
this.removeAttribute(attrName)
|
||||||
} else {
|
} else {
|
||||||
this.setAttribute(name, value)
|
this.setAttribute(attrName, value)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case Boolean:
|
case Boolean:
|
||||||
if (value === null || value === false) {
|
if (value === null || value === false) {
|
||||||
this.removeAttribute(name)
|
this.removeAttribute(attrName)
|
||||||
} else {
|
} else {
|
||||||
this.setAttribute(name, '')
|
this.setAttribute(attrName, '')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -170,79 +361,112 @@ export class Component extends HTMLElement {
|
||||||
* @param name<String>
|
* @param name<String>
|
||||||
* @param value<String|Boolean|Number>
|
* @param value<String|Boolean|Number>
|
||||||
*/
|
*/
|
||||||
[__attr2prop__](name, value) {
|
#attr2prop(name, value, old) {
|
||||||
let options = this.#getPropOptions(name)
|
let _name = boolMap[name]
|
||||||
|
let options, propName
|
||||||
|
|
||||||
this[name] = fixedValue(value, options)
|
if (_name) {
|
||||||
|
name = _name
|
||||||
|
}
|
||||||
|
propName = camelize(name)
|
||||||
|
|
||||||
|
options = this.#getPropOptions(propName)
|
||||||
|
|
||||||
|
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, oldValue) {
|
$requestUpdate(name) {
|
||||||
let shouldUpdate = true
|
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[__updated__]())
|
nextTick(_ => this.#confirmUpdate())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确认更新到视图
|
// 确认更新到视图
|
||||||
[__updated__]() {
|
#confirmUpdate() {
|
||||||
if (this[__pending__]) {
|
let props = this[__changed_props__]
|
||||||
try {
|
this.#render()
|
||||||
let props = this[__changed_props__]
|
this.#feedback(props)
|
||||||
this[__render__]()
|
this.#clearUpdate()
|
||||||
this[__feedback__](props)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
this[__clear_update__]()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新回调反馈
|
// 更新回调反馈
|
||||||
[__feedback__](props) {
|
#feedback(props) {
|
||||||
// 初始化时不触发updated回调
|
// 初始化时不触发updated回调
|
||||||
if (!this[__mounted__]) {
|
if (this[__mounted__] === false) {
|
||||||
this[__mounted__] = true
|
this[__mounted__] = true
|
||||||
this.mounted()
|
if (this.$animate?.immediate) {
|
||||||
|
this.$animate()
|
||||||
|
}
|
||||||
|
nextTick(_ => {
|
||||||
|
if (this.keepAlive) {
|
||||||
|
this.activated()
|
||||||
|
}
|
||||||
|
this.mounted()
|
||||||
|
this.$requestUpdate()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
this.updated(props)
|
nextTick(_ => this.updated(props))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[__clear_update__]() {
|
#clearUpdate() {
|
||||||
this[__changed_props__] = new Map()
|
this[__changed_props__].clear()
|
||||||
this[__pending__] = false
|
this[__pending__] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染视图
|
// 渲染视图
|
||||||
[__render__]() {
|
#render() {
|
||||||
let htmlText = this.render()
|
try {
|
||||||
|
let ast = this.render()
|
||||||
this[__children__] = render(htmlText, this.root, {
|
render(ast, this.root, {
|
||||||
host: this,
|
host: this
|
||||||
isConnected: !this[__mounted__] && this.isConnected
|
})
|
||||||
})
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 几个生命周期回调
|
// 几个生命周期回调
|
||||||
created() {}
|
created() {}
|
||||||
mounted() {}
|
mounted() {}
|
||||||
|
activated() {}
|
||||||
|
deactivated() {}
|
||||||
unmounted() {}
|
unmounted() {}
|
||||||
updated() {}
|
updated() {}
|
||||||
|
render() {
|
||||||
$on(type, callback) {
|
return html`<slot></slot>`
|
||||||
return bind(this, type, callback)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$off(type, callback) {
|
$on(type, callback, options) {
|
||||||
unbind(this, type, callback)
|
return bind(this, type, callback, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
$emit(type, data = {}) {
|
$off(type, callback, options) {
|
||||||
return fire(this, type, data)
|
unbind(this, type, callback, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
$emit(type, data = {}, stop = false) {
|
||||||
|
return fire(this, type, data, stop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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 $(selector, container) {
|
export function noop() {}
|
||||||
|
|
||||||
|
export function $(selector, container, multi) {
|
||||||
|
let fn = multi ? 'querySelectorAll' : 'querySelector'
|
||||||
if (container) {
|
if (container) {
|
||||||
return container.querySelector(selector)
|
return container[fn](selector)
|
||||||
}
|
}
|
||||||
return document.body.querySelector(selector)
|
return document.body[fn](selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $$(selector, container) {
|
export function $$(selector, container) {
|
||||||
if (container) {
|
return $(selector, container, true)
|
||||||
return container.querySelectorsAll(selector)
|
|
||||||
}
|
|
||||||
return document.body.querySelectorsAll(selector)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const nextTick = (function () {
|
export const nextTick = (function () {
|
||||||
|
@ -24,11 +24,14 @@ export const nextTick = (function () {
|
||||||
let bool = false
|
let bool = false
|
||||||
|
|
||||||
function callback() {
|
function callback() {
|
||||||
let n = queue.length
|
while (queue.length > 0) {
|
||||||
for (let i = 0; i < n; i++) {
|
let fn = queue.shift()
|
||||||
queue[i]()
|
try {
|
||||||
|
fn()
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
queue = queue.slice(n)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
new MutationObserver(callback).observe(node, { characterData: true })
|
new MutationObserver(callback).observe(node, { characterData: true })
|
||||||
|
@ -40,6 +43,46 @@ 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 {
|
||||||
|
@ -65,12 +108,20 @@ export function offset(node) {
|
||||||
/**
|
/**
|
||||||
* 事件绑定
|
* 事件绑定
|
||||||
*/
|
*/
|
||||||
export function bind(dom, type, selector, fn, phase = true) {
|
export function bind(dom, type = '', selector, fn, phase = false) {
|
||||||
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
|
phase = fn || false
|
||||||
fn = selector
|
fn = selector
|
||||||
selector = null
|
selector = null
|
||||||
} else {
|
} else {
|
||||||
|
@ -80,13 +131,25 @@ export function bind(dom, type, selector, fn, phase = true) {
|
||||||
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, ev.currentTarget)
|
let agents = $(selector, dom)
|
||||||
let elem = ev.target
|
let elem = ev.target
|
||||||
if (agents) {
|
if (agents) {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (elem === ev.currentTarget) {
|
if (elem === dom) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (agents.contains(elem)) {
|
if (agents.contains(elem)) {
|
||||||
|
@ -103,7 +166,25 @@ export function bind(dom, type, selector, fn, phase = true) {
|
||||||
}
|
}
|
||||||
|
|
||||||
events.forEach(function (t) {
|
events.forEach(function (t) {
|
||||||
dom.addEventListener(t.trim(), callback, phase)
|
t = t.trim()
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
@ -111,7 +192,7 @@ export function bind(dom, type, selector, fn, phase = true) {
|
||||||
/**
|
/**
|
||||||
* 解除事件绑定
|
* 解除事件绑定
|
||||||
*/
|
*/
|
||||||
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)
|
||||||
|
@ -147,9 +228,14 @@ export function clearOutsideClick(fn = noop) {
|
||||||
unbind(document, 'mousedown', fn)
|
unbind(document, 'mousedown', fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fire(el, name = 'click', data = {}) {
|
/**
|
||||||
let ev = document.createEvent('Events')
|
* @param el <DOM> 节点
|
||||||
ev.initEvent(name, true, true)
|
* @param name <String> 自定义的事件名
|
||||||
|
* @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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
<!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>
|
|
@ -0,0 +1,31 @@
|
||||||
|
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