Skip to content

Commit 3f71caa

Browse files
Merge pull request #1683 from iamfaran/feat/LCS-51-tab-index
[FEAT]: Tab Index for Inputs
2 parents f2b7d10 + 41cb384 commit 3f71caa

20 files changed

+100
-11
lines changed

client/packages/lowcoder/src/comps/comps/autoCompleteComp/autoCompleteComp.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
import styled, { css } from "styled-components";
2121
import { UICompBuilder } from "../../generators";
2222
import { FormDataPropertyView } from "../formComp/formDataConstants";
23-
import { jsonControl } from "comps/controls/codeControl";
23+
import { jsonControl, NumberControl } from "comps/controls/codeControl";
2424
import { dropdownControl } from "comps/controls/dropdownControl";
2525
import {
2626
getStyle,
@@ -92,6 +92,7 @@ const childrenMap = {
9292
inputFieldStyle: styleControl(InputLikeStyle , 'inputFieldStyle'),
9393
childrenInputFieldStyle: styleControl(ChildrenMultiSelectStyle, 'childrenInputFieldStyle'),
9494
animationStyle: styleControl(AnimationStyle , 'animationStyle'),
95+
tabIndex: NumberControl,
9596
};
9697

9798
const getValidate = (value: any): "" | "warning" | "error" | undefined => {
@@ -271,6 +272,7 @@ let AutoCompleteCompBase = (function () {
271272
suffix={hasIcon(props.suffixIcon) && props.suffixIcon}
272273
status={getValidate(validateState)}
273274
onPressEnter={undefined}
275+
tabIndex={typeof props.tabIndex === 'number' ? props.tabIndex : undefined}
274276
/>
275277
</AutoComplete>
276278
</>
@@ -354,6 +356,9 @@ let AutoCompleteCompBase = (function () {
354356
>
355357
{children.animationStyle.getPropertyView()}
356358
</Section>
359+
<Section name={sectionNames.advanced}>
360+
{children.tabIndex.propertyView({ label: trans("prop.tabIndex") })}
361+
</Section>
357362
</>
358363
);
359364
})

client/packages/lowcoder/src/comps/comps/dateComp/dateComp.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"
44
import {
55
BoolCodeControl,
66
CustomRuleControl,
7+
NumberControl,
78
RangeControl,
89
StringControl,
910
} from "../../controls/codeControl";
@@ -99,6 +100,7 @@ const commonChildren = {
99100
childrenInputFieldStyle: styleControl(ChildrenMultiSelectStyle, 'childrenInputFieldStyle'),
100101
timeZone: dropdownControl(timeZoneOptions, Intl.DateTimeFormat().resolvedOptions().timeZone),
101102
pickerMode: dropdownControl(PickerModeOptions, 'date'),
103+
tabIndex: NumberControl,
102104
};
103105
type CommonChildrenType = RecordConstructorToComp<typeof commonChildren>;
104106

@@ -185,6 +187,7 @@ export type DateCompViewProps = Pick<
185187
disabledTime: () => ReturnType<typeof disabledTime>;
186188
suffixIcon: ReactNode;
187189
placeholder?: string | [string, string];
190+
tabIndex?: number;
188191
};
189192

190193
const getFormattedDate = (
@@ -281,6 +284,7 @@ const DatePickerTmpCmp = new UICompBuilder(childrenMap, (props) => {
281284
onFocus={() => props.onEvent("focus")}
282285
onBlur={() => props.onEvent("blur")}
283286
suffixIcon={hasIcon(props.suffixIcon) && props.suffixIcon}
287+
tabIndex={typeof props.tabIndex === 'number' ? props.tabIndex : undefined}
284288
/>
285289
),
286290
showValidationWhenEmpty: props.showValidationWhenEmpty,
@@ -340,6 +344,7 @@ const DatePickerTmpCmp = new UICompBuilder(childrenMap, (props) => {
340344
<><Section name={sectionNames.advanced}>
341345
{timeFields(children, isMobile)}
342346
{children.suffixIcon.propertyView({ label: trans("button.suffixIcon") })}
347+
{children.tabIndex.propertyView({ label: trans("prop.tabIndex") })}
343348
</Section></>
344349
)}
345350
{(useContext(EditorContext).editorModeStatus === "logic" || useContext(EditorContext).editorModeStatus === "both") && !isMobile && commonAdvanceSection(children)}
@@ -475,7 +480,9 @@ let DateRangeTmpCmp = (function () {
475480
}}
476481
onFocus={() => props.onEvent("focus")}
477482
onBlur={() => props.onEvent("blur")}
478-
suffixIcon={hasIcon(props.suffixIcon) && props.suffixIcon} />
483+
suffixIcon={hasIcon(props.suffixIcon) && props.suffixIcon}
484+
tabIndex={typeof props.tabIndex === 'number' ? props.tabIndex : undefined}
485+
/>
479486
);
480487

481488
const startResult = validate({ ...props, value: props.start });
@@ -553,6 +560,7 @@ let DateRangeTmpCmp = (function () {
553560
<><Section name={sectionNames.advanced}>
554561
{timeFields(children, isMobile)}
555562
{children.suffixIcon.propertyView({ label: trans("button.suffixIcon") })}
563+
{children.tabIndex.propertyView({ label: trans("prop.tabIndex") })}
556564
</Section></>
557565
)}
558566
{(useContext(EditorContext).editorModeStatus === "logic" || useContext(EditorContext).editorModeStatus === "both") && commonAdvanceSection(children)}

client/packages/lowcoder/src/comps/comps/dateComp/dateRangeUIView.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ export interface DateRangeUIViewProps extends DateCompViewProps {
4444
placeholder?: string | [string, string];
4545
onChange: (start?: dayjs.Dayjs | null, end?: dayjs.Dayjs | null) => void;
4646
onPanelChange: (value: any, mode: [string, string]) => void;
47-
onClickDateRangeTimeZone:(value:any)=>void
47+
onClickDateRangeTimeZone:(value:any)=>void;
48+
tabIndex?: number;
4849
}
4950

5051
export const DateRangeUIView = (props: DateRangeUIViewProps) => {

client/packages/lowcoder/src/comps/comps/dateComp/dateUIView.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export interface DataUIViewProps extends DateCompViewProps {
3939
onChange: DatePickerProps<Dayjs>['onChange'];
4040
onPanelChange: () => void;
4141
onClickDateTimeZone:(value:any)=>void;
42-
42+
tabIndex?: number;
4343
}
4444

4545
const DateMobileUIView = React.lazy(() =>

client/packages/lowcoder/src/comps/comps/dateComp/timeComp.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"
44
import {
55
BoolCodeControl,
66
CustomRuleControl,
7+
NumberControl,
78
RangeControl,
89
StringControl,
910
} from "../../controls/codeControl";
@@ -92,6 +93,7 @@ const commonChildren = {
9293
suffixIcon: withDefault(IconControl, "/icon:regular/clock"),
9394
timeZone: dropdownControl(timeZoneOptions, Intl.DateTimeFormat().resolvedOptions().timeZone),
9495
viewRef: RefControl<CommonPickerMethods>,
96+
tabIndex: NumberControl,
9597
...validationChildren,
9698
};
9799

@@ -212,6 +214,7 @@ const TimePickerTmpCmp = new UICompBuilder(childrenMap, (props) => {
212214
onFocus={() => props.onEvent("focus")}
213215
onBlur={() => props.onEvent("blur")}
214216
suffixIcon={hasIcon(props.suffixIcon) && props.suffixIcon}
217+
tabIndex={typeof props.tabIndex === 'number' ? props.tabIndex : undefined}
215218
/>
216219
),
217220
showValidationWhenEmpty: props.showValidationWhenEmpty,
@@ -263,6 +266,7 @@ const TimePickerTmpCmp = new UICompBuilder(childrenMap, (props) => {
263266
{commonAdvanceSection(children)}
264267
{children.use12Hours.propertyView({ label: trans("prop.use12Hours") })}
265268
{children.suffixIcon.propertyView({ label: trans("button.suffixIcon") })}
269+
{children.tabIndex.propertyView({ label: trans("prop.tabIndex") })}
266270
</Section>
267271
)}
268272

@@ -368,6 +372,7 @@ const TimeRangeTmpCmp = (function () {
368372
onFocus={() => props.onEvent("focus")}
369373
onBlur={() => props.onEvent("blur")}
370374
suffixIcon={hasIcon(props.suffixIcon) && props.suffixIcon}
375+
tabIndex={typeof props.tabIndex === 'number' ? props.tabIndex : undefined}
371376
/>
372377
);
373378

@@ -439,6 +444,7 @@ const TimeRangeTmpCmp = (function () {
439444
{commonAdvanceSection(children)}
440445
{children.use12Hours.propertyView({ label: trans("prop.use12Hours") })}
441446
{children.suffixIcon.propertyView({ label: trans("button.suffixIcon") })}
447+
{children.tabIndex.propertyView({ label: trans("prop.tabIndex") })}
442448
</Section>
443449
)}
444450

client/packages/lowcoder/src/comps/comps/dateComp/timeRangeUIView.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface TimeRangeUIViewProps extends TimeCompViewProps {
3939
placeholder?: string | [string, string];
4040
onChange: (start?: dayjs.Dayjs | null, end?: dayjs.Dayjs | null) => void;
4141
handleTimeRangeZoneChange: (value:any) => void;
42+
tabIndex?: number;
4243
}
4344

4445
export const TimeRangeUIView = (props: TimeRangeUIViewProps) => {

client/packages/lowcoder/src/comps/comps/dateComp/timeUIView.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface TimeUIViewProps extends TimeCompViewProps {
3434
value: dayjs.Dayjs | null;
3535
onChange: (value: dayjs.Dayjs | null) => void;
3636
handleTimeZoneChange: (value:any) => void;
37+
tabIndex?: number;
3738
}
3839

3940
export const TimeUIView = (props: TimeUIViewProps) => {

client/packages/lowcoder/src/comps/comps/numberInputComp/numberInputComp.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ const childrenMap = {
272272
min: UndefinedNumberControl,
273273
max: UndefinedNumberControl,
274274
customRule: CustomRuleControl,
275+
tabIndex: NumberControl,
275276

276277
...formDataChildren,
277278
};
@@ -330,6 +331,7 @@ const CustomInputNumber = (props: RecordConstructorToView<typeof childrenMap>) =
330331
precision={props.precision}
331332
$style={props.inputFieldStyle}
332333
prefix={hasIcon(props.prefixIcon) ? props.prefixIcon : props.prefixText.value}
334+
tabIndex={typeof props.tabIndex === 'number' ? props.tabIndex : undefined}
333335
onPressEnter={() => {
334336
handleFinish();
335337
props.onEvent("submit");
@@ -436,6 +438,7 @@ let NumberInputTmpComp = (function () {
436438
})}
437439
{children.controls.propertyView({ label: trans("numberInput.controls") })}
438440
{readOnlyPropertyView(children)}
441+
{children.tabIndex.propertyView({ label: trans("prop.tabIndex") })}
439442
</Section>
440443
)}
441444

client/packages/lowcoder/src/comps/comps/numberInputComp/rangeSliderComp.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import { CommonNameConfig, NameConfig, withExposingConfigs } from "../../generat
66
import { SliderChildren, SliderPropertyView, SliderStyled, SliderWrapper } from "./sliderCompConstants";
77
import { hasIcon } from "comps/utils";
88
import { BoolControl } from "comps/controls/boolControl";
9+
import { NumberControl } from "comps/controls/codeControl";
910

1011
const RangeSliderBasicComp = (function () {
1112
const childrenMap = {
1213
...SliderChildren,
1314
start: numberExposingStateControl("start", 10),
1415
end: numberExposingStateControl("end", 60),
1516
vertical: BoolControl,
17+
tabIndex: NumberControl,
1618
};
1719
return new UICompBuilder(childrenMap, (props, dispatch) => {
1820
return props.label({
@@ -36,6 +38,7 @@ const RangeSliderBasicComp = (function () {
3638
$style={props.inputFieldStyle}
3739
style={{ margin: 0 }}
3840
$vertical={Boolean(props.vertical) || false}
41+
tabIndex={typeof props.tabIndex === 'number' ? props.tabIndex : undefined}
3942
onChange={([start, end]) => {
4043
props.start.onChange(start);
4144
props.end.onChange(end);
@@ -60,6 +63,7 @@ const RangeSliderBasicComp = (function () {
6063
tooltip: trans("rangeSlider.stepTooltip"),
6164
})}
6265
{children.vertical.propertyView({ label: trans("slider.vertical") })}
66+
{children.tabIndex.propertyView({ label: trans("prop.tabIndex") })}
6367
</Section>
6468

6569
<SliderPropertyView {...children} />

client/packages/lowcoder/src/comps/comps/numberInputComp/sliderComp.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { formDataChildren, FormDataPropertyView } from "../formComp/formDataCons
77
import { SliderChildren, SliderPropertyView, SliderStyled, SliderWrapper } from "./sliderCompConstants";
88
import { hasIcon } from "comps/utils";
99
import { BoolControl } from "comps/controls/boolControl";
10+
import { NumberControl } from "comps/controls/codeControl";
1011

1112
const SliderBasicComp = (function () {
1213
/**
@@ -16,6 +17,7 @@ const SliderBasicComp = (function () {
1617
...SliderChildren,
1718
value: numberExposingStateControl("value", 60),
1819
vertical: BoolControl,
20+
tabIndex: NumberControl,
1921
...formDataChildren,
2022
};
2123
return new UICompBuilder(childrenMap, (props) => {
@@ -39,6 +41,7 @@ const SliderBasicComp = (function () {
3941
$style={props.inputFieldStyle}
4042
style={{margin: 0}}
4143
$vertical={Boolean(props.vertical) || false}
44+
tabIndex={typeof props.tabIndex === 'number' ? props.tabIndex : undefined}
4245
onChange={(e) => {
4346
props.value.onChange(e);
4447
props.onEvent("change");
@@ -61,6 +64,7 @@ const SliderBasicComp = (function () {
6164
tooltip: trans("slider.stepTooltip"),
6265
})}
6366
{children.vertical.propertyView({ label: trans("slider.vertical") })}
67+
{children.tabIndex.propertyView({ label: trans("prop.tabIndex") })}
6468
</Section>
6569
<FormDataPropertyView {...children} />
6670
<SliderPropertyView {...children} />

client/packages/lowcoder/src/comps/comps/richTextEditorComp.tsx

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { StringControl } from "comps/controls/codeControl";
1+
import { StringControl, NumberControl } from "comps/controls/codeControl";
22
import { BoolControl } from "comps/controls/boolControl";
33
import { BoolCodeControl } from "../controls/codeControl";
44
import { stringExposingStateControl } from "comps/controls/codeStateControl";
@@ -180,6 +180,7 @@ const childrenMap = {
180180
toolbar: withDefault(StringControl, JSON.stringify(toolbarOptions)),
181181
onEvent: ChangeEventHandlerControl,
182182
style: styleControl(RichTextEditorStyle , 'style'),
183+
tabIndex: NumberControl,
183184

184185
...formDataChildren,
185186
};
@@ -196,6 +197,7 @@ interface IProps {
196197
onChange: (value: string) => void;
197198
$style: RichTextEditorStyleType;
198199
contentScrollBar: boolean;
200+
tabIndex?: number;
199201
}
200202

201203
const ReactQuillEditor = React.lazy(() => import("react-quill"));
@@ -226,6 +228,15 @@ function RichTextEditor(props: IProps) {
226228
[props.placeholder]
227229
);
228230

231+
useEffect(() => {
232+
if (editorRef.current && props.tabIndex !== undefined) {
233+
const editor = editorRef.current.getEditor();
234+
if (editor && editor.scroll && editor.scroll.domNode) {
235+
(editor.scroll.domNode as HTMLElement).tabIndex = props.tabIndex;
236+
}
237+
}
238+
}, [props.tabIndex, key]); // Also re-run when key changes due to placeholder update
239+
229240
const contains = (parent: HTMLElement, descendant: HTMLElement) => {
230241
try {
231242
// Firefox inserts inaccessible nodes around video elements
@@ -316,6 +327,7 @@ const RichTextEditorCompBase = new UICompBuilder(childrenMap, (props) => {
316327
onChange={handleChange}
317328
$style={props.style}
318329
contentScrollBar={props.contentScrollBar}
330+
tabIndex={props.tabIndex}
319331
/>
320332
);
321333
})
@@ -334,6 +346,7 @@ const RichTextEditorCompBase = new UICompBuilder(childrenMap, (props) => {
334346
{children.onEvent.getPropertyView()}
335347
{hiddenPropertyView(children)}
336348
{readOnlyPropertyView(children)}
349+
{children.tabIndex.propertyView({ label: trans("prop.tabIndex") })}
337350
{showDataLoadingIndicatorsPropertyView(children)}
338351
</Section>
339352
)}

client/packages/lowcoder/src/comps/comps/selectInputComp/checkboxComp.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { default as AntdCheckboxGroup } from "antd/es/checkbox/Group";
22
import { SelectInputOptionControl } from "comps/controls/optionsControl";
3-
import { BoolCodeControl } from "../../controls/codeControl";
3+
import { BoolCodeControl, NumberControl } from "../../controls/codeControl";
44
import { arrayStringExposingStateControl } from "../../controls/codeStateControl";
55
import { LabelControl } from "../../controls/labelControl";
66
import { ChangeEventHandlerControl } from "../../controls/eventHandlerControl";
@@ -115,6 +115,7 @@ export const getStyle = (style: CheckboxStyleType) => {
115115
const CheckboxGroup = styled(AntdCheckboxGroup) <{
116116
$style: CheckboxStyleType;
117117
$layout: ValueFromOption<typeof RadioLayoutOptions>;
118+
tabIndex?: number;
118119
}>`
119120
min-height: 32px;
120121
${(props) => props.$style && getStyle(props.$style)}
@@ -156,6 +157,7 @@ let CheckboxBasicComp = (function () {
156157
viewRef: RefControl<HTMLDivElement>,
157158
inputFieldStyle: styleControl(CheckboxStyle , 'inputFieldStyle'),
158159
animationStyle: styleControl(AnimationStyle , 'animationStyle'),
160+
tabIndex: NumberControl,
159161
...SelectInputValidationChildren,
160162
...formDataChildren,
161163
};
@@ -184,6 +186,7 @@ let CheckboxBasicComp = (function () {
184186
value: option.value,
185187
disabled: option.disabled,
186188
}))}
189+
tabIndex={typeof props.tabIndex === 'number' ? props.tabIndex : undefined}
187190
onChange={(values) => {
188191
handleChange(values as string[]);
189192
}}

client/packages/lowcoder/src/comps/comps/selectInputComp/radioComp.tsx

+19-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { EllipsisTextCss, ValueFromOption } from "lowcoder-design";
1313
import { trans } from "i18n";
1414
import { fixOldInputCompData } from "../textInputComp/textInputConstants";
1515
import { migrateOldData } from "comps/generators/simpleGenerators";
16+
import { useEffect, useRef } from "react";
1617

1718
const getStyle = (style: RadioStyleType, inputFieldStyle?:RadioStyleType ) => {
1819
return css`
@@ -102,6 +103,18 @@ let RadioBasicComp = (function () {
102103
validateState,
103104
handleChange,
104105
] = useSelectInputValidate(props);
106+
107+
const radioRef = useRef<HTMLDivElement | null>(null);
108+
109+
useEffect(() => {
110+
if (radioRef.current && typeof props.tabIndex === 'number') {
111+
const firstRadioInput = radioRef.current.querySelector('input[type="radio"]');
112+
if (firstRadioInput) {
113+
firstRadioInput.setAttribute('tabindex', props.tabIndex.toString());
114+
}
115+
}
116+
}, [props.tabIndex, props.options]);
117+
105118
return props.label({
106119
required: props.required,
107120
style: props.style,
@@ -110,7 +123,12 @@ let RadioBasicComp = (function () {
110123
animationStyle:props.animationStyle,
111124
children: (
112125
<Radio
113-
ref={props.viewRef}
126+
ref={(el) => {
127+
if (el) {
128+
props.viewRef(el);
129+
radioRef.current = el;
130+
}
131+
}}
114132
disabled={props.disabled}
115133
value={props.value.value}
116134
$style={props.style}

0 commit comments

Comments
 (0)