cdui-js 1.0.1

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.
Files changed (52) hide show
  1. package/.gitattributes +1 -0
  2. package/.prettierignore +2 -0
  3. package/.prettierrc +5 -0
  4. package/README.md +185 -0
  5. package/build/css.ts +293 -0
  6. package/build/icons.ts +108 -0
  7. package/cli/bin.js +77 -0
  8. package/css/all.css +8 -0
  9. package/css/atomic/align.css +35 -0
  10. package/css/atomic/all.css +13 -0
  11. package/css/atomic/cursor.css +7 -0
  12. package/css/atomic/display.css +11 -0
  13. package/css/atomic/flex.css +124 -0
  14. package/css/atomic/grid.css +0 -0
  15. package/css/atomic/hidden.css +37 -0
  16. package/css/atomic/other.css +23 -0
  17. package/css/atomic/overflow.css +47 -0
  18. package/css/atomic/position.css +58 -0
  19. package/css/base.css +16 -0
  20. package/css/carousel.css +46 -0
  21. package/css/combobox.css +58 -0
  22. package/css/icon.css +7 -0
  23. package/icons/backward.svg +3 -0
  24. package/icons/close.svg +3 -0
  25. package/icons/dropdown.svg +3 -0
  26. package/icons/forward.svg +3 -0
  27. package/icons/toggle.svg +3 -0
  28. package/index.ts +1 -0
  29. package/jsx-runtime.d.ts +1 -0
  30. package/package.json +21 -0
  31. package/src/animate-scroll-to.ts +65 -0
  32. package/src/components/Carousel.tsx +299 -0
  33. package/src/components/CollapsiblePanel.tsx +139 -0
  34. package/src/components/ComboBox.tsx +163 -0
  35. package/src/components/Dialog.tsx +36 -0
  36. package/src/components/For.tsx +26 -0
  37. package/src/components/Icon.tsx +39 -0
  38. package/src/components/If.tsx +14 -0
  39. package/src/components/KeepAlive.tsx +26 -0
  40. package/src/components/Pulldown.tsx +157 -0
  41. package/src/components/Switch.tsx +49 -0
  42. package/src/dom.ts +137 -0
  43. package/src/http.ts +282 -0
  44. package/src/index.ts +68 -0
  45. package/src/jsx.ts +2 -0
  46. package/src/language.ts +34 -0
  47. package/src/layout.ts +89 -0
  48. package/src/location.ts +133 -0
  49. package/src/message.ts +290 -0
  50. package/src/reactive.ts +211 -0
  51. package/src/template.ts +23 -0
  52. package/tsconfig.json +16 -0
package/.gitattributes ADDED
@@ -0,0 +1 @@
1
+ * text=auto
@@ -0,0 +1,2 @@
1
+ node_modules
2
+ dist
package/.prettierrc ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": true,
4
+ "printWidth": 120
5
+ }
package/README.md ADDED
@@ -0,0 +1,185 @@
1
+
2
+
3
+
4
+
5
+ # MVVM响应式编程
6
+
7
+ 为便于团队合作,建议项目中的所有代码都使用`typescrpt`,本项目使用与`React`类似的响应式`MVVM`框架作为主要开发方式。
8
+
9
+ 响应式`(Reactivity)`增强了应用程序的交互性。这种编程范式是指系统自动响应数据或状态变化的能力,确保用户界面 (UI) 和状态保持同步,从而减少手动更新的需要。
10
+
11
+
12
+ ## 响应式原理
13
+
14
+ ### 响应式数据
15
+
16
+ 响应式对象是响应式体系的核心元素,在数据管理和系统响应中发挥着重要作用。其负责存储和管理数据,以及触发整个系统的更新。响应式对象的每-个属性都绑定了`getter`和`setter`方法。
17
+
18
+ 读取响应式对象的属性时,会自动调用其相应的`getter`方法,此时系统会自动收集依赖并返回其存储的值。
19
+ 修改响应式对象的属性时,会自动调用其相应的`setter`方法,会修改其存储的值且自动更新相应依赖。
20
+
21
+ ```tsx
22
+ import { reactive } from 'cdui-js';
23
+
24
+ // 创建响应式数据
25
+ const state = reactive({ count: 0 });
26
+
27
+ // 读取响应式属性值
28
+ let count = state.count;
29
+
30
+ // 修改响应式属性值
31
+ state.count++;
32
+ ```
33
+
34
+ ### 订阅者
35
+
36
+ 订阅者负责追踪响应式数据的变化并在变化时自动执行相应函数,以保持系统与最新的数据变化同步。
37
+
38
+ 有三种订阅变化并同步的方式:
39
+
40
+ 1. watch
41
+
42
+ ```tsx
43
+ import { reactive, watch } from 'cdui-js';
44
+
45
+ // 创建响应式数据
46
+ const state = reactive({ count: 0 });
47
+
48
+ // 观测响应式数据的变化,当观测的响应式数据发生变化时,会自动执行观测函数
49
+ watch(() => state.count, (count) => {
50
+ console.log(count);
51
+ });
52
+ ```
53
+
54
+ 可以使用`as const`断言同时观测多个响应式数据。
55
+
56
+ ```tsx
57
+ import { reactive, watch } from 'cdui-js';
58
+
59
+ const state1 = reactive({ value: 0 });
60
+ const state2 = reactive({ value: 1 });
61
+
62
+ // 注意,as const 把返回值类型标记为元组
63
+ watch(() => [state1.value, state2.value] as const, ([value1, value2]) => {
64
+ console.log(value1, value1);
65
+ });
66
+ ```
67
+
68
+ 2. createEffect
69
+
70
+ `watch`方法只有在依赖发生变化时才执行相关依赖,而`createEffect`创建时立即执行追踪函数并自动收集相应的依赖。
71
+
72
+ ```tsx
73
+ import { reactive, createEffect } from 'cdui-js';
74
+
75
+ const state1 = reactive({ value: 0 });
76
+ const state2 = reactive({ value: 1 });
77
+
78
+ createEffect(() => {
79
+ console.log(state1.value);
80
+
81
+ // 注意短路问题(state1.value 发生变化时并不会执行回调函数)
82
+ console.log(state2.value || state1.value);
83
+ });
84
+ ```
85
+
86
+ 3. createMemo
87
+
88
+ `createMemo`用于缓存响应式计算结果,只有当依赖的响应式数据发生变化时才会重新计算,注意,`createMemo`同样存在短路的问题。
89
+
90
+ ```tsx
91
+ import { reactive, createMemo } from 'cdui-js';
92
+
93
+ const state = reactive({ value: 0 });
94
+
95
+ // 创建响应式缓存表示式(注意返回值是一个 function,只有 state.value 发生变化时才会重新计算)
96
+ const getValue = createMemo(() => state.value + 100);
97
+ ```
98
+
99
+ ## `TSX`组件
100
+
101
+ 本项目使用`tsx`作为模板,但是没有虚拟`DOM`,也更轻量高效。
102
+
103
+ ```tsx
104
+ import { reactive } from 'cdui-js';
105
+
106
+ function Counter() {
107
+ // 创建响应式状态
108
+ const state = reactive({ count: 0 });
109
+
110
+ return (
111
+ <div>
112
+ <span>Count: {state.count}</span>{" "}
113
+ {/* 当按钮点击的时候,会自动更新相关绑定 */}
114
+ <button type="button" onClick={() => state.count++}>
115
+ Increment
116
+ </button>
117
+ </div>
118
+ );
119
+ }
120
+ ```
121
+
122
+ 在这段代码中,当点击`Counter`组件内的按钮时,响应式数据`state.count`自增`1`,绑定了响应式属性`state.count`的部分会自动更新,但不会重新渲染整个组件。也就是说,在`return`语句之前的代码,只在函数调用时执行一次。
123
+
124
+ ### ref
125
+
126
+
127
+
128
+ ### 组件生命周期
129
+
130
+ 1. onMount
131
+
132
+ `onMount`在组件挂载后运行,只会执行一次,且在服务端渲染时不会执行,也不跟踪任何依赖项,此时可以访问相应的`DOM`对象。
133
+
134
+ ```tsx
135
+ import { onMount } from 'cdui-js';
136
+
137
+ function Component() {
138
+ onMount(async () => {
139
+
140
+ });
141
+
142
+ return <div>...</div>;
143
+ }
144
+ ```
145
+
146
+ 2. onCleanup
147
+
148
+ `onCleanup`用于在组件卸载时执行一些清理任务,以避免避免内存泄漏及一些不必要的操作。
149
+
150
+ ```tsx
151
+ import { onCleanup } from 'cdui-js';
152
+
153
+ function Component() {
154
+ const [count, setCount] = createSignal(0);
155
+
156
+ const timer = setInterval(() => {
157
+ setCount((prev) => prev + 1);
158
+ }, 1000);
159
+
160
+ // 组件卸载时清理定时器
161
+ onCleanup(() => {
162
+ clearInterval(timer);
163
+ });
164
+
165
+ return <div>Count: {count()}</div>;
166
+ }
167
+ ```
168
+
169
+ ### class
170
+
171
+
172
+ ### style
173
+
174
+
175
+ ### 事件
176
+
177
+
178
+
179
+ # `TSX`模板
180
+
181
+
182
+ # 路由
183
+
184
+
185
+ # 服务端渲染
package/build/css.ts ADDED
@@ -0,0 +1,293 @@
1
+ import fs from 'fs';
2
+
3
+ export interface Rule {
4
+ match: string;
5
+ build(outputs: string[], selector: string, value: string): void;
6
+ }
7
+
8
+ export const rules: Rule[] = [
9
+ {
10
+ match: '.margin',
11
+ build: (outputs: string[], selector: string, value: string) => {
12
+ outputs.push(`${selector} { margin: ${value} }`);
13
+ outputs.push(`${selector.replace('margin', 'margin-t')} { margin-top: ${value}; }`);
14
+ outputs.push(`${selector.replace('margin', 'margin-r')} { margin-right: ${value}; }`);
15
+ outputs.push(`${selector.replace('margin', 'margin-b')} { margin-bottom: ${value}; }`);
16
+ outputs.push(`${selector.replace('margin', 'margin-l')} { margin-left: ${value}; }`);
17
+ outputs.push(`${selector.replace('margin', 'margin-x')} { margin-left: ${value}; margin-right: ${value}; }`);
18
+ outputs.push(`${selector.replace('margin', 'margin-y')} { margin-top: ${value}; margin-bottom: ${value}; }\n`);
19
+
20
+ outputs.push(`${selector.replace('margin', '-margin-t')} { margin-top: -${value}; }`);
21
+ outputs.push(`${selector.replace('margin', '-margin-r')} { margin-right: -${value}; }`);
22
+ outputs.push(`${selector.replace('margin', '-margin-b')} { margin-bottom: -${value}; }`);
23
+ outputs.push(`${selector.replace('margin', '-margin-l')} { margin-left: -${value}; }`);
24
+
25
+ outputs.push(`${selector.replace('margin', 'padding-m')} { padding: ${value}; }`);
26
+ outputs.push(`${selector.replace('margin', 'padding-m-t')} { padding-top: ${value}; }`);
27
+ outputs.push(`${selector.replace('margin', 'padding-m-r')} { padding-right: ${value}; }`);
28
+ outputs.push(`${selector.replace('margin', 'padding-m-b')} { padding-bottom: ${value}; }`);
29
+ outputs.push(`${selector.replace('margin', 'padding-m-l')} { padding-left: ${value}; }`);
30
+ outputs.push(`${selector.replace('margin', 'padding-m-x')} { padding-left: ${value}; padding-right: ${value}; }`);
31
+ outputs.push(
32
+ `${selector.replace('margin', 'padding-m-y')} { padding-top: ${value}; padding-bottom: ${value}; }\n`,
33
+ );
34
+ },
35
+ },
36
+ {
37
+ match: '.border-c',
38
+ build: (outputs: string[], selector: string, value: string) => {
39
+ if (selector === '.border-c') {
40
+ outputs.push(`.border { border-color: ${value} }`);
41
+ outputs.push(`.border-t { border-top-color: ${value} }`);
42
+ outputs.push(`.border-r { border-right-color: ${value} }`);
43
+ outputs.push(`.border-b { border-bottom-color: ${value} }`);
44
+ outputs.push(`.border-l { border-left-color: ${value} }`);
45
+ }
46
+
47
+ outputs.push(`${selector} { border-color: ${value} }`);
48
+ outputs.push(`${selector.replace('border-c', 'border-t-c')} { border-top-color: ${value}; }`);
49
+ outputs.push(`${selector.replace('border-c', 'border-r-c')} { border-right-color: ${value}; }`);
50
+ outputs.push(`${selector.replace('border-c', 'border-b-c')} { border-bottom-color: ${value}; }`);
51
+ outputs.push(`${selector.replace('border-c', 'border-l-c')} { border-left-color: ${value}; }`);
52
+ outputs.push(
53
+ `${selector.replace('border-c', 'border-x-c')} { border-left-color: ${value}; border-right-color: ${value}; }`,
54
+ );
55
+ outputs.push(
56
+ `${selector.replace('border-c', 'border-y-c')} { border-top-color: ${value}; border-bottom-color: ${value}; }`,
57
+ );
58
+ },
59
+ },
60
+ {
61
+ match: '.border-s',
62
+ build: (outputs: string[], selector: string, value: string) => {
63
+ outputs.push(`${selector} { border-style: ${value} }`);
64
+ outputs.push(`${selector.replace('border-s', 'border-t-s')} { border-top-style: ${value}; }`);
65
+ outputs.push(`${selector.replace('border-s', 'border-r-s')} { border-right-style: ${value}; }`);
66
+ outputs.push(`${selector.replace('border-s', 'border-b-s')} { border-bottom-style: ${value}; }`);
67
+ outputs.push(`${selector.replace('border-s', 'border-l-s')} { border-left-style: ${value}; }`);
68
+ outputs.push(
69
+ `${selector.replace('border-s', 'border-x-s')} { border-left-style: ${value}; border-right-style: ${value}; }`,
70
+ );
71
+ outputs.push(
72
+ `${selector.replace('border-s', 'border-y-s')} { border-top-style: ${value}; border-bottom-style: ${value}; }`,
73
+ );
74
+ },
75
+ },
76
+ {
77
+ match: '.border',
78
+ build: (outputs: string[], selector: string, value: string) => {
79
+ outputs.push(`${selector} { border-width: ${value}; border-style: solid; }`);
80
+ outputs.push(
81
+ `${selector.replace('border', 'border-t')} { border-top-width: ${value}; border-top-style: solid; }`,
82
+ );
83
+ outputs.push(
84
+ `${selector.replace('border', 'border-r')} { border-right-width: ${value}; border-right-style: solid; }`,
85
+ );
86
+ outputs.push(
87
+ `${selector.replace('border', 'border-b')} { border-bottom-width: ${value}; border-bottom-style: solid; }`,
88
+ );
89
+ outputs.push(
90
+ `${selector.replace('border', 'border-l')} { border-left-width: ${value}; border-left-style: solid; }`,
91
+ );
92
+ outputs.push(
93
+ `${selector.replace('border', 'border-x')} { border-left-width: ${value}; border-right-width: ${value}; border-left-style: solid; border-right-style: solid; }`,
94
+ );
95
+ outputs.push(
96
+ `${selector.replace('border', 'border-y')} { border-top-width: ${value}; border-bottom-width: ${value}; border-top-style: solid; border-bottom-style: solid; }`,
97
+ );
98
+ },
99
+ },
100
+ {
101
+ match: '.round',
102
+ build: (outputs: string[], selector: string, value: string) => {
103
+ outputs.push(`${selector} { border-radius: ${value} }`);
104
+ },
105
+ },
106
+ {
107
+ match: '.padding',
108
+ build: (outputs: string[], selector: string, value: string) => {
109
+ outputs.push(`${selector} { padding: ${value} }`);
110
+ outputs.push(`${selector.replace('padding', 'padding-t')} { padding-top: ${value}; }`);
111
+ outputs.push(`${selector.replace('padding', 'padding-r')} { padding-right: ${value}; }`);
112
+ outputs.push(`${selector.replace('padding', 'padding-b')} { padding-bottom: ${value}; }`);
113
+ outputs.push(`${selector.replace('padding', 'padding-l')} { padding-left: ${value}; }`);
114
+ outputs.push(`${selector.replace('padding', 'padding-x')} { padding-left: ${value}; padding-right: ${value}; }`);
115
+ outputs.push(
116
+ `${selector.replace('padding', 'padding-y')} { padding-top: ${value}; padding-bottom: ${value}; }\n`,
117
+ );
118
+ },
119
+ },
120
+ {
121
+ match: '.bg-c',
122
+ build: (outputs: string[], selector: string, value: string) => {
123
+ outputs.push(`${selector} { background-color: ${value} }`);
124
+ },
125
+ },
126
+ {
127
+ match: '.color',
128
+ build: (outputs: string[], selector: string, value: string) => {
129
+ outputs.push(`${selector} { color: ${value} }`);
130
+ },
131
+ },
132
+ {
133
+ match: '.font-s',
134
+ build: (outputs: string[], selector: string, value: string) => {
135
+ outputs.push(`${selector} { font-size: ${value} }`);
136
+ },
137
+ },
138
+ {
139
+ match: '.font',
140
+ build: (outputs: string[], selector: string, value: string) => {
141
+ outputs.push(`${selector} { font-weight: ${value} }`);
142
+ },
143
+ },
144
+ {
145
+ match: '.icon-c',
146
+ build: (outputs: string[], selector: string, value: string) => {
147
+ if (selector === '.icon-c') {
148
+ outputs.unshift(`body { stroke: ${value}; fill: ${value}; }\n.icon { stroke: inherit; fill: inherit; }\n`);
149
+ }
150
+
151
+ outputs.push(`${selector} { stroke: ${value}; fill: ${value}; }`);
152
+ },
153
+ },
154
+ {
155
+ match: '.icon-s',
156
+ build: (outputs: string[], selector: string, value: string) => {
157
+ if (selector === '.icon-s') {
158
+ outputs.unshift(`.icon { width: ${value}; height: ${value}; }\n`);
159
+ }
160
+
161
+ outputs.push(`${selector} .icon { width: ${value}; height: ${value}; }`);
162
+ outputs.push(`${selector}.icon { width: ${value}; height: ${value}; }`);
163
+ },
164
+ },
165
+ {
166
+ match: '.button',
167
+ build: (outputs: string[], selector: string, value: string) => {
168
+ outputs.push(`${selector} { ${value} }`);
169
+ },
170
+ },
171
+ {
172
+ match: '.link',
173
+ build: (outputs: string[], selector: string, value: string) => {
174
+ if (selector === '.link') {
175
+ outputs.push(`a, .link { ${value} }`);
176
+ } else {
177
+ outputs.push(`${selector} { ${value} }`);
178
+ }
179
+ },
180
+ },
181
+ ];
182
+
183
+ const findRule = (name: string) => {
184
+ for (let i = 0, l = rules.length; i < l; i++) {
185
+ if (name.startsWith(rules[i].match)) {
186
+ return rules[i];
187
+ }
188
+ }
189
+ };
190
+
191
+ const parse = (rule: Rule, outputs: string[], selectorPrefix: string, name: string, value: string) => {
192
+ if (name.endsWith('-hover')) {
193
+ rule.build(outputs, selectorPrefix + name + ':hover', value);
194
+ rule.build(outputs, selectorPrefix + '.hover:hover ' + name, value);
195
+ return;
196
+ }
197
+
198
+ if (name.endsWith('-active')) {
199
+ rule.build(outputs, selectorPrefix + name + ':active', value);
200
+ rule.build(outputs, selectorPrefix + '.active:active ' + name, value);
201
+ return;
202
+ }
203
+
204
+ if (name.endsWith('-focus')) {
205
+ rule.build(outputs, selectorPrefix + name + ':focus', value);
206
+ rule.build(outputs, selectorPrefix + '.focus:focus ' + name, value);
207
+ return;
208
+ }
209
+
210
+ if (name.endsWith('-selected')) {
211
+ rule.build(outputs, selectorPrefix + name + '.selected', value);
212
+ rule.build(outputs, selectorPrefix + '.selected ' + name, value);
213
+ return;
214
+ }
215
+
216
+ rule.build(outputs, selectorPrefix + name, value);
217
+ };
218
+
219
+ /**
220
+ * 解析 CSS 规范生成原子样式
221
+ *
222
+ * @param cssRuleFile CSS 规范文件路径(markdown规范文档)
223
+ * @param cssFile 写 css 文件路径
224
+ */
225
+ export const buildCSS = (cssRuleFile: string, cssFile?: string) => {
226
+ let css = fs.readFileSync(cssRuleFile, 'utf8');
227
+ let lines = css.split(/\r?\n\s*/);
228
+ let selectorPrefix = '';
229
+ let outputs = [];
230
+
231
+ for (let i = 0, l = lines.length; i < l; i++) {
232
+ let line = lines[i].trim();
233
+
234
+ switch (line[0] || '') {
235
+ case '.':
236
+ let index = line.indexOf('{');
237
+ let name, value, rule: Rule;
238
+
239
+ // 样式组
240
+ if (index > 0 && (name = line.slice(0, index).trim()) && (rule = findRule(name))) {
241
+ value = line.slice(index + 1);
242
+
243
+ if ((value = value.replace(/}\s*\;*\s*/, '').trim())) {
244
+ rule.build(outputs, selectorPrefix + name, value);
245
+ }
246
+
247
+ continue;
248
+ }
249
+
250
+ // 单样式值
251
+ if ((index = line.indexOf(':')) > 0 && (name = line.slice(0, index).trim()) && (rule = findRule(name))) {
252
+ value = line
253
+ .slice(index + 1)
254
+ .replace(/;\s*/g, '')
255
+ .trim();
256
+
257
+ if (value && value !== '#') {
258
+ parse(rule, outputs, selectorPrefix, name, value);
259
+ }
260
+
261
+ continue;
262
+ }
263
+
264
+ console.error('不合法的格式:' + lines[i]);
265
+ break;
266
+
267
+ case '+': // 上级选择器
268
+ selectorPrefix = line.slice(1).trim() + ' ';
269
+ outputs.push('\n');
270
+ break;
271
+
272
+ case '#': // 新的分组
273
+ selectorPrefix = '';
274
+ outputs.push('\n');
275
+ break;
276
+
277
+ case '>':
278
+ case '':
279
+ break;
280
+
281
+ default:
282
+ console.error('不合法的格式:' + lines[i]);
283
+ }
284
+ }
285
+
286
+ css = outputs.join('\n');
287
+
288
+ if (cssFile) {
289
+ fs.writeFileSync(cssFile, css, 'utf8');
290
+ }
291
+
292
+ return css;
293
+ };
package/build/icons.ts ADDED
@@ -0,0 +1,108 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * 从指定 svg 文件读取 symbol 内容
6
+ *
7
+ * @param svgFile 指定 svg 文件
8
+ * @param removeColor 去色标记 fill: 去填充色 stroke:去描边色 both:都去
9
+ */
10
+ export const readIconFile = (svgFile: string, removeColor?: 'fill' | 'stroke' | 'both'): string => {
11
+ let name = path.basename(svgFile).replace('.svg', '');
12
+ let svg = fs.readFileSync(svgFile, 'utf8');
13
+ let index = svg.indexOf(' viewBox="');
14
+ let viewBox = svg.slice(index, (index = svg.indexOf('"', index + 12) + 1));
15
+ let content = svg.slice(svg.indexOf('>', index) + 1, svg.lastIndexOf('</svg>'));
16
+ let hasStroke = content.indexOf(' stroke="') > 0;
17
+ let hasFill = content.indexOf(' fill="') > 0;
18
+
19
+ // 没有描边
20
+ if (!hasStroke) {
21
+ // 强制禁止外部修改描边
22
+ viewBox += ' stroke="none"';
23
+ } else if (!hasFill) {
24
+ // 没有填充则强制禁止外部修改填充
25
+ viewBox += ' fill="none"';
26
+ }
27
+
28
+ if (removeColor) {
29
+ // 去填充色
30
+ if (hasFill && removeColor !== 'stroke') {
31
+ content = content.replace(/ fill=\"[^"]+\"/g, '');
32
+ }
33
+
34
+ // 去描边色
35
+ if (hasStroke && removeColor !== 'fill') {
36
+ content = content.replace(/ stroke=\"[^"]+\"/g, '');
37
+ }
38
+ }
39
+
40
+ return `<symbol id="icon-${name}"${viewBox}>${content}</symbol>`;
41
+ };
42
+
43
+ /**
44
+ * 从指定图标目录读取 svg symbol 内容
45
+ *
46
+ * @param svgDirectory 指定 svg 目录
47
+ * @param removeColor 去色标记 fill: 去填充色 stroke:去描边色 both:都去
48
+ */
49
+ export const readIconsDirectory = (svgDirectory: string, removeColor?: 'fill' | 'stroke' | 'both'): string => {
50
+ const outputs = [];
51
+ const files = fs.readdirSync(svgDirectory);
52
+
53
+ for (let i = 0, l = files.length; i < l; i++) {
54
+ let file = path.join(svgDirectory, files[i]);
55
+
56
+ if (path.extname(file) === '.svg') {
57
+ outputs.push(readIconFile(file, removeColor));
58
+ }
59
+ }
60
+
61
+ return outputs.join('\n');
62
+ };
63
+
64
+ /**
65
+ * 把图标 symbol 内容写入 html 文件
66
+ *
67
+ * @param htmlFile html 文件路径
68
+ * @param symbols 图标 symbol 集合
69
+ */
70
+ export const writeIconsToHtml = (htmlFile: string, symbols: string) => {
71
+ let html = fs.readFileSync(htmlFile, 'utf8');
72
+ let index = html.indexOf('<svg id="ICONS" ');
73
+
74
+ if (index > 0) {
75
+ let start = html.indexOf('>', index) + 1;
76
+ let end = html.indexOf('</svg>', start);
77
+
78
+ html = html.slice(0, start) + '\n' + symbols + '\n' + html.slice(end);
79
+ } else {
80
+ let start = html.indexOf('>', html.indexOf('<body')) + 1;
81
+
82
+ html =
83
+ html.slice(0, start) +
84
+ '\n<svg id="ICONS" aria-hidden="true" style="position: absolute; width: 0px; height: 0px; overflow: hidden;" xmlns="http://www.w3.org/2000/svg">' +
85
+ symbols +
86
+ '\n</svg>' +
87
+ html.slice(start);
88
+ }
89
+
90
+ fs.writeFileSync(htmlFile, html, 'utf8');
91
+ };
92
+
93
+ /**
94
+ * 写 svg 图标到模块文件
95
+ *
96
+ * @param moduleFile 模块文件名
97
+ * @param symbols svg 图标内容
98
+ */
99
+ export const writeIconsModule = (moduleFile: string, symbols: string) => {
100
+ fs.writeFileSync(
101
+ moduleFile,
102
+ `import { loadSvgIcons } from 'cdui-js';
103
+
104
+ loadSvgIcons(\`${symbols}\`);
105
+ `,
106
+ 'utf8',
107
+ );
108
+ };
package/cli/bin.js ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import inquirer from 'inquirer';
5
+ import download from 'download-git-repo';
6
+ import chalk from 'chalk';
7
+ import fs from 'fs-extra';
8
+ import path from 'path';
9
+
10
+ // 定义命令
11
+ program
12
+ .version('1.0.0')
13
+ .command('create <project-name>')
14
+ .description('Create a new project from a template')
15
+ .action(async (projectName) => {
16
+ // 1. 检查目录是否已存在
17
+ const targetDir = path.join(process.cwd(), projectName);
18
+
19
+ if (fs.existsSync(targetDir)) {
20
+ console.log(chalk.red(`Error: Directory ${projectName} already exists!`));
21
+ process.exit(1);
22
+ }
23
+
24
+ const template = {
25
+ url: 'https://github.com/china-difi/mui-template/archive/refs/heads/main.zip'
26
+ };
27
+
28
+ console.log(chalk.blue(`\n🚀 Downloading template from ${template.url}...`));
29
+
30
+ // 3. 下载模板到临时目录
31
+ const tmpDir = path.join(targetDir, '.tmp');
32
+
33
+ await new Promise((resolve, reject) => {
34
+ download(template, tmpDir, { clone: false }, (err) => {
35
+ if (err) {
36
+ reject(err);
37
+ } else {
38
+ resolve();
39
+ }
40
+ });
41
+ });
42
+
43
+ // 4. 读取模板的配置文件(可选,用于更复杂的交互)
44
+ let templateConfig = {};
45
+ const configPath = path.join(tmpDir, 'template.config.json');
46
+ if (fs.existsSync(configPath)) {
47
+ templateConfig = require(configPath);
48
+ }
49
+
50
+ // 5. 询问模板特定的变量(例如项目描述、作者)
51
+ const prompts = [];
52
+ if (templateConfig.prompts) {
53
+ prompts.push(...templateConfig.prompts);
54
+ }
55
+ const answers = await inquirer.prompt(prompts);
56
+
57
+ // 6. 移动文件并渲染
58
+ // 遍历临时目录所有文件,复制到目标目录,并替换变量
59
+ const files = await fs.readdir(tmpDir);
60
+ for (const file of files) {
61
+ if (file === '.git' || file === 'template.config.json') continue; // 忽略无关文件
62
+ const src = path.join(tmpDir, file);
63
+ const dest = path.join(targetDir, file);
64
+ await fs.move(src, dest, { overwrite: true });
65
+ }
66
+
67
+ // 7. 清理和完成
68
+ await fs.remove(tmpDir);
69
+ console.log(chalk.green(`\n✅ Project ${chalk.bold(projectName)} created successfully!`));
70
+ console.log(chalk.cyan(`\nNext steps:`));
71
+ console.log(` cd ${projectName}`);
72
+ console.log(` npm install`);
73
+ console.log(` npm run dev`);
74
+ });
75
+
76
+ // 解析命令行参数
77
+ program.parse(process.argv);
package/css/all.css ADDED
@@ -0,0 +1,8 @@
1
+ @import url(base.css);
2
+
3
+ @import url(icon.css);
4
+
5
+ @import url(combobox.css);
6
+ @import url(carousel.css);
7
+
8
+ @import url(atomic/all.css);