@instructure/ui-form-field 9.11.0 → 9.11.1

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/es/FormField/index.js +5 -1
  3. package/es/FormFieldGroup/__new-tests__/FormFieldGroup.test.js +4 -4
  4. package/es/FormFieldGroup/index.js +18 -4
  5. package/es/FormFieldLabel/index.js +6 -6
  6. package/es/FormFieldLayout/__new-tests__/FormFieldLayout.test.js +10 -8
  7. package/es/FormFieldLayout/index.js +79 -58
  8. package/es/FormFieldLayout/props.js +1 -6
  9. package/es/FormFieldLayout/styles.js +105 -9
  10. package/es/FormFieldLayout/theme.js +56 -0
  11. package/es/FormFieldMessages/props.js +3 -2
  12. package/es/FormFieldMessages/styles.js +5 -3
  13. package/es/FormPropTypes.js +6 -0
  14. package/lib/FormField/index.js +5 -1
  15. package/lib/FormFieldGroup/__new-tests__/FormFieldGroup.test.js +4 -4
  16. package/lib/FormFieldGroup/index.js +18 -4
  17. package/lib/FormFieldLabel/index.js +6 -5
  18. package/lib/FormFieldLayout/__new-tests__/FormFieldLayout.test.js +9 -7
  19. package/lib/FormFieldLayout/index.js +77 -58
  20. package/lib/FormFieldLayout/props.js +1 -6
  21. package/lib/FormFieldLayout/styles.js +105 -9
  22. package/lib/FormFieldLayout/theme.js +62 -0
  23. package/lib/FormFieldMessages/props.js +3 -2
  24. package/lib/FormFieldMessages/styles.js +5 -3
  25. package/lib/FormPropTypes.js +6 -0
  26. package/package.json +15 -15
  27. package/src/FormField/README.md +31 -3
  28. package/src/FormField/index.tsx +3 -0
  29. package/src/FormFieldGroup/__new-tests__/FormFieldGroup.test.tsx +4 -6
  30. package/src/FormFieldGroup/index.tsx +41 -6
  31. package/src/FormFieldLabel/index.tsx +8 -3
  32. package/src/FormFieldLayout/__new-tests__/FormFieldLayout.test.tsx +6 -8
  33. package/src/FormFieldLayout/index.tsx +83 -100
  34. package/src/FormFieldLayout/props.ts +30 -7
  35. package/src/FormFieldLayout/styles.ts +124 -12
  36. package/src/FormFieldLayout/theme.ts +59 -0
  37. package/src/FormFieldMessages/props.ts +8 -2
  38. package/src/FormFieldMessages/styles.ts +5 -4
  39. package/src/FormPropTypes.ts +4 -0
  40. package/tsconfig.build.tsbuildinfo +1 -1
  41. package/types/FormField/index.d.ts.map +1 -1
  42. package/types/FormFieldGroup/index.d.ts +1 -0
  43. package/types/FormFieldGroup/index.d.ts.map +1 -1
  44. package/types/FormFieldLabel/index.d.ts +2 -2
  45. package/types/FormFieldLabel/index.d.ts.map +1 -1
  46. package/types/FormFieldLayout/index.d.ts +8 -7
  47. package/types/FormFieldLayout/index.d.ts.map +1 -1
  48. package/types/FormFieldLayout/props.d.ts +27 -3
  49. package/types/FormFieldLayout/props.d.ts.map +1 -1
  50. package/types/FormFieldLayout/styles.d.ts +4 -3
  51. package/types/FormFieldLayout/styles.d.ts.map +1 -1
  52. package/types/FormFieldLayout/theme.d.ts +10 -0
  53. package/types/FormFieldLayout/theme.d.ts.map +1 -0
  54. package/types/FormFieldMessages/index.d.ts +8 -2
  55. package/types/FormFieldMessages/index.d.ts.map +1 -1
  56. package/types/FormFieldMessages/props.d.ts +5 -0
  57. package/types/FormFieldMessages/props.d.ts.map +1 -1
  58. package/types/FormFieldMessages/styles.d.ts +2 -3
  59. package/types/FormFieldMessages/styles.d.ts.map +1 -1
  60. package/types/FormPropTypes.d.ts +3 -0
  61. package/types/FormPropTypes.d.ts.map +1 -1
@@ -25,6 +25,12 @@
25
25
  import PropTypes from 'prop-types';
26
26
  const formMessageTypePropType = PropTypes.oneOf(['error', 'newError', 'hint', 'success', 'screenreader-only']);
27
27
  const formMessageChildPropType = PropTypes.node;
28
+
29
+ // TODO it will be easier if this would be just a string
30
+ /**
31
+ * The text to display in the form message
32
+ */
33
+
28
34
  /**
29
35
  * ---
30
36
  * category: utilities/form
@@ -55,7 +55,11 @@ class FormField extends _react.Component {
55
55
  return /*#__PURE__*/_react.default.createElement(_FormFieldLayout.FormFieldLayout, Object.assign({}, (0, _omitProps.omitProps)(this.props, FormField.allowedProps), (0, _pickProps.pickProps)(this.props, _FormFieldLayout.FormFieldLayout.allowedProps), {
56
56
  label: this.props.label,
57
57
  vAlign: this.props.vAlign,
58
- as: "label",
58
+ as: "label"
59
+ // This makes the control in focus when the label is clicked
60
+ // This is needed to prevent the wrong element to be focused, e.g.
61
+ // multi selects Tag-s
62
+ ,
59
63
  htmlFor: this.props.id,
60
64
  elementRef: this.handleRef
61
65
  }));
@@ -48,7 +48,7 @@ describe('<FormFieldGroup />', () => {
48
48
  description: "Please enter your full name"
49
49
  }, /*#__PURE__*/_react.default.createElement("label", null, "First: ", /*#__PURE__*/_react.default.createElement("input", null)), /*#__PURE__*/_react.default.createElement("label", null, "Middle: ", /*#__PURE__*/_react.default.createElement("input", null)), /*#__PURE__*/_react.default.createElement("label", null, "Last: ", /*#__PURE__*/_react.default.createElement("input", null))))),
50
50
  container = _render.container;
51
- const formFieldGroup = container.querySelector("fieldset[class$='-formFieldLayout']");
51
+ const formFieldGroup = container.querySelector("span[class$='-formFieldLayout__label']");
52
52
  const firstNameInput = _react2.screen.getByLabelText('First:');
53
53
  const middleNameInput = _react2.screen.getByLabelText('Middle:');
54
54
  const lastNameInput = _react2.screen.getByLabelText('Last:');
@@ -66,7 +66,7 @@ describe('<FormFieldGroup />', () => {
66
66
  description: "Please enter your full name"
67
67
  }, children)),
68
68
  container = _render2.container;
69
- const formFieldGroup = container.querySelector("fieldset[class$='-formFieldLayout']");
69
+ const formFieldGroup = container.querySelector('label');
70
70
  expect(formFieldGroup).toBeInTheDocument();
71
71
  });
72
72
  it('links the messages to the fieldset via aria-describedby', () => {
@@ -88,13 +88,13 @@ describe('<FormFieldGroup />', () => {
88
88
  expect(message).toHaveTextContent('Invalid name');
89
89
  expect(message).toHaveAttribute('id', messagesId);
90
90
  });
91
- it('displays description message inside the legend', () => {
91
+ it('displays description message inside the label', () => {
92
92
  const description = 'Please enter your full name';
93
93
  const _render4 = (0, _react2.render)( /*#__PURE__*/_react.default.createElement(_index.FormFieldGroup, {
94
94
  description: description
95
95
  }, _label5 || (_label5 = /*#__PURE__*/_react.default.createElement("label", null, "First: ", /*#__PURE__*/_react.default.createElement("input", null))), _label6 || (_label6 = /*#__PURE__*/_react.default.createElement("label", null, "Middle: ", /*#__PURE__*/_react.default.createElement("input", null))), _label7 || (_label7 = /*#__PURE__*/_react.default.createElement("label", null, "Last: ", /*#__PURE__*/_react.default.createElement("input", null))))),
96
96
  container = _render4.container;
97
- const legend = container.querySelector("legend[class$='-screenReaderContent']");
97
+ const legend = container.querySelector("span[class$='-formFieldLayout__label']");
98
98
  expect(legend).toBeInTheDocument();
99
99
  expect(legend).toHaveTextContent(description);
100
100
  });
@@ -67,13 +67,17 @@ let FormFieldGroup = exports.FormFieldGroup = (_dec = (0, _emotion.withStyle)(_s
67
67
  (_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2, this.makeStylesVariables);
68
68
  }
69
69
  get makeStylesVariables() {
70
+ // new form errors dont need borders
71
+ const oldInvalid = !!this.props.messages && this.props.messages.findIndex(message => {
72
+ return message.type === 'error';
73
+ }) >= 0;
70
74
  return {
71
- invalid: this.invalid
75
+ invalid: oldInvalid
72
76
  };
73
77
  }
74
78
  get invalid() {
75
79
  return !!this.props.messages && this.props.messages.findIndex(message => {
76
- return message.type === 'error';
80
+ return message.type === 'error' || message.type === 'newError';
77
81
  }) >= 0;
78
82
  }
79
83
  renderColumns() {
@@ -105,12 +109,21 @@ let FormFieldGroup = exports.FormFieldGroup = (_dec = (0, _emotion.withStyle)(_s
105
109
  makeStyles = _this$props3.makeStyles,
106
110
  isGroup = _this$props3.isGroup,
107
111
  props = (0, _objectWithoutProperties2.default)(_this$props3, _excluded);
112
+ // This is quite ugly, but according to ARIA spec the `aria-invalid` prop
113
+ // can only be used with certain roles see
114
+ // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-invalid#associated_roles
115
+ // `aria-invalid` is put on in FormFieldLayout because the error message
116
+ // DOM part gets there its ID.
117
+ let ariaInvalid = void 0;
118
+ if (this.props.role && this.invalid && ['application', 'checkbox', 'combobox', 'gridcell', 'listbox', 'radiogroup', 'slider', 'spinbutton', 'textbox', 'tree', 'columnheader', 'rowheader', 'searchbox', 'switch', 'treegrid'].includes(this.props.role)) {
119
+ ariaInvalid = 'true';
120
+ }
108
121
  return (0, _emotion.jsx)(_FormFieldLayout.FormFieldLayout, Object.assign({}, (0, _omitProps.omitProps)(props, FormFieldGroup.allowedProps), (0, _pickProps.pickProps)(props, _FormFieldLayout.FormFieldLayout.allowedProps), {
109
122
  vAlign: props.vAlign,
110
123
  layout: props.layout === 'inline' ? 'inline' : 'stacked',
111
124
  label: props.description,
112
125
  "aria-disabled": props.disabled ? 'true' : void 0,
113
- "aria-invalid": this.invalid ? 'true' : void 0,
126
+ "aria-invalid": ariaInvalid,
114
127
  elementRef: this.handleRef,
115
128
  isGroup: isGroup
116
129
  }), this.renderFields());
@@ -120,6 +133,7 @@ let FormFieldGroup = exports.FormFieldGroup = (_dec = (0, _emotion.withStyle)(_s
120
133
  disabled: false,
121
134
  rowSpacing: 'medium',
122
135
  colSpacing: 'small',
123
- vAlign: 'middle'
136
+ vAlign: 'middle',
137
+ isGroup: true
124
138
  }, _FormFieldGroup)) || _class);
125
139
  var _default = exports.default = FormFieldGroup;
@@ -8,11 +8,12 @@ exports.default = exports.FormFieldLabel = void 0;
8
8
  var _react = require("react");
9
9
  var _omitProps = require("@instructure/ui-react-utils/lib/omitProps.js");
10
10
  var _getElementType = require("@instructure/ui-react-utils/lib/getElementType.js");
11
+ var _deprecated = require("@instructure/ui-react-utils/lib/deprecated.js");
11
12
  var _emotion = require("@instructure/emotion");
12
13
  var _styles = _interopRequireDefault(require("./styles"));
13
14
  var _theme = _interopRequireDefault(require("./theme"));
14
15
  var _props = require("./props");
15
- var _dec, _class, _FormFieldLabel;
16
+ var _dec, _dec2, _class, _FormFieldLabel;
16
17
  /*
17
18
  * The MIT License (MIT)
18
19
  *
@@ -42,8 +43,7 @@ var _dec, _class, _FormFieldLabel;
42
43
  parent: FormField
43
44
  ---
44
45
 
45
- This is a helper component that is used by most of the custom form
46
- components. In most cases it shouldn't be used directly.
46
+ This is a helper component that is used by most of the custom form components. In most cases it shouldn't be used directly.
47
47
 
48
48
  ```js
49
49
  ---
@@ -52,8 +52,9 @@ type: example
52
52
  <FormFieldLabel>Hello</FormFieldLabel>
53
53
  ```
54
54
 
55
+ @deprecated This is an internal component that will be removed in the future
55
56
  **/
56
- let FormFieldLabel = exports.FormFieldLabel = (_dec = (0, _emotion.withStyle)(_styles.default, _theme.default), _dec(_class = (_FormFieldLabel = class FormFieldLabel extends _react.Component {
57
+ let FormFieldLabel = exports.FormFieldLabel = (_dec = (0, _emotion.withStyle)(_styles.default, _theme.default), _dec2 = (0, _deprecated.deprecated)('10', null, 'This component will be removed in a future version'), _dec(_class = _dec2(_class = (_FormFieldLabel = class FormFieldLabel extends _react.Component {
57
58
  constructor(...args) {
58
59
  super(...args);
59
60
  this.ref = null;
@@ -81,5 +82,5 @@ let FormFieldLabel = exports.FormFieldLabel = (_dec = (0, _emotion.withStyle)(_s
81
82
  }
82
83
  }, _FormFieldLabel.displayName = "FormFieldLabel", _FormFieldLabel.componentId = 'FormFieldLabel', _FormFieldLabel.propTypes = _props.propTypes, _FormFieldLabel.allowedProps = _props.allowedProps, _FormFieldLabel.defaultProps = {
83
84
  as: 'span'
84
- }, _FormFieldLabel)) || _class);
85
+ }, _FormFieldLabel)) || _class) || _class);
85
86
  var _default = exports.default = FormFieldLabel;
@@ -7,7 +7,7 @@ var _vitest = require("vitest");
7
7
  var _runAxeCheck = require("@instructure/ui-axe-check/lib/runAxeCheck.js");
8
8
  require("@testing-library/jest-dom");
9
9
  var _index = require("../index");
10
- var _FormFieldLayout, _FormFieldLayout2, _input;
10
+ var _FormFieldLayout, _FormFieldLayout2;
11
11
  /*
12
12
  * The MIT License (MIT)
13
13
  *
@@ -49,7 +49,7 @@ describe('<FormFieldLayout />', () => {
49
49
  }))),
50
50
  container = _render.container;
51
51
  const formFieldLayout = container.querySelector("label[class$='-formFieldLayout']");
52
- const formFieldLabel = container.querySelector("span[class$='-formFieldLabel']");
52
+ const formFieldLabel = container.querySelector("span[class$='-formFieldLayout__label']");
53
53
  expect(formFieldLayout).toBeInTheDocument();
54
54
  expect(formFieldLabel).toBeInTheDocument();
55
55
  expect(formFieldLabel).toHaveTextContent('Username');
@@ -64,13 +64,15 @@ describe('<FormFieldLayout />', () => {
64
64
  });
65
65
  it('should provide a ref to the input container', () => {
66
66
  const inputContainerRef = _vitest.vi.fn();
67
+ const ref = /*#__PURE__*/_react.default.createRef();
67
68
  (0, _react2.render)( /*#__PURE__*/_react.default.createElement(_index.FormFieldLayout, {
68
69
  label: "Username",
69
70
  inputContainerRef: inputContainerRef
70
- }, _input || (_input = /*#__PURE__*/_react.default.createElement("input", {
71
- type: "text"
72
- }))));
73
- const input = _react2.screen.getByLabelText('Username');
74
- expect(inputContainerRef).toHaveBeenCalledWith(input.parentElement);
71
+ }, /*#__PURE__*/_react.default.createElement("input", {
72
+ type: "text",
73
+ ref: ref
74
+ })));
75
+ expect(ref.current).toBeInstanceOf(HTMLInputElement);
76
+ expect(inputContainerRef).toHaveBeenCalledWith(ref.current.parentElement);
75
77
  });
76
78
  });
@@ -8,18 +8,14 @@ exports.default = exports.FormFieldLayout = void 0;
8
8
  var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
9
9
  var _react = require("react");
10
10
  var _hasVisibleChildren = require("@instructure/ui-a11y-utils/lib/hasVisibleChildren.js");
11
- var _ScreenReaderContent = require("@instructure/ui-a11y-content/lib/ScreenReaderContent");
12
- var _Grid = require("@instructure/ui-grid/lib/Grid");
13
- var _console = require("@instructure/console");
14
11
  var _omitProps = require("@instructure/ui-react-utils/lib/omitProps.js");
15
- var _pickProps = require("@instructure/ui-react-utils/lib/pickProps.js");
16
12
  var _getElementType = require("@instructure/ui-react-utils/lib/getElementType.js");
17
13
  var _withDeterministicId = require("@instructure/ui-react-utils/lib/DeterministicIdContext/withDeterministicId.js");
18
14
  var _emotion = require("@instructure/emotion");
19
- var _FormFieldLabel = require("../FormFieldLabel");
20
15
  var _FormFieldMessages = require("../FormFieldMessages");
21
16
  var _styles = _interopRequireDefault(require("./styles"));
22
17
  var _props = require("./props");
18
+ var _theme = _interopRequireDefault(require("./theme"));
23
19
  const _excluded = ["makeStyles", "styles", "messages", "isGroup"];
24
20
  var _dec, _dec2, _class, _FormFieldLayout;
25
21
  /*
@@ -51,10 +47,11 @@ var _dec, _dec2, _class, _FormFieldLayout;
51
47
  parent: FormField
52
48
  ---
53
49
  **/
54
- let FormFieldLayout = exports.FormFieldLayout = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 = (0, _emotion.withStyle)(_styles.default, null), _dec(_class = _dec2(_class = (_FormFieldLayout = class FormFieldLayout extends _react.Component {
50
+ let FormFieldLayout = exports.FormFieldLayout = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 = (0, _emotion.withStyle)(_styles.default, _theme.default), _dec(_class = _dec2(_class = (_FormFieldLayout = class FormFieldLayout extends _react.Component {
55
51
  constructor(props) {
56
52
  super(props);
57
53
  this._messagesId = void 0;
54
+ this._labelId = void 0;
58
55
  this.ref = null;
59
56
  this.handleRef = el => {
60
57
  const elementRef = this.props.elementRef;
@@ -63,71 +60,99 @@ let FormFieldLayout = exports.FormFieldLayout = (_dec = (0, _withDeterministicId
63
60
  elementRef(el);
64
61
  }
65
62
  };
63
+ this.makeStyleProps = () => {
64
+ var _this$props$messages;
65
+ const hasNewErrorMsgAndIsGroup = !!((_this$props$messages = this.props.messages) !== null && _this$props$messages !== void 0 && _this$props$messages.find(m => m.type === 'newError')) && !!this.props.isGroup;
66
+ return {
67
+ hasMessages: this.hasMessages,
68
+ hasVisibleLabel: this.hasVisibleLabel,
69
+ // if true render error message above the controls (and below the label)
70
+ hasNewErrorMsgAndIsGroup: hasNewErrorMsgAndIsGroup
71
+ };
72
+ };
66
73
  this.handleInputContainerRef = node => {
67
74
  if (typeof this.props.inputContainerRef === 'function') {
68
75
  this.props.inputContainerRef(node);
69
76
  }
70
77
  };
71
78
  this._messagesId = props.messagesId || props.deterministicId();
72
- (0, _console.logError)(typeof props.width !== 'undefined' || !props.inline || props.layout !== 'inline', `[FormFieldLayout] The 'inline' prop is true, and the 'layout' is set to 'inline'.
73
- This will cause a layout issue in Internet Explorer 11 unless you also add a value for the 'width' prop.`);
79
+ this._labelId = props.deterministicId('FormField-Label');
74
80
  }
75
81
  componentDidMount() {
76
82
  var _this$props$makeStyle, _this$props;
77
- (_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props);
83
+ (_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props, this.makeStyleProps());
78
84
  }
79
85
  componentDidUpdate() {
80
86
  var _this$props$makeStyle2, _this$props2;
81
- (_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2);
87
+ (_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2, this.makeStyleProps());
82
88
  }
83
89
  get hasVisibleLabel() {
84
- return this.props.label && (0, _hasVisibleChildren.hasVisibleChildren)(this.props.label);
90
+ return this.props.label ? (0, _hasVisibleChildren.hasVisibleChildren)(this.props.label) : false;
85
91
  }
86
92
  get hasMessages() {
87
- return this.props.messages && this.props.messages.length > 0;
93
+ if (!this.props.messages || this.props.messages.length == 0) {
94
+ return false;
95
+ }
96
+ for (const msg of this.props.messages) {
97
+ if (msg.text) {
98
+ if (typeof msg.text === 'string') {
99
+ return msg.text.length > 0;
100
+ }
101
+ // this is more complicated (e.g. an array, a React component,...)
102
+ // but we don't try to optimize here for these cases
103
+ return true;
104
+ }
105
+ }
106
+ return false;
88
107
  }
89
108
  get elementType() {
90
109
  return (0, _getElementType.getElementType)(FormFieldLayout, this.props);
91
110
  }
92
- get inlineContainerAndLabel() {
93
- // Return if both the component container and label will display inline
94
- return this.props.inline && this.props.layout === 'inline';
95
- }
96
111
  renderLabel() {
97
112
  if (this.hasVisibleLabel) {
98
- return (0, _emotion.jsx)(_Grid.Grid.Col, {
99
- textAlign: this.props.labelAlign,
100
- width: this.inlineContainerAndLabel ? 'auto' : 3
101
- }, (0, _emotion.jsx)(_FormFieldLabel.FormFieldLabel, {
102
- "aria-hidden": this.elementType === 'fieldset' ? 'true' : void 0
103
- }, this.props.label));
104
- } else if (this.elementType !== 'fieldset') {
105
- // to avoid duplicate label/legend content
106
- return this.props.label;
107
- } else {
108
- return null;
109
- }
110
- }
111
- renderLegend() {
112
- // note: the legend element must be the first child of a fieldset element for SR
113
- // so we render it twice in that case (once for SR-only and one that is visible)
114
- return (0, _emotion.jsx)(_ScreenReaderContent.ScreenReaderContent, {
115
- as: "legend"
116
- }, this.props.label, this.hasMessages && (0, _emotion.jsx)(_FormFieldMessages.FormFieldMessages, {
117
- messages: this.props.messages
118
- }));
113
+ var _this$props$styles2;
114
+ if (this.elementType == 'fieldset') {
115
+ var _this$props$styles;
116
+ // `legend` has some special built in CSS, this can only be reset
117
+ // this way https://stackoverflow.com/a/65866981/319473
118
+ return (0, _emotion.jsx)("legend", {
119
+ style: {
120
+ display: 'contents'
121
+ }
122
+ }, (0, _emotion.jsx)("span", {
123
+ css: (_this$props$styles = this.props.styles) === null || _this$props$styles === void 0 ? void 0 : _this$props$styles.formFieldLabel
124
+ }, this.props.label));
125
+ }
126
+ return (0, _emotion.jsx)("span", {
127
+ css: (_this$props$styles2 = this.props.styles) === null || _this$props$styles2 === void 0 ? void 0 : _this$props$styles2.formFieldLabel
128
+ }, this.props.label);
129
+ } else if (this.props.label) {
130
+ if (this.elementType == 'fieldset') {
131
+ return (0, _emotion.jsx)("legend", {
132
+ id: this._labelId,
133
+ style: {
134
+ display: 'contents'
135
+ }
136
+ }, this.props.label);
137
+ }
138
+ // needs to be wrapped because it needs an `id`
139
+ return (0, _emotion.jsx)("div", {
140
+ id: this._labelId,
141
+ style: {
142
+ display: 'contents'
143
+ }
144
+ }, this.props.label);
145
+ } else return null;
119
146
  }
120
147
  renderVisibleMessages() {
121
- return this.hasMessages ? (0, _emotion.jsx)(_Grid.Grid.Row, null, (0, _emotion.jsx)(_Grid.Grid.Col, {
122
- offset: this.inlineContainerAndLabel ? void 0 : 3,
123
- textAlign: this.inlineContainerAndLabel ? 'end' : void 0
124
- }, (0, _emotion.jsx)(_FormFieldMessages.FormFieldMessages, {
148
+ return this.hasMessages ? (0, _emotion.jsx)(_FormFieldMessages.FormFieldMessages, {
125
149
  id: this._messagesId,
126
- messages: this.props.messages
127
- }))) : null;
150
+ messages: this.props.messages,
151
+ gridArea: "messages"
152
+ }) : null;
128
153
  }
129
154
  render() {
130
- // any cast is needed to prevent Expression produces a union type that is too complex to represent errors
155
+ // Should be `<label>` if it's a FormField, fieldset if it's a group
131
156
  const ElementType = this.elementType;
132
157
  const _this$props3 = this.props,
133
158
  makeStyles = _this$props3.makeStyles,
@@ -136,26 +161,20 @@ let FormFieldLayout = exports.FormFieldLayout = (_dec = (0, _withDeterministicId
136
161
  isGroup = _this$props3.isGroup,
137
162
  props = (0, _objectWithoutProperties2.default)(_this$props3, _excluded);
138
163
  const width = props.width,
139
- layout = props.layout,
140
164
  children = props.children;
141
- const hasNewErrorMsg = !!(messages !== null && messages !== void 0 && messages.find(m => m.type === 'newError')) && isGroup;
142
- return (0, _emotion.jsx)(ElementType, Object.assign({}, (0, _omitProps.omitProps)(props, [...FormFieldLayout.allowedProps, ..._Grid.Grid.allowedProps]), {
165
+ const hasNewErrorMsgAndIsGroup = !!(messages !== null && messages !== void 0 && messages.find(m => m.type === 'newError')) && isGroup;
166
+ return (0, _emotion.jsx)(ElementType, Object.assign({}, (0, _omitProps.omitProps)(props, [...FormFieldLayout.allowedProps]), {
143
167
  css: styles === null || styles === void 0 ? void 0 : styles.formFieldLayout,
168
+ "aria-describedby": this.hasMessages ? this._messagesId : void 0,
169
+ "aria-errormessage": this.props['aria-invalid'] ? this._messagesId : void 0,
144
170
  style: {
145
171
  width
146
172
  },
147
- "aria-describedby": this.hasMessages ? this._messagesId : void 0,
148
173
  ref: this.handleRef
149
- }), this.elementType === 'fieldset' && this.renderLegend(), (0, _emotion.jsx)(_Grid.Grid, Object.assign({
150
- rowSpacing: "small",
151
- colSpacing: "small",
152
- startAt: layout === 'inline' && this.hasVisibleLabel ? 'medium' : null
153
- }, (0, _pickProps.pickProps)(props, _Grid.Grid.allowedProps)), (0, _emotion.jsx)(_Grid.Grid.Row, null, this.renderLabel(), (0, _emotion.jsx)(_Grid.Grid.Col, {
154
- width: this.inlineContainerAndLabel ? 'auto' : void 0,
155
- elementRef: this.handleInputContainerRef
156
- }, hasNewErrorMsg && (0, _emotion.jsx)("div", {
157
- css: styles === null || styles === void 0 ? void 0 : styles.groupErrorMessage
158
- }, this.renderVisibleMessages()), children)), !hasNewErrorMsg && this.renderVisibleMessages()));
174
+ }), this.renderLabel(), hasNewErrorMsgAndIsGroup && this.renderVisibleMessages(), (0, _emotion.jsx)("span", {
175
+ css: styles === null || styles === void 0 ? void 0 : styles.formFieldChildren,
176
+ ref: this.handleInputContainerRef
177
+ }, children), !hasNewErrorMsgAndIsGroup && this.renderVisibleMessages());
159
178
  }
160
179
  }, _FormFieldLayout.displayName = "FormFieldLayout", _FormFieldLayout.componentId = 'FormFieldLayout', _FormFieldLayout.propTypes = _props.propTypes, _FormFieldLayout.allowedProps = _props.allowedProps, _FormFieldLayout.defaultProps = {
161
180
  inline: false,
@@ -47,9 +47,4 @@ const propTypes = exports.propTypes = {
47
47
  elementRef: _propTypes.default.func,
48
48
  isGroup: _propTypes.default.bool
49
49
  };
50
- const allowedProps = exports.allowedProps = ['label', 'id', 'as', 'messages', 'messagesId', 'children', 'inline', 'layout', 'labelAlign', 'width', 'inputContainerRef', 'elementRef'
51
-
52
- // added vAlign because FormField and FormFieldGroup passes it, but not adding
53
- // it to allowedProps to prevent it from getting passed through accidentally
54
- //'vAlign'
55
- ];
50
+ const allowedProps = exports.allowedProps = ['label', 'id', 'as', 'messages', 'messagesId', 'children', 'inline', 'layout', 'labelAlign', 'width', 'inputContainerRef', 'elementRef', 'vAlign'];
@@ -28,6 +28,31 @@ exports.default = void 0;
28
28
  * SOFTWARE.
29
29
  */
30
30
 
31
+ const generateGridLayout = (isInlineLayout, hasNewErrorMsgAndIsGroup, hasVisibleLabel, hasMessages) => {
32
+ if (isInlineLayout) {
33
+ if (hasNewErrorMsgAndIsGroup) {
34
+ if (hasMessages) {
35
+ return `${hasVisibleLabel ? ' "label messages"' : '. messages'}
36
+ ". controls"`;
37
+ } else {
38
+ return `${hasVisibleLabel ? ' "label controls"' : '. controls'}`;
39
+ }
40
+ } else {
41
+ return `${hasVisibleLabel ? ' "label controls"' : '. controls'}
42
+ ${hasMessages ? ' ". messages"' : ''}`;
43
+ }
44
+ }
45
+ // stacked layout -- in this case we could use a simple `Flex`
46
+ if (hasNewErrorMsgAndIsGroup) {
47
+ return `${hasVisibleLabel ? ' "label"' : ''}
48
+ ${hasMessages ? ' "messages"' : ''}
49
+ "controls"`;
50
+ } else {
51
+ return `${hasVisibleLabel ? ' "label"' : ''}
52
+ "controls"
53
+ ${hasMessages ? ' "messages"' : ''}`;
54
+ }
55
+ };
31
56
  /**
32
57
  * ---
33
58
  * private: true
@@ -35,15 +60,55 @@ exports.default = void 0;
35
60
  * Generates the style object from the theme and provided additional information
36
61
  * @param {Object} componentTheme The theme variable object.
37
62
  * @param {Object} props the props of the component, the style is applied to
38
- * @param {Object} state the state of the component, the style is applied to
63
+ * @param {Object} styleProps
39
64
  * @return {Object} The final style object, which will be used in the component
40
65
  */
41
- const generateStyle = (_componentTheme, props) => {
42
- const inline = props.inline;
66
+ const generateStyle = (componentTheme, props, styleProps) => {
67
+ const inline = props.inline,
68
+ layout = props.layout,
69
+ vAlign = props.vAlign,
70
+ labelAlign = props.labelAlign;
71
+ const hasMessages = styleProps.hasMessages,
72
+ hasVisibleLabel = styleProps.hasVisibleLabel,
73
+ hasNewErrorMsgAndIsGroup = styleProps.hasNewErrorMsgAndIsGroup;
74
+ const isInlineLayout = layout === 'inline';
75
+ // This is quite ugly, we should simplify it
76
+ const gridTemplateAreas = generateGridLayout(isInlineLayout, hasNewErrorMsgAndIsGroup, hasVisibleLabel, hasMessages);
77
+ let gridTemplateColumns = '100%'; // stacked layout
78
+ if (isInlineLayout) {
79
+ gridTemplateColumns = '1fr 3fr';
80
+ if (inline) {
81
+ gridTemplateColumns = 'auto 3fr';
82
+ }
83
+ }
84
+ const labelStyles = {
85
+ all: 'initial',
86
+ display: 'block',
87
+ gridArea: 'label',
88
+ color: componentTheme.color,
89
+ fontFamily: componentTheme.fontFamily,
90
+ fontWeight: componentTheme.fontWeight,
91
+ fontSize: componentTheme.fontSize,
92
+ lineHeight: componentTheme.lineHeight,
93
+ ...(isInlineLayout && {
94
+ margin: '0',
95
+ // when inline add a small padding between the label and the control
96
+ paddingRight: componentTheme.inlinePadding,
97
+ // and use the horizontal alignment prop
98
+ [`@media screen and (width >= ${componentTheme.stackedOrInlineBreakpoint})`]: {
99
+ textAlign: labelAlign
100
+ }
101
+ })
102
+ };
103
+ let alignItems = 'start';
104
+ if (vAlign == 'top') {
105
+ alignItems = 'start';
106
+ } else if (vAlign == 'middle') {
107
+ alignItems = 'center';
108
+ } else if (vAlign == 'bottom') {
109
+ alignItems = 'end';
110
+ }
43
111
  return {
44
- groupErrorMessage: {
45
- margin: '0.5rem 0'
46
- },
47
112
  formFieldLayout: {
48
113
  label: 'formFieldLayout',
49
114
  all: 'initial',
@@ -54,13 +119,44 @@ const generateStyle = (_componentTheme, props) => {
54
119
  direction: 'inherit',
55
120
  textAlign: 'start',
56
121
  opacity: 'inherit',
57
- display: 'block',
122
+ display: 'grid',
123
+ alignItems: alignItems,
124
+ verticalAlign: 'middle',
125
+ // removes margin in inline layouts
126
+ gridTemplateColumns: gridTemplateColumns,
127
+ gridTemplateAreas: gridTemplateAreas,
128
+ [`@media screen and (width < ${componentTheme.stackedOrInlineBreakpoint})`]: {
129
+ // for small screens use the stacked layout
130
+ gridTemplateColumns: '100%',
131
+ gridTemplateAreas: generateGridLayout(false, hasNewErrorMsgAndIsGroup, hasVisibleLabel, hasMessages)
132
+ },
133
+ columnGap: '0.375rem',
134
+ rowGap: '0.75rem',
58
135
  width: '100%',
59
136
  ...(inline && {
60
- display: 'inline-block',
61
- verticalAlign: 'middle',
137
+ display: 'inline-grid',
62
138
  width: 'auto'
63
139
  })
140
+ },
141
+ formFieldLabel: {
142
+ label: 'formFieldLayout__label',
143
+ ...labelStyles,
144
+ // NOTE: needs separate groups for `:is()` and `:-webkit-any()` because of css selector group validation (see https://www.w3.org/TR/selectors-3/#grouping)
145
+ '&:is(label)': labelStyles,
146
+ '&:-webkit-any(label)': labelStyles
147
+ },
148
+ formFieldChildren: {
149
+ label: 'formFieldLayout__children',
150
+ gridArea: 'controls',
151
+ // add a small margin between the message and the controls
152
+ ...(hasMessages && hasNewErrorMsgAndIsGroup && {
153
+ marginTop: '0.375rem'
154
+ }),
155
+ ...(isInlineLayout && inline && {
156
+ [`@media screen and (width >= ${componentTheme.stackedOrInlineBreakpoint})`]: {
157
+ justifySelf: 'start'
158
+ }
159
+ })
64
160
  }
65
161
  };
66
162
  };
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ /*
8
+ * The MIT License (MIT)
9
+ *
10
+ * Copyright (c) 2015 - present Instructure, Inc.
11
+ *
12
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ * of this software and associated documentation files (the "Software"), to deal
14
+ * in the Software without restriction, including without limitation the rights
15
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ * copies of the Software, and to permit persons to whom the Software is
17
+ * furnished to do so, subject to the following conditions:
18
+ *
19
+ * The above copyright notice and this permission notice shall be included in all
20
+ * copies or substantial portions of the Software.
21
+ *
22
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ * SOFTWARE.
29
+ */
30
+
31
+ /**
32
+ * Generates the theme object for the component from the theme and provided additional information
33
+ * @param {Object} theme The actual theme object.
34
+ * @return {Object} The final theme object with the overrides and component variables
35
+ */
36
+ const generateComponentTheme = theme => {
37
+ const colors = theme.colors,
38
+ typography = theme.typography,
39
+ spacing = theme.spacing,
40
+ breakpoints = theme.breakpoints,
41
+ themeName = theme.key;
42
+ const themeSpecificStyle = {
43
+ canvas: {
44
+ color: theme['ic-brand-font-color-dark']
45
+ }
46
+ };
47
+ const componentVariables = {
48
+ color: colors === null || colors === void 0 ? void 0 : colors.licorice,
49
+ fontFamily: typography === null || typography === void 0 ? void 0 : typography.fontFamily,
50
+ fontWeight: typography === null || typography === void 0 ? void 0 : typography.fontWeightBold,
51
+ fontSize: typography === null || typography === void 0 ? void 0 : typography.fontSizeMedium,
52
+ lineHeight: typography === null || typography === void 0 ? void 0 : typography.lineHeightFit,
53
+ inlinePadding: spacing === null || spacing === void 0 ? void 0 : spacing.xxSmall,
54
+ stackedOrInlineBreakpoint: breakpoints === null || breakpoints === void 0 ? void 0 : breakpoints.medium,
55
+ spacing: theme.spacing
56
+ };
57
+ return {
58
+ ...componentVariables,
59
+ ...themeSpecificStyle[themeName]
60
+ };
61
+ };
62
+ var _default = exports.default = generateComponentTheme;
@@ -32,6 +32,7 @@ var _FormPropTypes = require("../FormPropTypes");
32
32
  */
33
33
 
34
34
  const propTypes = exports.propTypes = {
35
- messages: _propTypes.default.arrayOf(_FormPropTypes.FormPropTypes.message)
35
+ messages: _propTypes.default.arrayOf(_FormPropTypes.FormPropTypes.message),
36
+ gridArea: _propTypes.default.string
36
37
  };
37
- const allowedProps = exports.allowedProps = ['messages'];
38
+ const allowedProps = exports.allowedProps = ['messages', 'gridArea'];
@@ -35,16 +35,18 @@ exports.default = void 0;
35
35
  * Generates the style object from the theme and provided additional information
36
36
  * @param {Object} componentTheme The theme variable object.
37
37
  * @param {Object} props the props of the component, the style is applied to
38
- * @param {Object} state the state of the component, the style is applied to
39
38
  * @return {Object} The final style object, which will be used in the component
40
39
  */
41
- const generateStyle = componentTheme => {
40
+ const generateStyle = (componentTheme, props) => {
42
41
  return {
43
42
  formFieldMessages: {
44
43
  label: 'formFieldMessages',
45
44
  padding: 0,
46
45
  display: 'block',
47
- margin: `calc(-1 * ${componentTheme.topMargin}) 0 0 0`
46
+ margin: `calc(-${componentTheme.topMargin}) 0 0 0`,
47
+ ...(props.gridArea && {
48
+ gridArea: props.gridArea
49
+ })
48
50
  },
49
51
  message: {
50
52
  label: 'formFieldMessages__message',