cdui-js 1.0.22 → 1.0.24

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.
@@ -1,8 +1,29 @@
1
1
  import { JSX } from '../jsx';
2
- import { createSignal, combineClass, omitProps } from '../reactive';
3
- import { Canleandar as i18n } from '../i18n';
2
+ import { createSignal, combineClass, omitProps, render } from '../reactive';
3
+ import { Canlendar as i18n } from '../i18n';
4
4
  import { replaceTemplate } from '../template';
5
+
5
6
  import { For } from './For';
7
+ import { MonthWidget } from './MonthWidget';
8
+
9
+ export const parseDate = (value: Date | string | number) => {
10
+ if (value) {
11
+ switch (typeof value) {
12
+ case 'number':
13
+ return new Date(value);
14
+
15
+ case 'string':
16
+ return new Date(value.replace(/\//g, '-'));
17
+
18
+ default:
19
+ return value;
20
+ }
21
+ }
22
+ };
23
+
24
+ export const getYearMonth = (date: Date) => {
25
+ return [date.getFullYear(), date.getMonth() + 1] as const;
26
+ };
6
27
 
7
28
  // 渲染日期项
8
29
  const renderDates = (
@@ -16,9 +37,9 @@ const renderDates = (
16
37
  selectedValue?: Date,
17
38
  disableFn?: (year: number, month: number, date: number) => boolean,
18
39
  ) => {
19
- let today = todayValue.getFullYear() === year && todayValue.getMonth() === month ? todayValue.getDate() : -1;
40
+ let today = todayValue.getFullYear() === year && todayValue.getMonth() + 1 === month ? todayValue.getDate() : -1;
20
41
  let selected =
21
- selectedValue && selectedValue.getFullYear() === year && selectedValue.getMonth() === month
42
+ selectedValue && selectedValue.getFullYear() === year && selectedValue.getMonth() + 1 === month
22
43
  ? selectedValue.getDate()
23
44
  : -1;
24
45
 
@@ -26,7 +47,7 @@ const renderDates = (
26
47
  items.push(
27
48
  `<span class="datewidget-item${className}${today === i ? ' today' : ''}${selected === i ? ' selected' : ''}${
28
49
  disableFn && disableFn(year, month, i) ? ' disabled' : ''
29
- }" data-date="${year + '|' + month + '|' + i}">${i}</span>`,
50
+ }" data-day="${year + '|' + month + '|' + i}">${i}</span>`,
30
51
  );
31
52
  }
32
53
  };
@@ -74,14 +95,14 @@ const renderNextMonthItems = (
74
95
  };
75
96
 
76
97
  const renderItems = (
77
- currentValue: Date,
98
+ currentValue: readonly [year: number, month: number],
78
99
  selectedValue?: Date,
79
100
  disableFn?: (year: number, month: number, date: number) => boolean,
80
101
  ) => {
81
102
  let today = new Date();
82
- let year = currentValue.getFullYear();
83
- let month = currentValue.getMonth();
84
- let firstDate = new Date(year, month, 1); // 获取当前月的第一天
103
+ let year = currentValue[0];
104
+ let month = currentValue[1];
105
+ let firstDate = new Date(year, month - 1, 1); // 获取当前月的第一天
85
106
 
86
107
  let firstWeek = firstDate.getDay();
87
108
  let items = [];
@@ -95,55 +116,69 @@ const renderItems = (
95
116
  }
96
117
 
97
118
  // 获取当前月的天数
98
- days = new Date(year, month + 1, 0).getDate();
119
+ days = new Date(year, month, 0).getDate();
120
+
99
121
  // 渲染本月日期
100
122
  renderDates(items, year, month, 1, days, '', today, selectedValue, disableFn);
101
123
 
102
124
  // 当前月最后一天没有占满,渲染下月数据
103
125
  if ((index += days) < 42) {
104
- renderNextMonthItems(items, year, month, 42 - index, today, selectedValue, disableFn);
126
+ renderNextMonthItems(items, year, month + 1, 42 - index, today, selectedValue, disableFn);
105
127
  }
106
128
 
107
129
  return items.join('');
108
130
  };
109
131
 
110
- const switchMonth = (value: Date, offset: 1 | -1) => {
111
- let date = new Date(value.getTime());
112
- let month = value.getMonth();
132
+ const switchMonth = (value: readonly [year: number, month: number], offset: 1 | -1) => {
133
+ let year = value[0];
134
+ let month = value[1] + offset;
113
135
 
114
- date.setMonth(month + offset);
136
+ if (month > 12) {
137
+ year++;
138
+ month = 1;
139
+ } else if (!month) {
140
+ year--;
141
+ month = 12;
142
+ }
115
143
 
116
- return date;
144
+ return [year, month] as const;
117
145
  };
118
146
 
119
- export const parseDate = (value: Date | string | number) => {
120
- if (value) {
121
- switch (typeof value) {
122
- case 'number':
123
- return new Date(value);
147
+ const formatMonth = (month: number) => {
148
+ return month > 9 ? month : '0' + month;
149
+ };
124
150
 
125
- case 'string':
126
- return new Date(value.replace(/\//g, '-'));
151
+ const showMonthWidget = (dom: HTMLElement, setCurrentValue: (value) => void, selectedValue?: Date) => {
152
+ let value =
153
+ selectedValue && ([selectedValue.getFullYear(), selectedValue.getMonth() + 1] as [year: number, month: number]);
127
154
 
128
- default:
129
- return value;
130
- }
131
- }
132
- };
155
+ render(
156
+ () => (
157
+ <MonthWidget
158
+ value={value}
159
+ style={{ position: 'absolute', top: '0', left: '0', width: '100%', border: 'none' }}
160
+ onchange={(event) => {
161
+ let result = event.detail;
133
162
 
134
- const formatMonth = (value: Date) => {
135
- let month = value.getMonth() + 1;
163
+ dom.removeChild(event.target as HTMLElement);
136
164
 
137
- return month > 9 ? month : '0' + month;
165
+ if (!selectedValue || result[0] !== value[0] || result[1] !== value[1]) {
166
+ setCurrentValue(result);
167
+ }
168
+ }}
169
+ ></MonthWidget>
170
+ ),
171
+ dom,
172
+ );
138
173
  };
139
174
 
140
- const OMIT_PROPS = ['class', 'value', 'onValueChange', 'disableFn'] as const;
175
+ const OMIT_PROPS = ['class', 'value', 'disableFn'] as const;
141
176
 
142
177
  /**
143
178
  * 日历组件
144
179
  */
145
180
  export const Canlendar = (
146
- props: Omit<JSX.HTMLAttributes<never>, 'children'> & {
181
+ props: Omit<JSX.HTMLAttributes<never>, 'children' | 'onchange'> & {
147
182
  /**
148
183
  * 日期值
149
184
  */
@@ -151,24 +186,27 @@ export const Canlendar = (
151
186
  /**
152
187
  * 值变更事件
153
188
  */
154
- onValueChange?: (value: Date) => void;
189
+ onchange?: (event: CustomEvent<Date>) => void;
155
190
  /**
156
191
  * 禁用函数
157
192
  */
158
193
  disableFn?: (year: number, month: number, date: number) => boolean;
159
194
  },
160
195
  ) => {
161
- let domTitle: HTMLElement;
162
- let domBody: HTMLElement;
196
+ let dom: HTMLElement;
163
197
 
164
198
  const [selectedValue, setSelectedValue] = createSignal(parseDate(props.value));
165
- const [currentValue, setCurrentValue] = createSignal(selectedValue() || new Date());
199
+ const [currentValue, setCurrentValue] = createSignal(getYearMonth(selectedValue() || new Date()));
166
200
 
167
201
  return (
168
- <div class={combineClass('canlendar datewidget', props.class)} {...omitProps(props, OMIT_PROPS)}>
202
+ <div
203
+ ref={dom as any}
204
+ class={combineClass('canlendar datewidget', props.class)}
205
+ {...(omitProps(props, OMIT_PROPS) as any)}
206
+ >
169
207
  <div class="datewidget-header canlendar-header">
170
- <div ref={domTitle as any} class="datewidget-title">
171
- {replaceTemplate(i18n.Title, currentValue().getFullYear(), formatMonth(currentValue()))}
208
+ <div class="datewidget-title" onclick={() => showMonthWidget(dom, setCurrentValue, selectedValue())}>
209
+ {replaceTemplate(i18n.Title, currentValue()[0], formatMonth(currentValue()[1]))}
172
210
  </div>
173
211
  <svg class="icon icon-s" aria-hidden={true} onclick={() => setCurrentValue(switchMonth(currentValue(), -1))}>
174
212
  <use href="#icon-backward"></use>
@@ -181,21 +219,26 @@ export const Canlendar = (
181
219
  <For each={i18n.Weeks}>{(item) => <span>{item}</span>}</For>
182
220
  </div>
183
221
  <div
184
- ref={domBody as any}
185
222
  class="datewidget-body canlendar-body"
186
223
  onclick={(event) => {
187
224
  let target = event.target as HTMLElement;
188
- let date;
225
+ let day;
189
226
 
190
- while (target && target !== domBody) {
191
- if ((date = target.dataset.date)) {
227
+ while (target && target !== dom) {
228
+ if ((day = target.dataset.day)) {
192
229
  // 没有选中
193
230
  if (!target.classList.contains('selected')) {
194
- date = date.split('|');
195
- date = new Date(date[0] | 0, date[1] | 0, date[2] | 0);
231
+ day = day.split('|');
232
+ day = new Date(day[0] | 0, (day[1] | 0) - 1, day[2] | 0);
233
+
234
+ setSelectedValue(day);
196
235
 
197
- setSelectedValue(date);
198
- props.onValueChange && props.onValueChange(date);
236
+ dom.dispatchEvent(
237
+ new CustomEvent('change', {
238
+ detail: day,
239
+ bubbles: true, // 允许事件冒泡
240
+ }),
241
+ );
199
242
  }
200
243
 
201
244
  break;
@@ -1,48 +1,49 @@
1
1
  import { JSX } from '../jsx';
2
2
  import { combineClass, omitProps, useContext, watch } from '../reactive';
3
3
  import { disableAutoCloseEvent } from '../dom';
4
- import { Popup, PopupApi, PopupProps } from './Popup';
4
+ import { PopupApi, initDropdown } from '../popup';
5
+
5
6
  import { FormItemContext } from './provider';
6
7
 
7
- const OMIT_PROPS = ['class', 'value', 'readonly', 'popupOnFocus', 'popup', 'onPopup', 'api', 'children'] as const;
8
+ const OMIT_PROPS = ['class', 'value', 'format', /*'readonly', */ 'popup', 'popupOnFocus', 'api'] as const;
8
9
 
9
10
  /**
10
11
  * 下拉框组件
11
12
  */
12
13
  export const ComboBox = (
13
- props?: JSX.HTMLAttributes<never> &
14
- PopupProps & {
15
- /**
16
- * 值
17
- */
18
- value?: any;
19
- /**
20
- * 值样式
21
- *
22
- * @param value 当前值
23
- */
24
- format?(value: any): string;
25
- /**
26
- * 是否只读
27
- */
28
- readonly?: boolean;
29
- /**
30
- * 获取焦点时是否自动弹出
31
- */
32
- popupOnFocus?: boolean;
33
- /**
34
- * 弹出层属性
35
- */
36
- popup?: Omit<JSX.HTMLAttributes<never>, 'children'>;
37
- },
14
+ props?: Omit<JSX.HTMLAttributes<never>, 'children'> & {
15
+ /**
16
+ * 弹出层
17
+ */
18
+ popup: () => JSX.Element;
19
+ /**
20
+ * 值
21
+ */
22
+ value?: any;
23
+ /**
24
+ * 值样式
25
+ *
26
+ * @param value 当前值
27
+ */
28
+ format?(value: any): string;
29
+ // /**
30
+ // * 是否只读
31
+ // */
32
+ // readonly?: boolean;
33
+ /**
34
+ * 获取焦点时是否自动弹出
35
+ */
36
+ popupOnFocus?: boolean;
37
+ /**
38
+ * 值变更事件
39
+ */
40
+ onchange?: (event: CustomEvent<Date>) => void;
41
+ /**
42
+ * 外部调用接口
43
+ */
44
+ api: (api: PopupApi) => void;
45
+ },
38
46
  ) => {
39
- let popup: PopupApi;
40
-
41
- const initApi = (api) => {
42
- popup = api;
43
- props.api && props.api(popup);
44
- };
45
-
46
47
  const formItem = useContext(FormItemContext);
47
48
 
48
49
  const initFormItem = (dom: HTMLInputElement) => {
@@ -54,24 +55,29 @@ export const ComboBox = (
54
55
  formItem.init(dom);
55
56
  };
56
57
 
58
+ let api: PopupApi;
59
+
57
60
  return (
58
- <div class={combineClass('combobox', props.class)} {...omitProps(props, OMIT_PROPS)}>
59
- <div class="combobox-host" {...disableAutoCloseEvent}>
60
- <input
61
- ref={formItem && initFormItem}
62
- class="combobox-input"
63
- value={props.format ? props.format(props.value) : '' + (props.value || '')}
64
- readonly={props.readonly}
65
- onfocus={() => props.popupOnFocus && popup.openPopup()}
66
- onclick={() => props.readonly && !props.popupOnFocus && popup.togglePopup()}
67
- ></input>
68
- <svg class="icon icon-s" aria-hidden={true} onclick={() => popup.togglePopup()}>
69
- <use href="#icon-dropdown"></use>
70
- </svg>
71
- </div>
72
- <Popup api={initApi} onPopup={props.onPopup} {...props.popup}>
73
- {props.children}
74
- </Popup>
61
+ <div
62
+ ref={(dom) => {
63
+ api = initDropdown(dom, () => <div class="combobox-popup">{props.popup()}</div>);
64
+ props.api && props.api(api);
65
+ }}
66
+ class={combineClass('combobox', props.class)}
67
+ {...disableAutoCloseEvent}
68
+ {...omitProps(props, OMIT_PROPS)}
69
+ >
70
+ <input
71
+ ref={formItem && initFormItem}
72
+ class="combobox-input"
73
+ value={props.format ? props.format(props.value) : '' + (props.value || '')}
74
+ readonly={true}
75
+ onfocus={() => props.popupOnFocus && api.openPopup()}
76
+ onclick={() => !props.popupOnFocus && api.togglePopup()}
77
+ ></input>
78
+ <svg class="combobox-icon icon icon-s" aria-hidden={true} onclick={() => api.togglePopup()}>
79
+ <use href="#icon-dropdown"></use>
80
+ </svg>
75
81
  </div>
76
82
  );
77
83
  };
@@ -1,58 +1,94 @@
1
1
  import { JSX } from '../jsx';
2
- import { combineClass, createSignal, omitProps } from '../reactive';
2
+ import { combineClass, createSignal, omitProps, render, useContext, watch } from '../reactive';
3
3
  import { disableAutoCloseEvent } from '../dom';
4
- import { For } from './For';
5
- import { Popup, PopupApi } from './Popup';
6
- import { parseDate } from './Canlendar';
4
+ import { initDropdown, PopupApi } from '../popup';
7
5
 
8
- const formatDate = (date: Date, format: string) => {
9
- return date ? date.toLocaleString() : '';
10
- };
6
+ import { Canlendar, parseDate } from './Canlendar';
7
+ import { FormItemContext } from './provider';
11
8
 
12
- const showPopup = () => {};
9
+ const Date_regex = /[YyMDdHhmsSQq]+/g;
10
+ const Date_zeros = ['', '0', '00', '000', '0000'];
13
11
 
14
- const MobileTouchScroll = (props: { items: (number | string)[] }) => {
15
- return (
16
- <div>
17
- <For each={props.items}>{(item) => <div>{item}</div>}</For>
18
- </div>
19
- );
20
- };
12
+ const DATE_GET_YEAR = 'getFullYear';
13
+ const DATE_GET_MONTH = 'getMonth';
14
+ const DATE_GET_DATE = 'getDate';
15
+ const DATE_GET_HOUR = 'getHours';
16
+ const DATE_GET_MINUTE = 'getMinutes';
17
+ const DATE_GET_SECOND = 'getSeconds';
18
+
19
+ export const formatDate = (date: Date, format: string) => {
20
+ if (date) {
21
+ switch (format || (format = 'yyyy-MM-dd')) {
22
+ case 'GMT':
23
+ case 'ISO':
24
+ case 'UTC':
25
+ case 'Date':
26
+ case 'Time':
27
+ case 'Locale':
28
+ case 'LocaleDate':
29
+ case 'LocaleTime':
30
+ return (date as unknown as { [key: string]: Function })['to' + format + 'String']();
31
+
32
+ default:
33
+ return format.replace(Date_regex, (text) => {
34
+ let length = text.length;
35
+ let value;
36
+
37
+ switch (text[0]) {
38
+ case 'y':
39
+ case 'Y':
40
+ value = date[DATE_GET_YEAR]();
41
+ break;
42
+
43
+ case 'M':
44
+ value = date[DATE_GET_MONTH]() + 1;
45
+ break;
46
+
47
+ case 'd':
48
+ value = date[DATE_GET_DATE]();
49
+ break;
50
+
51
+ case 'H':
52
+ case 'h':
53
+ value = date[DATE_GET_HOUR]();
54
+ break;
55
+
56
+ case 'm':
57
+ value = date[DATE_GET_MINUTE]();
58
+ break;
59
+
60
+ case 's':
61
+ value = date[DATE_GET_SECOND]();
62
+ break;
21
63
 
22
- const formatMonth = (value: Date) => {
23
- let month = value.getMonth() + 1;
64
+ case 'S':
65
+ value = date.getMilliseconds();
66
+ break;
24
67
 
25
- return month > 9 ? month : '0' + month;
68
+ case 'Q':
69
+ case 'q':
70
+ value = ((date[DATE_GET_MONTH]() + 3) / 3) | 0;
71
+ break;
72
+ }
73
+
74
+ text = '' + value;
75
+ length -= text.length;
76
+
77
+ return length <= 0 ? text : Date_zeros[length] + text;
78
+ });
79
+ }
80
+ }
81
+
82
+ return '';
26
83
  };
27
84
 
28
- // const computeMobileList = (value: Date) => {
29
- // let year = value.getFullYear();
30
- // let month = value.getMonth() + 1;
31
- // let date = value.getDate();
32
-
33
- // return [
34
- // year > 5 ? [year - 2, year - 1, year, year + 1, year + 2]: [1,2,3,4,5],
35
- // [formatMonth(month - 2), ]
36
- // ];
37
- // };
38
-
39
- // const MobileDatePicker = (yearList: number[], monthList: string[], dateList: string[]) => {
40
- // return (
41
- // <div>
42
- // <MobileTouchScroll items={yearList}></MobileTouchScroll>
43
- // <MobileTouchScroll items={monthList}></MobileTouchScroll>
44
- // <MobileTouchScroll items={dateList}></MobileTouchScroll>
45
- // </div>
46
- // );
47
- // };
48
-
49
- const OMIT_PROPS = ['class', 'value', 'readonly', 'format', 'children'] as const;
85
+ const OMIT_PROPS = ['class', 'value', 'format', /*'readonly',*/ 'popupOnFocus', 'disableFn', 'api'] as const;
50
86
 
51
87
  /**
52
88
  * 日期选择组件
53
89
  */
54
90
  export const DatePicker = (
55
- props?: JSX.HTMLAttributes<never> & {
91
+ props?: Omit<JSX.HTMLAttributes<never>, 'children' | 'onchange'> & {
56
92
  /**
57
93
  * 值
58
94
  */
@@ -61,32 +97,82 @@ export const DatePicker = (
61
97
  * 日期格式
62
98
  */
63
99
  format?: string;
100
+ // /**
101
+ // * 是否只读
102
+ // */
103
+ // readonly?: boolean;
104
+ /**
105
+ * 获取焦点时是否自动弹出
106
+ */
107
+ popupOnFocus?: boolean;
108
+ /**
109
+ * 值变更事件
110
+ */
111
+ onchange?: (event: CustomEvent<Date>) => void;
64
112
  /**
65
- * 是否只读
113
+ * 禁用函数
66
114
  */
67
- readonly?: boolean;
115
+ disableFn?: (year: number, month: number, date: number) => boolean;
116
+ /**
117
+ * 外部调用接口
118
+ */
119
+ api?: (api: PopupApi) => void;
68
120
  },
69
121
  ) => {
70
- let popup: PopupApi;
122
+ let api: PopupApi;
123
+
124
+ const formItem = useContext(FormItemContext);
71
125
 
72
- const [value, setValue] = createSignal(props.value && parseDate(props.value));
126
+ const initFormItem = (dom: HTMLElement) => {
127
+ watch(
128
+ () => props.value,
129
+ (value) => formItem.setValue(value),
130
+ );
131
+
132
+ formItem.init(dom);
133
+ };
73
134
 
74
135
  return (
75
- <div class={combineClass('datepicker', props.class)} {...omitProps(props, OMIT_PROPS)}>
76
- <div class="datepicker-host" {...disableAutoCloseEvent}>
77
- <input
78
- class="datepicker-input"
79
- value={formatDate(value(), props.format)}
80
- readonly={props.readonly}
81
- onclick={() => props.readonly && popup.togglePopup()}
82
- ></input>
83
- <svg class="icon icon-s" aria-hidden={true} onclick={() => popup.togglePopup()}>
84
- <use href="#icon-dropdown"></use>
85
- </svg>
86
- </div>
87
- <Popup api={(api) => (popup = api)} onPopup={() => showPopup()}>
88
- {props.children}
89
- </Popup>
136
+ <div
137
+ ref={(dom: HTMLElement) => {
138
+ api = initDropdown(
139
+ dom,
140
+ () => (
141
+ <Canlendar
142
+ value={props.value}
143
+ onchange={(event) => {
144
+ dom.dispatchEvent(
145
+ new CustomEvent('change', {
146
+ detail: event.detail,
147
+ bubbles: true, // 允许事件冒泡
148
+ }),
149
+ );
150
+
151
+ api.closePopup();
152
+ }}
153
+ disableFn={props.disableFn}
154
+ ></Canlendar>
155
+ ),
156
+ { alignWidth: false },
157
+ );
158
+
159
+ formItem && initFormItem(dom);
160
+ props.api && props.api(api);
161
+ }}
162
+ class={combineClass('datepicker', props.class)}
163
+ {...disableAutoCloseEvent}
164
+ {...(omitProps(props, OMIT_PROPS) as any)}
165
+ >
166
+ <input
167
+ class="datepicker-input"
168
+ value={formatDate(parseDate(props.value), props.format)}
169
+ readonly={true}
170
+ onfocus={() => props.popupOnFocus && api.openPopup()}
171
+ onclick={() => !props.popupOnFocus && api.togglePopup()}
172
+ ></input>
173
+ <svg class="datepicker-icon icon icon-s" aria-hidden={true} onclick={() => api.togglePopup()}>
174
+ <use href="#icon-dropdown"></use>
175
+ </svg>
90
176
  </div>
91
177
  );
92
178
  };
@@ -466,6 +466,7 @@ const validate = async (
466
466
 
467
467
  if (error) {
468
468
  showError(child, error);
469
+ result = false;
469
470
  } else {
470
471
  removeError(child);
471
472
  }
@@ -11,7 +11,7 @@ export const MonthPicker = (
11
11
  /**
12
12
  * 值变更事件
13
13
  */
14
- onValueChange?: (value: Date) => void;
14
+ onchange?: (event: CustomEvent) => void;
15
15
  /**
16
16
  * 禁用函数
17
17
  */