@instructure/ui-form-field 11.7.9-snapshot-5 → 11.7.10
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 +8 -2
- package/es/FormField/v2/index.js +1 -6
- package/es/FormFieldGroup/v2/index.js +20 -12
- package/es/FormFieldLayout/v2/index.js +39 -5
- package/es/FormFieldLayout/v2/styles.js +2 -2
- package/lib/FormField/v2/index.js +1 -6
- package/lib/FormFieldGroup/v2/index.js +19 -11
- package/lib/FormFieldLayout/v2/index.js +39 -5
- package/lib/FormFieldLayout/v2/styles.js +2 -2
- package/package.json +14 -14
- package/src/FormField/v2/README.md +28 -2
- package/src/FormField/v2/index.tsx +1 -5
- package/src/FormField/v2/props.ts +10 -3
- package/src/FormFieldGroup/v2/index.tsx +21 -9
- package/src/FormFieldGroup/v2/props.ts +18 -3
- package/src/FormFieldLayout/v2/index.tsx +64 -8
- package/src/FormFieldLayout/v2/props.ts +39 -6
- package/src/FormFieldLayout/v2/styles.ts +2 -2
- package/src/exports/b.ts +4 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/FormField/v2/index.d.ts.map +1 -1
- package/types/FormField/v2/props.d.ts +10 -3
- package/types/FormField/v2/props.d.ts.map +1 -1
- package/types/FormFieldGroup/v2/index.d.ts +7 -4
- package/types/FormFieldGroup/v2/index.d.ts.map +1 -1
- package/types/FormFieldGroup/v2/props.d.ts +8 -3
- package/types/FormFieldGroup/v2/props.d.ts.map +1 -1
- package/types/FormFieldLayout/v2/index.d.ts.map +1 -1
- package/types/FormFieldLayout/v2/props.d.ts +34 -7
- package/types/FormFieldLayout/v2/props.d.ts.map +1 -1
- package/types/exports/b.d.ts +1 -1
- package/types/exports/b.d.ts.map +1 -1
|
@@ -34,7 +34,8 @@ import { Grid } from '@instructure/ui-grid/latest'
|
|
|
34
34
|
import {
|
|
35
35
|
pickProps,
|
|
36
36
|
omitProps,
|
|
37
|
-
safeCloneElement
|
|
37
|
+
safeCloneElement,
|
|
38
|
+
withDeterministicId
|
|
38
39
|
} from '@instructure/ui-react-utils'
|
|
39
40
|
import { withStyleNew } from '@instructure/emotion'
|
|
40
41
|
|
|
@@ -46,12 +47,14 @@ import generateStyle from './styles.js'
|
|
|
46
47
|
|
|
47
48
|
import { allowedProps } from './props.js'
|
|
48
49
|
import type { FormFieldGroupProps } from './props'
|
|
50
|
+
import type { FormFieldLayoutChildrenParams } from '../../FormFieldLayout/v2/props'
|
|
49
51
|
|
|
50
52
|
/**
|
|
51
53
|
---
|
|
52
54
|
category: components
|
|
53
55
|
---
|
|
54
56
|
**/
|
|
57
|
+
@withDeterministicId()
|
|
55
58
|
@withStyleNew(generateStyle)
|
|
56
59
|
class FormFieldGroup extends Component<FormFieldGroupProps> {
|
|
57
60
|
static displayName = 'FormFieldGroup'
|
|
@@ -67,6 +70,13 @@ class FormFieldGroup extends Component<FormFieldGroupProps> {
|
|
|
67
70
|
isGroup: true
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
constructor(props: FormFieldGroupProps) {
|
|
74
|
+
super(props)
|
|
75
|
+
this._defaultId = props.deterministicId!()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private readonly _defaultId: string
|
|
79
|
+
|
|
70
80
|
ref: Element | null = null
|
|
71
81
|
|
|
72
82
|
handleRef = (el: Element | null) => {
|
|
@@ -96,9 +106,10 @@ class FormFieldGroup extends Component<FormFieldGroupProps> {
|
|
|
96
106
|
)
|
|
97
107
|
}
|
|
98
108
|
|
|
99
|
-
renderColumns() {
|
|
100
|
-
const { disabled } = this.props
|
|
101
|
-
|
|
109
|
+
renderColumns(params: FormFieldLayoutChildrenParams) {
|
|
110
|
+
const { disabled, children } = this.props
|
|
111
|
+
const fields = typeof children === 'function' ? children(params) : children
|
|
112
|
+
return Children.map(fields, (child, index) => {
|
|
102
113
|
if (!child) return null
|
|
103
114
|
const el = child as ReactElement<any>
|
|
104
115
|
const renderedChild =
|
|
@@ -116,7 +127,7 @@ class FormFieldGroup extends Component<FormFieldGroupProps> {
|
|
|
116
127
|
})
|
|
117
128
|
}
|
|
118
129
|
|
|
119
|
-
renderChildren() {
|
|
130
|
+
renderChildren(params: FormFieldLayoutChildrenParams) {
|
|
120
131
|
return (
|
|
121
132
|
<Grid
|
|
122
133
|
colSpacing={this.props.colSpacing}
|
|
@@ -127,17 +138,17 @@ class FormFieldGroup extends Component<FormFieldGroupProps> {
|
|
|
127
138
|
(this.props.layout === 'columns' ? 'medium' : null)
|
|
128
139
|
}
|
|
129
140
|
>
|
|
130
|
-
<Grid.Row>{this.renderColumns()}</Grid.Row>
|
|
141
|
+
<Grid.Row>{this.renderColumns(params)}</Grid.Row>
|
|
131
142
|
</Grid>
|
|
132
143
|
)
|
|
133
144
|
}
|
|
134
145
|
|
|
135
|
-
renderFields() {
|
|
146
|
+
renderFields(params: FormFieldLayoutChildrenParams) {
|
|
136
147
|
const { styles } = this.props
|
|
137
148
|
|
|
138
149
|
return (
|
|
139
150
|
<span key="fields" css={styles?.formFieldGroup}>
|
|
140
|
-
{this.renderChildren()}
|
|
151
|
+
{this.renderChildren(params)}
|
|
141
152
|
</span>
|
|
142
153
|
)
|
|
143
154
|
}
|
|
@@ -180,12 +191,13 @@ class FormFieldGroup extends Component<FormFieldGroupProps> {
|
|
|
180
191
|
vAlign={props.vAlign}
|
|
181
192
|
layout={props.layout === 'inline' ? 'inline' : 'stacked'}
|
|
182
193
|
label={props.description}
|
|
194
|
+
id={props.id || this._defaultId}
|
|
183
195
|
aria-disabled={props.disabled ? 'true' : undefined}
|
|
184
196
|
aria-invalid={ariaInvalid}
|
|
185
197
|
elementRef={this.handleRef}
|
|
186
198
|
isGroup={isGroup}
|
|
187
199
|
>
|
|
188
|
-
{this.renderFields()}
|
|
200
|
+
{(params: FormFieldLayoutChildrenParams) => this.renderFields(params)}
|
|
189
201
|
</FormFieldLayout>
|
|
190
202
|
)
|
|
191
203
|
}
|
|
@@ -31,8 +31,12 @@ import type {
|
|
|
31
31
|
ComponentStyle,
|
|
32
32
|
Spacing
|
|
33
33
|
} from '@instructure/emotion'
|
|
34
|
-
import type {
|
|
34
|
+
import type {
|
|
35
|
+
FormFieldLayoutChildren,
|
|
36
|
+
FormFieldLayoutOwnProps
|
|
37
|
+
} from '../../FormFieldLayout/v2/props'
|
|
35
38
|
import type { FormMessage } from '../../utils/v1/FormPropTypes'
|
|
39
|
+
import type { WithDeterministicIdProps } from '@instructure/ui-react-utils'
|
|
36
40
|
|
|
37
41
|
type FormFieldGroupOwnProps = {
|
|
38
42
|
description: React.ReactNode
|
|
@@ -60,7 +64,11 @@ type FormFieldGroupOwnProps = {
|
|
|
60
64
|
* Whether the field group is read-only. When true, error and success messages will be hidden.
|
|
61
65
|
*/
|
|
62
66
|
readOnly?: boolean
|
|
63
|
-
|
|
67
|
+
/**
|
|
68
|
+
* The form controls. Can be a function that receives `{ describedBy }`, so
|
|
69
|
+
* the controls can reference the group messages via `aria-describedby`.
|
|
70
|
+
*/
|
|
71
|
+
children?: FormFieldLayoutChildren
|
|
64
72
|
layout?: 'stacked' | 'columns' | 'inline'
|
|
65
73
|
rowSpacing?: 'none' | 'small' | 'medium' | 'large'
|
|
66
74
|
colSpacing?: 'none' | 'small' | 'medium' | 'large'
|
|
@@ -85,11 +93,18 @@ type AllowedPropKeys = Readonly<Array<PropKeys>>
|
|
|
85
93
|
type FormFieldGroupProps = FormFieldGroupOwnProps &
|
|
86
94
|
WithStyleProps<null, FormFieldGroupStyle> &
|
|
87
95
|
OtherHTMLAttributes<FormFieldGroupOwnProps> &
|
|
96
|
+
WithDeterministicIdProps &
|
|
88
97
|
// Adding other props that can be passed to FormFieldLayout,
|
|
89
98
|
// excluding the ones we set manually
|
|
90
99
|
Omit<
|
|
91
100
|
FormFieldLayoutOwnProps,
|
|
92
|
-
|
|
101
|
+
| 'id'
|
|
102
|
+
| 'messages'
|
|
103
|
+
| 'messagesId'
|
|
104
|
+
| 'vAlign'
|
|
105
|
+
| 'layout'
|
|
106
|
+
| 'label'
|
|
107
|
+
| 'children'
|
|
93
108
|
>
|
|
94
109
|
|
|
95
110
|
type FormFieldGroupStyle = ComponentStyle<'formFieldGroup'>
|
|
@@ -32,6 +32,27 @@ import generateStyle from './styles.js'
|
|
|
32
32
|
import { allowedProps } from './props.js'
|
|
33
33
|
import type { FormFieldLayoutProps } from './props'
|
|
34
34
|
|
|
35
|
+
// Elements that handle clicks themselves, do not override it
|
|
36
|
+
const INTERACTIVE_SELECTOR = [
|
|
37
|
+
'a[href]',
|
|
38
|
+
'button',
|
|
39
|
+
'input',
|
|
40
|
+
'select',
|
|
41
|
+
'textarea',
|
|
42
|
+
'label',
|
|
43
|
+
'summary',
|
|
44
|
+
'iframe',
|
|
45
|
+
'embed',
|
|
46
|
+
'object',
|
|
47
|
+
'audio[controls]',
|
|
48
|
+
'video[controls]',
|
|
49
|
+
'[contenteditable]:not([contenteditable="false"])',
|
|
50
|
+
'[tabindex]'
|
|
51
|
+
].join(',')
|
|
52
|
+
|
|
53
|
+
// Clicks already forwarded by a nested FormFieldLayout
|
|
54
|
+
const forwardedClicks = new WeakSet<Event>()
|
|
55
|
+
|
|
35
56
|
/**
|
|
36
57
|
---
|
|
37
58
|
parent: FormField
|
|
@@ -42,10 +63,11 @@ const FormFieldLayout = forwardRef<Element, FormFieldLayoutProps>(
|
|
|
42
63
|
const {
|
|
43
64
|
inline = false,
|
|
44
65
|
layout = 'stacked',
|
|
45
|
-
as = '
|
|
66
|
+
as = 'div',
|
|
46
67
|
labelAlign = 'end',
|
|
47
68
|
vAlign,
|
|
48
69
|
label,
|
|
70
|
+
id,
|
|
49
71
|
messages,
|
|
50
72
|
messagesId: messagesIdProp,
|
|
51
73
|
labelId: labelIdProp,
|
|
@@ -107,6 +129,8 @@ const FormFieldLayout = forwardRef<Element, FormFieldLayoutProps>(
|
|
|
107
129
|
|
|
108
130
|
const hasVisibleLabel = label ? hasVisibleChildren(label) : false
|
|
109
131
|
|
|
132
|
+
const describedBy = hasMessages ? messagesId : undefined
|
|
133
|
+
|
|
110
134
|
const invalid = !!filteredMessages?.find(
|
|
111
135
|
(m) => m.type === 'error' || m.type === 'newError'
|
|
112
136
|
)
|
|
@@ -159,6 +183,31 @@ const FormFieldLayout = forwardRef<Element, FormFieldLayoutProps>(
|
|
|
159
183
|
[inputContainerRef]
|
|
160
184
|
)
|
|
161
185
|
|
|
186
|
+
// Clicks on the rest of the control area (icons, before/after content) need
|
|
187
|
+
// to be forwarded to the control.
|
|
188
|
+
const handleChildrenClick = (e: React.MouseEvent<HTMLElement>) => {
|
|
189
|
+
if (
|
|
190
|
+
// it's a group, no clear control to forward to
|
|
191
|
+
ElementType === 'fieldset' ||
|
|
192
|
+
e.defaultPrevented ||
|
|
193
|
+
forwardedClicks.has(e.nativeEvent)
|
|
194
|
+
) {
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
const container = e.currentTarget
|
|
198
|
+
const interactive = (e.target as Element).closest(INTERACTIVE_SELECTOR)
|
|
199
|
+
if (interactive && container.contains(interactive)) {
|
|
200
|
+
return // do not steal clicks for e.g. DateInput's calendar icon
|
|
201
|
+
}
|
|
202
|
+
const control = id ? container.ownerDocument.getElementById(id) : null
|
|
203
|
+
if (!control || !container.contains(control)) {
|
|
204
|
+
return // no control
|
|
205
|
+
}
|
|
206
|
+
forwardedClicks.add(e.nativeEvent)
|
|
207
|
+
control.focus()
|
|
208
|
+
control.click()
|
|
209
|
+
}
|
|
210
|
+
|
|
162
211
|
const renderLabel = () => {
|
|
163
212
|
const labelContent = hasVisibleLabel ? (
|
|
164
213
|
<>
|
|
@@ -188,9 +237,9 @@ const FormFieldLayout = forwardRef<Element, FormFieldLayoutProps>(
|
|
|
188
237
|
)
|
|
189
238
|
}
|
|
190
239
|
return (
|
|
191
|
-
<
|
|
240
|
+
<label css={styles?.formFieldLabel} id={labelId} htmlFor={id}>
|
|
192
241
|
{labelContent}
|
|
193
|
-
</
|
|
242
|
+
</label>
|
|
194
243
|
)
|
|
195
244
|
} else if (label) {
|
|
196
245
|
if (ElementType === 'fieldset') {
|
|
@@ -200,11 +249,10 @@ const FormFieldLayout = forwardRef<Element, FormFieldLayoutProps>(
|
|
|
200
249
|
</legend>
|
|
201
250
|
)
|
|
202
251
|
}
|
|
203
|
-
// needs to be wrapped because it needs an `id`
|
|
204
252
|
return (
|
|
205
|
-
<
|
|
253
|
+
<label id={labelId} htmlFor={id} style={{ display: 'contents' }}>
|
|
206
254
|
{label}
|
|
207
|
-
</
|
|
255
|
+
</label>
|
|
208
256
|
)
|
|
209
257
|
} else return null
|
|
210
258
|
}
|
|
@@ -230,8 +278,16 @@ const FormFieldLayout = forwardRef<Element, FormFieldLayoutProps>(
|
|
|
230
278
|
>
|
|
231
279
|
{renderLabel()}
|
|
232
280
|
{isGroup && renderMessages()}
|
|
233
|
-
|
|
234
|
-
|
|
281
|
+
{/* Clicks are forwarded to the control. Needed for e.g. Select's down arrow */}
|
|
282
|
+
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
|
283
|
+
<span
|
|
284
|
+
css={styles?.formFieldChildren}
|
|
285
|
+
ref={handleInputContainerRef}
|
|
286
|
+
onClick={handleChildrenClick}
|
|
287
|
+
>
|
|
288
|
+
{typeof children === 'function'
|
|
289
|
+
? children({ describedBy })
|
|
290
|
+
: children}
|
|
235
291
|
</span>
|
|
236
292
|
{!isGroup && renderMessages()}
|
|
237
293
|
</ElementType>
|
|
@@ -35,14 +35,38 @@ import type { FormMessage } from '../../utils/v1/FormPropTypes'
|
|
|
35
35
|
import type { WithDeterministicIdProps } from '@instructure/ui-react-utils'
|
|
36
36
|
import type { NewComponentTypes } from '@instructure/ui-themes'
|
|
37
37
|
|
|
38
|
+
type FormFieldLayoutChildrenParams = {
|
|
39
|
+
/**
|
|
40
|
+
* The id of the rendered messages, or `undefined` when no messages are
|
|
41
|
+
* shown. Add it to the control's `aria-describedby`.
|
|
42
|
+
*/
|
|
43
|
+
describedBy?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type FormFieldLayoutChildren =
|
|
47
|
+
| React.ReactNode
|
|
48
|
+
| ((params: FormFieldLayoutChildrenParams) => React.ReactNode)
|
|
49
|
+
|
|
38
50
|
type FormFieldLayoutOwnProps = {
|
|
51
|
+
/**
|
|
52
|
+
* The label text. Will be appended by a `*` if this field is required.
|
|
53
|
+
*/
|
|
39
54
|
label: React.ReactNode
|
|
40
55
|
/**
|
|
41
|
-
*
|
|
56
|
+
* The id of the form control (=the child of this component). It's applied as the
|
|
57
|
+
* [for](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/for)
|
|
58
|
+
* attribute of the label, so the control needs to have the same `id`.
|
|
59
|
+
* Not used when rendering as a `fieldset` (group)
|
|
42
60
|
*/
|
|
43
|
-
id
|
|
61
|
+
id: string
|
|
44
62
|
/**
|
|
45
|
-
*
|
|
63
|
+
* The element type to render as, `div` be default.
|
|
64
|
+
*
|
|
65
|
+
* Also determines how the `label` will be rendered:
|
|
66
|
+
* If its value is `fieldset` (for form groups), it be rendered in a
|
|
67
|
+
* [legend](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/legend),
|
|
68
|
+
* for all other cases it will be in a
|
|
69
|
+
* [label](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/label).
|
|
46
70
|
*/
|
|
47
71
|
as?: AsElementType
|
|
48
72
|
/**
|
|
@@ -57,11 +81,18 @@ type FormFieldLayoutOwnProps = {
|
|
|
57
81
|
*/
|
|
58
82
|
messagesId?: string
|
|
59
83
|
/**
|
|
60
|
-
* id for the label element
|
|
61
|
-
*
|
|
84
|
+
* `id` for the label/legend element. Useful when the control is not a
|
|
85
|
+
* [labelable](https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Content_categories#labelable)
|
|
86
|
+
* element (e.g. a custom widget), and needs to reference the label via
|
|
87
|
+
* `aria-labelledby={labelId}`.
|
|
62
88
|
*/
|
|
63
89
|
labelId?: string
|
|
64
|
-
|
|
90
|
+
/**
|
|
91
|
+
* The form control(s). Can be a function that receives
|
|
92
|
+
* `{ describedBy }`, so the control can reference the messages via
|
|
93
|
+
* `aria-describedby`.
|
|
94
|
+
*/
|
|
95
|
+
children?: FormFieldLayoutChildren
|
|
65
96
|
/**
|
|
66
97
|
* If `true` use an inline layout -- content will flow on the left/right side
|
|
67
98
|
* of this component
|
|
@@ -157,6 +188,8 @@ type FormFieldStyleProps = {
|
|
|
157
188
|
}
|
|
158
189
|
|
|
159
190
|
export type {
|
|
191
|
+
FormFieldLayoutChildren,
|
|
192
|
+
FormFieldLayoutChildrenParams,
|
|
160
193
|
FormFieldStyleProps,
|
|
161
194
|
FormFieldLayoutProps,
|
|
162
195
|
FormFieldLayoutStyle,
|
|
@@ -121,6 +121,7 @@ const generateStyle = (
|
|
|
121
121
|
fontWeight: componentTheme.fontWeight,
|
|
122
122
|
fontSize: componentTheme.fontSize,
|
|
123
123
|
lineHeight: componentTheme.lineHeight,
|
|
124
|
+
paddingBottom: hasVisibleMessage ? '0' : componentTheme.gapPrimitives,
|
|
124
125
|
...(isInlineLayout && {
|
|
125
126
|
margin: '0',
|
|
126
127
|
// when inline add a small padding between the label and the control
|
|
@@ -179,8 +180,7 @@ const generateStyle = (
|
|
|
179
180
|
...labelStyles,
|
|
180
181
|
// NOTE: needs separate groups for `:is()` and `:-webkit-any()` because of css selector group validation (see https://www.w3.org/TR/selectors-3/#grouping)
|
|
181
182
|
'&:is(label)': labelStyles,
|
|
182
|
-
'&:-webkit-any(label)': labelStyles
|
|
183
|
-
paddingBottom: hasVisibleMessage ? '0' : componentTheme.gapPrimitives
|
|
183
|
+
'&:-webkit-any(label)': labelStyles
|
|
184
184
|
},
|
|
185
185
|
formFieldChildren: {
|
|
186
186
|
label: 'formFieldLayout__children',
|
package/src/exports/b.ts
CHANGED
|
@@ -36,5 +36,8 @@ export type {
|
|
|
36
36
|
export type { FormFieldOwnProps, FormFieldProps } from '../FormField/v2/props'
|
|
37
37
|
export type { FormFieldMessageProps } from '../FormFieldMessage/v2/props'
|
|
38
38
|
export type { FormFieldMessagesProps } from '../FormFieldMessages/v2/props'
|
|
39
|
-
export type {
|
|
39
|
+
export type {
|
|
40
|
+
FormFieldLayoutProps,
|
|
41
|
+
FormFieldLayoutChildrenParams
|
|
42
|
+
} from '../FormFieldLayout/v2/props'
|
|
40
43
|
export type { FormFieldGroupProps } from '../FormFieldGroup/v2/props'
|