cdui-js 1.0.13 → 1.0.14

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/build/css.ts +9 -18
  2. package/css/all.css +8 -3
  3. package/css/base.css +4 -0
  4. package/css/button.css +3 -0
  5. package/css/canlendar.css +1 -26
  6. package/css/carousel.css +0 -3
  7. package/css/combobox.css +5 -25
  8. package/css/datepicker.css +38 -0
  9. package/css/form.css +19 -0
  10. package/css/popup.css +33 -0
  11. package/css/textbox.css +5 -0
  12. package/{css → demo/css}/build.ts +1 -1
  13. package/{css → demo/css}/css.md +45 -4
  14. package/demo/icons/build.ts +17 -0
  15. package/{index.html → demo/index.html} +20 -2
  16. package/demo/src/App.tsx +15 -0
  17. package/demo/src/css/all.css +3 -0
  18. package/demo/src/css/atomic.css +935 -0
  19. package/demo/src/main.ts +10 -0
  20. package/demo/{pages → src/pages}/Canlendar.tsx +1 -1
  21. package/demo/src/pages/ComboBox.tsx +10 -0
  22. package/demo/src/pages/DatePicker.tsx +5 -0
  23. package/demo/src/pages/Form.tsx +66 -0
  24. package/{vite.config.ts → demo/vite.config.ts} +2 -2
  25. package/icons/backward.svg +2 -2
  26. package/icons/close.svg +3 -3
  27. package/icons/dropdown.svg +2 -2
  28. package/icons/forward.svg +2 -2
  29. package/package.json +4 -2
  30. package/src/components/Button.tsx +5 -0
  31. package/src/components/Canlendar.tsx +23 -45
  32. package/src/components/Carousel.tsx +13 -4
  33. package/src/components/CollapsiblePanel.tsx +2 -4
  34. package/src/components/ComboBox.tsx +50 -146
  35. package/src/components/DatePicker.tsx +91 -0
  36. package/src/components/Dialog.ts +1 -1
  37. package/src/components/Form.tsx +141 -418
  38. package/src/components/Icon.tsx +2 -1
  39. package/src/components/Popup.tsx +220 -0
  40. package/src/components/Pulldown.tsx +2 -3
  41. package/src/components/TextBox.tsx +8 -0
  42. package/src/dom.ts +55 -39
  43. package/src/http.ts +23 -19
  44. package/src/i18n/languages/en.json +3 -3
  45. package/src/index.ts +5 -1
  46. package/src/reactive.ts +9 -0
  47. package/demo/App.tsx +0 -31
  48. package/demo/main.ts +0 -7
  49. package/demo/pages/ComboBox.tsx +0 -10
  50. package/demo/pages/Form.tsx +0 -32
  51. package/icons/build.ts +0 -24
  52. /package/demo/{test.js → src/test.js} +0 -0
@@ -1,6 +1,7 @@
1
1
  import { splitProps } from 'solid-js';
2
2
 
3
3
  import { JSX } from '../jsx';
4
+ import { combineClass } from '../reactive';
4
5
 
5
6
  /**
6
7
  * 图标组件
@@ -9,7 +10,7 @@ export const Icon = (props?: JSX.SvgSVGAttributes<never> & { name: string }) =>
9
10
  let [thisProps, restProps] = splitProps(props, ['class', 'name']);
10
11
 
11
12
  return (
12
- <svg class={['icon', thisProps.class].filter((item) => item).join(' ')} aria-hidden={true} {...restProps}>
13
+ <svg class={combineClass('icon', thisProps.class)} aria-hidden={true} {...restProps}>
13
14
  <use href={'#icon-' + thisProps.name}></use>
14
15
  </svg>
15
16
  );
@@ -0,0 +1,220 @@
1
+ import { JSX } from '../jsx';
2
+ import { layout } from '../layout';
3
+ import { combineClass, splitProps } from '../reactive';
4
+ import { disableAutoCloseEvent, hideMaskLayer, registerAutoClose, showMaskLayer } from '../dom';
5
+
6
+ const POPUP_TOP_CLASS = 'popup-top';
7
+ const POPUP_RIGHT_CLASS = 'popup-right';
8
+
9
+ /**
10
+ * 当前弹出层
11
+ */
12
+ const currentPopup = {
13
+ /**
14
+ * 当前 DOM 对象
15
+ */
16
+ dom: null as HTMLElement,
17
+ /**
18
+ * 是否显示了遮罩层
19
+ */
20
+ mask: false,
21
+ };
22
+
23
+ /**
24
+ * 是否使用下拉动画
25
+ */
26
+ let useDropdownTransition = true;
27
+
28
+ /**
29
+ * 显示遮罩层方法
30
+ */
31
+ let showMaskLayerFn = () => layout['le-480'];
32
+
33
+ /**
34
+ * 设置是否使用下拉动画
35
+ *
36
+ * @param use 是否使用
37
+ */
38
+ export const setUseDropdownTransition = (use: boolean) => {
39
+ useDropdownTransition = use;
40
+ };
41
+
42
+ /**
43
+ * 设置显示遮罩层的方法
44
+ *
45
+ * @param fn 显示遮罩层的方法
46
+ */
47
+ export const setShowMaskLayerFn = (fn: () => boolean) => {
48
+ showMaskLayerFn = fn || showMaskLayerFn;
49
+ };
50
+
51
+ const showPopup = (dom: HTMLElement, onPopup?: (dom: HTMLElement) => void | false) => {
52
+ // 下拉钩子
53
+ if (onPopup && onPopup(dom) === false) {
54
+ return;
55
+ }
56
+
57
+ let windowWidth = window.innerWidth;
58
+ let windowHeight = window.innerHeight;
59
+ let classList = dom.classList;
60
+ let style = dom.style;
61
+
62
+ // 有下拉
63
+ if (currentPopup.dom) {
64
+ // 先关闭
65
+ hidePopup();
66
+ }
67
+
68
+ // 记录当前弹出层
69
+ currentPopup.dom = dom;
70
+
71
+ if ((currentPopup.mask = showMaskLayerFn())) {
72
+ showMaskLayer(closePopup);
73
+ }
74
+
75
+ let host = dom.parentNode as HTMLElement;
76
+
77
+ // 先显示
78
+ style.width = host.offsetWidth + 'px';
79
+ style.height = 'auto';
80
+ style.display = 'block';
81
+
82
+ // 获取宽高及计算位置
83
+ let width = (dom.firstElementChild as HTMLElement).offsetWidth;
84
+ let height = dom.offsetHeight;
85
+ let rect = host.getBoundingClientRect();
86
+
87
+ // 同步子组件宽度
88
+ style.width = width + 'px';
89
+
90
+ if (windowHeight - rect.top - rect.height < height + 4 && rect.top >= height) {
91
+ classList.add(POPUP_TOP_CLASS);
92
+ } else {
93
+ classList.remove(POPUP_TOP_CLASS);
94
+ }
95
+
96
+ if (windowWidth - rect.left - rect.width > width + 4 && rect.left >= width) {
97
+ classList.add(POPUP_RIGHT_CLASS);
98
+ } else {
99
+ classList.remove(POPUP_RIGHT_CLASS);
100
+ }
101
+
102
+ // 过渡动画
103
+ if (useDropdownTransition) {
104
+ style.height = '0';
105
+
106
+ setTimeout(() => {
107
+ style.height = height + 'px';
108
+ });
109
+ }
110
+ };
111
+
112
+ const hidePopup = () => {
113
+ let popup = currentPopup;
114
+ let dom = popup.dom;
115
+ let style = dom.style;
116
+
117
+ currentPopup.dom = null;
118
+
119
+ if (popup.mask) {
120
+ hideMaskLayer();
121
+ }
122
+
123
+ // 过渡动画
124
+ if (useDropdownTransition) {
125
+ style.height = dom.offsetHeight + 'px';
126
+
127
+ setTimeout(() => {
128
+ style.height = '0';
129
+ });
130
+ } else {
131
+ style.display = 'none';
132
+ }
133
+ };
134
+
135
+ const togglePopup = (dom: HTMLElement, onPopup?: (dom: HTMLElement) => void | false, event?: Event) => {
136
+ // 有显示下拉
137
+ if (currentPopup.dom) {
138
+ // 不是当前下拉框
139
+ if (currentPopup.dom !== dom) {
140
+ // 先关闭
141
+ closePopup();
142
+ }
143
+
144
+ showPopup(dom, onPopup);
145
+ } else {
146
+ showPopup(dom, onPopup);
147
+ }
148
+
149
+ event && event.stopPropagation();
150
+ };
151
+
152
+ /**
153
+ * 弹出层外部访问接口
154
+ */
155
+ export interface PopupApi {
156
+ /**
157
+ * 打开弹出框
158
+ */
159
+ openPopup(): void;
160
+ /**
161
+ * 关闭弹出框
162
+ */
163
+ closePupup(): void;
164
+ /**
165
+ * 显示或关闭弹出层
166
+ */
167
+ togglePopup(): void;
168
+ }
169
+
170
+ /**
171
+ * 弹出层属性
172
+ */
173
+ export interface PopupProps {
174
+ /**
175
+ * 弹出对话框钩子
176
+ */
177
+ onPopup?: (dom: HTMLElement) => void | false;
178
+ /**
179
+ * 外部调用接口
180
+ */
181
+ api?: (api: PopupApi) => void;
182
+ }
183
+
184
+ /**
185
+ * 弹出层组件
186
+ */
187
+ export const Popup = (props?: JSX.HTMLAttributes<never> & PopupProps) => {
188
+ let [thisProps, restProps] = splitProps(props, ['onPopup', 'api', 'children']);
189
+ let popup: HTMLElement;
190
+
191
+ // 初始化外部调用接口
192
+ props.api &&
193
+ props.api({
194
+ openPopup: () => showPopup(popup, thisProps.onPopup),
195
+ closePupup: () => currentPopup.dom === popup && hidePopup(),
196
+ togglePopup: () => togglePopup(popup, thisProps.onPopup),
197
+ });
198
+
199
+ return (
200
+ <div
201
+ ref={popup as any}
202
+ class="popup"
203
+ style={{ display: 'none' }}
204
+ {...disableAutoCloseEvent}
205
+ ontransitionend={() => currentPopup.dom === popup || (popup.style.display = 'none')}
206
+ >
207
+ <div {...restProps}>{thisProps.children}</div>
208
+ </div>
209
+ );
210
+ };
211
+
212
+ /**
213
+ * 关闭当前弹出层
214
+ */
215
+ export const closePopup = () => {
216
+ currentPopup.dom && hidePopup();
217
+ };
218
+
219
+ // 注册点击关闭弹出层的方法
220
+ registerAutoClose(closePopup);
@@ -1,6 +1,5 @@
1
- // import { onCleanup, onMount, splitProps } from 'solid-js';
2
-
3
1
  // import { JSX } from '../jsx';
2
+ // import { combineClass, onCleanup, onMount, splitProps } from '../reactive';
4
3
  // import { addEventListener, removeEventListener } from '../dom';
5
4
 
6
5
  // /**
@@ -143,7 +142,7 @@
143
142
  // });
144
143
 
145
144
  // return (
146
- // <div ref={ref} class={['pulldown', thisProps.class].join(' ')} {...restProps}>
145
+ // <div ref={ref} class={combineClass('pulldown', thisProps.class)} {...restProps}>
147
146
  // {props.children ||
148
147
  // pulldownChildren ||
149
148
  // (pulldownChildren = (
@@ -0,0 +1,8 @@
1
+ import { JSX } from '../jsx';
2
+ import { combineClass, splitProps } from '../reactive';
3
+
4
+ export const TextBox = (props?: JSX.SvgSVGAttributes<never>) => {
5
+ let [thisProps, restProps] = splitProps(props, ['class']);
6
+
7
+ return <input type="text" class={combineClass('textbox', thisProps.class)} {...restProps}></input>;
8
+ };
package/src/dom.ts CHANGED
@@ -44,50 +44,52 @@ if (isBrowser) {
44
44
 
45
45
  root.addEventListener('wheel', disableGlobalScroll, { passive: false });
46
46
  root.addEventListener('touchmove', disableGlobalScroll, { passive: false });
47
- }
48
47
 
49
- /**
50
- * 显示遮罩层
51
- */
52
- export const showMaskLayer = (onclick?: () => void) => {
53
- if (isBrowser) {
54
- if (!maskLayers.length) {
55
- if (!maskLayer) {
56
- let div = (maskLayer = document.createElement('div'));
48
+ document.addEventListener('DOMContentLoaded', () => {
49
+ let div = (maskLayer = document.createElement('div'));
57
50
 
58
- div.className = 'mask-layer';
59
- div.style.cssText =
60
- 'position:fixed;top:0;left:0;right:0;bottom:0;display:none;background:rgba(0,0,0,0.4);z-index:8';
51
+ div.className = 'mask-layer';
52
+ div.style.cssText =
53
+ 'position:fixed;top:0;left:0;right:0;bottom:0;display:none;background:rgba(0,0,0,0.4);z-index:8';
61
54
 
62
- // 点击遮罩层方法
63
- div.addEventListener('click', (event) => {
64
- let callbackFn = maskLayers[maskLayers.length - 1];
55
+ // 点击遮罩层方法
56
+ div.addEventListener('click', (event) => {
57
+ let callbackFn = maskLayers[maskLayers.length - 1];
65
58
 
66
- callbackFn && callbackFn(event);
67
- });
59
+ callbackFn && callbackFn(event);
60
+ });
68
61
 
69
- document.body.appendChild(div);
70
- }
62
+ document.body.appendChild(div);
63
+ });
64
+ }
71
65
 
66
+ /**
67
+ * 空函数
68
+ */
69
+ const noop = () => {};
70
+
71
+ /**
72
+ * 显示遮罩层
73
+ */
74
+ export const showMaskLayer = isBrowser
75
+ ? (onclick?: () => void) => {
72
76
  maskLayer.style.display = 'block';
77
+ maskLayers.push(onclick);
73
78
  }
74
-
75
- maskLayers.push(onclick);
76
- }
77
- };
79
+ : noop;
78
80
 
79
81
  /**
80
82
  * 隐藏遮罩层
81
83
  */
82
- export const hideMaskLayer = () => {
83
- if (isBrowser) {
84
- maskLayers.pop();
84
+ export const hideMaskLayer = isBrowser
85
+ ? () => {
86
+ maskLayers.pop();
85
87
 
86
- if (!maskLayers.length) {
87
- maskLayer.style.display = 'none';
88
+ if (!maskLayers.length) {
89
+ maskLayer.style.display = 'none';
90
+ }
88
91
  }
89
- }
90
- };
92
+ : noop;
91
93
 
92
94
  const autocloseList = [];
93
95
 
@@ -117,21 +119,35 @@ export const unregisterAutoClose = (onclose: () => void) => {
117
119
  };
118
120
 
119
121
  /**
120
- * 侦听自动关闭事件(一般设置到 solid-js 的 App 节点上,且只能侦听一次)
122
+ * 是否禁止自动关闭
121
123
  */
122
- export const listenAutoCloseEvent = {
123
- onpointerdown: () => {
124
- for (let i = 0, l = autocloseList.length; i < l; i++) {
125
- autocloseList[i]();
124
+ let disableAutoClose = false;
125
+
126
+ /**
127
+ * 开始侦听自动关闭事件
128
+ */
129
+ export const startAutoCloseEvent = isBrowser
130
+ ? () => {
131
+ // 延迟注册解决 solidjs 事件顺序问题
132
+ setTimeout(() => {
133
+ document.addEventListener('pointerdown', () => {
134
+ if (disableAutoClose) {
135
+ disableAutoClose = false;
136
+ } else {
137
+ for (let i = 0, l = autocloseList.length; i < l; i++) {
138
+ autocloseList[i]();
139
+ }
140
+ }
141
+ });
142
+ });
126
143
  }
127
- },
128
- };
144
+ : noop;
129
145
 
130
146
  /**
131
147
  * 禁止自动关闭事件
132
148
  */
133
149
  export const disableAutoCloseEvent = {
134
- onpointerdown: (event: Event) => {
135
- event.stopPropagation();
150
+ onpointerdown: () => {
151
+ disableAutoClose = true;
136
152
  },
137
153
  };
package/src/http.ts CHANGED
@@ -10,7 +10,7 @@ export interface ResponseResult {
10
10
  /**
11
11
  * 自定义响应结果(type === 'custom' 时有效)
12
12
  */
13
- custom?: Promise<any>;
13
+ custom?: any;
14
14
  }
15
15
 
16
16
  /**
@@ -25,7 +25,7 @@ export const httpInterceptor = {
25
25
  /**
26
26
  * 响应拦截(返回 Promise true 表示需要重新发送请求,返回 Promise false 抛出默认异常)
27
27
  */
28
- response: (() => {}) as (response: Response, options?: RequestInit) => ResponseResult | void,
28
+ response: (() => {}) as (response: Response, options?: RequestInit) => Promise<ResponseResult> | void,
29
29
  };
30
30
 
31
31
  /**
@@ -73,30 +73,34 @@ Object.defineProperty(Promise.prototype, 'data', {
73
73
  },
74
74
  });
75
75
 
76
+ const respondDefault = (response: Response, json: boolean) => {
77
+ // 失败返回异常
78
+ return response.ok
79
+ ? json
80
+ ? response.json()
81
+ : response
82
+ : Promise.reject(response.status + ' ' + response.statusText);
83
+ };
84
+
76
85
  const handleResponse = (response: Response, url: string, json: boolean, options?: RequestInit) => {
77
86
  // 响应拦截
78
87
  let result = httpInterceptor.response(response, options);
79
88
 
80
89
  // 返回了异步对象
81
- if (result) {
82
- switch (result.type) {
83
- case 'custom':
84
- if (result.custom) {
85
- return result.custom;
86
- }
87
- break;
90
+ return result
91
+ ? result.then((result) => {
92
+ switch (result.type) {
93
+ case 'custom':
94
+ return result.custom || respondDefault(response, json);
88
95
 
89
- default:
90
- return sendInternal(url, json, options);
91
- }
92
- }
96
+ case 'retry':
97
+ return sendInternal(url, json, options);
93
98
 
94
- // 失败返回异常
95
- return response.ok
96
- ? json
97
- ? response.json()
98
- : response
99
- : Promise.reject(response.status + ' ' + response.statusText);
99
+ default:
100
+ return respondDefault(response, json);
101
+ }
102
+ })
103
+ : respondDefault(response, json);
100
104
  };
101
105
 
102
106
  /**
@@ -13,9 +13,9 @@
13
13
  },
14
14
  "Form": {
15
15
  "Required": "不能为空",
16
- "NotLessThan": "不能小于 ${min}",
17
- "NotGreaterThan": "不能大于 ${max}",
16
+ "NotLessThan": "不能小于 ${value}",
17
+ "NotGreaterThan": "不能大于 ${value}",
18
18
  "Between": "必须在 ${min} -> ${max} 之间",
19
- "NotDate": "${label}: \"${value}\" 不是一个有效的日期值"
19
+ "NotDate": "\"${value}\" 不是一个有效的日期值"
20
20
  }
21
21
  }
package/src/index.ts CHANGED
@@ -13,8 +13,12 @@ export * from './components/If';
13
13
  export * from './components/Switch';
14
14
  export * from './components/For';
15
15
  export * from './components/Icon';
16
- export * from './components/CollapsiblePanel';
16
+ export * from './components/TextBox';
17
+ export * from './components/Button';
17
18
  export * from './components/ComboBox';
19
+ export * from './components/Canlendar';
20
+ export * from './components/DatePicker';
21
+ export * from './components/CollapsiblePanel';
18
22
  export * from './components/Carousel';
19
23
  export * from './components/KeepAlive';
20
24
  export * from './components/Dialog';
package/src/reactive.ts CHANGED
@@ -299,6 +299,15 @@ export const toRaw = <T>(proxy: T): T => {
299
299
  return (proxy && proxy.__raw__) || proxy;
300
300
  };
301
301
 
302
+ /**
303
+ * 合并 class
304
+ *
305
+ * @param classList class 列表
306
+ */
307
+ export const combineClass = (...classList: (string | null | void)[]) => {
308
+ return classList.filter((item) => item).join(' ');
309
+ };
310
+
302
311
  /**
303
312
  * 观测响应式属性变化
304
313
  *
package/demo/App.tsx DELETED
@@ -1,31 +0,0 @@
1
- import { reactive } from '../src/reactive';
2
- import { CanlendarPage } from './pages/Canlendar';
3
- import { ComboBoxPage } from './pages/ComboBox';
4
- import { FormPage } from './pages/Form';
5
-
6
- const Test = (props: { value: number; text: string }) => {
7
- return (
8
- <div>
9
- <span>{props.value}</span>
10
- <span>{props.text}</span>
11
- </div>
12
- );
13
- };
14
-
15
- export const App = () => {
16
- const state = reactive({
17
- value: 111,
18
- });
19
-
20
- const a = { text: '222' };
21
-
22
- return (
23
- <div>
24
- <CanlendarPage></CanlendarPage>
25
- <ComboBoxPage></ComboBoxPage>
26
- <FormPage></FormPage>
27
- <Test value={state.value} text={'222' + a.text}></Test>
28
- <button onclick={() => (state.value = Math.random())}></button>
29
- </div>
30
- );
31
- };
package/demo/main.ts DELETED
@@ -1,7 +0,0 @@
1
- import '../css/all.css';
2
-
3
- import { render } from '../src';
4
- import { App } from './App';
5
-
6
- // 客户端渲染
7
- render(App, document.body);
@@ -1,10 +0,0 @@
1
- import { ComboBox } from '../../src/components/ComboBox';
2
-
3
- export const ComboBoxPage = () => {
4
- return (
5
- <ComboBox>
6
- <div>111</div>
7
- <div>222</div>
8
- </ComboBox>
9
- );
10
- };
@@ -1,32 +0,0 @@
1
- import { reactive } from '../../src/reactive';
2
- import { For } from '../../src/components/For';
3
- import { Form, FormItem } from '../../src/components/Form';
4
-
5
- export const FormPage = () => {
6
- const state = reactive({
7
- align: 'right' as 'right' | 'left',
8
- width: '100px',
9
- });
10
-
11
- return (
12
- <Form data={{}} rules={{}} align={state.align} labelWidth={state.width}>
13
- <FormItem field="a" label="111">
14
- <input></input>
15
- </FormItem>
16
- <FormItem
17
- field="b"
18
- label={
19
- <>
20
- <span>123</span>
21
- <span>456</span>
22
- </>
23
- }
24
- >
25
- <input></input>
26
- </FormItem>
27
- <button type="button" onclick={() => (state.align = state.align === 'left' ? 'right' : 'left')}>
28
- align
29
- </button>
30
- </Form>
31
- );
32
- };
package/icons/build.ts DELETED
@@ -1,24 +0,0 @@
1
- import path from 'path';
2
- import { fileURLToPath } from 'url';
3
-
4
- import { loadIconFile, loadIconsDirectory, saveIconsModule, saveIconsToHtml } from 'cdui-js/build/icons';
5
-
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = path.dirname(__filename);
8
-
9
- const icons = [
10
- // 加载 cdui-js/icons 下的图标
11
- loadIconsDirectory(path.join(__dirname, '../../../node_modules/cdui-js/icons'), 'both'),
12
-
13
- loadIconFile(path.join(__dirname, 'logo.svg')),
14
- loadIconsDirectory(path.join(__dirname, 'common/both')),
15
- loadIconsDirectory(path.join(__dirname, 'common/stroke'), 'stroke'),
16
- loadIconsDirectory(path.join(__dirname, 'header'), 'stroke'),
17
-
18
- loadIconsDirectory(path.join(__dirname, 'menu'), 'both'),
19
- loadIconsDirectory(path.join(__dirname, 'menu/color')),
20
-
21
- loadIconsDirectory(path.join(__dirname, 'my'), 'both'),
22
- ];
23
-
24
- saveIconsToHtml(path.join(__dirname, '../index.html'), icons.join('\n'));
File without changes