cdui-js 1.0.19 → 1.0.21

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/css/all.css CHANGED
@@ -8,7 +8,7 @@
8
8
  @import url(textbox.css);
9
9
  @import url(popup.css);
10
10
  @import url(combobox.css);
11
- @import url(canlendar.css);
11
+ @import url(datewidget.css);
12
12
  @import url(datepicker.css);
13
13
  @import url(mobile-datepicker.css);
14
14
  @import url(form.css);
@@ -1,24 +1,20 @@
1
- .canlendar,
2
- .monthwidget {
1
+ .datewidget {
3
2
  max-width: 380px;
4
3
  height: 320px;
5
4
  border-radius: 12px;
6
5
  padding: 16px;
7
- font-size: 12px;
8
6
  user-select: none;
7
+ font-size: 16px;
9
8
  }
10
9
 
11
- .canlendar-header,
12
- .canlendar-weeks,
13
- .monthwidget-header {
10
+ .datewidget-header {
14
11
  display: flex;
15
12
  align-items: center;
16
13
  height: 36px;
17
14
  line-height: 36px;
18
15
  }
19
16
 
20
- .canlendar-title,
21
- .monthwidget-title {
17
+ .datewidget-title {
22
18
  flex: auto;
23
19
  padding-left: 12px;
24
20
  font-weight: bold;
@@ -26,8 +22,7 @@
26
22
  cursor: pointer;
27
23
  }
28
24
 
29
- .canlendar-header > .icon,
30
- .monthwidget-header > .icon {
25
+ .datewidget-header > .icon {
31
26
  box-sizing: border-box;
32
27
  width: 40px;
33
28
  height: 100%;
@@ -35,46 +30,56 @@
35
30
  cursor: pointer;
36
31
  }
37
32
 
38
- .canlendar-weeks > span {
39
- flex: auto;
40
- text-align: center;
41
- vertical-align: middle;
33
+ .datewidget-body {
34
+ height: calc(100% - 36px);
42
35
  }
43
36
 
44
- .canlendar-body {
45
- height: calc(100% - 72px);
46
- }
47
-
48
- .canlendar-date,
49
- .monthwidget-month {
37
+ .datewidget-item {
50
38
  display: inline-flex;
51
39
  justify-content: center;
52
40
  align-items: center;
53
- width: 14.28%;
54
- height: 16.66%;
41
+ width: 25%;
42
+ height: 25%;
55
43
  cursor: pointer;
56
44
  }
57
45
 
58
- .canlendar-date.disabled,
59
- .monthwidget-month.disabled {
46
+ .datewidget-item.disabled {
60
47
  cursor: not-allowed;
61
48
  }
62
49
 
63
- .canlendar-date.today,
64
- .canlendar-date.selected,
65
- .monthwidget-month.today,
66
- .monthwidget-month.selected {
50
+ .datewidget-item.today,
51
+ .datewidget-item.selected {
67
52
  position: relative;
68
53
  }
69
54
 
70
- .canlendar-date.today::before,
71
- .canlendar-date.selected::before,
72
- .monthwidget-month.today::before,
73
- .monthwidget-month.selected::before {
55
+ .datewidget-item.today::before,
56
+ .datewidget-item.selected::before {
74
57
  position: absolute;
75
58
  content: '';
59
+ width: 60px;
60
+ height: 60px;
61
+ border-radius: 60px;
62
+ z-index: -1;
63
+ }
64
+
65
+ .canlendar-weeks > span {
66
+ flex: auto;
67
+ text-align: center;
68
+ vertical-align: middle;
69
+ }
70
+
71
+ .canlendar-body {
72
+ height: calc(100% - 72px);
73
+ font-size: 12px;
74
+ }
75
+
76
+ .canlendar-body .datewidget-item {
77
+ width: 14.28%;
78
+ height: 16.66%;
79
+ }
80
+
81
+ .canlendar-body .datewidget-item.today::before,
82
+ .canlendar-body .datewidget-item.selected::before {
76
83
  width: 40px;
77
84
  height: 40px;
78
- border-radius: 40px;
79
- z-index: -1;
80
85
  }
package/demo/css/css.md CHANGED
@@ -287,16 +287,17 @@
287
287
  .combobox:has(.combobox-input:focus) { border: 1px solid #1b212d; }
288
288
 
289
289
 
290
- # canlendar 日历
290
+ # canlendar monthwidget yearwidget 日历 年月 年
291
+
292
+ .datewidget { border: 1px solid #e4e4e4; }
293
+ .datewidget-header > .icon { stroke: #1b212d; }
294
+ .datewidget-item.disabled { color: #e4e4e4; }
295
+ .datewidget-item.prev-block, .datewidget-item.next-block { color: #888f97; }
296
+ .datewidget-item.today::before { background: #f5f5f5; }
297
+ .datewidget-item.selected { color: white; }
298
+ .datewidget-item.selected::before { background: #ff4000; }
291
299
 
292
- .canlendar { border: 1px solid #e4e4e4; }
293
- .canlendar-header > .icon { stroke: #1b212d; }
294
300
  .canlendar-weeks > span { color: #888f97; }
295
- .canlendar-date.disabled { color: #e4e4e4; }
296
- .canlendar-date.prev-month, .canlendar-date.next-month { color: #888f97; }
297
- .canlendar-date.today::before { background: #f5f5f5; }
298
- .canlendar-date.selected { color: white; }
299
- .canlendar-date.selected::before { background: #ff4000; }
300
301
 
301
302
 
302
303
  # datepicker 日期选择
package/demo/src/App.tsx CHANGED
@@ -1,38 +1,14 @@
1
- import { batch, createEffect, omitProps, pickProps, reactive } from '../../src/reactive';
2
- import { Icon } from '../../src/components/Icon';
3
1
  import { CanlendarPage } from './pages/Canlendar';
2
+ import { CarouselPage } from './pages/Carousel';
4
3
  import { ComboBoxPage } from './pages/ComboBox';
5
4
  import { DatePickerPage } from './pages/DatePicker';
6
5
  import { FormPage } from './pages/Form';
7
6
  import { MobileDatePickerPage } from './pages/MobileDatePicker';
8
- import { TextBox } from '../../src/components/TextBox';
9
-
10
- const Test = (props: { text1: string; text2: string }) => {
11
- let dom: HTMLElement;
12
- return (
13
- <div>
14
- <div>{pickProps(props, ['text1']).text1}</div>
15
- <div>{omitProps(props, ['text1']).text2}</div>
16
- <TextBox ref={dom as any}></TextBox>
17
- </div>
18
- );
19
- };
20
7
 
21
8
  export const App = () => {
22
- let state = reactive({
23
- text1: '111',
24
- text2: '222',
25
- });
26
-
27
- createEffect(() => {
28
- console.log(state.text1, state.text2);
29
- });
30
-
31
9
  return (
32
10
  <div style={{ 'min-height': '100%' }}>
33
- <Icon name="dropdown" class="test" onclick={() => alert(1111)} style={{ background: 'red' }}></Icon>
34
- <Test text1={state.text1} text2={state.text2}></Test>
35
- <button onclick={() => batch(() => (state.text1 = state.text2 = '' + Math.random()))}>click</button>
11
+ <CarouselPage></CarouselPage>
36
12
  <CanlendarPage></CanlendarPage>
37
13
  <DatePickerPage></DatePickerPage>
38
14
  <MobileDatePickerPage></MobileDatePickerPage>
@@ -913,14 +913,14 @@ body { stroke: #A2A7AD; fill: #A2A7AD; }
913
913
  .combobox:has(.combobox-input:focus) { border: 1px solid #1b212d; }
914
914
 
915
915
 
916
- .canlendar { border: 1px solid #e4e4e4; }
917
- .canlendar-header > .icon { stroke: #1b212d; }
916
+ .datewidget { border: 1px solid #e4e4e4; }
917
+ .datewidget-header > .icon { stroke: #1b212d; }
918
+ .datewidget-item.disabled { color: #e4e4e4; }
919
+ .datewidget-item.prev-block, .datewidget-item.next-block { color: #888f97; }
920
+ .datewidget-item.today::before { background: #f5f5f5; }
921
+ .datewidget-item.selected { color: white; }
922
+ .datewidget-item.selected::before { background: #ff4000; }
918
923
  .canlendar-weeks > span { color: #888f97; }
919
- .canlendar-date.disabled { color: #e4e4e4; }
920
- .canlendar-date.prev-month, .canlendar-date.next-month { color: #888f97; }
921
- .canlendar-date.today::before { background: #f5f5f5; }
922
- .canlendar-date.selected { color: white; }
923
- .canlendar-date.selected::before { background: #ff4000; }
924
924
 
925
925
 
926
926
  .datepicker { border: 1px solid #e4e4e4; background: white; }
@@ -1,12 +1,18 @@
1
1
  import { Canlendar } from '../../../src/components/Canlendar';
2
+ import { MonthWidget } from '../../../src/components/MonthWidget';
3
+ import { YearWidget } from '../../../src/components/YearWidget';
2
4
 
3
5
  export const CanlendarPage = () => {
4
6
  return (
5
- <Canlendar
6
- value={new Date()}
7
- onValueChange={(date) => {
8
- console.log(date);
9
- }}
10
- ></Canlendar>
7
+ <div>
8
+ <Canlendar
9
+ value={new Date()}
10
+ onValueChange={(date) => {
11
+ console.log(date);
12
+ }}
13
+ ></Canlendar>
14
+ <MonthWidget></MonthWidget>
15
+ <YearWidget></YearWidget>
16
+ </div>
11
17
  );
12
18
  };
@@ -0,0 +1,40 @@
1
+ import { Carousel, CarouselApi, CarouselButtons } from '../../../src/components/Carousel';
2
+
3
+ const carousels = [
4
+ {
5
+ text: '页面1',
6
+ },
7
+ {
8
+ text: '页面2',
9
+ },
10
+ {
11
+ text: '页面3',
12
+ },
13
+ {
14
+ text: '页面4',
15
+ },
16
+ ];
17
+
18
+ export const CarouselPage = () => {
19
+ let carousel: CarouselApi;
20
+
21
+ return (
22
+ <div class="relative">
23
+ <Carousel
24
+ class="row-gap"
25
+ api={(api) => (carousel = api)}
26
+ each={carousels}
27
+ disabledScroll={true}
28
+ autoplay={false}
29
+ style={{ width: '100%', height: '200px' }}
30
+ >
31
+ {(item) => (
32
+ <div class="border round" style={{ flex: 'none', width: '100%', height: '100%', 'margin-right': '4px' }}>
33
+ {item.text}
34
+ </div>
35
+ )}
36
+ </Carousel>
37
+ <CarouselButtons carousel={carousel}></CarouselButtons>
38
+ </div>
39
+ );
40
+ };
@@ -59,10 +59,7 @@ export const FormPage = () => {
59
59
  {(item) => (
60
60
  <FormItem field={item.field} label={item.label} required={item.required}>
61
61
  <div>
62
- <input type="text" value={state.b}></input>
63
- {/* <item.Input></item.Input> */}
64
- <TextBox></TextBox>
65
- <TextBox></TextBox>
62
+ <item.Input></item.Input>
66
63
  </div>
67
64
  </FormItem>
68
65
  )}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdui-js",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -24,7 +24,7 @@ const renderDates = (
24
24
 
25
25
  for (let i = from; i <= to; i++) {
26
26
  items.push(
27
- `<span class="canlendar-date${className}${today === i ? ' today' : ''}${selected === i ? ' selected' : ''}${
27
+ `<span class="datewidget-item${className}${today === i ? ' today' : ''}${selected === i ? ' selected' : ''}${
28
28
  disableFn && disableFn(year, month, i) ? ' disabled' : ''
29
29
  }" data-date="${year + '|' + month + '|' + i}">${i}</span>`,
30
30
  );
@@ -51,7 +51,7 @@ const renderPrevMonthItems = (
51
51
  month = 11;
52
52
  }
53
53
 
54
- renderDates(items, year, month, days - index + 1, days, ' prev-month', todayValue, selectedValue, disableFn);
54
+ renderDates(items, year, month, days - index + 1, days, ' prev-block', todayValue, selectedValue, disableFn);
55
55
  };
56
56
 
57
57
  const renderNextMonthItems = (
@@ -70,7 +70,7 @@ const renderNextMonthItems = (
70
70
  month = 0;
71
71
  }
72
72
 
73
- renderDates(items, year, month, 1, days, ' next-month', todayValue, selectedValue, disableFn);
73
+ renderDates(items, year, month, 1, days, ' next-block', todayValue, selectedValue, disableFn);
74
74
  };
75
75
 
76
76
  const renderItems = (
@@ -139,6 +139,9 @@ const formatMonth = (value: Date) => {
139
139
 
140
140
  const OMIT_PROPS = ['class', 'value', 'onValueChange', 'disableFn'] as const;
141
141
 
142
+ /**
143
+ * 日历组件
144
+ */
142
145
  export const Canlendar = (
143
146
  props: Omit<JSX.HTMLAttributes<never>, 'children'> & {
144
147
  /**
@@ -158,14 +161,14 @@ export const Canlendar = (
158
161
  let domTitle: HTMLElement;
159
162
  let domBody: HTMLElement;
160
163
 
161
- const [selectedValue, setSelectedDate] = createSignal(parseDate(props.value));
164
+ const [selectedValue, setSelectedValue] = createSignal(parseDate(props.value));
162
165
  const [currentValue, setCurrentValue] = createSignal(selectedValue() || new Date());
163
166
 
164
167
  return (
165
- <div class={combineClass('canlendar', props.class)} {...omitProps(props, OMIT_PROPS)}>
166
- <div class="canlendar-header">
167
- <div ref={domTitle as any} class="canlendar-title">
168
- {replaceTemplate(i18n.Month, currentValue().getFullYear(), formatMonth(currentValue()))}
168
+ <div class={combineClass('canlendar datewidget', props.class)} {...omitProps(props, OMIT_PROPS)}>
169
+ <div class="datewidget-header canlendar-header">
170
+ <div ref={domTitle as any} class="datewidget-title">
171
+ {replaceTemplate(i18n.Title, currentValue().getFullYear(), formatMonth(currentValue()))}
169
172
  </div>
170
173
  <svg class="icon icon-s" aria-hidden={true} onclick={() => setCurrentValue(switchMonth(currentValue(), -1))}>
171
174
  <use href="#icon-backward"></use>
@@ -174,25 +177,30 @@ export const Canlendar = (
174
177
  <use href="#icon-forward"></use>
175
178
  </svg>
176
179
  </div>
177
- <div class="canlendar-weeks">
180
+ <div class="datewidget-header canlendar-weeks">
178
181
  <For each={i18n.Weeks}>{(item) => <span>{item}</span>}</For>
179
182
  </div>
180
183
  <div
181
184
  ref={domBody as any}
182
- class="canlendar-body"
185
+ class="datewidget-body canlendar-body"
183
186
  onclick={(event) => {
184
187
  let target = event.target as HTMLElement;
185
- let date, onValueChange;
186
-
187
- if (target && (date = target.dataset.date) && (onValueChange = props.onValueChange)) {
188
- let dom = domBody.querySelector('.selected') as HTMLElement;
189
-
190
- if (dom !== target) {
191
- date = date.split('|');
192
- date = new Date(date[0] | 0, date[1] | 0, date[2] | 0);
193
-
194
- setSelectedDate(date);
195
- onValueChange(date);
188
+ let date;
189
+
190
+ while (target && target !== domBody) {
191
+ if ((date = target.dataset.date)) {
192
+ // 没有选中
193
+ if (!target.classList.contains('selected')) {
194
+ date = date.split('|');
195
+ date = new Date(date[0] | 0, date[1] | 0, date[2] | 0);
196
+
197
+ setSelectedValue(date);
198
+ props.onValueChange && props.onValueChange(date);
199
+ }
200
+
201
+ break;
202
+ } else {
203
+ target = target.parentNode as HTMLElement;
196
204
  }
197
205
  }
198
206
  }}
@@ -15,9 +15,7 @@ import {
15
15
  import { For } from './For';
16
16
  import { Icon } from './Icon';
17
17
 
18
- const CLASS_NAME = 'carousel-vertical';
19
-
20
- const EVENT_OPTIONS = { passive: true, capture: true };
18
+ const EVENT_OPTIONS = { passive: false, capture: true };
21
19
 
22
20
  const DOTS = new Array(100).join('0').split('');
23
21
 
@@ -48,7 +46,7 @@ if (isBrowser) {
48
46
  );
49
47
  }
50
48
 
51
- const OMIT_PROPS = ['class', 'each', 'children', 'autoplay', 'interval', 'vertical', 'api'] as const;
49
+ const OMIT_PROPS = ['class', 'each', 'children', 'autoplay', 'interval', 'disabledScroll', 'api'] as const;
52
50
 
53
51
  /**
54
52
  * 轮播组件外部调用接口
@@ -94,9 +92,9 @@ export const Carousel = <T, U extends JSX.Element>(
94
92
  */
95
93
  interval?: number;
96
94
  /**
97
- * 是否竖直滚动
95
+ * 是否禁止滚动(触控时不允许上下滚动)
98
96
  */
99
- vertical?: boolean;
97
+ disabledScroll?: boolean;
100
98
  /**
101
99
  * 外部调用接口
102
100
  */
@@ -113,11 +111,6 @@ export const Carousel = <T, U extends JSX.Element>(
113
111
  // 自动滚动计时器
114
112
  let autoplayTimer: any;
115
113
 
116
- // 获取滚动方向
117
- const scrollType = createMemo(() => (props.vertical ? 'scrollTop' : 'scrollLeft'));
118
- const offsetType = createMemo(() => (props.vertical ? 'offsetTop' : 'offsetLeft'));
119
- const screenType = createMemo(() => (props.vertical ? 'screenY' : 'screenX'));
120
-
121
114
  // 滚动到指定索引
122
115
  const scrollTo = (index: number) => {
123
116
  let children = ref.children;
@@ -135,7 +128,7 @@ export const Carousel = <T, U extends JSX.Element>(
135
128
  animateScrollIntoView(ref, children[index % length] as HTMLElement).then(() => {
136
129
  if (index >= count) {
137
130
  // 滚动到对应节点
138
- ref[scrollType()] = (children[index - count] as HTMLElement)[offsetType()];
131
+ ref.scrollLeft = (children[index - count] as HTMLElement).offsetLeft;
139
132
  // 调整到指定节点
140
133
  index -= count;
141
134
  }
@@ -152,7 +145,7 @@ export const Carousel = <T, U extends JSX.Element>(
152
145
  let count = props.each.length;
153
146
 
154
147
  // 先滚动到对应节点的填充节点
155
- ref[scrollType()] = (ref.children[index + count] as HTMLElement)[offsetType()];
148
+ ref.scrollLeft = (ref.children[index + count] as HTMLElement).offsetLeft;
156
149
  // 调整到指定节点
157
150
  index += count;
158
151
  }
@@ -174,21 +167,31 @@ export const Carousel = <T, U extends JSX.Element>(
174
167
  checkFirstIndex();
175
168
 
176
169
  // 记录按下时状态
177
- pressdown = (event.changedTouches[0] || event.touches[0])[screenType()];
178
- pressdownScroll = ref[scrollType()];
170
+ pressdown = (event.changedTouches[0] || event.touches[0]).screenX;
171
+ pressdownScroll = ref.scrollLeft;
179
172
 
180
173
  // 取消自动播放
181
174
  clearTimeout(autoplayTimer);
175
+
176
+ if (props.disabledScroll) {
177
+ event.preventDefault();
178
+ return false;
179
+ }
182
180
  };
183
181
 
184
182
  const ontouchmove = (event: TouchEvent) => {
185
183
  if (pressdown >= 0) {
186
- ref[scrollType()] = pressdownScroll - ((event.changedTouches[0] || event.touches[0])[screenType()] - pressdown);
184
+ ref.scrollLeft = pressdownScroll - ((event.changedTouches[0] || event.touches[0]).screenX - pressdown);
185
+ }
186
+
187
+ if (props.disabledScroll) {
188
+ event.preventDefault();
189
+ return false;
187
190
  }
188
191
  };
189
192
 
190
193
  const ontouchend = (event: TouchEvent) => {
191
- let distance = (event.changedTouches[0] || event.touches[0])[screenType()] - pressdown;
194
+ let distance = (event.changedTouches[0] || event.touches[0]).screenX - pressdown;
192
195
  let index = currentIndex();
193
196
 
194
197
  // 清除按下状态
@@ -200,13 +203,18 @@ export const Carousel = <T, U extends JSX.Element>(
200
203
  } else if (distance < -20) {
201
204
  // 如果是第一个位置,则恢复滚动位置
202
205
  if (index === 0) {
203
- ref[scrollType()] = ref.children[offsetType()];
206
+ ref.scrollLeft = (ref.children[0] as HTMLElement).offsetLeft;
204
207
  }
205
208
 
206
209
  scrollTo(index + 1);
207
210
  } else {
208
211
  animateScrollIntoView(ref, ref.children[index % ref.children.length] as HTMLElement);
209
212
  }
213
+
214
+ if (props.disabledScroll) {
215
+ event.preventDefault();
216
+ return false;
217
+ }
210
218
  };
211
219
 
212
220
  const autoplay = () => {
@@ -244,18 +252,6 @@ export const Carousel = <T, U extends JSX.Element>(
244
252
  ) as CarouselApi,
245
253
  );
246
254
 
247
- createEffect(() => {
248
- let classList = ref.classList;
249
-
250
- if (props.vertical) {
251
- if (!classList.contains(CLASS_NAME)) {
252
- classList.add();
253
- }
254
- } else {
255
- classList.remove(CLASS_NAME);
256
- }
257
- });
258
-
259
255
  createEffect(autoplay);
260
256
 
261
257
  onMount(() => {
@@ -1,36 +1,61 @@
1
- import { createSignal, combineClass } from '../reactive';
2
-
3
1
  import { JSX } from '../jsx';
4
- import { Canleandar as i18n } from '../i18n';
2
+ import { MonthWidget as i18n } from '../i18n';
5
3
  import { replaceTemplate } from '../template';
4
+ import { createSignal, combineClass } from '../reactive';
6
5
  import { For } from './For';
7
6
 
8
- const formatMonth = (month: number) => {
9
- return month > 9 ? month : '0' + month;
10
- };
11
-
12
7
  const getCurrentMonth = () => {
13
8
  let date = new Date();
14
- return [date.getFullYear(), date.getMonth() + 1];
9
+ return [date.getFullYear(), date.getMonth() + 1] as [year: number, month: number];
15
10
  };
16
11
 
17
- const switchMonth = (value: [year: number, month: number], offset: 1 | -1) => {
18
- let year = value[0];
19
- let month = value[1] + offset;
12
+ const MONTH_LIST = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
20
13
 
21
- if (month === 0) {
22
- month = 12;
23
- year--;
24
- } else if (month > 12) {
25
- month = 1;
26
- year++;
14
+ const MonthItem = (props: {
15
+ value: number;
16
+ selectedValue: [year: number, month: number];
17
+ currentValue: [year: number, month: number];
18
+ onclick: (event: Event) => void;
19
+ disableFn?: (year: number, month: number) => boolean;
20
+ }) => {
21
+ let year = 0;
22
+ let month = props.value;
23
+
24
+ if (month > 12) {
25
+ year = 1;
26
+ month -= 12;
27
27
  }
28
28
 
29
- return [year, month];
30
- };
29
+ const checkCurrent = () => {
30
+ const today = new Date();
31
+ const currentValue = props.currentValue;
32
+
33
+ return today.getFullYear() === currentValue[0] + year && today.getMonth() + 1 === month;
34
+ };
31
35
 
32
- const MONTH_LIST = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
36
+ const checkSelected = () => {
37
+ let selectedValue = props.selectedValue;
38
+ const currentValue = props.currentValue;
33
39
 
40
+ return selectedValue && selectedValue[0] === currentValue[0] + year && selectedValue[1] === month;
41
+ };
42
+
43
+ return (
44
+ <span
45
+ class={`datewidget-item${year ? ' next-block' : ''}${checkCurrent() ? ' today' : ''}${
46
+ checkSelected() ? ' selected' : ''
47
+ }${props.disableFn && props.disableFn(props.currentValue[0] + year, month) ? ' disabled' : ''}`}
48
+ data-month={`${year}|${month}`}
49
+ onclick={props.onclick}
50
+ >
51
+ {replaceTemplate(i18n.Format, month)}
52
+ </span>
53
+ );
54
+ };
55
+
56
+ /**
57
+ * 月份组件
58
+ */
34
59
  export const MonthWidget = (
35
60
  props: Omit<JSX.HTMLAttributes<never>, 'children'> & {
36
61
  /**
@@ -44,48 +69,61 @@ export const MonthWidget = (
44
69
  /**
45
70
  * 禁用函数
46
71
  */
47
- disableFn?: (year: number, month: number, date: number) => boolean;
72
+ disableFn?: (year: number, month: number) => boolean;
48
73
  },
49
74
  ) => {
50
75
  let domTitle: HTMLElement;
51
76
  let domBody: HTMLElement;
52
77
 
53
- const [selectedValue, setSelectedDate] = createSignal(props.value);
78
+ const [selectedValue, setSelectedValue] = createSignal(props.value);
54
79
  const [currentValue, setCurrentValue] = createSignal(selectedValue() || getCurrentMonth());
55
80
 
81
+ const onclick = (event: Event) => {
82
+ let target = event.currentTarget as HTMLElement;
83
+ let month = target.dataset.month as any;
84
+
85
+ // 没有选中
86
+ if (month && !target.classList.contains('selected')) {
87
+ month = month.split('|');
88
+ month = [currentValue()[0] + (month[0] | 0), month[1] | 0];
89
+
90
+ setSelectedValue(month);
91
+ props.onValueChange && props.onValueChange(month);
92
+ }
93
+ };
94
+
56
95
  return (
57
- <div>
58
- <div class="monthwidget-header">
59
- <div ref={domTitle as any} class="monthwidget-title">
60
- {replaceTemplate(i18n.Month, currentValue()[0], formatMonth(currentValue()[1]))}
96
+ <div class={combineClass('monthwidget datewidget', props.class)}>
97
+ <div class="datewidget-header">
98
+ <div ref={domTitle as any} class="datewidget-title">
99
+ {replaceTemplate(i18n.Title, currentValue()[0])}
61
100
  </div>
62
- <svg class="icon icon-s" aria-hidden={true} onclick={() => setCurrentValue(switchMonth(selectedValue(), -1))}>
101
+ <svg
102
+ class="icon icon-s"
103
+ aria-hidden={true}
104
+ onclick={() => setCurrentValue([currentValue()[0] - 1, currentValue()[1]])}
105
+ >
63
106
  <use href="#icon-backward"></use>
64
107
  </svg>
65
- <svg class="icon icon-s" aria-hidden={true} onclick={() => setCurrentValue(switchMonth(selectedValue(), 1))}>
108
+ <svg
109
+ class="icon icon-s"
110
+ aria-hidden={true}
111
+ onclick={() => setCurrentValue([currentValue()[0] + 1, currentValue()[1]])}
112
+ >
66
113
  <use href="#icon-forward"></use>
67
114
  </svg>
68
115
  </div>
69
- <div
70
- ref={domBody as any}
71
- class="monthwidget-body"
72
- onclick={(event) => {
73
- let target = event.target as HTMLElement;
74
- let value, onValueChange;
75
-
76
- if (target && (onValueChange = props.onValueChange)) {
77
- let dom = domBody.querySelector('.selected') as HTMLElement;
78
-
79
- if (dom !== target) {
80
- value = [currentValue()[0], +target.textContent];
81
-
82
- setSelectedDate(value);
83
- onValueChange(value);
84
- }
85
- }
86
- }}
87
- >
88
- <For each={MONTH_LIST}>{(item) => <span>{item}</span>}</For>
116
+ <div ref={domBody as any} class="datewidget-body">
117
+ <For each={MONTH_LIST}>
118
+ {(item) => (
119
+ <MonthItem
120
+ value={item}
121
+ selectedValue={selectedValue()}
122
+ currentValue={currentValue()}
123
+ onclick={onclick}
124
+ ></MonthItem>
125
+ )}
126
+ </For>
89
127
  </div>
90
128
  </div>
91
129
  );
@@ -1,13 +1,121 @@
1
+ import { JSX } from '../jsx';
2
+ import { YearWidget as i18n } from '../i18n';
3
+ import { replaceTemplate } from '../template';
1
4
  import { createSignal, combineClass } from '../reactive';
5
+ import { For } from './For';
2
6
 
3
- import { JSX } from '../jsx';
7
+ // 渲染日期项
8
+ const renderDates = (
9
+ items: string[],
10
+ year: number,
11
+ month: number,
12
+ from: number,
13
+ to: number,
14
+ className: string,
15
+ todayValue: Date,
16
+ selectedValue?: Date,
17
+ disableFn?: (year: number, month: number, date: number) => boolean,
18
+ ) => {
19
+ let today = todayValue.getFullYear() === year && todayValue.getMonth() === month ? todayValue.getDate() : -1;
20
+ let selected =
21
+ selectedValue && selectedValue.getFullYear() === year && selectedValue.getMonth() === month
22
+ ? selectedValue.getDate()
23
+ : -1;
24
+
25
+ for (let i = from; i <= to; i++) {
26
+ items.push(
27
+ `<span class="datewidget-item${className}${today === i ? ' today' : ''}${selected === i ? ' selected' : ''}${
28
+ disableFn && disableFn(year, month, i) ? ' disabled' : ''
29
+ }" data-date="${year + '|' + month + '|' + i}">${i}</span>`,
30
+ );
31
+ }
32
+ };
33
+
34
+ // 渲染上月日期项
35
+ const renderPrevMonthItems = (
36
+ items: string[],
37
+ year: number,
38
+ month: number,
39
+ index: number,
40
+ todayValue: Date,
41
+ selectedValue?: Date,
42
+ disableFn?: (year: number, month: number, date: number) => boolean,
43
+ ) => {
44
+ // 获取上月天数
45
+ let days = new Date(year, month, 0).getDate();
46
+
47
+ if (month > 0) {
48
+ month--;
49
+ } else {
50
+ year--;
51
+ month = 11;
52
+ }
53
+
54
+ renderDates(items, year, month, days - index + 1, days, ' prev-block', todayValue, selectedValue, disableFn);
55
+ };
56
+
57
+ const renderNextMonthItems = (
58
+ items: string[],
59
+ year: number,
60
+ month: number,
61
+ days: number,
62
+ todayValue: Date,
63
+ selectedValue?: Date,
64
+ disableFn?: (year: number, month: number, date: number) => boolean,
65
+ ) => {
66
+ if (month < 11) {
67
+ month++;
68
+ } else {
69
+ year++;
70
+ month = 0;
71
+ }
72
+
73
+ renderDates(items, year, month, 1, days, ' next-block', todayValue, selectedValue, disableFn);
74
+ };
75
+
76
+ const renderItems = (
77
+ currentValue: Date,
78
+ selectedValue?: Date,
79
+ disableFn?: (year: number, month: number, date: number) => boolean,
80
+ ) => {
81
+ let today = new Date();
82
+ let year = currentValue.getFullYear();
83
+ let month = currentValue.getMonth();
84
+ let firstDate = new Date(year, month, 1); // 获取当前月的第一天
85
+
86
+ let firstWeek = firstDate.getDay();
87
+ let items = [];
88
+ let index = 0;
89
+ let days;
90
+
91
+ // 当前月第一天不是周一,渲染上月数据
92
+ if (firstWeek !== 1) {
93
+ index = firstWeek > 0 ? firstWeek - 1 : 6;
94
+ renderPrevMonthItems(items, year, month, index, today, selectedValue, disableFn);
95
+ }
96
+
97
+ // 获取当前月的天数
98
+ days = new Date(year, month + 1, 0).getDate();
99
+ // 渲染本月日期
100
+ renderDates(items, year, month, 1, days, '', today, selectedValue, disableFn);
4
101
 
102
+ // 当前月最后一天没有占满,渲染下月数据
103
+ if ((index += days) < 42) {
104
+ renderNextMonthItems(items, year, month, 42 - index, today, selectedValue, disableFn);
105
+ }
106
+
107
+ return items.join('');
108
+ };
109
+
110
+ /**
111
+ * 年份组件
112
+ */
5
113
  export const YearWidget = (
6
114
  props: Omit<JSX.HTMLAttributes<never>, 'children'> & {
7
115
  /**
8
- * 日期值
116
+ * 年月值
9
117
  */
10
- value?: Date | string | number;
118
+ value?: [year: number, month: number];
11
119
  /**
12
120
  * 值变更事件
13
121
  */
@@ -15,8 +123,76 @@ export const YearWidget = (
15
123
  /**
16
124
  * 禁用函数
17
125
  */
18
- disableFn?: (year: number, month: number, date: number) => boolean;
126
+ disableFn?: (year: number, month: number) => boolean;
19
127
  },
20
128
  ) => {
21
- return <div></div>;
129
+ let domTitle: HTMLElement;
130
+ let domBody: HTMLElement;
131
+
132
+ // const [selectedValue, setSelectedValue] = createSignal(props.value);
133
+ // const [currentValue, setCurrentValue] = createSignal(selectedValue() || getCurrentMonth());
134
+
135
+ // const onclick = (event: Event) => {
136
+ // let target = event.currentTarget as HTMLElement;
137
+ // let month = target.dataset.month as any;
138
+
139
+ // // 没有选中
140
+ // if (month && !target.classList.contains('selected')) {
141
+ // month = month.split('|');
142
+ // month = [currentValue()[0] + (month[0] | 0), month[1] | 0];
143
+
144
+ // setSelectedValue(month);
145
+ // props.onValueChange && props.onValueChange(month);
146
+ // }
147
+ // };
148
+
149
+ return (
150
+ <div class={combineClass('monthwidget datewidget', props.class)}>
151
+ {/* <div class="datewidget-header">
152
+ <div ref={domTitle as any} class="datewidget-title">
153
+ {replaceTemplate(i18n.Title, currentValue()[0], formatMonth(currentValue()[1]))}
154
+ </div>
155
+ <svg
156
+ class="icon icon-s"
157
+ aria-hidden={true}
158
+ onclick={() => setCurrentValue([currentValue()[0] - 1, currentValue()[1]])}
159
+ >
160
+ <use href="#icon-backward"></use>
161
+ </svg>
162
+ <svg
163
+ class="icon icon-s"
164
+ aria-hidden={true}
165
+ onclick={() => setCurrentValue([currentValue()[0] + 1, currentValue()[1]])}
166
+ >
167
+ <use href="#icon-forward"></use>
168
+ </svg>
169
+ </div>
170
+ <div
171
+ ref={domBody as any}
172
+ class="datewidget-body canlendar-body"
173
+ onclick={(event) => {
174
+ let target = event.target as HTMLElement;
175
+ let date;
176
+
177
+ while (target && target !== domBody) {
178
+ if ((date = target.dataset.date)) {
179
+ // 没有选中
180
+ if (!target.classList.contains('selected')) {
181
+ date = date.split('|');
182
+ date = new Date(date[0] | 0, date[1] | 0, date[2] | 0);
183
+
184
+ setSelectedValue(date);
185
+ props.onValueChange && props.onValueChange(date);
186
+ }
187
+
188
+ break;
189
+ } else {
190
+ target = target.parentNode as HTMLElement;
191
+ }
192
+ }
193
+ }}
194
+ innerHTML={renderItems(currentValue(), selectedValue(), props.disableFn)}
195
+ ></div> */}
196
+ </div>
197
+ );
22
198
  };
package/src/i18n/index.ts CHANGED
@@ -1,5 +1,15 @@
1
1
  import i18n from './languages/en.json';
2
2
 
3
+ /**
4
+ * 年份面板
5
+ */
6
+ export let YearWidget = i18n.YearWidget;
7
+
8
+ /**
9
+ * 月份面板
10
+ */
11
+ export let MonthWidget = i18n.MonthWidget;
12
+
3
13
  /**
4
14
  * 日历
5
15
  */
@@ -16,6 +26,8 @@ export let Form = i18n.Form;
16
26
  * @param data 当前语言数据
17
27
  */
18
28
  export const switchLanguage = (data: typeof i18n) => {
29
+ YearWidget = i18n.YearWidget;
30
+ MonthWidget = i18n.MonthWidget;
19
31
  Canleandar = i18n.Canlendar;
20
32
  Form = i18n.Form;
21
33
  };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "Canlendar": {
3
- "Month": "${0}年${1}月",
3
+ "Title": "${0}年${1}月",
4
4
  "Weeks": [
5
5
  "一",
6
6
  "二",
@@ -11,6 +11,13 @@
11
11
  "日"
12
12
  ]
13
13
  },
14
+ "MonthWidget": {
15
+ "Title": "${0}年",
16
+ "Format": "${0}月"
17
+ },
18
+ "YearWidget": {
19
+ "Title": "${0} - ${1}"
20
+ },
14
21
  "Form": {
15
22
  "Required": "不能为空",
16
23
  "NotLessThan": "不能小于 ${value}",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "Canlendar": {
3
- "Month": "${0}年${1}月",
3
+ "Title": "${0}年${1}月",
4
4
  "Weeks": [
5
5
  "一",
6
6
  "二",
@@ -10,5 +10,19 @@
10
10
  "六",
11
11
  "日"
12
12
  ]
13
+ },
14
+ "MonthWidget": {
15
+ "Title": "${0}年",
16
+ "Format": "${0}月"
17
+ },
18
+ "YearWidget": {
19
+ "Title": "${0} - ${1}"
20
+ },
21
+ "Form": {
22
+ "Required": "不能为空",
23
+ "NotLessThan": "不能小于 ${value}",
24
+ "NotGreaterThan": "不能大于 ${value}",
25
+ "Between": "必须在 ${min} -> ${max} 之间",
26
+ "NotDate": "\"${value}\" 不是一个有效的日期值"
13
27
  }
14
28
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "Canlendar": {
3
- "Month": "${0}年${1}月",
3
+ "Title": "${0}年${1}月",
4
4
  "Weeks": [
5
5
  "一",
6
6
  "二",
@@ -10,5 +10,19 @@
10
10
  "六",
11
11
  "日"
12
12
  ]
13
+ },
14
+ "MonthWidget": {
15
+ "Title": "${0}年",
16
+ "Format": "${0}月"
17
+ },
18
+ "YearWidget": {
19
+ "Title": "${0} - ${1}"
20
+ },
21
+ "Form": {
22
+ "Required": "不能为空",
23
+ "NotLessThan": "不能小于 ${value}",
24
+ "NotGreaterThan": "不能大于 ${value}",
25
+ "Between": "必须在 ${min} -> ${max} 之间",
26
+ "NotDate": "\"${value}\" 不是一个有效的日期值"
13
27
  }
14
28
  }
package/src/index.ts CHANGED
@@ -26,6 +26,7 @@ export * from './components/Form';
26
26
  export * from './components/CollapsiblePanel';
27
27
  export * from './components/Carousel';
28
28
  export * from './components/KeepAlive';
29
+ export * from './components/Popup';
29
30
  export * from './components/Dialog';
30
31
 
31
32
  export * from './ssr/render';