@startupjs-ui/object-input 0.1.3
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/CHANGELOG.md +20 -0
- package/README.mdx +236 -0
- package/index.cssx.styl +8 -0
- package/index.d.ts +33 -0
- package/index.tsx +123 -0
- package/package.json +21 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
## [0.1.3](https://github.com/startupjs/startupjs-ui/compare/v0.1.2...v0.1.3) (2025-12-29)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @startupjs-ui/object-input
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.1.2](https://github.com/startupjs/startupjs-ui/compare/v0.1.1...v0.1.2) (2025-12-29)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* add mdx and docs packages. Refactor docs to get rid of any @startupjs/ui usage and use startupjs-ui instead ([703c926](https://github.com/startupjs/startupjs-ui/commit/703c92636efb0421ffd11783f692fc892b74018f))
|
|
20
|
+
* **object-input:** refactor ObjectInput component ([f21693c](https://github.com/startupjs/startupjs-ui/commit/f21693c7f2a31198f445ec3656fb780feb2269bd))
|
package/README.mdx
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { $ } from 'startupjs'
|
|
3
|
+
import ObjectInput, { _PropsJsonSchema as ObjectInputPropsJsonSchema } from './index'
|
|
4
|
+
import { Sandbox } from '@startupjs-ui/docs'
|
|
5
|
+
|
|
6
|
+
# ObjectInput
|
|
7
|
+
|
|
8
|
+
ObjectInput lets you build dynamic forms using its declarative API.
|
|
9
|
+
|
|
10
|
+
ObjectInput accepts an inputs metadata object with a required `input` key to specify what type of input to display in the `properties` property.
|
|
11
|
+
|
|
12
|
+
Possible types are: [array](/docs/forms/Array), [checkbox](/docs/forms/Checkbox), [date](/docs/forms/DateTimePicker), [datetime](/docs/forms/DateTimePicker), [multiselect](/docs/forms/Multiselect), [number](/docs/forms/NumberInput), [object](/docs/forms/ObjectInput), [password](/docs/forms/PasswordInput), [radio](/docs/forms/Radio), [select](/docs/forms/Select), [time](/docs/forms/DateTimePicker), [text](/docs/forms/TextInput).
|
|
13
|
+
|
|
14
|
+
```jsx
|
|
15
|
+
import { ObjectInput } from 'startupjs-ui'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Simple example
|
|
19
|
+
|
|
20
|
+
It's important to have `properties` object defined as a constant either outside of your component or cached with `useMemo()`, otherwise component is going to re-render the whole form tree whenever you are editing one of the field.
|
|
21
|
+
|
|
22
|
+
```jsx example
|
|
23
|
+
const $value = $()
|
|
24
|
+
const properties = useMemo(() => ({
|
|
25
|
+
email: {
|
|
26
|
+
input: 'text',
|
|
27
|
+
label: 'Email'
|
|
28
|
+
},
|
|
29
|
+
password: {
|
|
30
|
+
input: 'text',
|
|
31
|
+
label: 'Password',
|
|
32
|
+
description: "Make sure it's at least 15 characters OR at least 8 characters including a number and a lowercase letter"
|
|
33
|
+
}
|
|
34
|
+
}), [])
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<ObjectInput
|
|
38
|
+
$value={$value}
|
|
39
|
+
properties={properties}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Row
|
|
45
|
+
|
|
46
|
+
You can show nested inputs in a row by passing the `row` flag.
|
|
47
|
+
|
|
48
|
+
```jsx example
|
|
49
|
+
const $value = $()
|
|
50
|
+
const properties = {
|
|
51
|
+
email: {
|
|
52
|
+
input: 'text',
|
|
53
|
+
label: 'Email'
|
|
54
|
+
},
|
|
55
|
+
password: {
|
|
56
|
+
input: 'text',
|
|
57
|
+
label: 'Password',
|
|
58
|
+
description: "Make sure it's at least 15 characters OR at least 8 characters including a number and a lowercase letter"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<ObjectInput
|
|
64
|
+
$value={$value}
|
|
65
|
+
properties={properties}
|
|
66
|
+
row
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Inputs order
|
|
72
|
+
|
|
73
|
+
ObjectInput accepts an array in the `order` property to specify in what order should the inputs be displayed.
|
|
74
|
+
|
|
75
|
+
```jsx example
|
|
76
|
+
const $value = $()
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<ObjectInput
|
|
80
|
+
$value={$value}
|
|
81
|
+
order={['password', 'email']}
|
|
82
|
+
properties={{
|
|
83
|
+
email: {
|
|
84
|
+
input: 'text',
|
|
85
|
+
label: 'Email'
|
|
86
|
+
},
|
|
87
|
+
password: {
|
|
88
|
+
input: 'text',
|
|
89
|
+
label: 'Password',
|
|
90
|
+
description: "Make sure it's at least 15 characters OR at least 8 characters including a number and a lowercase letter"
|
|
91
|
+
}
|
|
92
|
+
}}
|
|
93
|
+
/>
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Displaying errors
|
|
98
|
+
|
|
99
|
+
The `errors` property accepts an object with error texts for inputs, where the key is the input key from the `properties` object and its value is the error text.
|
|
100
|
+
|
|
101
|
+
```jsx example
|
|
102
|
+
const $value = $()
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<ObjectInput
|
|
106
|
+
$value={$value}
|
|
107
|
+
errors={{
|
|
108
|
+
email: 'Email input error',
|
|
109
|
+
password: 'Password input error'
|
|
110
|
+
}}
|
|
111
|
+
properties={{
|
|
112
|
+
email: {
|
|
113
|
+
input: 'text',
|
|
114
|
+
label: 'Email'
|
|
115
|
+
},
|
|
116
|
+
password: {
|
|
117
|
+
input: 'text',
|
|
118
|
+
label: 'Password',
|
|
119
|
+
description: "Make sure it's at least 15 characters OR at least 8 characters including a number and a lowercase letter"
|
|
120
|
+
}
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Disabled
|
|
127
|
+
|
|
128
|
+
```jsx example
|
|
129
|
+
const $value = $()
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<ObjectInput
|
|
133
|
+
$value={$value}
|
|
134
|
+
properties={{
|
|
135
|
+
email: {
|
|
136
|
+
input: 'text',
|
|
137
|
+
label: 'Email'
|
|
138
|
+
},
|
|
139
|
+
password: {
|
|
140
|
+
input: 'text',
|
|
141
|
+
label: 'Password',
|
|
142
|
+
description: "Make sure it's at least 15 characters OR at least 8 characters including a number and a lowercase letter"
|
|
143
|
+
}
|
|
144
|
+
}}
|
|
145
|
+
disabled
|
|
146
|
+
/>
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Readonly
|
|
151
|
+
|
|
152
|
+
```jsx example
|
|
153
|
+
const $value = $({ email: 'startupjs@gmail.com', password: '123456' })
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<ObjectInput
|
|
157
|
+
$value={$value}
|
|
158
|
+
properties={{
|
|
159
|
+
email: {
|
|
160
|
+
input: 'text',
|
|
161
|
+
label: 'Email'
|
|
162
|
+
},
|
|
163
|
+
password: {
|
|
164
|
+
input: 'text',
|
|
165
|
+
label: 'Password',
|
|
166
|
+
description: "Make sure it's at least 15 characters OR at least 8 characters including a number and a lowercase letter"
|
|
167
|
+
}
|
|
168
|
+
}}
|
|
169
|
+
readonly
|
|
170
|
+
/>
|
|
171
|
+
)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Advanced usage
|
|
175
|
+
|
|
176
|
+
ObjectInput supports `dependsOn` and `dependsValue` properties for each input object in `properties` to dynamically display inputs.
|
|
177
|
+
|
|
178
|
+
- `dependsOn` is used to specify the object key which the current input depends on
|
|
179
|
+
- `dependsValue` is used to specify at what value of the input with key `dependsOn` should the current input be shown
|
|
180
|
+
|
|
181
|
+
In the example below the dependent `password` input will only be shown if the `email` input is not empty.
|
|
182
|
+
|
|
183
|
+
**Caution**: When the `dependsOn` field changes and dependent field is no longer visible its previous value is preserved.
|
|
184
|
+
|
|
185
|
+
```jsx example
|
|
186
|
+
const $value = $()
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<ObjectInput
|
|
190
|
+
$value={$value}
|
|
191
|
+
properties={{
|
|
192
|
+
email: {
|
|
193
|
+
input: 'text',
|
|
194
|
+
label: 'Email'
|
|
195
|
+
},
|
|
196
|
+
password: {
|
|
197
|
+
input: 'text',
|
|
198
|
+
label: 'Password',
|
|
199
|
+
dependsOn: 'email',
|
|
200
|
+
description: "Make sure it's at least 15 characters OR at least 8 characters including a number and a lowercase letter"
|
|
201
|
+
}
|
|
202
|
+
}}
|
|
203
|
+
/>
|
|
204
|
+
)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Sandbox
|
|
208
|
+
|
|
209
|
+
export function SandboxWrapper () {
|
|
210
|
+
const $value = $()
|
|
211
|
+
const properties = useMemo(() => ({
|
|
212
|
+
email: {
|
|
213
|
+
input: 'text',
|
|
214
|
+
label: 'Email'
|
|
215
|
+
},
|
|
216
|
+
password: {
|
|
217
|
+
input: 'text',
|
|
218
|
+
label: 'Password',
|
|
219
|
+
description: "Make sure it's at least 15 characters OR at least 8 characters including a number and a lowercase letter"
|
|
220
|
+
}
|
|
221
|
+
}), [])
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<Sandbox
|
|
225
|
+
Component={ObjectInput}
|
|
226
|
+
propsJsonSchema={ObjectInputPropsJsonSchema}
|
|
227
|
+
props={{
|
|
228
|
+
$value,
|
|
229
|
+
properties
|
|
230
|
+
}}
|
|
231
|
+
block
|
|
232
|
+
/>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
<SandboxWrapper />
|
package/index.cssx.styl
ADDED
package/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// DO NOT MODIFY THIS FILE - IT IS AUTOMATICALLY GENERATED ON COMMITS.
|
|
3
|
+
|
|
4
|
+
import { type ReactNode } from 'react';
|
|
5
|
+
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
6
|
+
import './index.cssx.styl';
|
|
7
|
+
declare const _default: import("react").ComponentType<ObjectInputProps>;
|
|
8
|
+
export default _default;
|
|
9
|
+
export declare const _PropsJsonSchema: {};
|
|
10
|
+
export interface ObjectInputProps {
|
|
11
|
+
/** Custom styles for the wrapper */
|
|
12
|
+
style?: StyleProp<ViewStyle>;
|
|
13
|
+
/** Custom styles for the inner input container */
|
|
14
|
+
inputStyle?: StyleProp<ViewStyle>;
|
|
15
|
+
/** Model binding for object values */
|
|
16
|
+
$value: any;
|
|
17
|
+
/** Error messages keyed by property name @default {} */
|
|
18
|
+
errors?: Record<string, any>;
|
|
19
|
+
/** Input metadata keyed by property name */
|
|
20
|
+
properties: Record<string, any>;
|
|
21
|
+
/** Order of rendered inputs */
|
|
22
|
+
order?: string[];
|
|
23
|
+
/** Render inputs in a row */
|
|
24
|
+
row?: boolean;
|
|
25
|
+
/** Disable interactions */
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
/** Render as read-only */
|
|
28
|
+
readonly?: boolean;
|
|
29
|
+
/** Custom wrapper renderer (used by Input layout wrappers) */
|
|
30
|
+
_renderWrapper?: (params: {
|
|
31
|
+
style: StyleProp<ViewStyle> | undefined;
|
|
32
|
+
}, children: ReactNode) => ReactNode;
|
|
33
|
+
}
|
package/index.tsx
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { type ReactNode } from 'react'
|
|
2
|
+
import { type StyleProp, type ViewStyle } from 'react-native'
|
|
3
|
+
import { pug, observer } from 'startupjs'
|
|
4
|
+
import { themed } from '@startupjs-ui/core'
|
|
5
|
+
import Div from '@startupjs-ui/div'
|
|
6
|
+
import Input from '@startupjs-ui/input'
|
|
7
|
+
import './index.cssx.styl'
|
|
8
|
+
|
|
9
|
+
export default observer(
|
|
10
|
+
themed('ObjectInput', ObjectInput)
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
export const _PropsJsonSchema = {/* ObjectInputProps */}
|
|
14
|
+
|
|
15
|
+
export interface ObjectInputProps {
|
|
16
|
+
/** Custom styles for the wrapper */
|
|
17
|
+
style?: StyleProp<ViewStyle>
|
|
18
|
+
/** Custom styles for the inner input container */
|
|
19
|
+
inputStyle?: StyleProp<ViewStyle>
|
|
20
|
+
/** Model binding for object values */
|
|
21
|
+
$value: any
|
|
22
|
+
/** Error messages keyed by property name @default {} */
|
|
23
|
+
errors?: Record<string, any>
|
|
24
|
+
/** Input metadata keyed by property name */
|
|
25
|
+
properties: Record<string, any>
|
|
26
|
+
/** Order of rendered inputs */
|
|
27
|
+
order?: string[]
|
|
28
|
+
/** Render inputs in a row */
|
|
29
|
+
row?: boolean
|
|
30
|
+
/** Disable interactions */
|
|
31
|
+
disabled?: boolean
|
|
32
|
+
/** Render as read-only */
|
|
33
|
+
readonly?: boolean
|
|
34
|
+
/** Custom wrapper renderer (used by Input layout wrappers) */
|
|
35
|
+
_renderWrapper?: (params: { style: StyleProp<ViewStyle> | undefined }, children: ReactNode) => ReactNode
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ObjectInput ({
|
|
39
|
+
style,
|
|
40
|
+
inputStyle,
|
|
41
|
+
$value,
|
|
42
|
+
errors = {},
|
|
43
|
+
properties,
|
|
44
|
+
order,
|
|
45
|
+
row,
|
|
46
|
+
disabled,
|
|
47
|
+
readonly,
|
|
48
|
+
_renderWrapper
|
|
49
|
+
}: ObjectInputProps): ReactNode {
|
|
50
|
+
if (!$value || !properties) {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const value = $value.get() || {}
|
|
55
|
+
|
|
56
|
+
const resolvedOrder = getOrder(order, properties)
|
|
57
|
+
|
|
58
|
+
function getInputs () {
|
|
59
|
+
return resolvedOrder
|
|
60
|
+
.filter((key) => {
|
|
61
|
+
const { dependsOn, dependsValue } = properties[key]
|
|
62
|
+
return resolvesDeps(value, dependsOn, dependsValue)
|
|
63
|
+
})
|
|
64
|
+
.map((key) => {
|
|
65
|
+
const { dependsOn, dependsValue, ...inputProps } = properties[key]
|
|
66
|
+
return {
|
|
67
|
+
...inputProps,
|
|
68
|
+
key,
|
|
69
|
+
$value: $value[key]
|
|
70
|
+
}
|
|
71
|
+
// TODO: When the dependsOn field changes and this field is no longer visible -- clear it.
|
|
72
|
+
}).filter(Boolean)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const inputs = getInputs()
|
|
76
|
+
|
|
77
|
+
if (inputs.length === 0) return null
|
|
78
|
+
|
|
79
|
+
if (!_renderWrapper) {
|
|
80
|
+
_renderWrapper = ({ style }, children): ReactNode => {
|
|
81
|
+
return pug`
|
|
82
|
+
Div(style=style row=row)= children
|
|
83
|
+
`
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return _renderWrapper({
|
|
88
|
+
style: [style, inputStyle]
|
|
89
|
+
}, inputs.map(({ key, ...inputProps }, index): ReactNode => pug`
|
|
90
|
+
Input.input(
|
|
91
|
+
key=key
|
|
92
|
+
styleName={ push: index !== 0, row, column: !row }
|
|
93
|
+
error=errors[key]
|
|
94
|
+
disabled=disabled
|
|
95
|
+
readonly=readonly
|
|
96
|
+
...inputProps
|
|
97
|
+
)
|
|
98
|
+
`))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getOrder (order: string[] | undefined, properties: Record<string, any>): string[] {
|
|
102
|
+
return order ?? Object.keys(properties)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resolvesDeps (
|
|
106
|
+
value: Record<string, any> = {},
|
|
107
|
+
dependsOn?: string,
|
|
108
|
+
dependsValue?: any
|
|
109
|
+
): boolean {
|
|
110
|
+
if (!dependsOn) return true
|
|
111
|
+
const dependencyValue = value[dependsOn]
|
|
112
|
+
return (
|
|
113
|
+
(dependsValue != null && dependencyValue === dependsValue) ||
|
|
114
|
+
(dependsValue != null && Array.isArray(dependsValue) &&
|
|
115
|
+
dependsValue.includes(dependencyValue)
|
|
116
|
+
) ||
|
|
117
|
+
(
|
|
118
|
+
(dependsValue == null || (typeof dependsValue === 'string' && dependsValue.trim() === '')) &&
|
|
119
|
+
dependencyValue != null &&
|
|
120
|
+
!(typeof dependencyValue === 'string' && dependencyValue.trim() === '')
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@startupjs-ui/object-input",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"main": "index.tsx",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@startupjs-ui/core": "^0.1.3",
|
|
12
|
+
"@startupjs-ui/div": "^0.1.3",
|
|
13
|
+
"@startupjs-ui/input": "^0.1.3"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"react": "*",
|
|
17
|
+
"react-native": "*",
|
|
18
|
+
"startupjs": "*"
|
|
19
|
+
},
|
|
20
|
+
"gitHead": "fd964ebc3892d3dd0a6c85438c0af619cc50c3f0"
|
|
21
|
+
}
|