Skip to content

Commit 770e635

Browse files
committed
feat: add ui:readOnly ui schema property
1 parent 1fd8712 commit 770e635

7 files changed

Lines changed: 74 additions & 17 deletions

File tree

packages/shared-lib/src/lib/JSONSchemaUtil.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ export enum SchemaFormUIField {
2828
* If an integer property, whether to treat it as zero-based
2929
*/
3030
ZeroBased = 'ui:zeroBased',
31+
/**
32+
* Whether the property is read-only. This will disable the input and hide any buttons for modifying the value.
33+
*/
34+
ReadOnly = 'ui:readOnly',
3135
/**
3236
* Override the presentation with a special mode.
3337
* Currently only valid for:

packages/webui/src/client/lib/Components/FloatInput.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ interface IFloatInputControlProps {
66
classNames?: string
77
modifiedClassName?: string
88
disabled?: boolean
9+
readOnly?: boolean
910
placeholder?: string
1011

1112
/** Call handleUpdate on every change, before focus is lost */
@@ -24,6 +25,7 @@ export function FloatInputControl({
2425
modifiedClassName,
2526
value,
2627
disabled,
28+
readOnly,
2729
placeholder,
2830
handleUpdate,
2931
updateOnKey,
@@ -36,31 +38,37 @@ export function FloatInputControl({
3638

3739
const handleChange = useCallback(
3840
(event: React.ChangeEvent<HTMLInputElement>) => {
41+
if (readOnly) return
42+
3943
const number = parseFloat(event.target.value.replace(',', '.'))
4044
setEditingValue(number)
4145

4246
if (updateOnKey && !isNaN(number)) {
4347
handleUpdate(zeroBased ? number - 1 : number)
4448
}
4549
},
46-
[handleUpdate, updateOnKey, zeroBased]
50+
[handleUpdate, updateOnKey, zeroBased, readOnly]
4751
)
4852
const handleBlur = useCallback(
4953
(event: React.FocusEvent<HTMLInputElement>) => {
54+
if (readOnly) return
55+
5056
const number = parseFloat(event.currentTarget.value.replace(',', '.'))
5157
if (!isNaN(number)) {
5258
handleUpdate(zeroBased ? number - 1 : number)
5359
}
5460

5561
setEditingValue(null)
5662
},
57-
[handleUpdate, zeroBased]
63+
[handleUpdate, zeroBased, readOnly]
5864
)
5965
const handleFocus = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
6066
setEditingValue(parseFloat(event.currentTarget.value.replace(',', '.')))
6167
}, [])
6268
const handleKeyUp = useCallback(
6369
(event: React.KeyboardEvent<HTMLInputElement>) => {
70+
if (readOnly) return
71+
6472
if (event.key === 'Escape') {
6573
setEditingValue(null)
6674
} else if (event.key === 'Enter') {
@@ -70,7 +78,7 @@ export function FloatInputControl({
7078
}
7179
}
7280
},
73-
[handleUpdate, zeroBased]
81+
[handleUpdate, zeroBased, readOnly]
7482
)
7583

7684
let showValue: string | number | undefined = editingValue ?? undefined
@@ -93,6 +101,7 @@ export function FloatInputControl({
93101
onFocus={handleFocus}
94102
onKeyUp={handleKeyUp}
95103
disabled={disabled}
104+
readOnly={readOnly}
96105
/>
97106
)
98107
}

packages/webui/src/client/lib/Components/IntInput.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ interface IIntInputControlProps {
66
classNames?: string
77
modifiedClassName?: string
88
disabled?: boolean
9+
readOnly?: boolean
910
placeholder?: string
1011

1112
/** Call handleUpdate on every change, before focus is lost */
@@ -24,6 +25,7 @@ export function IntInputControl({
2425
modifiedClassName,
2526
value,
2627
disabled,
28+
readOnly,
2729
placeholder,
2830
handleUpdate,
2931
updateOnKey,
@@ -36,31 +38,37 @@ export function IntInputControl({
3638

3739
const handleChange = useCallback(
3840
(event: React.ChangeEvent<HTMLInputElement>) => {
41+
if (readOnly) return
42+
3943
const number = parseInt(event.target.value, 10)
4044
setEditingValue(number)
4145

4246
if (updateOnKey && !isNaN(number)) {
4347
handleUpdate(zeroBased ? number - 1 : number)
4448
}
4549
},
46-
[handleUpdate, updateOnKey, zeroBased]
50+
[handleUpdate, updateOnKey, zeroBased, readOnly]
4751
)
4852
const handleBlur = useCallback(
4953
(event: React.FocusEvent<HTMLInputElement>) => {
54+
if (readOnly) return
55+
5056
const number = parseInt(event.currentTarget.value, 10)
5157
if (!isNaN(number)) {
5258
handleUpdate(zeroBased ? number - 1 : number)
5359
}
5460

5561
setEditingValue(null)
5662
},
57-
[handleUpdate, zeroBased]
63+
[handleUpdate, zeroBased, readOnly]
5864
)
5965
const handleFocus = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
6066
setEditingValue(parseInt(event.currentTarget.value, 10))
6167
}, [])
6268
const handleKeyUp = useCallback(
6369
(event: React.KeyboardEvent<HTMLInputElement>) => {
70+
if (readOnly) return
71+
6472
if (event.key === 'Escape') {
6573
setEditingValue(null)
6674
} else if (event.key === 'Enter') {
@@ -70,7 +78,7 @@ export function IntInputControl({
7078
}
7179
}
7280
},
73-
[handleUpdate, zeroBased]
81+
[handleUpdate, zeroBased, readOnly]
7482
)
7583

7684
let showValue: string | number | undefined = editingValue ?? undefined
@@ -93,6 +101,7 @@ export function IntInputControl({
93101
onFocus={handleFocus}
94102
onKeyUp={handleKeyUp}
95103
disabled={disabled}
104+
readOnly={readOnly}
96105
/>
97106
)
98107
}

packages/webui/src/client/lib/Components/MultiLineTextInput.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface IMultiLineTextInputControlProps {
2020
classNames?: string
2121
modifiedClassName?: string
2222
disabled?: boolean
23+
readOnly?: boolean
2324
placeholder?: string
2425

2526
/** Call handleUpdate on every change, before focus is lost */
@@ -33,6 +34,7 @@ export function MultiLineTextInputControl({
3334
modifiedClassName,
3435
value,
3536
disabled,
37+
readOnly,
3638
placeholder,
3739
handleUpdate,
3840
updateOnKey,
@@ -41,20 +43,24 @@ export function MultiLineTextInputControl({
4143

4244
const handleChange = useCallback(
4345
(event: React.ChangeEvent<HTMLTextAreaElement>) => {
46+
if (readOnly) return
47+
4448
setEditingValue(event.target.value)
4549

4650
if (updateOnKey) {
4751
handleUpdate(splitValueIntoLines(event.target.value))
4852
}
4953
},
50-
[handleUpdate, updateOnKey]
54+
[handleUpdate, updateOnKey, readOnly]
5155
)
5256
const handleBlur = useCallback(
5357
(event: React.FocusEvent<HTMLTextAreaElement>) => {
58+
if (readOnly) return
59+
5460
handleUpdate(splitValueIntoLines(event.target.value))
5561
setEditingValue(null)
5662
},
57-
[handleUpdate]
63+
[handleUpdate, readOnly]
5864
)
5965
const handleFocus = useCallback((event: React.FocusEvent<HTMLTextAreaElement>) => {
6066
setEditingValue(event.currentTarget.value)
@@ -82,6 +88,7 @@ export function MultiLineTextInputControl({
8288
onKeyUp={handleKeyUp}
8389
onKeyPress={handleKeyPress}
8490
disabled={disabled}
91+
readOnly={readOnly}
8592
/>
8693
)
8794
}

packages/webui/src/client/lib/Components/TextInput.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ interface ITextInputControlProps {
1313
classNames?: string
1414
modifiedClassName?: string
1515
disabled?: boolean
16+
readOnly?: boolean
1617
placeholder?: string
1718
spellCheck?: boolean
1819

@@ -29,6 +30,7 @@ export function TextInputControl({
2930
modifiedClassName,
3031
value,
3132
disabled,
33+
readOnly,
3234
placeholder,
3335
spellCheck,
3436
suggestions,
@@ -39,16 +41,20 @@ export function TextInputControl({
3941

4042
const handleChange = useCallback(
4143
(event: React.ChangeEvent<HTMLInputElement>) => {
44+
if (readOnly) return
45+
4246
setEditingValue(event.target.value)
4347

4448
if (updateOnKey) {
4549
handleUpdate(event.target.value)
4650
}
4751
},
48-
[handleUpdate, updateOnKey]
52+
[handleUpdate, updateOnKey, readOnly]
4953
)
5054
const handleBlur = useCallback(
5155
(event: React.FocusEvent<HTMLInputElement>) => {
56+
if (readOnly) return
57+
5258
let value: string = event.target.value
5359
if (value) {
5460
value = value.trim()
@@ -57,20 +63,22 @@ export function TextInputControl({
5763

5864
setEditingValue(null)
5965
},
60-
[handleUpdate]
66+
[handleUpdate, readOnly]
6167
)
6268
const handleFocus = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
6369
setEditingValue(event.currentTarget.value)
6470
}, [])
6571
const handleKeyUp = useCallback(
6672
(event: React.KeyboardEvent<HTMLInputElement>) => {
73+
if (readOnly) return
74+
6775
if (event.key === 'Escape') {
6876
setEditingValue(null)
6977
} else if (event.key === 'Enter') {
7078
handleUpdate(event.currentTarget.value)
7179
}
7280
},
73-
[handleUpdate]
81+
[handleUpdate, readOnly]
7482
)
7583

7684
const fieldId = useMemo(() => getRandomString(), [])
@@ -85,6 +93,7 @@ export function TextInputControl({
8593
onBlur={handleBlur}
8694
onFocus={handleFocus}
8795
onKeyUp={handleKeyUp}
96+
readOnly={readOnly}
8897
disabled={disabled}
8998
spellCheck={spellCheck}
9099
list={suggestions ? fieldId : undefined}

packages/webui/src/client/lib/Components/TimeMsInput.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import React, { useCallback, useState } from 'react'
22
import ClassNames from 'classnames'
33
import Form from 'react-bootstrap/Form'
44

5-
interface IIntInputControlProps {
5+
interface ITimeMsInputControlProps {
66
classNames?: string
77
modifiedClassName?: string
88
disabled?: boolean
9+
readOnly?: boolean
910
placeholder?: string
1011

1112
/** Call handleUpdate on every change, before focus is lost */
@@ -89,13 +90,14 @@ export function TimeMsInputControl({
8990
modifiedClassName,
9091
value,
9192
disabled,
93+
readOnly,
9294
placeholder,
9395
handleUpdate,
9496
updateOnKey,
9597
min,
9698
max,
9799
multipleOf,
98-
}: Readonly<IIntInputControlProps>): JSX.Element {
100+
}: Readonly<ITimeMsInputControlProps>): JSX.Element {
99101
const [editingValue, setEditingValue] = useState<string | null>(null)
100102

101103
const isValidValue = useCallback((value: number): boolean => {
@@ -108,25 +110,28 @@ export function TimeMsInputControl({
108110

109111
const handleChange = useCallback(
110112
(event: React.ChangeEvent<HTMLInputElement>) => {
113+
if (readOnly) return
114+
111115
const number = parseTime(event.target.value)
112116
setEditingValue(event.target.value)
113117

114118
if (updateOnKey && !isNaN(number) && isValidValue(number)) {
115119
handleUpdate(number)
116120
}
117121
},
118-
[handleUpdate, updateOnKey, isValidValue]
122+
[handleUpdate, updateOnKey, isValidValue, readOnly]
119123
)
120124
const handleBlur = useCallback(
121125
(event: React.FocusEvent<HTMLInputElement>) => {
126+
if (readOnly) return
122127
const number = parseTime(event.currentTarget.value)
123128
if (!isNaN(number) && isValidValue(number)) {
124129
handleUpdate(number)
125130
}
126131

127132
setEditingValue(null)
128133
},
129-
[handleUpdate, isValidValue]
134+
[handleUpdate, isValidValue, readOnly]
130135
)
131136
const handleFocus = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
132137
setEditingValue(event.currentTarget.value)
@@ -135,6 +140,8 @@ export function TimeMsInputControl({
135140
}, [])
136141
const handleKeyUp = useCallback(
137142
(event: React.KeyboardEvent<HTMLInputElement>) => {
143+
if (readOnly) return
144+
138145
if (event.key === 'Escape') {
139146
setEditingValue(null)
140147
} else if (event.key === 'Enter') {
@@ -144,7 +151,7 @@ export function TimeMsInputControl({
144151
}
145152
}
146153
},
147-
[handleUpdate, isValidValue]
154+
[handleUpdate, isValidValue, readOnly]
148155
)
149156
const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {
150157
// allow ctrl/cmd + any key, to allow for shortcuts like ctrl+a, ctrl+c, ctrl+v, etc.
@@ -170,6 +177,7 @@ export function TimeMsInputControl({
170177
onFocus={handleFocus}
171178
onKeyUp={handleKeyUp}
172179
onKeyDown={handleKeyDown}
180+
readOnly={readOnly}
173181
disabled={disabled}
174182
/>
175183
)

0 commit comments

Comments
 (0)