@atlaskit/form 14.0.0 → 14.2.0

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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # @atlaskit/form
2
2
 
3
+ ## 14.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`10985771cb1e5`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/10985771cb1e5) -
8
+ Add props to form component for more transparent prop application to underlying HTML form element.
9
+
10
+ ## 14.1.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [`ba5410321550c`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ba5410321550c) -
15
+ Add new streamlined field implementation through the usage of the `component` prop and it's
16
+ associated `*Message` props. This accounts for the majority of field implementations in products
17
+ and will increase velocity in releasing accessibility improvements to all insteances using this
18
+ implementation.
19
+
3
20
  ## 14.0.0
4
21
 
5
22
  ### Major Changes
@@ -401,6 +401,282 @@ const formElement = (
401
401
  'should convert from function with single prop on form',
402
402
  );
403
403
 
404
+ describe('Migrate existing props', () => {
405
+ defineInlineTest(
406
+ { default: transformer, parser: 'tsx' },
407
+ {},
408
+ `
409
+ import React from 'react';
410
+ import Form from '@atlaskit/form';
411
+
412
+ const FormComponent1 = () => (
413
+ <Form onSubmit={() => {}}>
414
+ {({ formProps }) => (
415
+ <form {...formProps} autocomplete="off" id="foo" name="bar" noValidate>
416
+ <input />
417
+ </form>
418
+ )}
419
+ </Form>
420
+ );
421
+
422
+ const FormComponent2 = () => (
423
+ <>
424
+ <Form onSubmit={() => {}}>
425
+ {({ formProps }) => (
426
+ <form {...formProps} autocomplete="off" id="foo" name="bar" noValidate>
427
+ <input />
428
+ </form>
429
+ )}
430
+ </Form>
431
+ </>
432
+ );
433
+
434
+ class FormComponent3 extends React.Component {
435
+ render() {
436
+ return (
437
+ <Form onSubmit={() => {}}>
438
+ {({ formProps }) => (
439
+ <form {...formProps} autocomplete="off" id="foo" name="bar" noValidate>
440
+ <input />
441
+ </form>
442
+ )}
443
+ </Form>
444
+ );
445
+ }
446
+ }
447
+
448
+ const formElement = (
449
+ <Form onSubmit={() => {}}>
450
+ {({ formProps }) => (
451
+ <form {...formProps} autocomplete="off" id="foo" name="bar" noValidate>
452
+ <input />
453
+ </form>
454
+ )}
455
+ </Form>
456
+ );
457
+ `,
458
+ `
459
+ import React from 'react';
460
+ import Form from '@atlaskit/form';
461
+
462
+ const FormComponent1 = () => (
463
+ <Form onSubmit={() => {}} autocomplete="off" id="foo" name="bar" noValidate><input /></Form>
464
+ );
465
+
466
+ const FormComponent2 = () => (
467
+ <>
468
+ <Form onSubmit={() => {}} autocomplete="off" id="foo" name="bar" noValidate><input /></Form>
469
+ </>
470
+ );
471
+
472
+ class FormComponent3 extends React.Component {
473
+ render() {
474
+ return (<Form onSubmit={() => {}} autocomplete="off" id="foo" name="bar" noValidate><input /></Form>);
475
+ }
476
+ }
477
+
478
+ const formElement = (
479
+ <Form onSubmit={() => {}} autocomplete="off" id="foo" name="bar" noValidate><input /></Form>
480
+ );
481
+ `,
482
+ 'should migrate existing props on `form` into their respective props on `Form`',
483
+ );
484
+
485
+ defineInlineTest(
486
+ { default: transformer, parser: 'tsx' },
487
+ {},
488
+ `
489
+ import React from 'react';
490
+ import Form from '@atlaskit/form';
491
+
492
+ const FormComponent1 = () => (
493
+ <Form onSubmit={() => {}}>
494
+ {({ formProps }) => (
495
+ <form {...formProps} aria-label="foo" aria-labelledby="bar">
496
+ <input />
497
+ </form>
498
+ )}
499
+ </Form>
500
+ );
501
+
502
+ const FormComponent2 = () => (
503
+ <>
504
+ <Form onSubmit={() => {}}>
505
+ {({ formProps }) => (
506
+ <form {...formProps} aria-label="foo" aria-labelledby="bar">
507
+ <input />
508
+ </form>
509
+ )}
510
+ </Form>
511
+ </>
512
+ );
513
+
514
+ class FormComponent3 extends React.Component {
515
+ render() {
516
+ return (
517
+ <Form onSubmit={() => {}}>
518
+ {({ formProps }) => (
519
+ <form {...formProps} aria-label="foo" aria-labelledby="bar">
520
+ <input />
521
+ </form>
522
+ )}
523
+ </Form>
524
+ );
525
+ }
526
+ }
527
+
528
+ const formElement = (
529
+ <Form onSubmit={() => {}}>
530
+ {({ formProps }) => (
531
+ <form {...formProps} aria-label="foo" aria-labelledby="bar">
532
+ <input />
533
+ </form>
534
+ )}
535
+ </Form>
536
+ );
537
+ `,
538
+ `
539
+ import React from 'react';
540
+ import Form from '@atlaskit/form';
541
+
542
+ const FormComponent1 = () => (
543
+ <Form onSubmit={() => {}} label="foo" labelId="bar"><input /></Form>
544
+ );
545
+
546
+ const FormComponent2 = () => (
547
+ <>
548
+ <Form onSubmit={() => {}} label="foo" labelId="bar"><input /></Form>
549
+ </>
550
+ );
551
+
552
+ class FormComponent3 extends React.Component {
553
+ render() {
554
+ return (<Form onSubmit={() => {}} label="foo" labelId="bar"><input /></Form>);
555
+ }
556
+ }
557
+
558
+ const formElement = (
559
+ <Form onSubmit={() => {}} label="foo" labelId="bar"><input /></Form>
560
+ );
561
+ `,
562
+ 'should migrate existing props on `form` into different names',
563
+ );
564
+
565
+ defineInlineTest(
566
+ { default: transformer, parser: 'tsx' },
567
+ {},
568
+ `
569
+ import React from 'react';
570
+ import Form from '@atlaskit/form';
571
+
572
+ const FormComponent1 = () => (
573
+ <Form onSubmit={() => {}}>
574
+ {({ formProps }) => (
575
+ <form {...formProps} autocomplete="off" id="foo" name="bar" noValidate quu="qux">
576
+ <input />
577
+ </form>
578
+ )}
579
+ </Form>
580
+ );
581
+
582
+ const FormComponent2 = () => (
583
+ <>
584
+ <Form onSubmit={() => {}}>
585
+ {({ formProps }) => (
586
+ <form {...formProps} autocomplete="off" id="foo" name="bar" noValidate quu="qux">
587
+ <input />
588
+ </form>
589
+ )}
590
+ </Form>
591
+ </>
592
+ );
593
+
594
+ class FormComponent3 extends React.Component {
595
+ render() {
596
+ return (
597
+ <Form onSubmit={() => {}}>
598
+ {({ formProps }) => (
599
+ <form {...formProps} autocomplete="off" id="foo" name="bar" noValidate quu="qux">
600
+ <input />
601
+ </form>
602
+ )}
603
+ </Form>
604
+ );
605
+ }
606
+ }
607
+
608
+ const formElement = (
609
+ <Form onSubmit={() => {}}>
610
+ {({ formProps }) => (
611
+ <form {...formProps} autocomplete="off" id="foo" name="bar" noValidate quu="qux">
612
+ <input />
613
+ </form>
614
+ )}
615
+ </Form>
616
+ );
617
+ `,
618
+ `
619
+ import React from 'react';
620
+ import Form from '@atlaskit/form';
621
+
622
+ const FormComponent1 = () => (
623
+ <Form
624
+ onSubmit={() => {}}
625
+ formProps={{
626
+ quu: "qux"
627
+ }}
628
+ autocomplete="off"
629
+ id="foo"
630
+ name="bar"
631
+ noValidate><input /></Form>
632
+ );
633
+
634
+ const FormComponent2 = () => (
635
+ <>
636
+ <Form
637
+ onSubmit={() => {}}
638
+ formProps={{
639
+ quu: "qux"
640
+ }}
641
+ autocomplete="off"
642
+ id="foo"
643
+ name="bar"
644
+ noValidate><input /></Form>
645
+ </>
646
+ );
647
+
648
+ class FormComponent3 extends React.Component {
649
+ render() {
650
+ return (
651
+ <Form
652
+ onSubmit={() => {}}
653
+ formProps={{
654
+ quu: "qux"
655
+ }}
656
+ autocomplete="off"
657
+ id="foo"
658
+ name="bar"
659
+ noValidate><input /></Form>
660
+ );
661
+ }
662
+ }
663
+
664
+ const formElement = (
665
+ <Form
666
+ onSubmit={() => {}}
667
+ formProps={{
668
+ quu: "qux"
669
+ }}
670
+ autocomplete="off"
671
+ id="foo"
672
+ name="bar"
673
+ noValidate><input /></Form>
674
+ );
675
+ `,
676
+ 'should migrate existing props on `form` into their respective props on `Form` and also use `formProps` if needed',
677
+ );
678
+ });
679
+
404
680
  defineInlineTest(
405
681
  { default: transformer, parser: 'tsx' },
406
682
  {},
@@ -2,6 +2,7 @@ import {
2
2
  type API,
3
3
  type FileInfo,
4
4
  type JSCodeshift,
5
+ type JSXAttribute,
5
6
  type JSXElement,
6
7
  type Options,
7
8
  } from 'jscodeshift';
@@ -16,6 +17,14 @@ import {
16
17
  } from './utils/helpers';
17
18
 
18
19
  const importPath = '@atlaskit/form';
20
+ const EXISTING_FORM_ATTRIBUTES: Record<string, any> = {
21
+ autocomplete: 'autocomplete',
22
+ id: 'id',
23
+ 'aria-label': 'label',
24
+ 'aria-labelledby': 'labelId',
25
+ name: 'name',
26
+ noValidate: 'noValidate',
27
+ };
19
28
 
20
29
  const convertToSimpleForm = (j: JSCodeshift, collection: Collection<any>) => {
21
30
  const importDeclarationCollection = getImportDeclarationCollection(j, collection, importPath);
@@ -98,6 +107,8 @@ const convertToSimpleForm = (j: JSCodeshift, collection: Collection<any>) => {
98
107
  // We are required to do it this way instead of a map to make the types work correctly.
99
108
  // We also have to use `any` here and below because typing in this SUCKS.
100
109
  const nonFormPropsAttributes: any[] = [];
110
+ // These are the attributes that exist on `Form` that can migrate directly over.
111
+ const existingFormPropsAttributes: JSXAttribute[] = [];
101
112
  htmlForm?.openingElement?.attributes?.forEach((attr) => {
102
113
  if (otherSpreadPropsSeen) {
103
114
  return;
@@ -112,7 +123,9 @@ const convertToSimpleForm = (j: JSCodeshift, collection: Collection<any>) => {
112
123
  }
113
124
  }
114
125
 
115
- if (attr.name.type !== 'JSXIdentifier') {
126
+ const attrName = attr.name;
127
+
128
+ if (attrName.type !== 'JSXIdentifier') {
116
129
  return;
117
130
  }
118
131
 
@@ -120,6 +133,11 @@ const convertToSimpleForm = (j: JSCodeshift, collection: Collection<any>) => {
120
133
  return;
121
134
  }
122
135
 
136
+ if (Object.keys(EXISTING_FORM_ATTRIBUTES).includes(attrName.name)) {
137
+ existingFormPropsAttributes.push(attr);
138
+ return;
139
+ }
140
+
123
141
  let value: any;
124
142
  if (attr.value === null) {
125
143
  value = j.booleanLiteral(true) as any;
@@ -129,7 +147,7 @@ const convertToSimpleForm = (j: JSCodeshift, collection: Collection<any>) => {
129
147
  value = attr.value as any;
130
148
  }
131
149
 
132
- nonFormPropsAttributes.push(j.property('init', attr.name, value));
150
+ nonFormPropsAttributes.push(j.property('init', attrName, value));
133
151
  });
134
152
 
135
153
  // We don't know how to handle other spread props in the formProps object, so ignore it
@@ -145,6 +163,16 @@ const convertToSimpleForm = (j: JSCodeshift, collection: Collection<any>) => {
145
163
  );
146
164
  addJSXAttributeToJSXElement(j, jsxElementPath, formPropsAttr, 1);
147
165
  }
166
+ existingFormPropsAttributes.forEach((attr) => {
167
+ // add existing props to new `Form`
168
+ const fromName = attr.name.name;
169
+ if (typeof fromName !== 'string') {
170
+ return;
171
+ }
172
+ const toName = EXISTING_FORM_ATTRIBUTES[fromName];
173
+ attr.name.name = toName;
174
+ addJSXAttributeToJSXElement(j, jsxElementPath, attr, 1);
175
+ });
148
176
 
149
177
  // replace functional child with inner (all children of HTML `form`)
150
178
  const htmlFormChildren = htmlForm.children?.filter((child) => child.type !== 'JSXText');
package/dist/cjs/field.js CHANGED
@@ -14,7 +14,6 @@ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/sli
14
14
  var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
15
15
  var _react = _interopRequireWildcard(require("react"));
16
16
  var _useId = require("@atlaskit/ds-lib/use-id");
17
- var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
18
17
  var _fieldIdContext = require("./field-id-context");
19
18
  var _form = require("./form");
20
19
  var _label = require("./label");
@@ -263,7 +262,7 @@ function Field(props) {
263
262
  testId: props.testId && "".concat(props.testId, "--label")
264
263
  }, props.label, props.isRequired && /*#__PURE__*/_react.default.createElement(_requiredAsterisk.default, null), props.elementAfterLabel), /*#__PURE__*/_react.default.createElement(_fieldIdContext.FieldId.Provider, {
265
264
  value: fieldId
266
- }, component && (0, _platformFeatureFlags.fg)('platform_design-system-team_field-upgrade') ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, component && component({
265
+ }, component ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, component({
267
266
  fieldProps: extendedFieldProps
268
267
  }), /*#__PURE__*/_react.default.createElement(_messages.MessageWrapper, null, props.helperMessage && /*#__PURE__*/_react.default.createElement(_messages.HelperMessage, null, props.helperMessage), state.error && /*#__PURE__*/_react.default.createElement(_messages.ErrorMessage, null, props.errorMessage || state.error), state.meta.touched && state.valid && props.validMessage && /*#__PURE__*/_react.default.createElement(_messages.ValidMessage, null, props.validMessage))) : children({
269
268
  fieldProps: extendedFieldProps,
package/dist/cjs/form.js CHANGED
@@ -5,8 +5,7 @@ var _typeof = require("@babel/runtime/helpers/typeof");
5
5
  Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
- exports.IsDisabledContext = exports.FormContext = void 0;
9
- exports.default = Form;
8
+ exports.default = exports.IsDisabledContext = exports.FormContext = void 0;
10
9
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
11
10
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
12
11
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
@@ -14,6 +13,8 @@ var _react = _interopRequireWildcard(require("react"));
14
13
  var _finalForm = require("final-form");
15
14
  var _finalFormFocus = _interopRequireDefault(require("final-form-focus"));
16
15
  var _set = _interopRequireDefault(require("lodash/set"));
16
+ var _forwardRefWithGeneric = _interopRequireDefault(require("@atlaskit/ds-lib/forward-ref-with-generic"));
17
+ var _mergeRefs = _interopRequireDefault(require("@atlaskit/ds-lib/merge-refs"));
17
18
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
18
19
  var _utils = require("./utils");
19
20
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
@@ -42,10 +43,17 @@ var FormContext = exports.FormContext = /*#__PURE__*/(0, _react.createContext)({
42
43
  * An is disabled context creates the context for when a value is disabled.
43
44
  */
44
45
  var IsDisabledContext = exports.IsDisabledContext = /*#__PURE__*/(0, _react.createContext)(false);
45
- function Form(props) {
46
- var userProvidedFormProps = props.formProps,
46
+ var FormBase = function FormBase(props, ref) {
47
+ var autocomplete = props.autocomplete,
48
+ userProvidedFormProps = props.formProps,
49
+ id = props.id,
50
+ label = props.label,
51
+ labelId = props.labelId,
52
+ name = props.name,
53
+ noValidate = props.noValidate,
47
54
  onSubmit = props.onSubmit,
48
- testId = props.testId;
55
+ testId = props.testId,
56
+ xcss = props.xcss;
49
57
  var formRef = (0, _react.useRef)(null);
50
58
  var onSubmitRef = (0, _react.useRef)(onSubmit);
51
59
  onSubmitRef.current = onSubmit;
@@ -146,14 +154,29 @@ function Form(props) {
146
154
  subscribe: form.subscribe
147
155
  };
148
156
  }, [registerField, getCurrentValue, form.subscribe]);
157
+ var conditionalFormProps = {
158
+ autocomplete: autocomplete,
159
+ className: xcss,
160
+ id: id,
161
+ 'aria-label': label,
162
+ 'aria-labelledby': labelId,
163
+ name: name,
164
+ noValidate: noValidate,
165
+ 'data-testid': testId
166
+ };
149
167
 
150
168
  // Abstracting so we can use the same for both rendering patterns
151
- var formProps = _objectSpread({
169
+ var formProps = {
152
170
  onKeyDown: handleKeyDown,
153
171
  onSubmit: handleSubmit,
154
- ref: formRef
155
- }, testId && {
156
- 'data-testid': testId
172
+ ref: ref ? (0, _mergeRefs.default)([ref, formRef]) : formRef
173
+ };
174
+
175
+ // We don't want to add undefined values to the component
176
+ Object.keys(conditionalFormProps).forEach(function (attr) {
177
+ if (conditionalFormProps[attr] !== undefined) {
178
+ formProps[attr] = conditionalFormProps[attr];
179
+ }
157
180
  });
158
181
  var childrenContent = function () {
159
182
  if (typeof children === 'function') {
@@ -183,4 +206,16 @@ function Form(props) {
183
206
  }, /*#__PURE__*/_react.default.createElement(IsDisabledContext.Provider, {
184
207
  value: isDisabled
185
208
  }, childrenContent));
186
- }
209
+ };
210
+
211
+ /**
212
+ * __Form__
213
+ *
214
+ * A form allows users to input information.
215
+ *
216
+ * - [Examples](https://atlassian.design/components/form/examples)
217
+ * - [Code](https://atlassian.design/components/form/code)
218
+ * - [Usage](https://atlassian.design/components/form/usage)
219
+ */
220
+ var Form = (0, _forwardRefWithGeneric.default)(FormBase);
221
+ var _default = exports.default = Form;
@@ -3,7 +3,6 @@ import "./field.compiled.css";
3
3
  import { ax, ix } from "@compiled/react/runtime";
4
4
  import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
5
5
  import { useId } from '@atlaskit/ds-lib/use-id';
6
- import { fg } from '@atlaskit/platform-feature-flags';
7
6
  import { FieldId } from './field-id-context';
8
7
  import { FormContext, IsDisabledContext } from './form';
9
8
  import { Label } from './label';
@@ -246,7 +245,7 @@ export default function Field(props) {
246
245
  testId: props.testId && `${props.testId}--label`
247
246
  }, props.label, props.isRequired && /*#__PURE__*/React.createElement(RequiredAsterisk, null), props.elementAfterLabel), /*#__PURE__*/React.createElement(FieldId.Provider, {
248
247
  value: fieldId
249
- }, component && fg('platform_design-system-team_field-upgrade') ? /*#__PURE__*/React.createElement(React.Fragment, null, component && component({
248
+ }, component ? /*#__PURE__*/React.createElement(React.Fragment, null, component({
250
249
  fieldProps: extendedFieldProps
251
250
  }), /*#__PURE__*/React.createElement(MessageWrapper, null, props.helperMessage && /*#__PURE__*/React.createElement(HelperMessage, null, props.helperMessage), state.error && /*#__PURE__*/React.createElement(ErrorMessage, null, props.errorMessage || state.error), state.meta.touched && state.valid && props.validMessage && /*#__PURE__*/React.createElement(ValidMessage, null, props.validMessage))) : children({
252
251
  fieldProps: extendedFieldProps,
@@ -3,6 +3,8 @@ import React, { createContext, useCallback, useEffect, useMemo, useRef, useState
3
3
  import { createForm } from 'final-form';
4
4
  import createDecorator from 'final-form-focus';
5
5
  import set from 'lodash/set';
6
+ import forwardRefWithGeneric from '@atlaskit/ds-lib/forward-ref-with-generic';
7
+ import mergeRefs from '@atlaskit/ds-lib/merge-refs';
6
8
  import { fg } from '@atlaskit/platform-feature-flags';
7
9
  import { getFirstErrorField } from './utils';
8
10
  /**
@@ -26,11 +28,18 @@ export const FormContext = /*#__PURE__*/createContext({
26
28
  * An is disabled context creates the context for when a value is disabled.
27
29
  */
28
30
  export const IsDisabledContext = /*#__PURE__*/createContext(false);
29
- export default function Form(props) {
31
+ const FormBase = (props, ref) => {
30
32
  const {
33
+ autocomplete,
31
34
  formProps: userProvidedFormProps,
35
+ id,
36
+ label,
37
+ labelId,
38
+ name,
39
+ noValidate,
32
40
  onSubmit,
33
- testId
41
+ testId,
42
+ xcss
34
43
  } = props;
35
44
  const formRef = useRef(null);
36
45
  const onSubmitRef = useRef(onSubmit);
@@ -124,16 +133,30 @@ export default function Form(props) {
124
133
  subscribe: form.subscribe
125
134
  };
126
135
  }, [registerField, getCurrentValue, form.subscribe]);
136
+ const conditionalFormProps = {
137
+ autocomplete,
138
+ className: xcss,
139
+ id,
140
+ 'aria-label': label,
141
+ 'aria-labelledby': labelId,
142
+ name,
143
+ noValidate,
144
+ 'data-testid': testId
145
+ };
127
146
 
128
147
  // Abstracting so we can use the same for both rendering patterns
129
148
  const formProps = {
130
149
  onKeyDown: handleKeyDown,
131
150
  onSubmit: handleSubmit,
132
- ref: formRef,
133
- ...(testId && {
134
- 'data-testid': testId
135
- })
151
+ ref: ref ? mergeRefs([ref, formRef]) : formRef
136
152
  };
153
+
154
+ // We don't want to add undefined values to the component
155
+ Object.keys(conditionalFormProps).forEach(attr => {
156
+ if (conditionalFormProps[attr] !== undefined) {
157
+ formProps[attr] = conditionalFormProps[attr];
158
+ }
159
+ });
137
160
  const childrenContent = (() => {
138
161
  if (typeof children === 'function') {
139
162
  const result = children.length > 0 ? children({
@@ -159,4 +182,16 @@ export default function Form(props) {
159
182
  }, /*#__PURE__*/React.createElement(IsDisabledContext.Provider, {
160
183
  value: isDisabled
161
184
  }, childrenContent));
162
- }
185
+ };
186
+
187
+ /**
188
+ * __Form__
189
+ *
190
+ * A form allows users to input information.
191
+ *
192
+ * - [Examples](https://atlassian.design/components/form/examples)
193
+ * - [Code](https://atlassian.design/components/form/code)
194
+ * - [Usage](https://atlassian.design/components/form/usage)
195
+ */
196
+ const Form = forwardRefWithGeneric(FormBase);
197
+ export default Form;
package/dist/esm/field.js CHANGED
@@ -8,7 +8,6 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
8
8
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
9
9
  import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
10
10
  import { useId } from '@atlaskit/ds-lib/use-id';
11
- import { fg } from '@atlaskit/platform-feature-flags';
12
11
  import { FieldId } from './field-id-context';
13
12
  import { FormContext, IsDisabledContext } from './form';
14
13
  import { Label } from './label';
@@ -254,7 +253,7 @@ export default function Field(props) {
254
253
  testId: props.testId && "".concat(props.testId, "--label")
255
254
  }, props.label, props.isRequired && /*#__PURE__*/React.createElement(RequiredAsterisk, null), props.elementAfterLabel), /*#__PURE__*/React.createElement(FieldId.Provider, {
256
255
  value: fieldId
257
- }, component && fg('platform_design-system-team_field-upgrade') ? /*#__PURE__*/React.createElement(React.Fragment, null, component && component({
256
+ }, component ? /*#__PURE__*/React.createElement(React.Fragment, null, component({
258
257
  fieldProps: extendedFieldProps
259
258
  }), /*#__PURE__*/React.createElement(MessageWrapper, null, props.helperMessage && /*#__PURE__*/React.createElement(HelperMessage, null, props.helperMessage), state.error && /*#__PURE__*/React.createElement(ErrorMessage, null, props.errorMessage || state.error), state.meta.touched && state.valid && props.validMessage && /*#__PURE__*/React.createElement(ValidMessage, null, props.validMessage))) : children({
260
259
  fieldProps: extendedFieldProps,
package/dist/esm/form.js CHANGED
@@ -7,6 +7,8 @@ import React, { createContext, useCallback, useEffect, useMemo, useRef, useState
7
7
  import { createForm } from 'final-form';
8
8
  import createDecorator from 'final-form-focus';
9
9
  import set from 'lodash/set';
10
+ import forwardRefWithGeneric from '@atlaskit/ds-lib/forward-ref-with-generic';
11
+ import mergeRefs from '@atlaskit/ds-lib/merge-refs';
10
12
  import { fg } from '@atlaskit/platform-feature-flags';
11
13
  import { getFirstErrorField } from './utils';
12
14
  /**
@@ -32,10 +34,17 @@ export var FormContext = /*#__PURE__*/createContext({
32
34
  * An is disabled context creates the context for when a value is disabled.
33
35
  */
34
36
  export var IsDisabledContext = /*#__PURE__*/createContext(false);
35
- export default function Form(props) {
36
- var userProvidedFormProps = props.formProps,
37
+ var FormBase = function FormBase(props, ref) {
38
+ var autocomplete = props.autocomplete,
39
+ userProvidedFormProps = props.formProps,
40
+ id = props.id,
41
+ label = props.label,
42
+ labelId = props.labelId,
43
+ name = props.name,
44
+ noValidate = props.noValidate,
37
45
  onSubmit = props.onSubmit,
38
- testId = props.testId;
46
+ testId = props.testId,
47
+ xcss = props.xcss;
39
48
  var formRef = useRef(null);
40
49
  var onSubmitRef = useRef(onSubmit);
41
50
  onSubmitRef.current = onSubmit;
@@ -136,14 +145,29 @@ export default function Form(props) {
136
145
  subscribe: form.subscribe
137
146
  };
138
147
  }, [registerField, getCurrentValue, form.subscribe]);
148
+ var conditionalFormProps = {
149
+ autocomplete: autocomplete,
150
+ className: xcss,
151
+ id: id,
152
+ 'aria-label': label,
153
+ 'aria-labelledby': labelId,
154
+ name: name,
155
+ noValidate: noValidate,
156
+ 'data-testid': testId
157
+ };
139
158
 
140
159
  // Abstracting so we can use the same for both rendering patterns
141
- var formProps = _objectSpread({
160
+ var formProps = {
142
161
  onKeyDown: handleKeyDown,
143
162
  onSubmit: handleSubmit,
144
- ref: formRef
145
- }, testId && {
146
- 'data-testid': testId
163
+ ref: ref ? mergeRefs([ref, formRef]) : formRef
164
+ };
165
+
166
+ // We don't want to add undefined values to the component
167
+ Object.keys(conditionalFormProps).forEach(function (attr) {
168
+ if (conditionalFormProps[attr] !== undefined) {
169
+ formProps[attr] = conditionalFormProps[attr];
170
+ }
147
171
  });
148
172
  var childrenContent = function () {
149
173
  if (typeof children === 'function') {
@@ -173,4 +197,16 @@ export default function Form(props) {
173
197
  }, /*#__PURE__*/React.createElement(IsDisabledContext.Provider, {
174
198
  value: isDisabled
175
199
  }, childrenContent));
176
- }
200
+ };
201
+
202
+ /**
203
+ * __Form__
204
+ *
205
+ * A form allows users to input information.
206
+ *
207
+ * - [Examples](https://atlassian.design/components/form/examples)
208
+ * - [Code](https://atlassian.design/components/form/code)
209
+ * - [Usage](https://atlassian.design/components/form/usage)
210
+ */
211
+ var Form = forwardRefWithGeneric(FormBase);
212
+ export default Form;
@@ -1,5 +1,6 @@
1
1
  import React, { type ReactNode } from 'react';
2
2
  import { type FieldConfig, type FieldSubscriber, type FieldSubscription, type FormApi, type FormState, type Unsubscribe } from 'final-form';
3
+ import type { StrictXCSSProp, XCSSAllProperties, XCSSAllPseudos } from '@atlaskit/css';
3
4
  import { type OnSubmitHandler } from './types';
4
5
  type DefaultValue<FieldValue> = (value?: FieldValue) => FieldValue;
5
6
  type RegisterField = <FieldValue>(name: string, defaultValue: FieldValue | DefaultValue<FieldValue>, subscriber: FieldSubscriber<FieldValue>, subscription: FieldSubscription, config: FieldConfig<FieldValue>) => Unsubscribe;
@@ -21,7 +22,7 @@ export declare const FormContext: React.Context<{
21
22
  */
22
23
  export declare const IsDisabledContext: React.Context<boolean>;
23
24
  interface FormChildrenProps {
24
- ref: React.RefObject<HTMLFormElement>;
25
+ ref: React.RefObject<HTMLFormElement> | ((value: HTMLFormElement | null) => void);
25
26
  onSubmit: (event?: React.FormEvent<HTMLFormElement> | React.SyntheticEvent<HTMLElement>) => void;
26
27
  onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;
27
28
  }
@@ -37,11 +38,22 @@ type FormChildrenArgs<FormValues> = {
37
38
  reset: (initialValues?: FormValues) => void;
38
39
  };
39
40
  type ExcludeReservedFormProps = {
41
+ autocomplete?: never;
42
+ id?: never;
43
+ label?: never;
44
+ labelId?: never;
40
45
  onKeyDown?: never;
41
46
  onSubmit?: never;
47
+ name?: never;
48
+ noValidate?: never;
42
49
  ref?: never;
50
+ xcss?: never;
43
51
  };
44
52
  export interface FormProps<FormValues> {
53
+ /**
54
+ * Indicates whether the value of the form's controls can be automatically completed by the browser. It is `on` by default.
55
+ */
56
+ autocomplete?: 'off' | 'on';
45
57
  /**
46
58
  * The contents rendered inside of the form. This is a function where the props will be passed from the form. The function props you can access are `dirty`, `submitting` and `disabled`.
47
59
  * You can read more about these props in [react-final form documentation](https://final-form.org/docs/final-form/types/FormState).
@@ -56,17 +68,53 @@ export interface FormProps<FormValues> {
56
68
  [x: string]: any;
57
69
  } & ExcludeReservedFormProps;
58
70
  /**
59
- * Event handler called when the form is submitted. Fields must be free of validation errors.
71
+ * `id` attribute applied to the `form` element.
72
+ */
73
+ id?: string;
74
+ /**
75
+ * Accessible name to be applied to the form element. Maps to the `aria-label` attribute.
76
+ */
77
+ label?: string;
78
+ /**
79
+ * ID of the element that has the accessible name to be applied to the form element. Maps to the `aria-labelledby` attribute.
80
+ */
81
+ labelId?: string;
82
+ /**
83
+ * Event handler called when the form is submitted. Fields must be free of validation errors.
60
84
  */
61
85
  onSubmit: OnSubmitHandler<FormValues>;
62
86
  /**
63
- * Sets the form and its fields as disabled. Users cannot edit or focus on the fields.
87
+ * Sets the form and its fields as disabled. Users cannot edit or focus on the fields.
64
88
  */
65
89
  isDisabled?: boolean;
90
+ /**
91
+ * `name` attribute applied to the `form` element.
92
+ */
93
+ name?: string;
94
+ /**
95
+ * Indicates if the inputs within the form will bypass HTML5 constraint
96
+ * validation when submitted. This is not recommended to be used because it
97
+ * can cause experiences to be inaccessible. It is `false` by default but will
98
+ * be set to `true` in the future to increase accessibility, so it is **not recommended**.
99
+ */
100
+ noValidate?: boolean;
66
101
  /**
67
102
  * A test identifier for the form element. This will be applied as `data-testid` attribute.
68
103
  */
69
104
  testId?: string;
105
+ /**
106
+ * Apply a subset of permitted styles powered by Atlassian Design System design tokens.
107
+ */
108
+ xcss?: StrictXCSSProp<XCSSAllProperties, XCSSAllPseudos>;
70
109
  }
71
- export default function Form<FormValues extends Record<string, any> = {}>(props: FormProps<FormValues>): React.JSX.Element;
72
- export {};
110
+ /**
111
+ * __Form__
112
+ *
113
+ * A form allows users to input information.
114
+ *
115
+ * - [Examples](https://atlassian.design/components/form/examples)
116
+ * - [Code](https://atlassian.design/components/form/code)
117
+ * - [Usage](https://atlassian.design/components/form/usage)
118
+ */
119
+ declare const Form: <FormValues extends Record<string, any> = {}>(props: FormProps<FormValues> & React.RefAttributes<HTMLFormElement>) => React.ReactElement | null;
120
+ export default Form;
@@ -1,5 +1,6 @@
1
1
  import React, { type ReactNode } from 'react';
2
2
  import { type FieldConfig, type FieldSubscriber, type FieldSubscription, type FormApi, type FormState, type Unsubscribe } from 'final-form';
3
+ import type { StrictXCSSProp, XCSSAllProperties, XCSSAllPseudos } from '@atlaskit/css';
3
4
  import { type OnSubmitHandler } from './types';
4
5
  type DefaultValue<FieldValue> = (value?: FieldValue) => FieldValue;
5
6
  type RegisterField = <FieldValue>(name: string, defaultValue: FieldValue | DefaultValue<FieldValue>, subscriber: FieldSubscriber<FieldValue>, subscription: FieldSubscription, config: FieldConfig<FieldValue>) => Unsubscribe;
@@ -21,7 +22,7 @@ export declare const FormContext: React.Context<{
21
22
  */
22
23
  export declare const IsDisabledContext: React.Context<boolean>;
23
24
  interface FormChildrenProps {
24
- ref: React.RefObject<HTMLFormElement>;
25
+ ref: React.RefObject<HTMLFormElement> | ((value: HTMLFormElement | null) => void);
25
26
  onSubmit: (event?: React.FormEvent<HTMLFormElement> | React.SyntheticEvent<HTMLElement>) => void;
26
27
  onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;
27
28
  }
@@ -37,11 +38,22 @@ type FormChildrenArgs<FormValues> = {
37
38
  reset: (initialValues?: FormValues) => void;
38
39
  };
39
40
  type ExcludeReservedFormProps = {
41
+ autocomplete?: never;
42
+ id?: never;
43
+ label?: never;
44
+ labelId?: never;
40
45
  onKeyDown?: never;
41
46
  onSubmit?: never;
47
+ name?: never;
48
+ noValidate?: never;
42
49
  ref?: never;
50
+ xcss?: never;
43
51
  };
44
52
  export interface FormProps<FormValues> {
53
+ /**
54
+ * Indicates whether the value of the form's controls can be automatically completed by the browser. It is `on` by default.
55
+ */
56
+ autocomplete?: 'off' | 'on';
45
57
  /**
46
58
  * The contents rendered inside of the form. This is a function where the props will be passed from the form. The function props you can access are `dirty`, `submitting` and `disabled`.
47
59
  * You can read more about these props in [react-final form documentation](https://final-form.org/docs/final-form/types/FormState).
@@ -56,17 +68,53 @@ export interface FormProps<FormValues> {
56
68
  [x: string]: any;
57
69
  } & ExcludeReservedFormProps;
58
70
  /**
59
- * Event handler called when the form is submitted. Fields must be free of validation errors.
71
+ * `id` attribute applied to the `form` element.
72
+ */
73
+ id?: string;
74
+ /**
75
+ * Accessible name to be applied to the form element. Maps to the `aria-label` attribute.
76
+ */
77
+ label?: string;
78
+ /**
79
+ * ID of the element that has the accessible name to be applied to the form element. Maps to the `aria-labelledby` attribute.
80
+ */
81
+ labelId?: string;
82
+ /**
83
+ * Event handler called when the form is submitted. Fields must be free of validation errors.
60
84
  */
61
85
  onSubmit: OnSubmitHandler<FormValues>;
62
86
  /**
63
- * Sets the form and its fields as disabled. Users cannot edit or focus on the fields.
87
+ * Sets the form and its fields as disabled. Users cannot edit or focus on the fields.
64
88
  */
65
89
  isDisabled?: boolean;
90
+ /**
91
+ * `name` attribute applied to the `form` element.
92
+ */
93
+ name?: string;
94
+ /**
95
+ * Indicates if the inputs within the form will bypass HTML5 constraint
96
+ * validation when submitted. This is not recommended to be used because it
97
+ * can cause experiences to be inaccessible. It is `false` by default but will
98
+ * be set to `true` in the future to increase accessibility, so it is **not recommended**.
99
+ */
100
+ noValidate?: boolean;
66
101
  /**
67
102
  * A test identifier for the form element. This will be applied as `data-testid` attribute.
68
103
  */
69
104
  testId?: string;
105
+ /**
106
+ * Apply a subset of permitted styles powered by Atlassian Design System design tokens.
107
+ */
108
+ xcss?: StrictXCSSProp<XCSSAllProperties, XCSSAllPseudos>;
70
109
  }
71
- export default function Form<FormValues extends Record<string, any> = {}>(props: FormProps<FormValues>): React.JSX.Element;
72
- export {};
110
+ /**
111
+ * __Form__
112
+ *
113
+ * A form allows users to input information.
114
+ *
115
+ * - [Examples](https://atlassian.design/components/form/examples)
116
+ * - [Code](https://atlassian.design/components/form/code)
117
+ * - [Usage](https://atlassian.design/components/form/usage)
118
+ */
119
+ declare const Form: <FormValues extends Record<string, any> = {}>(props: FormProps<FormValues> & React.RefAttributes<HTMLFormElement>) => React.ReactElement | null;
120
+ export default Form;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/form",
3
- "version": "14.0.0",
3
+ "version": "14.2.0",
4
4
  "description": "A form allows users to input information.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -28,7 +28,7 @@
28
28
  "@atlaskit/css": "^0.14.0",
29
29
  "@atlaskit/ds-lib": "^5.1.0",
30
30
  "@atlaskit/heading": "^5.2.0",
31
- "@atlaskit/icon": "^28.2.0",
31
+ "@atlaskit/icon": "^28.3.0",
32
32
  "@atlaskit/platform-feature-flags": "^1.1.0",
33
33
  "@atlaskit/primitives": "^14.15.0",
34
34
  "@atlaskit/tokens": "^6.3.0",
@@ -52,7 +52,7 @@
52
52
  "@atlaskit/docs": "^11.1.0",
53
53
  "@atlaskit/link": "^3.2.0",
54
54
  "@atlaskit/lozenge": "^13.0.0",
55
- "@atlaskit/modal-dialog": "^14.3.0",
55
+ "@atlaskit/modal-dialog": "^14.4.0",
56
56
  "@atlaskit/radio": "^8.3.0",
57
57
  "@atlaskit/range": "^9.2.0",
58
58
  "@atlaskit/section-message": "^8.7.0",
@@ -110,9 +110,6 @@
110
110
  "platform_dst_form_screenreader_message_fix": {
111
111
  "type": "boolean"
112
112
  },
113
- "platform_design-system-team_field-upgrade": {
114
- "type": "boolean"
115
- },
116
113
  "platform-form-field-error-focus": {
117
114
  "type": "boolean"
118
115
  },