@instructure/ui-text-input 8.23.1-snapshot.9 → 8.24.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
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [8.24.0](https://github.com/instructure/instructure-ui/compare/v8.23.0...v8.24.0) (2022-04-26)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **ui-text-input:** fix empty TextInput before/after elements having padding ([a5786c9](https://github.com/instructure/instructure-ui/commit/a5786c9083448dc1d7d8b5eecac11788c8b26fec))
11
+
6
12
  # [8.23.0](https://github.com/instructure/instructure-ui/compare/v8.22.0...v8.23.0) (2022-04-07)
7
13
 
8
14
  **Note:** Version bump only for package @instructure/ui-text-input
@@ -50,6 +50,8 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle,
50
50
  super(props);
51
51
  this.ref = null;
52
52
  this._input = null;
53
+ this._beforeElement = null;
54
+ this._afterElement = null;
53
55
  this._defaultId = void 0;
54
56
  this._messagesId = void 0;
55
57
  this._focusListener = null;
@@ -65,10 +67,16 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle,
65
67
 
66
68
  this.makeStyleProps = () => {
67
69
  const interaction = this.interaction;
70
+ const _this$state = this.state,
71
+ hasFocus = _this$state.hasFocus,
72
+ beforeElementHasWidth = _this$state.beforeElementHasWidth,
73
+ afterElementHasWidth = _this$state.afterElementHasWidth;
68
74
  return {
69
75
  disabled: interaction === 'disabled',
70
76
  invalid: this.invalid,
71
- focused: this.state.hasFocus
77
+ focused: hasFocus,
78
+ beforeElementHasWidth,
79
+ afterElementHasWidth
72
80
  };
73
81
  };
74
82
 
@@ -107,7 +115,9 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle,
107
115
  };
108
116
 
109
117
  this.state = {
110
- hasFocus: false
118
+ hasFocus: false,
119
+ beforeElementHasWidth: void 0,
120
+ afterElementHasWidth: void 0
111
121
  };
112
122
  this._defaultId = props.deterministicId();
113
123
  this._messagesId = props.deterministicId('TextInput-messages');
@@ -116,11 +126,15 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle,
116
126
  componentDidMount() {
117
127
  var _this$props$makeStyle, _this$props;
118
128
 
119
- (_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props, this.makeStyleProps());
120
-
121
129
  if (this._input) {
122
130
  this._focusListener = addEventListener(this._input, 'focus', this.handleFocus);
131
+ this.setState({
132
+ beforeElementHasWidth: this.getElementHasWidth(this._beforeElement),
133
+ afterElementHasWidth: this.getElementHasWidth(this._afterElement)
134
+ });
123
135
  }
136
+
137
+ (_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props, this.makeStyleProps());
124
138
  }
125
139
 
126
140
  componentWillUnmount() {
@@ -129,9 +143,21 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle,
129
143
  }
130
144
  }
131
145
 
132
- componentDidUpdate() {
146
+ componentDidUpdate(prevProps) {
133
147
  var _this$props$makeStyle2, _this$props2;
134
148
 
149
+ if (prevProps.renderBeforeInput !== this.props.renderBeforeInput) {
150
+ this.setState({
151
+ beforeElementHasWidth: this.getElementHasWidth(this._beforeElement)
152
+ });
153
+ }
154
+
155
+ if (prevProps.renderAfterInput !== this.props.renderAfterInput) {
156
+ this.setState({
157
+ afterElementHasWidth: this.getElementHasWidth(this._afterElement)
158
+ });
159
+ }
160
+
135
161
  (_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2, this.makeStyleProps());
136
162
  }
137
163
 
@@ -217,6 +243,19 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle,
217
243
  }));
218
244
  }
219
245
 
246
+ getElementHasWidth(element) {
247
+ if (!element) {
248
+ return void 0;
249
+ }
250
+
251
+ const computedStyle = getComputedStyle(element);
252
+ const width = computedStyle.width,
253
+ paddingInlineStart = computedStyle.paddingInlineStart,
254
+ paddingInlineEnd = computedStyle.paddingInlineEnd;
255
+ const elementWidth = parseFloat(width) - parseFloat(paddingInlineStart) - parseFloat(paddingInlineEnd);
256
+ return elementWidth > 0;
257
+ }
258
+
220
259
  render() {
221
260
  const _this$props4 = this.props,
222
261
  width = _this$props4.width,
@@ -227,9 +266,9 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle,
227
266
  messages = _this$props4.messages,
228
267
  inputContainerRef = _this$props4.inputContainerRef,
229
268
  styles = _this$props4.styles;
230
- const hasBeforeElement = renderBeforeInput && callRenderProp(renderBeforeInput);
231
- const hasAfterElement = renderAfterInput && callRenderProp(renderAfterInput);
232
- const renderBeforeOrAfter = hasBeforeElement || hasAfterElement;
269
+ const beforeElement = renderBeforeInput ? callRenderProp(renderBeforeInput) : null;
270
+ const afterElement = renderAfterInput ? callRenderProp(renderAfterInput) : null;
271
+ const renderBeforeOrAfter = !!beforeElement || !!afterElement;
233
272
  return jsx(FormField, {
234
273
  id: this.id,
235
274
  label: callRenderProp(renderLabel),
@@ -244,17 +283,23 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle,
244
283
  css: styles === null || styles === void 0 ? void 0 : styles.facade
245
284
  }, renderBeforeOrAfter ? jsx("div", null, jsx("span", {
246
285
  css: styles === null || styles === void 0 ? void 0 : styles.layout
247
- }, hasBeforeElement && jsx("span", {
248
- css: styles === null || styles === void 0 ? void 0 : styles.beforeElement
249
- }, callRenderProp(renderBeforeInput)), jsx("span", {
286
+ }, beforeElement && jsx("span", {
287
+ css: styles === null || styles === void 0 ? void 0 : styles.beforeElement,
288
+ ref: e => {
289
+ this._beforeElement = e;
290
+ }
291
+ }, beforeElement), jsx("span", {
250
292
  css: styles === null || styles === void 0 ? void 0 : styles.innerWrapper
251
293
  }, jsx("span", {
252
294
  css: styles === null || styles === void 0 ? void 0 : styles.inputLayout
253
295
  }, jsx("span", {
254
296
  css: styles === null || styles === void 0 ? void 0 : styles.innerWrapper
255
- }, this.renderInput()), hasAfterElement && jsx("span", {
256
- css: styles === null || styles === void 0 ? void 0 : styles.afterElement
257
- }, callRenderProp(renderAfterInput)))))) :
297
+ }, this.renderInput()), afterElement && jsx("span", {
298
+ css: styles === null || styles === void 0 ? void 0 : styles.afterElement,
299
+ ref: e => {
300
+ this._afterElement = e;
301
+ }
302
+ }, afterElement))))) :
258
303
  /* If no prepended or appended content, don't render Flex layout */
259
304
  this.renderInput()));
260
305
  }
@@ -38,7 +38,9 @@ const generateStyle = (componentTheme, props, state) => {
38
38
  shouldNotWrap = props.shouldNotWrap;
39
39
  const disabled = state.disabled,
40
40
  invalid = state.invalid,
41
- focused = state.focused;
41
+ focused = state.focused,
42
+ beforeElementHasWidth = state.beforeElementHasWidth,
43
+ afterElementHasWidth = state.afterElementHasWidth;
42
44
  const sizeVariants = {
43
45
  small: {
44
46
  fontSize: componentTheme.smallFontSize,
@@ -120,7 +122,6 @@ const generateStyle = (componentTheme, props, state) => {
120
122
  flexDirection: 'row'
121
123
  };
122
124
  const flexItemBase = { ...viewBase,
123
- minWidth: '0.0625rem',
124
125
  flexShrink: 0
125
126
  };
126
127
  return {
@@ -168,11 +169,17 @@ const generateStyle = (componentTheme, props, state) => {
168
169
  beforeElement: {
169
170
  label: 'textInput__beforeElement',
170
171
  ...flexItemBase,
171
- paddingInlineStart: componentTheme.padding
172
+ paddingInlineStart: componentTheme.padding,
173
+ // we only override the padding once the width is calculated,
174
+ // it needs the padding on render
175
+ ...(beforeElementHasWidth === false && {
176
+ paddingInlineStart: 0
177
+ })
172
178
  },
173
179
  innerWrapper: {
174
180
  label: 'textInput__innerWrapper',
175
181
  ...flexItemBase,
182
+ minWidth: '0.0625rem',
176
183
  flexShrink: 1,
177
184
  flexGrow: 1
178
185
  },
@@ -183,7 +190,12 @@ const generateStyle = (componentTheme, props, state) => {
183
190
  afterElement: {
184
191
  label: 'textInput__afterElement',
185
192
  ...flexItemBase,
186
- paddingInlineEnd: componentTheme.padding
193
+ paddingInlineEnd: componentTheme.padding,
194
+ // we only override the padding once the width is calculated,
195
+ // it needs the padding on render
196
+ ...(afterElementHasWidth === false && {
197
+ paddingInlineEnd: 0
198
+ })
187
199
  }
188
200
  };
189
201
  };
@@ -53,6 +53,8 @@ let TextInput = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 =
53
53
  super(props);
54
54
  this.ref = null;
55
55
  this._input = null;
56
+ this._beforeElement = null;
57
+ this._afterElement = null;
56
58
  this._defaultId = void 0;
57
59
  this._messagesId = void 0;
58
60
  this._focusListener = null;
@@ -68,10 +70,16 @@ let TextInput = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 =
68
70
 
69
71
  this.makeStyleProps = () => {
70
72
  const interaction = this.interaction;
73
+ const _this$state = this.state,
74
+ hasFocus = _this$state.hasFocus,
75
+ beforeElementHasWidth = _this$state.beforeElementHasWidth,
76
+ afterElementHasWidth = _this$state.afterElementHasWidth;
71
77
  return {
72
78
  disabled: interaction === 'disabled',
73
79
  invalid: this.invalid,
74
- focused: this.state.hasFocus
80
+ focused: hasFocus,
81
+ beforeElementHasWidth,
82
+ afterElementHasWidth
75
83
  };
76
84
  };
77
85
 
@@ -110,7 +118,9 @@ let TextInput = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 =
110
118
  };
111
119
 
112
120
  this.state = {
113
- hasFocus: false
121
+ hasFocus: false,
122
+ beforeElementHasWidth: void 0,
123
+ afterElementHasWidth: void 0
114
124
  };
115
125
  this._defaultId = props.deterministicId();
116
126
  this._messagesId = props.deterministicId('TextInput-messages');
@@ -119,11 +129,15 @@ let TextInput = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 =
119
129
  componentDidMount() {
120
130
  var _this$props$makeStyle, _this$props;
121
131
 
122
- (_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props, this.makeStyleProps());
123
-
124
132
  if (this._input) {
125
133
  this._focusListener = (0, _addEventListener.addEventListener)(this._input, 'focus', this.handleFocus);
134
+ this.setState({
135
+ beforeElementHasWidth: this.getElementHasWidth(this._beforeElement),
136
+ afterElementHasWidth: this.getElementHasWidth(this._afterElement)
137
+ });
126
138
  }
139
+
140
+ (_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props, this.makeStyleProps());
127
141
  }
128
142
 
129
143
  componentWillUnmount() {
@@ -132,9 +146,21 @@ let TextInput = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 =
132
146
  }
133
147
  }
134
148
 
135
- componentDidUpdate() {
149
+ componentDidUpdate(prevProps) {
136
150
  var _this$props$makeStyle2, _this$props2;
137
151
 
152
+ if (prevProps.renderBeforeInput !== this.props.renderBeforeInput) {
153
+ this.setState({
154
+ beforeElementHasWidth: this.getElementHasWidth(this._beforeElement)
155
+ });
156
+ }
157
+
158
+ if (prevProps.renderAfterInput !== this.props.renderAfterInput) {
159
+ this.setState({
160
+ afterElementHasWidth: this.getElementHasWidth(this._afterElement)
161
+ });
162
+ }
163
+
138
164
  (_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2, this.makeStyleProps());
139
165
  }
140
166
 
@@ -219,6 +245,19 @@ let TextInput = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 =
219
245
  }));
220
246
  }
221
247
 
248
+ getElementHasWidth(element) {
249
+ if (!element) {
250
+ return void 0;
251
+ }
252
+
253
+ const computedStyle = getComputedStyle(element);
254
+ const width = computedStyle.width,
255
+ paddingInlineStart = computedStyle.paddingInlineStart,
256
+ paddingInlineEnd = computedStyle.paddingInlineEnd;
257
+ const elementWidth = parseFloat(width) - parseFloat(paddingInlineStart) - parseFloat(paddingInlineEnd);
258
+ return elementWidth > 0;
259
+ }
260
+
222
261
  render() {
223
262
  const _this$props4 = this.props,
224
263
  width = _this$props4.width,
@@ -229,9 +268,9 @@ let TextInput = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 =
229
268
  messages = _this$props4.messages,
230
269
  inputContainerRef = _this$props4.inputContainerRef,
231
270
  styles = _this$props4.styles;
232
- const hasBeforeElement = renderBeforeInput && (0, _callRenderProp.callRenderProp)(renderBeforeInput);
233
- const hasAfterElement = renderAfterInput && (0, _callRenderProp.callRenderProp)(renderAfterInput);
234
- const renderBeforeOrAfter = hasBeforeElement || hasAfterElement;
271
+ const beforeElement = renderBeforeInput ? (0, _callRenderProp.callRenderProp)(renderBeforeInput) : null;
272
+ const afterElement = renderAfterInput ? (0, _callRenderProp.callRenderProp)(renderAfterInput) : null;
273
+ const renderBeforeOrAfter = !!beforeElement || !!afterElement;
235
274
  return (0, _emotion.jsx)(_FormField.FormField, {
236
275
  id: this.id,
237
276
  label: (0, _callRenderProp.callRenderProp)(renderLabel),
@@ -246,17 +285,23 @@ let TextInput = (_dec = (0, _withDeterministicId.withDeterministicId)(), _dec2 =
246
285
  css: styles === null || styles === void 0 ? void 0 : styles.facade
247
286
  }, renderBeforeOrAfter ? (0, _emotion.jsx)("div", null, (0, _emotion.jsx)("span", {
248
287
  css: styles === null || styles === void 0 ? void 0 : styles.layout
249
- }, hasBeforeElement && (0, _emotion.jsx)("span", {
250
- css: styles === null || styles === void 0 ? void 0 : styles.beforeElement
251
- }, (0, _callRenderProp.callRenderProp)(renderBeforeInput)), (0, _emotion.jsx)("span", {
288
+ }, beforeElement && (0, _emotion.jsx)("span", {
289
+ css: styles === null || styles === void 0 ? void 0 : styles.beforeElement,
290
+ ref: e => {
291
+ this._beforeElement = e;
292
+ }
293
+ }, beforeElement), (0, _emotion.jsx)("span", {
252
294
  css: styles === null || styles === void 0 ? void 0 : styles.innerWrapper
253
295
  }, (0, _emotion.jsx)("span", {
254
296
  css: styles === null || styles === void 0 ? void 0 : styles.inputLayout
255
297
  }, (0, _emotion.jsx)("span", {
256
298
  css: styles === null || styles === void 0 ? void 0 : styles.innerWrapper
257
- }, this.renderInput()), hasAfterElement && (0, _emotion.jsx)("span", {
258
- css: styles === null || styles === void 0 ? void 0 : styles.afterElement
259
- }, (0, _callRenderProp.callRenderProp)(renderAfterInput)))))) :
299
+ }, this.renderInput()), afterElement && (0, _emotion.jsx)("span", {
300
+ css: styles === null || styles === void 0 ? void 0 : styles.afterElement,
301
+ ref: e => {
302
+ this._afterElement = e;
303
+ }
304
+ }, afterElement))))) :
260
305
  /* If no prepended or appended content, don't render Flex layout */
261
306
  this.renderInput()));
262
307
  }
@@ -45,7 +45,9 @@ const generateStyle = (componentTheme, props, state) => {
45
45
  shouldNotWrap = props.shouldNotWrap;
46
46
  const disabled = state.disabled,
47
47
  invalid = state.invalid,
48
- focused = state.focused;
48
+ focused = state.focused,
49
+ beforeElementHasWidth = state.beforeElementHasWidth,
50
+ afterElementHasWidth = state.afterElementHasWidth;
49
51
  const sizeVariants = {
50
52
  small: {
51
53
  fontSize: componentTheme.smallFontSize,
@@ -127,7 +129,6 @@ const generateStyle = (componentTheme, props, state) => {
127
129
  flexDirection: 'row'
128
130
  };
129
131
  const flexItemBase = { ...viewBase,
130
- minWidth: '0.0625rem',
131
132
  flexShrink: 0
132
133
  };
133
134
  return {
@@ -175,11 +176,17 @@ const generateStyle = (componentTheme, props, state) => {
175
176
  beforeElement: {
176
177
  label: 'textInput__beforeElement',
177
178
  ...flexItemBase,
178
- paddingInlineStart: componentTheme.padding
179
+ paddingInlineStart: componentTheme.padding,
180
+ // we only override the padding once the width is calculated,
181
+ // it needs the padding on render
182
+ ...(beforeElementHasWidth === false && {
183
+ paddingInlineStart: 0
184
+ })
179
185
  },
180
186
  innerWrapper: {
181
187
  label: 'textInput__innerWrapper',
182
188
  ...flexItemBase,
189
+ minWidth: '0.0625rem',
183
190
  flexShrink: 1,
184
191
  flexGrow: 1
185
192
  },
@@ -190,7 +197,12 @@ const generateStyle = (componentTheme, props, state) => {
190
197
  afterElement: {
191
198
  label: 'textInput__afterElement',
192
199
  ...flexItemBase,
193
- paddingInlineEnd: componentTheme.padding
200
+ paddingInlineEnd: componentTheme.padding,
201
+ // we only override the padding once the width is calculated,
202
+ // it needs the padding on render
203
+ ...(afterElementHasWidth === false && {
204
+ paddingInlineEnd: 0
205
+ })
194
206
  }
195
207
  };
196
208
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-text-input",
3
- "version": "8.23.1-snapshot.9+11828d0b3",
3
+ "version": "8.24.0",
4
4
  "description": "A styled HTML text input component.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -23,23 +23,23 @@
23
23
  },
24
24
  "license": "MIT",
25
25
  "devDependencies": {
26
- "@instructure/ui-babel-preset": "8.23.1-snapshot.9+11828d0b3",
27
- "@instructure/ui-badge": "8.23.1-snapshot.9+11828d0b3",
28
- "@instructure/ui-color-utils": "8.23.1-snapshot.9+11828d0b3",
29
- "@instructure/ui-test-utils": "8.23.1-snapshot.9+11828d0b3",
30
- "@instructure/ui-themes": "8.23.1-snapshot.9+11828d0b3"
26
+ "@instructure/ui-babel-preset": "8.24.0",
27
+ "@instructure/ui-badge": "8.24.0",
28
+ "@instructure/ui-color-utils": "8.24.0",
29
+ "@instructure/ui-test-utils": "8.24.0",
30
+ "@instructure/ui-themes": "8.24.0"
31
31
  },
32
32
  "dependencies": {
33
33
  "@babel/runtime": "^7.13.10",
34
- "@instructure/emotion": "8.23.1-snapshot.9+11828d0b3",
35
- "@instructure/shared-types": "8.23.1-snapshot.9+11828d0b3",
36
- "@instructure/ui-dom-utils": "8.23.1-snapshot.9+11828d0b3",
37
- "@instructure/ui-form-field": "8.23.1-snapshot.9+11828d0b3",
38
- "@instructure/ui-icons": "8.23.1-snapshot.9+11828d0b3",
39
- "@instructure/ui-prop-types": "8.23.1-snapshot.9+11828d0b3",
40
- "@instructure/ui-react-utils": "8.23.1-snapshot.9+11828d0b3",
41
- "@instructure/ui-tag": "8.23.1-snapshot.9+11828d0b3",
42
- "@instructure/ui-testable": "8.23.1-snapshot.9+11828d0b3",
34
+ "@instructure/emotion": "8.24.0",
35
+ "@instructure/shared-types": "8.24.0",
36
+ "@instructure/ui-dom-utils": "8.24.0",
37
+ "@instructure/ui-form-field": "8.24.0",
38
+ "@instructure/ui-icons": "8.24.0",
39
+ "@instructure/ui-prop-types": "8.24.0",
40
+ "@instructure/ui-react-utils": "8.24.0",
41
+ "@instructure/ui-tag": "8.24.0",
42
+ "@instructure/ui-testable": "8.24.0",
43
43
  "prop-types": "^15"
44
44
  },
45
45
  "peerDependencies": {
@@ -48,6 +48,5 @@
48
48
  "publishConfig": {
49
49
  "access": "public"
50
50
  },
51
- "sideEffects": false,
52
- "gitHead": "11828d0b32844a522d267e189c8bc52aa928843a"
51
+ "sideEffects": false
53
52
  }
@@ -75,7 +75,11 @@ class TextInput extends Component<TextInputProps, TextInputState> {
75
75
 
76
76
  constructor(props: TextInputProps) {
77
77
  super(props)
78
- this.state = { hasFocus: false }
78
+ this.state = {
79
+ hasFocus: false,
80
+ beforeElementHasWidth: undefined,
81
+ afterElementHasWidth: undefined
82
+ }
79
83
  this._defaultId = props.deterministicId!()
80
84
  this._messagesId = props.deterministicId!('TextInput-messages')
81
85
  }
@@ -83,8 +87,12 @@ class TextInput extends Component<TextInputProps, TextInputState> {
83
87
  ref: Element | null = null
84
88
 
85
89
  private _input: HTMLInputElement | null = null
86
- private _defaultId: string
87
- private _messagesId: string
90
+ private _beforeElement: HTMLSpanElement | null = null
91
+ private _afterElement: HTMLSpanElement | null = null
92
+
93
+ private readonly _defaultId: string
94
+ private readonly _messagesId: string
95
+
88
96
  private _focusListener: { remove(): void } | null = null
89
97
 
90
98
  handleRef = (el: Element | null) => {
@@ -96,15 +104,22 @@ class TextInput extends Component<TextInputProps, TextInputState> {
96
104
  elementRef(el)
97
105
  }
98
106
  }
107
+
99
108
  componentDidMount() {
100
- this.props.makeStyles?.(this.makeStyleProps())
101
109
  if (this._input) {
102
110
  this._focusListener = addEventListener(
103
111
  this._input,
104
112
  'focus',
105
113
  this.handleFocus
106
114
  )
115
+
116
+ this.setState({
117
+ beforeElementHasWidth: this.getElementHasWidth(this._beforeElement),
118
+ afterElementHasWidth: this.getElementHasWidth(this._afterElement)
119
+ })
107
120
  }
121
+
122
+ this.props.makeStyles?.(this.makeStyleProps())
108
123
  }
109
124
 
110
125
  componentWillUnmount() {
@@ -113,16 +128,30 @@ class TextInput extends Component<TextInputProps, TextInputState> {
113
128
  }
114
129
  }
115
130
 
116
- componentDidUpdate() {
131
+ componentDidUpdate(prevProps: TextInputProps) {
132
+ if (prevProps.renderBeforeInput !== this.props.renderBeforeInput) {
133
+ this.setState({
134
+ beforeElementHasWidth: this.getElementHasWidth(this._beforeElement)
135
+ })
136
+ }
137
+ if (prevProps.renderAfterInput !== this.props.renderAfterInput) {
138
+ this.setState({
139
+ afterElementHasWidth: this.getElementHasWidth(this._afterElement)
140
+ })
141
+ }
142
+
117
143
  this.props.makeStyles?.(this.makeStyleProps())
118
144
  }
119
145
 
120
146
  makeStyleProps = (): TextInputStyleProps => {
121
147
  const { interaction } = this
148
+ const { hasFocus, beforeElementHasWidth, afterElementHasWidth } = this.state
122
149
  return {
123
150
  disabled: interaction === 'disabled',
124
151
  invalid: this.invalid,
125
- focused: this.state.hasFocus
152
+ focused: hasFocus,
153
+ beforeElementHasWidth,
154
+ afterElementHasWidth
126
155
  }
127
156
  }
128
157
 
@@ -243,6 +272,22 @@ class TextInput extends Component<TextInputProps, TextInputState> {
243
272
  )
244
273
  }
245
274
 
275
+ getElementHasWidth(element: HTMLSpanElement | null) {
276
+ if (!element) {
277
+ return undefined
278
+ }
279
+
280
+ const computedStyle = getComputedStyle(element)
281
+ const { width, paddingInlineStart, paddingInlineEnd } = computedStyle
282
+
283
+ const elementWidth =
284
+ parseFloat(width) -
285
+ parseFloat(paddingInlineStart) -
286
+ parseFloat(paddingInlineEnd)
287
+
288
+ return elementWidth > 0
289
+ }
290
+
246
291
  render() {
247
292
  const {
248
293
  width,
@@ -255,11 +300,14 @@ class TextInput extends Component<TextInputProps, TextInputState> {
255
300
  styles
256
301
  } = this.props
257
302
 
258
- const hasBeforeElement =
259
- renderBeforeInput && callRenderProp(renderBeforeInput)
260
- const hasAfterElement = renderAfterInput && callRenderProp(renderAfterInput)
303
+ const beforeElement: React.ReactNode = renderBeforeInput
304
+ ? callRenderProp(renderBeforeInput)
305
+ : null
306
+ const afterElement: React.ReactNode = renderAfterInput
307
+ ? callRenderProp(renderAfterInput)
308
+ : null
261
309
 
262
- const renderBeforeOrAfter = hasBeforeElement || hasAfterElement
310
+ const renderBeforeOrAfter = !!beforeElement || !!afterElement
263
311
 
264
312
  return (
265
313
  <FormField
@@ -277,9 +325,14 @@ class TextInput extends Component<TextInputProps, TextInputState> {
277
325
  {renderBeforeOrAfter ? (
278
326
  <div>
279
327
  <span css={styles?.layout}>
280
- {hasBeforeElement && (
281
- <span css={styles?.beforeElement}>
282
- {callRenderProp(renderBeforeInput)}
328
+ {beforeElement && (
329
+ <span
330
+ css={styles?.beforeElement}
331
+ ref={(e) => {
332
+ this._beforeElement = e
333
+ }}
334
+ >
335
+ {beforeElement}
283
336
  </span>
284
337
  )}
285
338
  <span css={styles?.innerWrapper}>
@@ -289,9 +342,14 @@ class TextInput extends Component<TextInputProps, TextInputState> {
289
342
  */}
290
343
  <span css={styles?.inputLayout}>
291
344
  <span css={styles?.innerWrapper}>{this.renderInput()}</span>
292
- {hasAfterElement && (
293
- <span css={styles?.afterElement}>
294
- {callRenderProp(renderAfterInput)}
345
+ {afterElement && (
346
+ <span
347
+ css={styles?.afterElement}
348
+ ref={(e) => {
349
+ this._afterElement = e
350
+ }}
351
+ >
352
+ {afterElement}
295
353
  </span>
296
354
  )}
297
355
  </span>
@@ -183,7 +183,8 @@ type TextInputProps = TextInputOwnProps &
183
183
  > &
184
184
  // The component will handle pass this prop to FormField, but it shouldn't be
185
185
  // listed as a prop
186
- Pick<FormFieldProps, 'layout'> & WithDeterministicIdProps
186
+ Pick<FormFieldProps, 'layout'> &
187
+ WithDeterministicIdProps
187
188
 
188
189
  type TextInputStyle = ComponentStyle<
189
190
  | 'textInput'
@@ -249,12 +250,16 @@ const allowedProps: AllowedPropKeys = [
249
250
 
250
251
  type TextInputState = {
251
252
  hasFocus: boolean
253
+ beforeElementHasWidth?: boolean
254
+ afterElementHasWidth?: boolean
252
255
  }
253
256
 
254
257
  type TextInputStyleProps = {
255
258
  disabled: boolean
256
259
  invalid: boolean
257
260
  focused: TextInputState['hasFocus']
261
+ beforeElementHasWidth: TextInputState['beforeElementHasWidth']
262
+ afterElementHasWidth: TextInputState['afterElementHasWidth']
258
263
  }
259
264
 
260
265
  export type {