cdui-js 1.0.18 → 1.0.20
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 +1 -1
- package/css/{canlendar.css → datewidget.css} +25 -33
- package/css/form.css +7 -1
- package/demo/css/css.md +9 -9
- package/demo/src/App.tsx +2 -23
- package/demo/src/css/atomic.css +7 -7
- package/demo/src/pages/Canlendar.tsx +10 -6
- package/demo/src/pages/Carousel.tsx +40 -0
- package/demo/src/pages/ComboBox.tsx +18 -1
- package/demo/src/pages/Form.tsx +32 -2
- package/package.json +1 -1
- package/src/components/Button.tsx +1 -1
- package/src/components/Canlendar.tsx +6 -6
- package/src/components/Carousel.tsx +26 -30
- package/src/components/ComboBox.tsx +22 -3
- package/src/components/Form.tsx +126 -106
- package/src/components/MonthWidget.tsx +5 -5
- package/src/components/Popup.tsx +9 -2
- package/src/components/TextBox.tsx +20 -4
- package/src/components/provider.ts +25 -1
package/css/all.css
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
.
|
|
2
|
-
.monthwidget {
|
|
1
|
+
.datewidget {
|
|
3
2
|
max-width: 380px;
|
|
4
3
|
height: 320px;
|
|
5
4
|
border-radius: 12px;
|
|
@@ -8,17 +7,14 @@
|
|
|
8
7
|
user-select: none;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
30
|
-
.monthwidget-header > .icon {
|
|
25
|
+
.datewidget-header > .icon {
|
|
31
26
|
box-sizing: border-box;
|
|
32
27
|
width: 40px;
|
|
33
28
|
height: 100%;
|
|
@@ -35,42 +30,24 @@
|
|
|
35
30
|
cursor: pointer;
|
|
36
31
|
}
|
|
37
32
|
|
|
38
|
-
.
|
|
39
|
-
flex: auto;
|
|
40
|
-
text-align: center;
|
|
41
|
-
vertical-align: middle;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.canlendar-body {
|
|
45
|
-
height: calc(100% - 72px);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.canlendar-date,
|
|
49
|
-
.monthwidget-month {
|
|
33
|
+
.datewidget-item {
|
|
50
34
|
display: inline-flex;
|
|
51
35
|
justify-content: center;
|
|
52
36
|
align-items: center;
|
|
53
|
-
width: 14.28%;
|
|
54
|
-
height: 16.66%;
|
|
55
37
|
cursor: pointer;
|
|
56
38
|
}
|
|
57
39
|
|
|
58
|
-
.
|
|
59
|
-
.monthwidget-month.disabled {
|
|
40
|
+
.datewidget-item.disabled {
|
|
60
41
|
cursor: not-allowed;
|
|
61
42
|
}
|
|
62
43
|
|
|
63
|
-
.
|
|
64
|
-
.
|
|
65
|
-
.monthwidget-month.today,
|
|
66
|
-
.monthwidget-month.selected {
|
|
44
|
+
.datewidget-item.today,
|
|
45
|
+
.datewidget-item.selected {
|
|
67
46
|
position: relative;
|
|
68
47
|
}
|
|
69
48
|
|
|
70
|
-
.
|
|
71
|
-
.
|
|
72
|
-
.monthwidget-month.today::before,
|
|
73
|
-
.monthwidget-month.selected::before {
|
|
49
|
+
.datewidget-item.today::before,
|
|
50
|
+
.datewidget-item.selected::before {
|
|
74
51
|
position: absolute;
|
|
75
52
|
content: '';
|
|
76
53
|
width: 40px;
|
|
@@ -78,3 +55,18 @@
|
|
|
78
55
|
border-radius: 40px;
|
|
79
56
|
z-index: -1;
|
|
80
57
|
}
|
|
58
|
+
|
|
59
|
+
.canlendar-weeks > span {
|
|
60
|
+
flex: auto;
|
|
61
|
+
text-align: center;
|
|
62
|
+
vertical-align: middle;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.canlendar-body {
|
|
66
|
+
height: calc(100% - 72px);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.canlendar-body .datewidget-item {
|
|
70
|
+
width: 14.28%;
|
|
71
|
+
height: 16.66%;
|
|
72
|
+
}
|
package/css/form.css
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
.form-align-left,
|
|
2
2
|
.form-align-right {
|
|
3
3
|
display: flex;
|
|
4
|
-
align-items: center;
|
|
5
4
|
}
|
|
6
5
|
|
|
7
6
|
.form-align-left > label,
|
|
8
7
|
.form-align-right > label {
|
|
8
|
+
margin-top: 4px;
|
|
9
9
|
width: 100px;
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -17,3 +17,9 @@
|
|
|
17
17
|
.form-align-right > label {
|
|
18
18
|
padding-left: 8px;
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
.form-error {
|
|
22
|
+
margin-top: 4px;
|
|
23
|
+
font-size: 12px;
|
|
24
|
+
color: #ff295a;
|
|
25
|
+
}
|
package/demo/css/css.md
CHANGED
|
@@ -287,17 +287,17 @@
|
|
|
287
287
|
.combobox:has(.combobox-input:focus) { border: 1px solid #1b212d; }
|
|
288
288
|
|
|
289
289
|
|
|
290
|
-
# canlendar 日历
|
|
290
|
+
# canlendar monthwidget yearwidget 日历 年月 年
|
|
291
291
|
|
|
292
|
-
.
|
|
293
|
-
.
|
|
294
|
-
.
|
|
295
|
-
.
|
|
296
|
-
.
|
|
297
|
-
.
|
|
298
|
-
.
|
|
299
|
-
.canlendar-date.selected::before { background: #ff4000; }
|
|
292
|
+
.datewidget { border: 1px solid #e4e4e4; }
|
|
293
|
+
.datewidget-header > .icon { stroke: #1b212d; }
|
|
294
|
+
.datewidget-item.disabled { color: #e4e4e4; }
|
|
295
|
+
.datewidget-item.prev-month, .datewidget-item.next-month { color: #888f97; }
|
|
296
|
+
.datewidget-item.today::before { background: #f5f5f5; }
|
|
297
|
+
.datewidget-item.selected { color: white; }
|
|
298
|
+
.datewidget-item.selected::before { background: #ff4000; }
|
|
300
299
|
|
|
300
|
+
.canlendar-weeks > span { color: #888f97; }
|
|
301
301
|
|
|
302
302
|
# datepicker 日期选择
|
|
303
303
|
|
package/demo/src/App.tsx
CHANGED
|
@@ -1,35 +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
7
|
|
|
9
|
-
const Test = (props: { text1: string; text2: string }) => {
|
|
10
|
-
return (
|
|
11
|
-
<div>
|
|
12
|
-
<div>{pickProps(props, ['text1']).text1}</div>
|
|
13
|
-
<div>{omitProps(props, ['text1']).text2}</div>
|
|
14
|
-
</div>
|
|
15
|
-
);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
8
|
export const App = () => {
|
|
19
|
-
let state = reactive({
|
|
20
|
-
text1: '111',
|
|
21
|
-
text2: '222',
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
createEffect(() => {
|
|
25
|
-
console.log(state.text1, state.text2);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
9
|
return (
|
|
29
10
|
<div style={{ 'min-height': '100%' }}>
|
|
30
|
-
<
|
|
31
|
-
<Test text1={state.text1} text2={state.text2}></Test>
|
|
32
|
-
<button onclick={() => batch(() => (state.text1 = state.text2 = '' + Math.random()))}>click</button>
|
|
11
|
+
<CarouselPage></CarouselPage>
|
|
33
12
|
<CanlendarPage></CanlendarPage>
|
|
34
13
|
<DatePickerPage></DatePickerPage>
|
|
35
14
|
<MobileDatePickerPage></MobileDatePickerPage>
|
package/demo/src/css/atomic.css
CHANGED
|
@@ -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
|
-
.
|
|
917
|
-
.
|
|
916
|
+
.datewidget { border: 1px solid #e4e4e4; }
|
|
917
|
+
.datewidget-header > .icon { stroke: #1b212d; }
|
|
918
|
+
.datewidget-item.disabled { color: #e4e4e4; }
|
|
919
|
+
.datewidget-item.prev-month, .datewidget-item.next-month { 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,16 @@
|
|
|
1
1
|
import { Canlendar } from '../../../src/components/Canlendar';
|
|
2
|
+
import { MonthWidget } from '../../../src/components/MonthWidget';
|
|
2
3
|
|
|
3
4
|
export const CanlendarPage = () => {
|
|
4
5
|
return (
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
<div>
|
|
7
|
+
<Canlendar
|
|
8
|
+
value={new Date()}
|
|
9
|
+
onValueChange={(date) => {
|
|
10
|
+
console.log(date);
|
|
11
|
+
}}
|
|
12
|
+
></Canlendar>
|
|
13
|
+
<MonthWidget></MonthWidget>
|
|
14
|
+
</div>
|
|
11
15
|
);
|
|
12
16
|
};
|
|
@@ -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
|
+
};
|
|
@@ -1,8 +1,25 @@
|
|
|
1
|
+
import { reactive } from '../../../src';
|
|
1
2
|
import { ComboBox } from '../../../src/components/ComboBox';
|
|
2
3
|
|
|
3
4
|
export const ComboBoxPage = () => {
|
|
5
|
+
let combobox;
|
|
6
|
+
|
|
7
|
+
const state = reactive({
|
|
8
|
+
value: '111',
|
|
9
|
+
});
|
|
10
|
+
|
|
4
11
|
return (
|
|
5
|
-
<ComboBox
|
|
12
|
+
<ComboBox
|
|
13
|
+
api={(api) => (combobox = api)}
|
|
14
|
+
value={state.value}
|
|
15
|
+
popup={{
|
|
16
|
+
style: { width: '200px', padding: '8px 0' },
|
|
17
|
+
onclick: (event) => {
|
|
18
|
+
state.value = event.target.textContent;
|
|
19
|
+
combobox.closePopup();
|
|
20
|
+
},
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
6
23
|
<div>111</div>
|
|
7
24
|
<div>222</div>
|
|
8
25
|
</ComboBox>
|
package/demo/src/pages/Form.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { reactive } from '../../../src/reactive';
|
|
2
2
|
import { For } from '../../../src/components/For';
|
|
3
3
|
import { TextBox } from '../../../src/components/TextBox';
|
|
4
|
+
import { ComboBox } from '../../../src/components/ComboBox';
|
|
4
5
|
import { Form, FormApi, FormItem, ValidateRules } from '../../../src/components/Form';
|
|
5
6
|
|
|
6
7
|
const rules: ValidateRules = {
|
|
@@ -18,27 +19,52 @@ export const FormPage = () => {
|
|
|
18
19
|
field: 'a',
|
|
19
20
|
label: '111',
|
|
20
21
|
required: true,
|
|
22
|
+
Input: () => <TextBox></TextBox>,
|
|
21
23
|
},
|
|
22
24
|
{
|
|
23
25
|
field: 'b',
|
|
24
26
|
label: '222',
|
|
25
27
|
required: true,
|
|
26
28
|
labelWidth: '100px',
|
|
29
|
+
Input: () => {
|
|
30
|
+
let combobox;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<ComboBox
|
|
34
|
+
api={(api) => (combobox = api)}
|
|
35
|
+
value={state.b}
|
|
36
|
+
popup={{
|
|
37
|
+
onclick: (event) => {
|
|
38
|
+
state.b = +event.target.textContent;
|
|
39
|
+
combobox.closePopup();
|
|
40
|
+
},
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
<div>1</div>
|
|
44
|
+
<div>2</div>
|
|
45
|
+
</ComboBox>
|
|
46
|
+
);
|
|
47
|
+
},
|
|
27
48
|
},
|
|
28
49
|
],
|
|
50
|
+
a: 1,
|
|
51
|
+
b: 2,
|
|
29
52
|
});
|
|
30
53
|
|
|
31
54
|
let form: FormApi;
|
|
32
55
|
|
|
33
56
|
return (
|
|
34
|
-
<Form data={
|
|
57
|
+
<Form data={state} rules={{}} align={state.align} labelWidth={state.labelWidth} api={(api) => (form = api)}>
|
|
35
58
|
<For each={state.items}>
|
|
36
59
|
{(item) => (
|
|
37
60
|
<FormItem field={item.field} label={item.label} required={item.required}>
|
|
38
|
-
<
|
|
61
|
+
<div>
|
|
62
|
+
<item.Input></item.Input>
|
|
63
|
+
</div>
|
|
39
64
|
</FormItem>
|
|
40
65
|
)}
|
|
41
66
|
</For>
|
|
67
|
+
<div>{`a: ${state.a} b: ${state.b}`}</div>
|
|
42
68
|
<button
|
|
43
69
|
type="button"
|
|
44
70
|
onclick={() =>
|
|
@@ -47,6 +73,7 @@ export const FormPage = () => {
|
|
|
47
73
|
label: '???',
|
|
48
74
|
required: false,
|
|
49
75
|
labelWidth: '100px',
|
|
76
|
+
Input: () => <TextBox></TextBox>,
|
|
50
77
|
})
|
|
51
78
|
}
|
|
52
79
|
>
|
|
@@ -61,6 +88,9 @@ export const FormPage = () => {
|
|
|
61
88
|
<button type="button" onclick={() => form.validate()}>
|
|
62
89
|
validate
|
|
63
90
|
</button>
|
|
91
|
+
<button type="button" onclick={() => form.clearErrors()}>
|
|
92
|
+
clearErrors
|
|
93
|
+
</button>
|
|
64
94
|
</Form>
|
|
65
95
|
);
|
|
66
96
|
};
|
package/package.json
CHANGED
|
@@ -24,7 +24,7 @@ const renderDates = (
|
|
|
24
24
|
|
|
25
25
|
for (let i = from; i <= to; i++) {
|
|
26
26
|
items.push(
|
|
27
|
-
`<span class="
|
|
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
|
);
|
|
@@ -162,9 +162,9 @@ export const Canlendar = (
|
|
|
162
162
|
const [currentValue, setCurrentValue] = createSignal(selectedValue() || new Date());
|
|
163
163
|
|
|
164
164
|
return (
|
|
165
|
-
<div class={combineClass('canlendar', props.class)} {...omitProps(props, OMIT_PROPS)}>
|
|
166
|
-
<div class="canlendar-header">
|
|
167
|
-
<div ref={domTitle as any} class="
|
|
165
|
+
<div class={combineClass('canlendar datewidget', props.class)} {...omitProps(props, OMIT_PROPS)}>
|
|
166
|
+
<div class="datewidget-header canlendar-header">
|
|
167
|
+
<div ref={domTitle as any} class="datewidget-title">
|
|
168
168
|
{replaceTemplate(i18n.Month, currentValue().getFullYear(), formatMonth(currentValue()))}
|
|
169
169
|
</div>
|
|
170
170
|
<svg class="icon icon-s" aria-hidden={true} onclick={() => setCurrentValue(switchMonth(currentValue(), -1))}>
|
|
@@ -174,12 +174,12 @@ export const Canlendar = (
|
|
|
174
174
|
<use href="#icon-forward"></use>
|
|
175
175
|
</svg>
|
|
176
176
|
</div>
|
|
177
|
-
<div class="canlendar-weeks">
|
|
177
|
+
<div class="datewidget-header canlendar-weeks">
|
|
178
178
|
<For each={i18n.Weeks}>{(item) => <span>{item}</span>}</For>
|
|
179
179
|
</div>
|
|
180
180
|
<div
|
|
181
181
|
ref={domBody as any}
|
|
182
|
-
class="canlendar-body"
|
|
182
|
+
class="datewidget-body canlendar-body"
|
|
183
183
|
onclick={(event) => {
|
|
184
184
|
let target = event.target as HTMLElement;
|
|
185
185
|
let date, onValueChange;
|
|
@@ -15,9 +15,7 @@ import {
|
|
|
15
15
|
import { For } from './For';
|
|
16
16
|
import { Icon } from './Icon';
|
|
17
17
|
|
|
18
|
-
const
|
|
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', '
|
|
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
|
-
|
|
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
|
|
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
|
|
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])
|
|
178
|
-
pressdownScroll = ref
|
|
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
|
|
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])
|
|
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
|
|
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,7 +1,8 @@
|
|
|
1
1
|
import { JSX } from '../jsx';
|
|
2
|
-
import { combineClass, omitProps } from '../reactive';
|
|
2
|
+
import { combineClass, omitProps, useContext, watch } from '../reactive';
|
|
3
3
|
import { disableAutoCloseEvent } from '../dom';
|
|
4
4
|
import { Popup, PopupApi, PopupProps } from './Popup';
|
|
5
|
+
import { FormItemContext } from './provider';
|
|
5
6
|
|
|
6
7
|
const OMIT_PROPS = ['class', 'value', 'readonly', 'popupOnFocus', 'popup', 'onPopup', 'api', 'children'] as const;
|
|
7
8
|
|
|
@@ -14,7 +15,13 @@ export const ComboBox = (
|
|
|
14
15
|
/**
|
|
15
16
|
* 值
|
|
16
17
|
*/
|
|
17
|
-
value?:
|
|
18
|
+
value?: any;
|
|
19
|
+
/**
|
|
20
|
+
* 值样式
|
|
21
|
+
*
|
|
22
|
+
* @param value 当前值
|
|
23
|
+
*/
|
|
24
|
+
format?(value: any): string;
|
|
18
25
|
/**
|
|
19
26
|
* 是否只读
|
|
20
27
|
*/
|
|
@@ -36,12 +43,24 @@ export const ComboBox = (
|
|
|
36
43
|
props.api && props.api(popup);
|
|
37
44
|
};
|
|
38
45
|
|
|
46
|
+
const formItem = useContext(FormItemContext);
|
|
47
|
+
|
|
48
|
+
const initFormItem = (dom: HTMLInputElement) => {
|
|
49
|
+
watch(
|
|
50
|
+
() => props.value,
|
|
51
|
+
(value) => formItem.setValue(value),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
formItem.init(dom);
|
|
55
|
+
};
|
|
56
|
+
|
|
39
57
|
return (
|
|
40
58
|
<div class={combineClass('combobox', props.class)} {...omitProps(props, OMIT_PROPS)}>
|
|
41
59
|
<div class="combobox-host" {...disableAutoCloseEvent}>
|
|
42
60
|
<input
|
|
61
|
+
ref={formItem && initFormItem}
|
|
43
62
|
class="combobox-input"
|
|
44
|
-
value={props.value || ''}
|
|
63
|
+
value={props.format ? props.format(props.value) : '' + (props.value || '')}
|
|
45
64
|
readonly={props.readonly}
|
|
46
65
|
onfocus={() => props.popupOnFocus && popup.openPopup()}
|
|
47
66
|
onclick={() => props.readonly && !props.popupOnFocus && popup.togglePopup()}
|
package/src/components/Form.tsx
CHANGED
|
@@ -33,10 +33,6 @@ export interface FormItemProps {
|
|
|
33
33
|
* 是否隐藏
|
|
34
34
|
*/
|
|
35
35
|
hidden?: boolean;
|
|
36
|
-
/**
|
|
37
|
-
* 点击 label 自动获取焦点的组件(选择器)
|
|
38
|
-
*/
|
|
39
|
-
for?: string;
|
|
40
36
|
/**
|
|
41
37
|
* 错误信息
|
|
42
38
|
*/
|
|
@@ -47,6 +43,55 @@ export interface FormItemProps {
|
|
|
47
43
|
requiredError?: string;
|
|
48
44
|
}
|
|
49
45
|
|
|
46
|
+
/**
|
|
47
|
+
* 表单外部调用接口
|
|
48
|
+
*/
|
|
49
|
+
export interface FormApi {
|
|
50
|
+
/**
|
|
51
|
+
* 校验表单
|
|
52
|
+
*
|
|
53
|
+
* @param filter 过滤器(校验部分字段)
|
|
54
|
+
*/
|
|
55
|
+
validate(filter?: (target: ValidateTarget) => void | boolean): Promise<boolean>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 滚动到第一个错误位置
|
|
59
|
+
*/
|
|
60
|
+
scrollToError(): void;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 清除所有错误信息
|
|
64
|
+
*/
|
|
65
|
+
clearErrors(): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface FormProps {
|
|
69
|
+
/**
|
|
70
|
+
* 表单数据
|
|
71
|
+
*/
|
|
72
|
+
data: object;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 表单校验规则
|
|
76
|
+
*/
|
|
77
|
+
rules: ValidateRules;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* label 对齐方式
|
|
81
|
+
*/
|
|
82
|
+
align?: 'left' | 'top' | 'right';
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 标签宽度
|
|
86
|
+
*/
|
|
87
|
+
labelWidth?: string;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 外部调用接口
|
|
91
|
+
*/
|
|
92
|
+
api?: (api: FormApi) => void;
|
|
93
|
+
}
|
|
94
|
+
|
|
50
95
|
/**
|
|
51
96
|
* 校验目标
|
|
52
97
|
*/
|
|
@@ -198,11 +243,6 @@ export interface ValidateRules {
|
|
|
198
243
|
[key: string]: ValidateRule;
|
|
199
244
|
}
|
|
200
245
|
|
|
201
|
-
/**
|
|
202
|
-
* 表单上下文
|
|
203
|
-
*/
|
|
204
|
-
const FormContext = createContext<Pick<FormItemProps, 'align' | 'labelWidth'>>();
|
|
205
|
-
|
|
206
246
|
const replaceError = (item: FormItemProps, error: string, value?: unknown) => {
|
|
207
247
|
let label = item.label;
|
|
208
248
|
|
|
@@ -393,18 +433,20 @@ const validate = async (
|
|
|
393
433
|
let error;
|
|
394
434
|
|
|
395
435
|
// 有设置了字段且未隐藏
|
|
396
|
-
if (!item.hidden && (field =
|
|
436
|
+
if (!item.hidden && (field = item.field)) {
|
|
437
|
+
let fields = field.split('.');
|
|
438
|
+
|
|
397
439
|
// 必填
|
|
398
440
|
if (item.required) {
|
|
399
441
|
// 当前值
|
|
400
|
-
let value = findValue(data,
|
|
442
|
+
let value = findValue(data, fields);
|
|
401
443
|
|
|
402
444
|
if (value == null || value === '') {
|
|
403
445
|
error = replaceError(item, item.requiredError || i18n.Required, value);
|
|
404
446
|
}
|
|
405
|
-
} else if ((rule = findRule(rules,
|
|
447
|
+
} else if ((rule = findRule(rules, fields))) {
|
|
406
448
|
// 当前值
|
|
407
|
-
let value = findValue(data,
|
|
449
|
+
let value = findValue(data, fields);
|
|
408
450
|
// 校验目标
|
|
409
451
|
let target = {
|
|
410
452
|
data,
|
|
@@ -443,6 +485,41 @@ const validate = async (
|
|
|
443
485
|
return result;
|
|
444
486
|
};
|
|
445
487
|
|
|
488
|
+
function scrollToError(this: HTMLFormElement) {
|
|
489
|
+
let error = this.querySelector('.form-error') as HTMLElement;
|
|
490
|
+
|
|
491
|
+
if (error) {
|
|
492
|
+
(error.parentNode as HTMLElement).scrollIntoView();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function clearErrors(this: HTMLFormElement) {
|
|
497
|
+
let errors = this.querySelectorAll('.form-error');
|
|
498
|
+
|
|
499
|
+
for (let i = errors.length; i--; ) {
|
|
500
|
+
errors[i].parentNode.removeChild(errors[i]);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const setValue = (data: object, field: string, value: any) => {
|
|
505
|
+
let fields = field.split('.');
|
|
506
|
+
let last = fields.length - 1;
|
|
507
|
+
|
|
508
|
+
for (let i = 0; i < last; i++) {
|
|
509
|
+
if ((data = data[fields[i]])) {
|
|
510
|
+
} else {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
data[fields[last]] = value;
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* 表单上下文
|
|
520
|
+
*/
|
|
521
|
+
const FormContext = createContext<FormProps>();
|
|
522
|
+
|
|
446
523
|
const OMIT_ITEM_PROPS = [
|
|
447
524
|
'class',
|
|
448
525
|
'field',
|
|
@@ -451,7 +528,6 @@ const OMIT_ITEM_PROPS = [
|
|
|
451
528
|
'align',
|
|
452
529
|
'required',
|
|
453
530
|
'hidden',
|
|
454
|
-
'for',
|
|
455
531
|
'error',
|
|
456
532
|
'requiredError',
|
|
457
533
|
'children',
|
|
@@ -461,17 +537,27 @@ const OMIT_ITEM_PROPS = [
|
|
|
461
537
|
* 表单项
|
|
462
538
|
*/
|
|
463
539
|
export const FormItem = (props?: JSX.HTMLAttributes<never> & FormItemProps) => {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
(
|
|
470
|
-
|
|
540
|
+
let domItem: HTMLElement;
|
|
541
|
+
let domInput: HTMLElement;
|
|
542
|
+
|
|
543
|
+
const form = useContext(FormContext);
|
|
544
|
+
const provider = {
|
|
545
|
+
getValue: () => findValue(form.data, props.field.split('.')),
|
|
546
|
+
setValue: (value: any) => setValue(form.data, props.field, value),
|
|
547
|
+
init: (input: HTMLElement) => {
|
|
548
|
+
if (!domInput) {
|
|
549
|
+
domInput = input;
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
};
|
|
471
554
|
|
|
472
555
|
return (
|
|
473
556
|
<div
|
|
474
|
-
ref={
|
|
557
|
+
ref={(dom) => {
|
|
558
|
+
domItem = dom;
|
|
559
|
+
(domItem as any).FORM_ITEM = props;
|
|
560
|
+
}}
|
|
475
561
|
class={combineClass(
|
|
476
562
|
'form-item',
|
|
477
563
|
'form-align-' + (props.align || form.align || 'left'),
|
|
@@ -481,104 +567,38 @@ export const FormItem = (props?: JSX.HTMLAttributes<never> & FormItemProps) => {
|
|
|
481
567
|
)}
|
|
482
568
|
{...omitProps(props, OMIT_ITEM_PROPS)}
|
|
483
569
|
>
|
|
484
|
-
<label
|
|
570
|
+
<label style={{ width: props.labelWidth || form.labelWidth }} onclick={() => domInput && domInput.focus()}>
|
|
485
571
|
{props.label}
|
|
486
572
|
</label>
|
|
487
573
|
<div class="form-body">
|
|
488
|
-
<FormItemContext.Provider value={
|
|
574
|
+
<FormItemContext.Provider value={provider}>{props.children}</FormItemContext.Provider>
|
|
489
575
|
</div>
|
|
490
576
|
</div>
|
|
491
577
|
);
|
|
492
578
|
};
|
|
493
579
|
|
|
494
|
-
function scrollToError(this: HTMLFormElement) {
|
|
495
|
-
let error = this.querySelector('.form-error') as HTMLElement;
|
|
496
|
-
|
|
497
|
-
if (error) {
|
|
498
|
-
(error.parentNode as HTMLElement).scrollIntoView();
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
function clearErrors(this: HTMLFormElement) {
|
|
503
|
-
let errors = this.querySelectorAll('.form-error');
|
|
504
|
-
|
|
505
|
-
for (let i = errors.length; i--; ) {
|
|
506
|
-
errors[i].parentNode.removeChild(errors[i]);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
580
|
const OMIT_FORM_PROPS = ['data', 'rules', 'align', 'labelWidth', 'api', 'children'] as const;
|
|
511
581
|
|
|
512
|
-
/**
|
|
513
|
-
* 表单外部调用接口
|
|
514
|
-
*/
|
|
515
|
-
export interface FormApi {
|
|
516
|
-
/**
|
|
517
|
-
* 校验表单
|
|
518
|
-
*
|
|
519
|
-
* @param filter 过滤器(校验部分字段)
|
|
520
|
-
*/
|
|
521
|
-
validate(filter?: (target: ValidateTarget) => void | boolean): Promise<boolean>;
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* 滚动到第一个错误位置
|
|
525
|
-
*/
|
|
526
|
-
scrollToError(): void;
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* 清除所有错误信息
|
|
530
|
-
*/
|
|
531
|
-
clearErrors(): void;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
582
|
/**
|
|
535
583
|
* 表单组件
|
|
536
584
|
*/
|
|
537
|
-
export const Form = (
|
|
538
|
-
props?: JSX.HTMLAttributes<never> & {
|
|
539
|
-
/**
|
|
540
|
-
* 表单数据
|
|
541
|
-
*/
|
|
542
|
-
data: object;
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* 表单校验规则
|
|
546
|
-
*/
|
|
547
|
-
rules: ValidateRules;
|
|
548
|
-
|
|
549
|
-
/**
|
|
550
|
-
* label 对齐方式
|
|
551
|
-
*/
|
|
552
|
-
align?: 'left' | 'top' | 'right';
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* 标签宽度
|
|
556
|
-
*/
|
|
557
|
-
labelWidth?: string;
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* 外部调用接口
|
|
561
|
-
*/
|
|
562
|
-
api?: (api: FormApi) => void;
|
|
563
|
-
},
|
|
564
|
-
) => {
|
|
565
|
-
let form: HTMLFormElement;
|
|
566
|
-
|
|
567
|
-
onMount(() => {
|
|
568
|
-
// 初始化外部调用接口
|
|
569
|
-
props.api &&
|
|
570
|
-
props.api(
|
|
571
|
-
(form.api = {
|
|
572
|
-
validate: (filter?: (target: ValidateTarget) => void | boolean) =>
|
|
573
|
-
validate(form, props.rules, props.data, filter),
|
|
574
|
-
scrollToError: scrollToError.bind(form),
|
|
575
|
-
clearErrors: clearErrors.bind(form),
|
|
576
|
-
}),
|
|
577
|
-
);
|
|
578
|
-
});
|
|
579
|
-
|
|
585
|
+
export const Form = (props?: JSX.HTMLAttributes<never> & FormProps) => {
|
|
580
586
|
return (
|
|
581
|
-
<form
|
|
587
|
+
<form
|
|
588
|
+
ref={(dom) => {
|
|
589
|
+
// 初始化外部调用接口
|
|
590
|
+
props.api &&
|
|
591
|
+
props.api(
|
|
592
|
+
(dom.api = {
|
|
593
|
+
validate: (filter?: (target: ValidateTarget) => void | boolean) =>
|
|
594
|
+
validate(dom, props.rules, props.data, filter),
|
|
595
|
+
scrollToError: scrollToError.bind(dom),
|
|
596
|
+
clearErrors: clearErrors.bind(dom),
|
|
597
|
+
}),
|
|
598
|
+
);
|
|
599
|
+
}}
|
|
600
|
+
{...omitProps(props, OMIT_FORM_PROPS)}
|
|
601
|
+
>
|
|
582
602
|
<FormContext.Provider value={props}>{props.children}</FormContext.Provider>
|
|
583
603
|
</form>
|
|
584
604
|
);
|
|
@@ -29,7 +29,7 @@ const switchMonth = (value: [year: number, month: number], offset: 1 | -1) => {
|
|
|
29
29
|
return [year, month];
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
const MONTH_LIST = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
|
32
|
+
const MONTH_LIST = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4];
|
|
33
33
|
|
|
34
34
|
export const MonthWidget = (
|
|
35
35
|
props: Omit<JSX.HTMLAttributes<never>, 'children'> & {
|
|
@@ -54,9 +54,9 @@ export const MonthWidget = (
|
|
|
54
54
|
const [currentValue, setCurrentValue] = createSignal(selectedValue() || getCurrentMonth());
|
|
55
55
|
|
|
56
56
|
return (
|
|
57
|
-
<div>
|
|
58
|
-
<div class="
|
|
59
|
-
<div ref={domTitle as any} class="
|
|
57
|
+
<div class={combineClass('monthwidget datewidget', props.class)}>
|
|
58
|
+
<div class="datewidget-header">
|
|
59
|
+
<div ref={domTitle as any} class="datewidget-title">
|
|
60
60
|
{replaceTemplate(i18n.Month, currentValue()[0], formatMonth(currentValue()[1]))}
|
|
61
61
|
</div>
|
|
62
62
|
<svg class="icon icon-s" aria-hidden={true} onclick={() => setCurrentValue(switchMonth(selectedValue(), -1))}>
|
|
@@ -68,7 +68,7 @@ export const MonthWidget = (
|
|
|
68
68
|
</div>
|
|
69
69
|
<div
|
|
70
70
|
ref={domBody as any}
|
|
71
|
-
class="
|
|
71
|
+
class="datewidget-body"
|
|
72
72
|
onclick={(event) => {
|
|
73
73
|
let target = event.target as HTMLElement;
|
|
74
74
|
let value, onValueChange;
|
package/src/components/Popup.tsx
CHANGED
|
@@ -155,6 +155,10 @@ const OMIT_PROPS = ['onPopup', 'api', 'children'] as const;
|
|
|
155
155
|
* 弹出层外部访问接口
|
|
156
156
|
*/
|
|
157
157
|
export interface PopupApi {
|
|
158
|
+
/**
|
|
159
|
+
* 是否已经弹出
|
|
160
|
+
*/
|
|
161
|
+
popup: boolean;
|
|
158
162
|
/**
|
|
159
163
|
* 打开弹出框
|
|
160
164
|
*/
|
|
@@ -162,7 +166,7 @@ export interface PopupApi {
|
|
|
162
166
|
/**
|
|
163
167
|
* 关闭弹出框
|
|
164
168
|
*/
|
|
165
|
-
|
|
169
|
+
closePopup(): void;
|
|
166
170
|
/**
|
|
167
171
|
* 显示或关闭弹出层
|
|
168
172
|
*/
|
|
@@ -192,8 +196,11 @@ export const Popup = (props?: JSX.HTMLAttributes<never> & PopupProps) => {
|
|
|
192
196
|
// 初始化外部调用接口
|
|
193
197
|
props.api &&
|
|
194
198
|
props.api({
|
|
199
|
+
get popup() {
|
|
200
|
+
return currentPopup.dom === popup;
|
|
201
|
+
},
|
|
195
202
|
openPopup: () => showPopup(popup, props.onPopup),
|
|
196
|
-
|
|
203
|
+
closePopup: () => currentPopup.dom === popup && hidePopup(),
|
|
197
204
|
togglePopup: () => togglePopup(popup, props.onPopup),
|
|
198
205
|
});
|
|
199
206
|
|
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import { JSX } from '../jsx';
|
|
2
|
-
import { combineClass, omitProps } from '../reactive';
|
|
2
|
+
import { combineClass, omitProps, useContext } from '../reactive';
|
|
3
|
+
import { FormItemContext } from './provider';
|
|
3
4
|
|
|
4
|
-
const OMIT_PROPS = ['class'] as const;
|
|
5
|
+
const OMIT_PROPS = ['class', 'value', 'onchange'] as const;
|
|
5
6
|
|
|
6
|
-
export const TextBox = (props?: JSX.
|
|
7
|
-
|
|
7
|
+
export const TextBox = (props?: JSX.InputHTMLAttributes<never>) => {
|
|
8
|
+
const formItem = useContext(FormItemContext);
|
|
9
|
+
|
|
10
|
+
const initFormItem = (dom: HTMLInputElement) => {
|
|
11
|
+
dom.addEventListener('change', () => formItem.setValue(dom.value));
|
|
12
|
+
formItem.init(dom);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<input
|
|
17
|
+
ref={formItem && initFormItem}
|
|
18
|
+
type="text"
|
|
19
|
+
class={combineClass('textbox', props.class)}
|
|
20
|
+
value={formItem ? formItem.getValue() : props.value}
|
|
21
|
+
{...omitProps(props, OMIT_PROPS)}
|
|
22
|
+
></input>
|
|
23
|
+
);
|
|
8
24
|
};
|
|
@@ -2,7 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
import { createContext } from '../reactive';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* 表单项提供者值类型
|
|
7
|
+
*/
|
|
8
|
+
export interface FormItemProviderValue {
|
|
9
|
+
/**
|
|
10
|
+
* 获取绑定值
|
|
11
|
+
*/
|
|
12
|
+
getValue(): any;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 设置绑定值
|
|
16
|
+
*
|
|
17
|
+
* @param value 绑定值
|
|
18
|
+
*/
|
|
19
|
+
setValue(value: any): void;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 初始化表单输入组件
|
|
23
|
+
*
|
|
24
|
+
* @param input 表单输入组件
|
|
25
|
+
*/
|
|
26
|
+
init(input: HTMLElement): boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
5
29
|
/**
|
|
6
30
|
* 表单项上下文
|
|
7
31
|
*/
|
|
8
|
-
export const FormItemContext = createContext<
|
|
32
|
+
export const FormItemContext = createContext<FormItemProviderValue>();
|