@k3000/ce 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -60
- package/app/comp/comp-test.mjs +24 -0
- package/{comp/new-form.mjs → app/comp/my-form.mjs} +2 -5
- package/app/comp/nav-bar.mjs +92 -0
- package/app/comp/page-container.mjs +55 -0
- package/app/comp/tab-bar.mjs +60 -0
- package/app/index.html +39 -0
- package/app/pages/not-found.mjs +7 -0
- package/app/pages/page-category.mjs +7 -0
- package/app/pages/page-home.mjs +14 -0
- package/app/pages/page-my.mjs +7 -0
- package/app/pages/page-test.mjs +22 -0
- package/bin.mjs +48 -0
- package/ce.mjs +1 -0
- package/comm/router-view.mjs +96 -0
- package/console/api/mock.mjs +211 -0
- package/console/components/common-action-button.mjs +63 -0
- package/console/components/common-header.mjs +26 -0
- package/console/components/common-input.mjs +44 -0
- package/console/components/common-modal.mjs +45 -0
- package/console/components/common-pagination.mjs +87 -0
- package/console/components/common-toolbar.mjs +39 -0
- package/console/components/layout-container.mjs +46 -0
- package/console/components/layout-header.mjs +179 -0
- package/console/components/layout-menu.mjs +221 -0
- package/console/index.html +50 -0
- package/console/pages/page-dashboard.mjs +15 -0
- package/console/pages/system-menu.mjs +179 -0
- package/console/pages/system-role.mjs +199 -0
- package/console/pages/system-user.mjs +199 -0
- package/index.mjs +854 -98
- package/package.json +8 -2
- package/comp/div-box.mjs +0 -20
- package/index.html +0 -17
package/README.md
CHANGED
|
@@ -1,84 +1,153 @@
|
|
|
1
|
-
|
|
1
|
+
# @k3000/ce - Custom Element 响应式框架
|
|
2
2
|
|
|
3
|
+
这是一个极轻量、**零构建 (No Build)** 的原生 Web Components 框架。它利用现代浏览器的 ES Modules 和 Custom Elements API,无需任何打包工具。
|
|
4
|
+
|
|
5
|
+
链接:[讨论沟通链接](https://qshfu.com/doc/index.html#/public/MKjPmZhaz)
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## ✨ 核心特性
|
|
9
|
+
|
|
10
|
+
- 🚀 **零构建**: 无需 Webpack/Vite,直接在浏览器运行。
|
|
11
|
+
- ⚛️ **响应式系统**: 基于 `Object.defineProperty` 的自动数据追踪与视图更新。
|
|
12
|
+
- 模板指令**: 内置 `each` (循环)、`connect` (条件渲染) 指令。
|
|
13
|
+
- 🛠️ **响应式 Hooks**: 提供 `useAttr`, `useWatch`, `useRef`, `useEvent` 等现代 API。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 🚀 快速上手
|
|
18
|
+
|
|
19
|
+
### 1. 全局安装
|
|
3
20
|
```
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
</head>
|
|
9
|
-
<body>
|
|
10
|
-
<div-box>
|
|
11
|
-
<span slot="title">标题</span>
|
|
12
|
-
<form is="new-form" get="info.json">
|
|
13
|
-
<input name="name"/>
|
|
14
|
-
<input name="age"/>
|
|
15
|
-
</form>
|
|
16
|
-
</div-box>
|
|
17
|
-
</body>
|
|
21
|
+
.\> npm i @k3000/ce -g #全局安装使用ce命令
|
|
22
|
+
.\> cd demo #进入需要的目录
|
|
23
|
+
.\demo> ce copy #复制核心框架文件可以带参数,ce copy dist.js,将ce.mjs复制到当前目录,并改名为:dist.js
|
|
24
|
+
.\demo> ce demo #复制使用示例一共三个目录和一个数据文件:/app、/comm、/console、/info.json
|
|
18
25
|
```
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
|
|
27
|
+
### 2. 项目引入
|
|
28
|
+
在 `index.html` 中通过 Script 标签引入核心框架:
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<script type="module">
|
|
32
|
+
import { config } from '../ce.mjs'
|
|
33
|
+
|
|
34
|
+
config({
|
|
35
|
+
dir: './console/components',
|
|
36
|
+
ext: 'mjs'
|
|
37
|
+
})
|
|
38
|
+
</script>
|
|
21
39
|
```
|
|
40
|
+
|
|
41
|
+
### 3. 定义组件
|
|
42
|
+
每个组件为一个独立的 `.mjs` 文件。
|
|
43
|
+
|
|
44
|
+
**示例: `my-counter.mjs`**
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
import { useWatch } from "../../ce.mjs";
|
|
48
|
+
|
|
49
|
+
// 1. 定义 HTML 结构 (支持插值 {{ }})
|
|
50
|
+
export const innerHTML = `
|
|
51
|
+
<div class="p-4 border rounded-xl shadow-lg bg-white/60 backdrop-blur-md">
|
|
52
|
+
<h3 class="text-lg font-bold">计数器: {{count}}</h3>
|
|
53
|
+
<div class="mt-4 gap-2 flex">
|
|
54
|
+
<button onclick="{{increment}}" class="px-4 py-2 bg-blue-500 text-white rounded-lg">+1</button>
|
|
55
|
+
<button onclick="{{() => this.count = 0}}" class="px-4 py-2 bg-gray-200 rounded-lg">重置</button>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
// 2. 定义逻辑
|
|
22
61
|
export default class extends HTMLElement {
|
|
62
|
+
count = 0;
|
|
23
63
|
|
|
24
64
|
constructor() {
|
|
65
|
+
super();
|
|
66
|
+
this.style.display = 'contents';
|
|
67
|
+
|
|
68
|
+
// 开启响应式监听 (可选,模板中使用的变量会自动监听)
|
|
69
|
+
const watch = useWatch(this);
|
|
70
|
+
watch({
|
|
71
|
+
count: (val) => console.log('Count changed to:', val)
|
|
72
|
+
});
|
|
73
|
+
}
|
|
25
74
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
this.attachShadow({ mode: 'open' });
|
|
29
|
-
this.shadowRoot.innerHTML = `
|
|
30
|
-
<style>
|
|
31
|
-
:host {
|
|
32
|
-
display: block;
|
|
33
|
-
background-color: #f0f0f0;
|
|
34
|
-
padding: 10px;
|
|
35
|
-
}
|
|
36
|
-
</style>
|
|
37
|
-
<h2><slot name="title"></slot></h2>
|
|
38
|
-
<slot></slot>
|
|
39
|
-
`;
|
|
75
|
+
increment() {
|
|
76
|
+
this.count++;
|
|
40
77
|
}
|
|
41
78
|
}
|
|
42
79
|
```
|
|
43
|
-
new-form.mjs
|
|
44
|
-
```
|
|
45
|
-
export default class extends HTMLFormElement {
|
|
46
|
-
// connected前执行,此处处理好数据逻辑,入参是父级的数据
|
|
47
|
-
data(data) {
|
|
48
80
|
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
// node挂载时执行,入参是data()返回的数据
|
|
52
|
-
connected(data) {
|
|
53
|
-
|
|
54
|
-
if (this.attributes.get) {
|
|
81
|
+
---
|
|
55
82
|
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
}
|
|
83
|
+
## 📖 核心 API (Hooks)
|
|
59
84
|
|
|
60
|
-
|
|
85
|
+
### `useAttr(this)`
|
|
86
|
+
获取当前组件的所有 HTML 属性,返回一个简单的键值对对象。
|
|
87
|
+
```javascript
|
|
88
|
+
const attr = useAttr(this);
|
|
89
|
+
console.log(attr.title); // 获取 <my-comp title="xxx"></my-comp> 的值
|
|
90
|
+
```
|
|
61
91
|
|
|
62
|
-
|
|
92
|
+
### `useRef(this)`
|
|
93
|
+
获取模板中标记了 `ref` 属性的 DOM 元素。
|
|
94
|
+
```javascript
|
|
95
|
+
// HTML: <path ref="pathNode"></path>
|
|
96
|
+
ready() {
|
|
97
|
+
const refs = useRef(this);
|
|
98
|
+
refs.pathNode.setAttribute('d', 'M10 10...');
|
|
99
|
+
}
|
|
100
|
+
```
|
|
63
101
|
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
### `useWatch(target)`
|
|
103
|
+
手动对对象属性进行响应式监听。
|
|
104
|
+
```javascript
|
|
105
|
+
const watch = useWatch(this.data);
|
|
106
|
+
watch({
|
|
107
|
+
username: (newVal, oldVal) => {
|
|
108
|
+
// 返回 false 可以阻止赋值
|
|
109
|
+
return true;
|
|
66
110
|
}
|
|
111
|
+
}, true); // 第二个参数为 true 表示立即执行一次
|
|
112
|
+
```
|
|
67
113
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
114
|
+
### `useEvent()`
|
|
115
|
+
获取全局事件总线,用于跨组件通信。
|
|
116
|
+
```javascript
|
|
117
|
+
const event = useEvent();
|
|
118
|
+
event.dispatch('user-login', { id: 1 }); // 发送事件
|
|
119
|
+
event.add('user-login', (data) => { ... }); // 监听事件
|
|
120
|
+
```
|
|
71
121
|
|
|
72
|
-
|
|
122
|
+
---
|
|
73
123
|
|
|
74
|
-
|
|
124
|
+
## 🏗️ 模板指令
|
|
75
125
|
|
|
76
|
-
|
|
126
|
+
### `each="{{list}}"` (循环)
|
|
127
|
+
```html
|
|
128
|
+
<tr each="{{userList}}" class="hover:bg-gray-50">
|
|
129
|
+
<td>{{username}}</td>
|
|
130
|
+
<td>{{email}}</td>
|
|
131
|
+
</tr>
|
|
132
|
+
```
|
|
77
133
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
134
|
+
### `connect="{{isVisible}}"` (条件渲染)
|
|
135
|
+
类似于 `v-if`,控制元素在 DOM 树中的物理存在。
|
|
136
|
+
```html
|
|
137
|
+
<div connect="{{status === 'active'}}">
|
|
138
|
+
现在可见
|
|
139
|
+
</div>
|
|
140
|
+
```
|
|
83
141
|
|
|
142
|
+
### `on[event]="{{handler}}"` (事件绑定)
|
|
143
|
+
```html
|
|
144
|
+
<button onclick="{{handleClick}}">方法绑定</button>
|
|
145
|
+
<button onclick="{{() => this.count++}}">内联绑定</button>
|
|
84
146
|
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## ⚠️ 注意事项
|
|
151
|
+
- **ES Modules**: 必须在 HTTP 服务器环境下运行。
|
|
152
|
+
- **作用域**: 模板指令中的代码默认运行在组件实例的作用域下。
|
|
153
|
+
- **ready()**: 当你需要操作 DOM 时,请在 `ready()` 生命周期中执行。
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {useAttr, useRef} from "../../ce.mjs";
|
|
2
|
+
|
|
3
|
+
export const shadowRoot = `
|
|
4
|
+
<label>
|
|
5
|
+
<input ref="fileInput" type="file" />
|
|
6
|
+
</label>
|
|
7
|
+
`
|
|
8
|
+
|
|
9
|
+
export default class extends HTMLElement {
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
|
|
13
|
+
super()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
ready() {
|
|
17
|
+
|
|
18
|
+
const refs = useRef(this.shadowRoot)
|
|
19
|
+
const attr = useAttr(this)
|
|
20
|
+
|
|
21
|
+
refs.fileInput.accept = attr.accept
|
|
22
|
+
refs.fileInput.onchange = () => this.change(refs.fileInput.files)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {useEvent} from "../../ce.mjs";
|
|
2
|
+
|
|
3
|
+
export const shadowRoot = `
|
|
4
|
+
<style>
|
|
5
|
+
.container {
|
|
6
|
+
display: flex;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
|
|
9
|
+
.left {
|
|
10
|
+
cursor: pointer;
|
|
11
|
+
user-select: none;
|
|
12
|
+
padding-left: 20px; /* 给箭头留位置 */
|
|
13
|
+
|
|
14
|
+
&:before {
|
|
15
|
+
content: "";
|
|
16
|
+
position: absolute;
|
|
17
|
+
left: 8px;
|
|
18
|
+
top: 50%;
|
|
19
|
+
|
|
20
|
+
/* 1. 设置正方形 */
|
|
21
|
+
width: 10px;
|
|
22
|
+
height: 10px;
|
|
23
|
+
|
|
24
|
+
/* 2. 只给相邻的两条边上色,形成 L 型 */
|
|
25
|
+
border-left: 2px solid #333;
|
|
26
|
+
border-bottom: 2px solid #333;
|
|
27
|
+
|
|
28
|
+
/* 3. 旋转 45度,让 L 指向左边 */
|
|
29
|
+
/* translateY(-50%) 是为了修正垂直居中 */
|
|
30
|
+
transform: translateY(-50%) rotate(45deg);
|
|
31
|
+
|
|
32
|
+
/* 4. 可选:设置转角圆润一点 */
|
|
33
|
+
border-radius: 1px;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
</style>
|
|
38
|
+
<nav class="container" style="display: {{none ? 'none' : 'flex'}};">
|
|
39
|
+
<div class="left" onclick="{{back}}">
|
|
40
|
+
<span></span>
|
|
41
|
+
<span>
|
|
42
|
+
<slot name="left"></slot>
|
|
43
|
+
</span>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="title">
|
|
46
|
+
<slot name="title"></slot>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="right">
|
|
49
|
+
<slot name="right"></slot>
|
|
50
|
+
</div>
|
|
51
|
+
</nav>
|
|
52
|
+
`
|
|
53
|
+
|
|
54
|
+
export default class extends HTMLElement {
|
|
55
|
+
|
|
56
|
+
none = true
|
|
57
|
+
|
|
58
|
+
except = []
|
|
59
|
+
|
|
60
|
+
prePath = ''
|
|
61
|
+
|
|
62
|
+
constructor() {
|
|
63
|
+
|
|
64
|
+
super()
|
|
65
|
+
|
|
66
|
+
this.display(location.hash.slice(1) || '/')
|
|
67
|
+
|
|
68
|
+
const event = useEvent()
|
|
69
|
+
|
|
70
|
+
event.add('route.change', (...args) => this.display(...args))
|
|
71
|
+
|
|
72
|
+
event.add('except routers', (...args) => {
|
|
73
|
+
|
|
74
|
+
this.except = args
|
|
75
|
+
|
|
76
|
+
this.display(this.prePath)
|
|
77
|
+
|
|
78
|
+
}, {preValue: true})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
display(path) {
|
|
82
|
+
|
|
83
|
+
this.prePath = path
|
|
84
|
+
|
|
85
|
+
this.none = this.except.includes(path)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
back() {
|
|
89
|
+
|
|
90
|
+
history.back()
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {useEvent} from "../../ce.mjs";
|
|
2
|
+
|
|
3
|
+
export const shadowRoot = `
|
|
4
|
+
<style>
|
|
5
|
+
#page-container {
|
|
6
|
+
min-height: 100vh;margin: 0;display: flex;flex-direction: column;
|
|
7
|
+
> header {position: sticky;top: 0}
|
|
8
|
+
> main {flex: 1}
|
|
9
|
+
> footer {position: sticky;bottom: 0}
|
|
10
|
+
}
|
|
11
|
+
</style>
|
|
12
|
+
<div id="page-container">
|
|
13
|
+
<header><slot name="header"></slot></header>
|
|
14
|
+
<main><slot name="main"></slot></main>
|
|
15
|
+
<footer><slot name="footer"></slot></footer>
|
|
16
|
+
</div>
|
|
17
|
+
`
|
|
18
|
+
|
|
19
|
+
export default class extends HTMLElement {
|
|
20
|
+
constructor() {
|
|
21
|
+
super()
|
|
22
|
+
document.body.style.margin = '0'
|
|
23
|
+
const event = useEvent()
|
|
24
|
+
event.dispatch('except routers', '/', '/home', '/category', '/my')
|
|
25
|
+
event.dispatch('init routers', [
|
|
26
|
+
{
|
|
27
|
+
path: '/',
|
|
28
|
+
dir: './app/pages',
|
|
29
|
+
component: 'page-home'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
path: '/home',
|
|
33
|
+
dir: './app/pages',
|
|
34
|
+
component: 'page-home'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
path: '/category',
|
|
38
|
+
dir: './app/pages',
|
|
39
|
+
component: 'page-category'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
path: '/my',
|
|
43
|
+
dir: './app/pages',
|
|
44
|
+
component: 'page-my'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
path: '/test',
|
|
48
|
+
dir: './app/pages',
|
|
49
|
+
component: 'page-test'
|
|
50
|
+
}
|
|
51
|
+
])
|
|
52
|
+
}
|
|
53
|
+
test() {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {useEvent} from "../../ce.mjs";
|
|
2
|
+
|
|
3
|
+
export const shadowRoot = `
|
|
4
|
+
<style>
|
|
5
|
+
.container {
|
|
6
|
+
display: flex;
|
|
7
|
+
justify-content: space-evenly;
|
|
8
|
+
cursor: pointer;
|
|
9
|
+
user-select: none;
|
|
10
|
+
}
|
|
11
|
+
</style>
|
|
12
|
+
<div class="container" style="display: {{none ? 'none' : 'flex'}};">
|
|
13
|
+
<slot></slot>
|
|
14
|
+
</div>
|
|
15
|
+
`
|
|
16
|
+
|
|
17
|
+
export const innerHTML = `
|
|
18
|
+
<div data-to="/home" onclick="{{routeTo}}" class="tab-bar-item {{['/home', '/'].includes(current) ? 'active' : ''}}">首页</div>
|
|
19
|
+
<div data-to="/category" onclick="{{routeTo}}" class="tab-bar-item {{current === '/category' ? 'active' : ''}}">分类</div>
|
|
20
|
+
<div data-to="/my" onclick="{{routeTo}}" class="tab-bar-item {{current === '/my' ? 'active' : ''}}">我的</div>
|
|
21
|
+
`
|
|
22
|
+
|
|
23
|
+
export default class extends HTMLElement {
|
|
24
|
+
|
|
25
|
+
none = false
|
|
26
|
+
|
|
27
|
+
current = location.hash.slice(1)
|
|
28
|
+
|
|
29
|
+
except = []
|
|
30
|
+
|
|
31
|
+
event = useEvent()
|
|
32
|
+
|
|
33
|
+
constructor() {
|
|
34
|
+
|
|
35
|
+
super()
|
|
36
|
+
|
|
37
|
+
this.event.add('except routers', (...routes) => {
|
|
38
|
+
|
|
39
|
+
this.except = routes
|
|
40
|
+
|
|
41
|
+
this.change(this.current)
|
|
42
|
+
|
|
43
|
+
}, {preValue: true})
|
|
44
|
+
|
|
45
|
+
this.event.add('route.change', path => this.change(path), {preValue: true})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
change(path) {
|
|
49
|
+
|
|
50
|
+
this.current = path
|
|
51
|
+
|
|
52
|
+
this.none = !this.except.includes(path)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
routeTo(event) {
|
|
56
|
+
|
|
57
|
+
this.event.dispatch('route.to', event.target.dataset.to)
|
|
58
|
+
// .then(res => console.log(res))
|
|
59
|
+
}
|
|
60
|
+
}
|
package/app/index.html
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport"
|
|
6
|
+
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"/>
|
|
7
|
+
<title>demo</title>
|
|
8
|
+
<script type="module">
|
|
9
|
+
import { config } from '../ce.mjs'
|
|
10
|
+
|
|
11
|
+
config({
|
|
12
|
+
dir: './app/comp',
|
|
13
|
+
ext: 'mjs'
|
|
14
|
+
})
|
|
15
|
+
</script>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<page-container>
|
|
19
|
+
<nav-bar slot="header">
|
|
20
|
+
<span slot="left">返回</span>
|
|
21
|
+
<span slot="title">标题</span>
|
|
22
|
+
<span slot="right">菜单</span>
|
|
23
|
+
</nav-bar>
|
|
24
|
+
<router-view dir="../../comm" slot="main"></router-view>
|
|
25
|
+
<style>
|
|
26
|
+
.tab-bar-item {
|
|
27
|
+
flex: 1;
|
|
28
|
+
text-align: center;
|
|
29
|
+
|
|
30
|
+
&.active {
|
|
31
|
+
font-weight: bold;
|
|
32
|
+
background-color: lightyellow;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
<tab-bar slot="footer"></tab-bar>
|
|
37
|
+
</page-container>
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
export const shadowRoot = `
|
|
3
|
+
<div>home</div>
|
|
4
|
+
<b onclick="{{routeTo('/test')}}" style="user-select: none">test</b>
|
|
5
|
+
`
|
|
6
|
+
|
|
7
|
+
export default class extends HTMLElement {
|
|
8
|
+
constructor() {
|
|
9
|
+
super()
|
|
10
|
+
}
|
|
11
|
+
routeTo(to) {
|
|
12
|
+
location.hash = '#' + to
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
export const shadowRoot = `
|
|
3
|
+
<form is="my-form" get="../info.json" test="{{test}}" change="{{change}}">
|
|
4
|
+
<input name="name"/>
|
|
5
|
+
<input name="age"/>
|
|
6
|
+
<comp-test accept="image/*" change="{{change}}"></comp-test>
|
|
7
|
+
<button type="button" onclick="{{test}}">test</button>
|
|
8
|
+
</form>
|
|
9
|
+
`
|
|
10
|
+
|
|
11
|
+
export default class extends HTMLElement {
|
|
12
|
+
|
|
13
|
+
change(files) {
|
|
14
|
+
|
|
15
|
+
console.log(files)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test() {
|
|
19
|
+
|
|
20
|
+
console.log('test')
|
|
21
|
+
}
|
|
22
|
+
}
|
package/bin.mjs
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {cpSync, existsSync, mkdirSync} from "node:fs";
|
|
3
|
+
import {resolve, dirname} from "node:path";
|
|
4
|
+
|
|
5
|
+
const [, , key, value] = process.argv
|
|
6
|
+
|
|
7
|
+
const path = import.meta.url.substring(process.platform === 'win32' ? 8 : 7)
|
|
8
|
+
|
|
9
|
+
const getDir = path => decodeURI(dirname(path));
|
|
10
|
+
/**
|
|
11
|
+
* 将本目录下的文件或者文件夹复制到项目运行目录
|
|
12
|
+
* @param {string} source 自动加上'/'
|
|
13
|
+
* @param {string} destination
|
|
14
|
+
* @param {boolean} force
|
|
15
|
+
* @example
|
|
16
|
+
* myCopyFn('source', 'destination')
|
|
17
|
+
*/
|
|
18
|
+
const myCopyFn = (source, destination = source, force = false) => {
|
|
19
|
+
|
|
20
|
+
return cpSync( `${getDir(path)}/${source}`, destination, {recursive: true, force})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// console.log(`key: ${key}, value: ${value}, path: ${path}, getDir: ${getDir(path)}`);
|
|
24
|
+
|
|
25
|
+
switch (key) {
|
|
26
|
+
|
|
27
|
+
case 'copy':
|
|
28
|
+
|
|
29
|
+
let target = value || 'ce.mjs'
|
|
30
|
+
|
|
31
|
+
if (!target.endsWith('.mjs') && !target.endsWith('.js')) {
|
|
32
|
+
|
|
33
|
+
target += '.js'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
myCopyFn('index.mjs', target, true)
|
|
37
|
+
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
case 'demo':
|
|
41
|
+
|
|
42
|
+
myCopyFn('app', 'app', !!value)
|
|
43
|
+
myCopyFn('comm', 'comm', !!value)
|
|
44
|
+
myCopyFn('console', 'console', !!value)
|
|
45
|
+
myCopyFn('info.json', 'info.json', !!value)
|
|
46
|
+
|
|
47
|
+
break
|
|
48
|
+
}
|
package/ce.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let dir="./comp",ext="mjs",getVariables=e=>{const t=e.match(/\{\{.+?}}/g);if(!t)return new Set;const o=new Set,n=new Set(["true","false","null","undefined","this","new","typeof","in","instanceof"]);return t.forEach((e=>{const t=e.slice(2,-2).match(/\b[a-zA-Z_$][\w$]*(?:\s*\.\s*[a-zA-Z_$][\w$]*)*(?!\s*\()/g);t&&t.forEach((e=>{n.has(e)||o.add(e)}))})),o},createTmpNode=(e="")=>document.createTextNode(""),literalsCode=e=>`with (scope) {\n\treturn \`${e.replace(/{{(.*?)}}/g,((e,t)=>`\${${t}}`))}\` \n}`,functionCode=e=>`with (scope) {\n\treturn ${e} \n}`,handleArray=(e,t)=>{const o=e.ownerElement;if(e.nodeMap||(e.nodeMap=[],e.mark=createTmpNode("数组结束标记"),o.parentNode.replaceChild(e.mark,o),e.ownerElement=o.cloneNode(!0)),!Array.isArray(t))return e;const n=e.mark.parentNode;let r=0;for(;r<t.length;r++)if(void 0!==e.nodeMap[r]){if(e.nodeMap[r][0]!==t[r]){const s=o.cloneNode(!0);try{t[r]._=e.scope}catch(e){}bindNode(t[r],s,!0),n.replaceChild(s,e.nodeMap[r][1]),e.nodeMap[r]=[t[r],s]}}else{const s=o.cloneNode(!0);try{t[r]._=e.scope}catch(e){}bindNode(t[r],s,!0),n.insertBefore(s,e.mark),e.nodeMap[r]=[t[r],s]}const s=r;for(;r<e.nodeMap.length;r++)n.removeChild(e.nodeMap[r][1]);return e.nodeMap.splice(s,e.nodeMap.length-s),e};export const config=e=>{e.dir&&(dir=e.dir),e.ext&&(ext=e.ext),"function"==typeof e.getVariables&&(getVariables=e.getVariables),"function"==typeof e.createTmpNode&&(createTmpNode=e.createTmpNode),"function"==typeof e.literalsCode&&(literalsCode=e.literalsCode),"function"==typeof e.functionCode&&(functionCode=e.functionCode),"function"==typeof e.handleArray&&(handleArray=e.handleArray)};const getCEName=e=>e.tagName.includes("-")?e.tagName.toLowerCase():e.hasAttribute("is")?e.getAttribute("is"):void 0;export const throttle=(e,t=41)=>{let o;return(...n)=>{clearTimeout(o),o=setTimeout((()=>e(...n)),t)}};const objectPool=new WeakMap,importPool=new WeakSet,addListener=(e,t,o)=>{getVariables(t).forEach((t=>{let n="",r=e,s=0,a=t.split(".");for([s,n]of a.entries()){if(s===a.length-1)break;if(null===r||"object"!=typeof r||!Reflect.has(r,n))return;if(Array.isArray(r[n])&&s===a.length-2)break;r=r[n]}if(null!==r&&"object"==typeof r&&(!Object.getOwnPropertyDescriptor(r,n)||Object.getOwnPropertyDescriptor(r,n).configurable))if(objectPool.has(r)){const e=objectPool.get(r);if(e.has(n))e.get(n).add(o);else{const t=new Set([o]);e.set(n,t);let s=r[n];Object.defineProperty(r,n,{get:()=>s,set:e=>{s!==e&&(s=e,t.forEach((e=>e())))}})}}else{const e=new Set([o]);objectPool.set(r,new Map([[n,e]]));let t=r[n];Object.defineProperty(r,n,{get:()=>t,set:o=>{t!==o&&(t=o,e.forEach((e=>e())))}})}}))},ceMap=Object.create(null),disposeCE=(e,t)=>{if(e.querySelectorAll("*:not(:defined)").forEach((e=>importCE(e))),ceMap[t].shadowRoot&&(e.attachShadow({mode:"open"}),e.shadowRoot.appendChild(ceMap[t].shadowRoot.create(e))),ceMap[t].innerHTML||ceMap[t].outerHTML){const o=(ceMap[t].innerHTML||ceMap[t].outerHTML).create(e);if(!ceMap[t].shadowRoot){const t=Array.from(o.querySelectorAll("slot")),n=t.reduce(((e,t)=>(e[t.getAttribute("name")||""]=t,e)),{}),r=Array.from(e.childNodes);for(const e of r){const t="function"==typeof e.getAttribute&&e.getAttribute("slot")||"";n[t]&&n[t].parentNode.insertBefore(e,n[t])}for(const e of t)e.parentNode.removeChild(e)}if(ceMap[t].outerHTML){for(;o.firstChild;)e.parentElement.insertBefore(o.firstChild,e);e.parentElement.removeChild(e)}else e.appendChild(o)}"function"==typeof e.ready&&e.ready()};let tmp=null;const importCE=(e,t=void 0)=>{if(t=t??getCEName(e),importPool.has(e))return;if(importPool.add(e),void 0!==customElements.get(t))return void disposeCE(e,t);const o=e.nodeName.includes("-");let n=dir;if(e.attributes.dir&&e.attributes.dir.nodeValue){const t=e.attributes.dir.nodeValue;e.removeAttribute("dir"),n=t.startsWith("/")||t.startsWith("./")?t:`${n}/${t}`}import(`${n}/${t}.${ext}`).then((n=>{void 0===customElements.get(t)&&(ceMap[t]=Object.create(null),"string"==typeof n.innerHTML&&(ceMap[t].innerHTML=bind(n.innerHTML)),"string"==typeof n.outerHTML&&(ceMap[t].outerHTML=bind(n.outerHTML)),"string"==typeof n.shadowRoot&&(ceMap[t].shadowRoot=bind(n.shadowRoot)),o?customElements.define(t,n.default):customElements.define(t,n.default,{extends:e.nodeName.toLowerCase()})),disposeCE(e,t)})).catch((e=>{console.error(e)}))},disposeNode=(e,t)=>{const o=t.nodeValue;if(null===o.match(/{{(.*?)}}/))return;const n=new Function("scope",literalsCode(o));addListener(e,o,throttle((()=>t.nodeValue=n.call(t,e)))),t.nodeValue=n.call(t,e)},disposeAttrValue=(e,t,o,n)=>{if(!n.startsWith("{{")||!n.endsWith("}}")||n.slice(2).includes("{{")){const r=new Function("scope",literalsCode(n));return()=>o.nodeValue=r.call(t,e)}const r=new Function("scope",functionCode(n.slice(2,-2)));return()=>{const n=r.call(t,e);switch(n){case!0:o.nodeValue="",t.attributes.setNamedItem(o);break;case!1:t.removeAttribute(o.nodeName);break;default:o.nodeValue=n,t.attributes.setNamedItem(o)}}},disposeSpecialAttr=(e,t,o,n)=>{const r=t.ownerElement;let s=t.nodeName;if(s.startsWith("on")){if(r.removeAttribute(s),!o.includes("(")){const t=o.split(".");let n="",a=e,i=0;for([i,n]of t.entries()){if(i===t.length-1)break;if(null===a||"object"!=typeof a||!Reflect.has(a,n)){a={};break}a=a[n]}if("function"==typeof a[n])return r[s]=e=>a[n](e),!0}const t=new Function("scope","event",functionCode(o));return r[s]=o=>t.call(r,e,o),!0}if("connect"===s){r.removeAttribute(s);const n=new Function("scope",functionCode(o)),a=createTmpNode("节点断开重连的标记"),i=()=>{n(e)?a.parentNode?.replaceChild(r,a):r.parentNode?.replaceChild(a,r)};return addListener(e,t.nodeValue,throttle((()=>i()))),i(),!0}if("each"===s){r.removeAttribute(s);let n={ownerElement:r,scope:e};if(Reflect.has(e,o)){const r=()=>n=handleArray(n,e[o]);return addListener(e,t.nodeValue,throttle((()=>r()))),r(),!0}const a=new Function("scope",functionCode(o));return handleArray(n,a(e)),!0}if(n){r.removeAttribute(s);const n=new Function("scope",functionCode(o));s.includes("-")&&(s=s.split("-").map(((e,t)=>e&&t?e[0].toUpperCase()+e.slice(1):e)).join(""));const a=()=>{const t=n(e);r[s]="function"==typeof t?t.bind(e):t};return addListener(e,t.nodeValue,throttle((()=>a()))),a(),!0}return!1},disposeAttr=(e,t,o)=>{const n=t.nodeValue;if(null===n.match(/{{(.*?)}}/))return;if(n.startsWith("{{")&&n.endsWith("}}")&&disposeSpecialAttr(e,t,n.slice(2,-2),o))return;const r=disposeAttrValue(e,t.ownerElement,t,n);addListener(e,n,throttle((()=>r()))),r()};export const bindNode=(e,t,o)=>{const n=o?[t]:Array.from(t.childNodes);for(const o of n){if(o.attributes)for(const n of Array.from(o.attributes))if(disposeAttr(e,n,getCEName(o)),"each"===n.nodeName&&n.nodeValue.startsWith("{{")&&n.nodeValue.endsWith("}}"))return t;if(o.childNodes&&bindNode(e,o),3===o.nodeType&&null!==o.nodeValue&&disposeNode(e,o),1===o.nodeType){const e=getCEName(o);e&&importCE(o,e)}}return t};export const bind=e=>{const t=document.createElement("template");return t.innerHTML=e,{create:e=>bindNode(e,document.importNode(t.content,!0))}};const style=document.createElement("style");style.innerHTML="\n*:not(:defined) {\n display: none\n}\n",document.head.appendChild(style),addEventListener("DOMContentLoaded",(()=>document.querySelectorAll("*:not(:defined)").forEach((e=>importCE(e)))));class EventBus{value=void 0;targets=new Map;add(e,t=Object.create(null)){this.targets.set(e,t),t.preValue&&void 0!==this.value&&e(...this.value)}remove(e){this.targets.delete(e)}dispatch(...e){return this.value=e,Promise.allSettled(this.targets.entries().map((([t,{once:o}])=>(o&&this.targets.delete(t),t(...e)))))}}const eventBusObj=Object.create(null),eventBus=Object.defineProperties(Object.create(null),{add:{value:(e,t,o)=>{Reflect.has(eventBusObj,e)||(eventBusObj[e]=new EventBus),eventBusObj[e].add(t,o)}},remove:{value:(e,t)=>{Reflect.has(eventBusObj,e)&&eventBusObj[e].remove(t)}},dispatch:{value:(e,...t)=>(Reflect.has(eventBusObj,e)||(eventBusObj[e]=new EventBus),eventBusObj[e].dispatch(...t))}});export const useEvent=()=>eventBus;export const useRef=e=>{const t=Object.create(null);for(const o of e.querySelectorAll("[ref]"))t[o.attributes.ref.nodeValue]=o;return t};export const useAttr=e=>{const t=Object.create(null);for(const o of e.attributes)t[o.nodeName]=o.nodeValue;return t};export const useWatch=e=>(t,o=!1)=>{for(const[n,r]of Object.entries(t)){if("function"!=typeof r)continue;let t=e[n];Object.defineProperty(e,n,{get:()=>t,set:e=>{if(!1!==r(e,t))return t=e}}),o&&r(t,t)}};
|