cdui-js 1.0.21 → 1.0.23
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/datewidget.css +8 -0
- package/demo/src/App.tsx +0 -2
- package/demo/src/pages/Canlendar.tsx +4 -4
- package/demo/src/pages/Form.tsx +19 -0
- package/package.json +1 -1
- package/src/components/Canlendar.tsx +91 -48
- package/src/components/ComboBox.tsx +8 -6
- package/src/components/DatePicker.tsx +30 -45
- package/src/components/Form.tsx +1 -0
- package/src/components/MonthPicker.tsx +1 -1
- package/src/components/MonthWidget.tsx +65 -36
- package/src/components/Popup.tsx +43 -10
- package/src/components/YearPicker.tsx +1 -1
- package/src/components/YearWidget.tsx +62 -138
- package/src/{components/Dialog.ts → dialog.ts} +5 -2
- package/src/i18n/index.ts +2 -2
- package/src/index.ts +2 -1
- package/src/location.ts +7 -5
- package/src/popup.ts +33 -0
- package/src/reactive.ts +3 -0
- package/src/ssr/render.ts +5 -5
- package/demo/src/pages/MobileDatePicker.tsx +0 -5
- package/src/components/MobileDatePicker.tsx +0 -444
package/src/ssr/render.ts
CHANGED
|
@@ -69,7 +69,7 @@ const renderToStringAsync = async <T>(context: ServerContext, fn: () => T) => {
|
|
|
69
69
|
*/
|
|
70
70
|
const renderPage = async (
|
|
71
71
|
App: Component<any>,
|
|
72
|
-
|
|
72
|
+
i18n: { [key: string]: any },
|
|
73
73
|
language: string,
|
|
74
74
|
root: string,
|
|
75
75
|
template: string,
|
|
@@ -90,8 +90,8 @@ const renderPage = async (
|
|
|
90
90
|
if (language !== 'en') {
|
|
91
91
|
template = template.replace('lang="en"', 'lang="' + language + '"');
|
|
92
92
|
|
|
93
|
-
if (
|
|
94
|
-
scripts.push('<script type="text/javascript">window.I18N=' + JSON.stringify(
|
|
93
|
+
if (i18n[language]) {
|
|
94
|
+
scripts.push('<script type="text/javascript">window.I18N=' + JSON.stringify(i18n[language]) + '</script>');
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -138,7 +138,7 @@ export const SSR_ERRORS = [];
|
|
|
138
138
|
*/
|
|
139
139
|
export const renderSSRPages = async (
|
|
140
140
|
App: Component<any>,
|
|
141
|
-
|
|
141
|
+
i18n: { [key: string]: any },
|
|
142
142
|
language: string,
|
|
143
143
|
root: string,
|
|
144
144
|
template: string,
|
|
@@ -154,7 +154,7 @@ export const renderSSRPages = async (
|
|
|
154
154
|
|
|
155
155
|
console.log(`rendering:${language} ${page.path}`);
|
|
156
156
|
|
|
157
|
-
await renderPage(App,
|
|
157
|
+
await renderPage(App, i18n, language, root, template, page);
|
|
158
158
|
|
|
159
159
|
console.log(`rendered: ${language} ${page.path} time: ${Date.now() - now}`);
|
|
160
160
|
} catch (err) {
|
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
import { JSX } from '../jsx';
|
|
2
|
-
import { animateScrollTo } from '../animate-scroll-to';
|
|
3
|
-
import {
|
|
4
|
-
batch,
|
|
5
|
-
combineClass,
|
|
6
|
-
createEffect,
|
|
7
|
-
createMemo,
|
|
8
|
-
createSignal,
|
|
9
|
-
omitProps,
|
|
10
|
-
onCleanup,
|
|
11
|
-
onMount,
|
|
12
|
-
untrack,
|
|
13
|
-
} from '../reactive';
|
|
14
|
-
|
|
15
|
-
import { For } from './For';
|
|
16
|
-
import { parseDate } from './Canlendar';
|
|
17
|
-
|
|
18
|
-
const ITEM_HEIGHT = 44;
|
|
19
|
-
const VIEWPORT_ROWS = 5;
|
|
20
|
-
/** 上下各多渲染的行数,保证快速滑动时不断档 */
|
|
21
|
-
const ROW_OVERSCAN = 5;
|
|
22
|
-
const REPEAT_BLOCKS = 99;
|
|
23
|
-
const TELEPORT_SHIFT = 35;
|
|
24
|
-
|
|
25
|
-
const PAD = ((VIEWPORT_ROWS * ITEM_HEIGHT - ITEM_HEIGHT) / 2) | 0;
|
|
26
|
-
|
|
27
|
-
const daysInMonth = (year: number, month: number) => new Date(year, month, 0).getDate();
|
|
28
|
-
|
|
29
|
-
type LoopColumnProps = {
|
|
30
|
-
class?: string;
|
|
31
|
-
values: readonly number[];
|
|
32
|
-
value: () => number;
|
|
33
|
-
onChange: (v: number) => void;
|
|
34
|
-
format: (v: number) => string;
|
|
35
|
-
/**
|
|
36
|
-
* 额外依赖,用于在同长度列表变化时(如换月)重置滚动位置
|
|
37
|
-
*/
|
|
38
|
-
scrollToken?: () => void;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
type VisibleRow = { r: number; v: number };
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 单列循环滚动(年 / 月 / 日),可视区域虚拟化,仅挂载少量行节点
|
|
45
|
-
*/
|
|
46
|
-
const MobileDateLoopColumn = (props: LoopColumnProps) => {
|
|
47
|
-
let wheelEl: HTMLDivElement;
|
|
48
|
-
let teleporting = false;
|
|
49
|
-
let settleTimer: ReturnType<typeof setTimeout>;
|
|
50
|
-
let dragging = false;
|
|
51
|
-
let scrollRaf = 0;
|
|
52
|
-
|
|
53
|
-
const [scrollTop, setScrollTop] = createSignal(0);
|
|
54
|
-
|
|
55
|
-
const blockHeight = () => props.values.length * ITEM_HEIGHT;
|
|
56
|
-
|
|
57
|
-
const rowCount = () => {
|
|
58
|
-
const n = props.values.length;
|
|
59
|
-
return n ? REPEAT_BLOCKS * n : 0;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const innerHeightPx = () => {
|
|
63
|
-
const rc = rowCount();
|
|
64
|
-
return rc ? PAD * 2 + rc * ITEM_HEIGHT : PAD * 2;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const visibleRows = createMemo((): VisibleRow[] => {
|
|
68
|
-
const vals = props.values;
|
|
69
|
-
const n = vals.length;
|
|
70
|
-
const rc = rowCount();
|
|
71
|
-
if (!n || !rc) {
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const st = scrollTop();
|
|
76
|
-
const ch = VIEWPORT_ROWS * ITEM_HEIGHT;
|
|
77
|
-
const y0 = st;
|
|
78
|
-
const y1 = st + ch;
|
|
79
|
-
let r0 = Math.floor((y0 - PAD) / ITEM_HEIGHT) - ROW_OVERSCAN;
|
|
80
|
-
let r1 = Math.ceil((y1 - PAD) / ITEM_HEIGHT) + ROW_OVERSCAN;
|
|
81
|
-
if (r0 < 0) {
|
|
82
|
-
r0 = 0;
|
|
83
|
-
}
|
|
84
|
-
if (r1 > rc - 1) {
|
|
85
|
-
r1 = rc - 1;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const out: VisibleRow[] = [];
|
|
89
|
-
for (let r = r0; r <= r1; r++) {
|
|
90
|
-
out.push({ r, v: vals[r % n] });
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return out;
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const flushScrollTop = () => {
|
|
97
|
-
if (wheelEl) {
|
|
98
|
-
setScrollTop(wheelEl.scrollTop);
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const scheduleScrollTop = () => {
|
|
103
|
-
if (scrollRaf) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
scrollRaf = requestAnimationFrame(() => {
|
|
108
|
-
scrollRaf = 0;
|
|
109
|
-
flushScrollTop();
|
|
110
|
-
});
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const syncScroll = () => {
|
|
114
|
-
if (!wheelEl || !props.values.length) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
let idx = props.values.indexOf(props.value());
|
|
119
|
-
if (idx < 0) {
|
|
120
|
-
idx = 0;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const n = props.values.length;
|
|
124
|
-
const mid = (REPEAT_BLOCKS / 2) | 0;
|
|
125
|
-
const R = mid * n + idx;
|
|
126
|
-
teleporting = true;
|
|
127
|
-
wheelEl.scrollTop = R * ITEM_HEIGHT;
|
|
128
|
-
setScrollTop(wheelEl.scrollTop);
|
|
129
|
-
requestAnimationFrame(() => {
|
|
130
|
-
teleporting = false;
|
|
131
|
-
});
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const maybeTeleport = () => {
|
|
135
|
-
if (teleporting || !wheelEl) {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const block = blockHeight();
|
|
140
|
-
const n = props.values.length;
|
|
141
|
-
if (!n) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const st = wheelEl.scrollTop;
|
|
146
|
-
const low = TELEPORT_SHIFT * block;
|
|
147
|
-
const high = (REPEAT_BLOCKS - TELEPORT_SHIFT) * block;
|
|
148
|
-
|
|
149
|
-
if (st < low) {
|
|
150
|
-
teleporting = true;
|
|
151
|
-
wheelEl.scrollTop = st + TELEPORT_SHIFT * block;
|
|
152
|
-
requestAnimationFrame(() => {
|
|
153
|
-
teleporting = false;
|
|
154
|
-
flushScrollTop();
|
|
155
|
-
});
|
|
156
|
-
} else if (st > high) {
|
|
157
|
-
teleporting = true;
|
|
158
|
-
wheelEl.scrollTop = st - TELEPORT_SHIFT * block;
|
|
159
|
-
requestAnimationFrame(() => {
|
|
160
|
-
teleporting = false;
|
|
161
|
-
flushScrollTop();
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const snapAndCommit = () => {
|
|
167
|
-
if (!wheelEl || !props.values.length) {
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const n = props.values.length;
|
|
172
|
-
const rc = rowCount();
|
|
173
|
-
const st = wheelEl.scrollTop;
|
|
174
|
-
const ch = wheelEl.clientHeight;
|
|
175
|
-
const center = st + ch / 2;
|
|
176
|
-
let R = Math.round((center - PAD - ITEM_HEIGHT / 2) / ITEM_HEIGHT);
|
|
177
|
-
if (R < 0) {
|
|
178
|
-
R = 0;
|
|
179
|
-
}
|
|
180
|
-
if (R > rc - 1) {
|
|
181
|
-
R = rc - 1;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const idx = R % n;
|
|
185
|
-
const next = props.values[idx];
|
|
186
|
-
const target = R * ITEM_HEIGHT;
|
|
187
|
-
|
|
188
|
-
animateScrollTo(wheelEl, undefined, target, 120).then(() => {
|
|
189
|
-
setScrollTop(wheelEl.scrollTop);
|
|
190
|
-
if (next !== props.value()) {
|
|
191
|
-
props.onChange(next);
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
const scheduleSettle = () => {
|
|
197
|
-
clearTimeout(settleTimer);
|
|
198
|
-
settleTimer = setTimeout(() => {
|
|
199
|
-
if (!dragging) {
|
|
200
|
-
snapAndCommit();
|
|
201
|
-
}
|
|
202
|
-
}, 80);
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
onMount(() => {
|
|
206
|
-
syncScroll();
|
|
207
|
-
|
|
208
|
-
const onScroll = () => {
|
|
209
|
-
if (!teleporting) {
|
|
210
|
-
maybeTeleport();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
scheduleScrollTop();
|
|
214
|
-
scheduleSettle();
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
wheelEl.addEventListener('scroll', onScroll, { passive: true });
|
|
218
|
-
|
|
219
|
-
const onTouchStart = () => {
|
|
220
|
-
dragging = true;
|
|
221
|
-
clearTimeout(settleTimer);
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
const onTouchEnd = () => {
|
|
225
|
-
dragging = false;
|
|
226
|
-
scheduleSettle();
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
wheelEl.addEventListener('touchstart', onTouchStart, { passive: true });
|
|
230
|
-
wheelEl.addEventListener('touchend', onTouchEnd);
|
|
231
|
-
wheelEl.addEventListener('touchcancel', onTouchEnd);
|
|
232
|
-
|
|
233
|
-
const onScrollEnd = () => {
|
|
234
|
-
dragging = false;
|
|
235
|
-
flushScrollTop();
|
|
236
|
-
snapAndCommit();
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
wheelEl.addEventListener('scrollend', onScrollEnd);
|
|
240
|
-
|
|
241
|
-
onCleanup(() => {
|
|
242
|
-
clearTimeout(settleTimer);
|
|
243
|
-
cancelAnimationFrame(scrollRaf);
|
|
244
|
-
scrollRaf = 0;
|
|
245
|
-
wheelEl.removeEventListener('scroll', onScroll);
|
|
246
|
-
wheelEl.removeEventListener('touchstart', onTouchStart);
|
|
247
|
-
wheelEl.removeEventListener('touchend', onTouchEnd);
|
|
248
|
-
wheelEl.removeEventListener('touchcancel', onTouchEnd);
|
|
249
|
-
wheelEl.removeEventListener('scrollend', onScrollEnd);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
createEffect(() => {
|
|
254
|
-
props.scrollToken?.();
|
|
255
|
-
props.value();
|
|
256
|
-
props.values.length;
|
|
257
|
-
|
|
258
|
-
untrack(() => {
|
|
259
|
-
queueMicrotask(syncScroll);
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
return (
|
|
264
|
-
<div class={combineClass('mobile-date-picker-column', props.class)}>
|
|
265
|
-
<div ref={wheelEl as any} class="mobile-date-picker-wheel scrollbar-hidden">
|
|
266
|
-
<div class="mobile-date-picker-wheel-inner" style={{ height: `${innerHeightPx()}px` }}>
|
|
267
|
-
<For each={visibleRows()}>
|
|
268
|
-
{(row) => (
|
|
269
|
-
<div class="mobile-date-picker-item" style={{ top: `${PAD + row.r * ITEM_HEIGHT}px` }} data-value={row.v}>
|
|
270
|
-
{props.format(row.v)}
|
|
271
|
-
</div>
|
|
272
|
-
)}
|
|
273
|
-
</For>
|
|
274
|
-
</div>
|
|
275
|
-
</div>
|
|
276
|
-
</div>
|
|
277
|
-
);
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
const pad2 = (n: number) => (n > 9 ? String(n) : '0' + n);
|
|
281
|
-
|
|
282
|
-
const defaultYearRange = (min: number, max: number) => {
|
|
283
|
-
const a: number[] = [];
|
|
284
|
-
|
|
285
|
-
for (let y = min; y <= max; y++) {
|
|
286
|
-
a.push(y);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return a;
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
const monthValues = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* 移动端日期触控选择:年、月、日三栏循环滚动
|
|
296
|
-
*/
|
|
297
|
-
export const MobileDatePicker = (
|
|
298
|
-
props?: JSX.HTMLAttributes<never> & {
|
|
299
|
-
/**
|
|
300
|
-
* 当前日期
|
|
301
|
-
*/
|
|
302
|
-
value?: Date | string | number;
|
|
303
|
-
/**
|
|
304
|
-
* 值变化回调
|
|
305
|
-
*/
|
|
306
|
-
onValueChange?: (value: Date) => void;
|
|
307
|
-
/**
|
|
308
|
-
* 最小年份
|
|
309
|
-
*/
|
|
310
|
-
minYear?: number;
|
|
311
|
-
/**
|
|
312
|
-
* 最大年份
|
|
313
|
-
*/
|
|
314
|
-
maxYear?: number;
|
|
315
|
-
},
|
|
316
|
-
) => {
|
|
317
|
-
const minY = () => props.minYear ?? 1970;
|
|
318
|
-
const maxY = () => props.maxYear ?? 2100;
|
|
319
|
-
|
|
320
|
-
const init = () => {
|
|
321
|
-
let d = props.value != null ? parseDate(props.value) : null;
|
|
322
|
-
d = d || new Date();
|
|
323
|
-
let y = d.getFullYear();
|
|
324
|
-
let m = d.getMonth() + 1;
|
|
325
|
-
let day = d.getDate();
|
|
326
|
-
y = Math.min(maxY(), Math.max(minY(), y));
|
|
327
|
-
const dim = daysInMonth(y, m);
|
|
328
|
-
day = Math.min(dim, Math.max(1, day));
|
|
329
|
-
return { y, m, day };
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
const [year, setYear] = createSignal(init().y);
|
|
333
|
-
const [month, setMonth] = createSignal(init().m);
|
|
334
|
-
const [day, setDay] = createSignal(init().day);
|
|
335
|
-
|
|
336
|
-
const years = createMemo(() => defaultYearRange(minY(), maxY()));
|
|
337
|
-
|
|
338
|
-
const days = createMemo(() => {
|
|
339
|
-
const dim = daysInMonth(year(), month());
|
|
340
|
-
const a: number[] = [];
|
|
341
|
-
|
|
342
|
-
for (let i = 1; i <= dim; i++) {
|
|
343
|
-
a.push(i);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return a;
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
createEffect(() => {
|
|
350
|
-
const v = props.value;
|
|
351
|
-
|
|
352
|
-
if (v == null) {
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
let d = parseDate(v);
|
|
357
|
-
if (!d) {
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
let y = d.getFullYear();
|
|
362
|
-
let m = d.getMonth() + 1;
|
|
363
|
-
let dd = d.getDate();
|
|
364
|
-
y = Math.min(maxY(), Math.max(minY(), y));
|
|
365
|
-
const dim = daysInMonth(y, m);
|
|
366
|
-
dd = Math.min(dim, Math.max(1, dd));
|
|
367
|
-
|
|
368
|
-
batch(() => {
|
|
369
|
-
setYear(y);
|
|
370
|
-
setMonth(m);
|
|
371
|
-
setDay(dd);
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
createEffect(() => {
|
|
376
|
-
minY();
|
|
377
|
-
maxY();
|
|
378
|
-
const y = year();
|
|
379
|
-
const c = Math.min(maxY(), Math.max(minY(), y));
|
|
380
|
-
if (c !== y) {
|
|
381
|
-
setYear(c);
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
createEffect(() => {
|
|
386
|
-
const dim = daysInMonth(year(), month());
|
|
387
|
-
if (day() > dim) {
|
|
388
|
-
setDay(dim);
|
|
389
|
-
}
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
const emit = (y: number, m: number, d: number) => {
|
|
393
|
-
const dim = daysInMonth(y, m);
|
|
394
|
-
const dd = Math.min(dim, Math.max(1, d));
|
|
395
|
-
props.onValueChange?.(new Date(y, m - 1, dd));
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
const onYear = (y: number) => {
|
|
399
|
-
setYear(y);
|
|
400
|
-
const dim = daysInMonth(y, month());
|
|
401
|
-
const dd = Math.min(dim, day());
|
|
402
|
-
if (dd !== day()) {
|
|
403
|
-
setDay(dd);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
emit(y, month(), dd);
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
const onMonth = (m: number) => {
|
|
410
|
-
setMonth(m);
|
|
411
|
-
const dim = daysInMonth(year(), m);
|
|
412
|
-
const dd = Math.min(dim, day());
|
|
413
|
-
if (dd !== day()) {
|
|
414
|
-
setDay(dd);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
emit(year(), m, dd);
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
const onDay = (d: number) => {
|
|
421
|
-
setDay(d);
|
|
422
|
-
emit(year(), month(), d);
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
return (
|
|
426
|
-
<div
|
|
427
|
-
class={combineClass('mobile-date-picker', props.class)}
|
|
428
|
-
{...omitProps(props, ['class', 'value', 'onValueChange', 'minYear', 'maxYear'])}
|
|
429
|
-
>
|
|
430
|
-
<MobileDateLoopColumn values={years()} value={year} onChange={onYear} format={(y) => String(y)} />
|
|
431
|
-
<MobileDateLoopColumn values={monthValues} value={month} onChange={onMonth} format={(m) => pad2(m)} />
|
|
432
|
-
<MobileDateLoopColumn
|
|
433
|
-
values={days()}
|
|
434
|
-
value={day}
|
|
435
|
-
onChange={onDay}
|
|
436
|
-
format={(d) => pad2(d)}
|
|
437
|
-
scrollToken={() => {
|
|
438
|
-
year();
|
|
439
|
-
month();
|
|
440
|
-
}}
|
|
441
|
-
/>
|
|
442
|
-
</div>
|
|
443
|
-
);
|
|
444
|
-
};
|