lightning-base-components 1.17.5-alpha → 1.17.7-alpha

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightning-base-components",
3
- "version": "1.17.5-alpha",
3
+ "version": "1.17.7-alpha",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "external",
@@ -1,7 +1,6 @@
1
1
  import { LightningElement } from 'lwc';
2
2
 
3
3
  export default class AriaObserverConnect extends LightningElement {
4
-
5
4
  connectIds = 'foo bar';
6
5
 
7
6
  handleUpdateIds() {
@@ -3,7 +3,6 @@ import { LightningElement, api } from 'lwc';
3
3
  import AriaObserver from 'lightning/ariaObserver';
4
4
 
5
5
  export default class AriaObserverConnectChild extends LightningElement {
6
-
7
6
  constructor() {
8
7
  super();
9
8
  this.ariaObserver = new AriaObserver(this);
@@ -38,8 +37,7 @@ export default class AriaObserverConnectChild extends LightningElement {
38
37
  this.ariaObserver.connect({
39
38
  targetSelector: 'input',
40
39
  attribute: 'aria-labelledby',
41
- id: refs
40
+ id: refs,
42
41
  });
43
42
  }
44
-
45
43
  }
@@ -165,7 +165,7 @@ export default class AriaObserver {
165
165
  }
166
166
  }
167
167
 
168
- privateExtractCorrectElements(selector, elements) {
168
+ privateExtractCorrectElements(selector = '', elements) {
169
169
  // Example: 'foo' + '-1'
170
170
  const selectors = selector.split(/\s/g);
171
171
  const matchSelectors = `(${selectors.join('|')})`;
@@ -193,11 +193,25 @@ export default class AriaObserver {
193
193
  for (const liveId in this.liveIds) {
194
194
  if (Object.prototype.hasOwnProperty.call(this.liveIds, liveId)) {
195
195
  const thisId = this.liveIds[liveId];
196
- if (!thisId.elements) {
196
+ if (!thisId.elements?.length) {
197
+ const splitRefIds = splitIds(liveId);
197
198
  // element refs are cached
198
- thisId.elements = Array.prototype.slice.call(
199
- root.querySelectorAll(thisId.selector)
200
- );
199
+ const refElements = [
200
+ ...root.querySelectorAll(thisId.selector),
201
+ ];
202
+
203
+ thisId.elements = refElements.sort((a, b) => {
204
+ const idA = a
205
+ .getAttribute('id')
206
+ ?.replace(/-[0-9]+$/g, '');
207
+ const idB = b
208
+ .getAttribute('id')
209
+ ?.replace(/-[0-9]+$/g, '');
210
+
211
+ return (
212
+ splitRefIds.indexOf(idA) - splitRefIds.indexOf(idB)
213
+ );
214
+ });
201
215
  }
202
216
  const newThisId = this.privateExtractCorrectElements(
203
217
  thisId.refs,
@@ -143,6 +143,7 @@ export default class LightningButtonIcon extends LightningPrimitiveButton {
143
143
  tooltipValue = null;
144
144
  tooltipType = TooltipType.Info;
145
145
  rendered = false;
146
+ showTitle = true;
146
147
 
147
148
  /**
148
149
  * Generate a tooltip with the specified value and current tooltip type
@@ -164,7 +165,7 @@ export default class LightningButtonIcon extends LightningPrimitiveButton {
164
165
  }
165
166
 
166
167
  get computedTitle() {
167
- return this.title || this.alternativeText || '';
168
+ return this.showTitle ? this.title || this.alternativeText || '' : null;
168
169
  }
169
170
 
170
171
  normalizeVariant(variant) {
@@ -322,9 +323,14 @@ export default class LightningButtonIcon extends LightningPrimitiveButton {
322
323
  cancelable: true,
323
324
  bubbles: true,
324
325
  detail: {
326
+ // Tooltip type should be toggle for some consumers like helptext
325
327
  setTooltipType: (tooltipType) => {
326
328
  this.tooltipType = tooltipType;
327
329
  },
330
+ // Title should not be set for some consumers like helptext (see W-12496300)
331
+ showTitle: (showTitle) => {
332
+ this.showTitle = showTitle;
333
+ },
328
334
  },
329
335
  })
330
336
  );
@@ -13,7 +13,7 @@
13
13
  </template>
14
14
 
15
15
  <template if:true={showMapLink}>
16
- <a title={mapQuery} href={mapUrl}
16
+ <a aria-label={mapQuery} href={mapUrl}
17
17
  tabindex={internalTabIndex} target="_blank" rel="noopener">
18
18
  <template for:each={addressLines} for:item="line">
19
19
  <div key={line} class="slds-truncate">
@@ -148,11 +148,12 @@ export default class LightningHelptext extends LightningElement {
148
148
 
149
149
  /**
150
150
  * Set lightning-button-icon tooltips to be created with toggle events
151
- *
151
+ * and without the title attribute computed from alternative text (see W-12496300)
152
152
  * @param {*} event
153
153
  */
154
154
  handleButtonIconRegister(event) {
155
155
  event.stopPropagation();
156
156
  event.detail.setTooltipType(TooltipType.Toggle);
157
+ event.detail.showTitle(false);
157
158
  }
158
159
  }
@@ -322,6 +322,8 @@ A modal can only fire events captured by the component that opened it, not the m
322
322
 
323
323
  To capture modal events, attach them in the `.open()` method invoked by the component that opens the modal.
324
324
 
325
+ Capturing modal events requires [Lightning Web Security (LWS)](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.security_lwsec_enable) to be enabled in the Salesforce org. See the **Modal Events with LWS and Lightning Locker** section for more information.
326
+
325
327
  For example, here's a custom `select` event dispatched from `MyModal`.
326
328
 
327
329
  ```js
@@ -374,6 +376,14 @@ handleOpenModal() {
374
376
 
375
377
  See [Create and Dispatch Events](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/events_create_dispatch) in the LWC Dev Guide for more information about events.
376
378
 
379
+ #### Modal Events with LWS and Lightning Locker
380
+
381
+ Modal events work as expected when Lightning Web Security (LWS) is enabled within a Salesforce org, as described in the **Modal Events** section. If LWS isn't enabled in an org, Lightning Locker is in effect.
382
+
383
+ LWS is replacing Lightning Locker over time and is already enabled in many customer orgs. New orgs have LWS enabled by default. To enable LWS, see [Enable Lightning Web Security in an Org](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/security_lwsec_enable) in the Lightning Web Components Developer Guide.
384
+
385
+ Under Lightning Locker, when you fire events within `LightningModal`, the browser throws a `TypeError` related to `dispatchEvent`. If your modal component runs in an org that can’t enable LWS yet, the workaround is to wrap the code that calls `dispatchEvent` in a child component that extends `LightningElement`. Use the wrapper component as a child of one of the modal components in the modal template.
386
+
377
387
  #### Modal Events with Aura
378
388
 
379
389
  For `LightningModal`, only the top level LWC component or application can communicate to the parent Aura component or application layer with eventing. This topmost component is usually the one that opens the `LightningModal`. `LightningModal`'s child components can't event to a parent Aura component or application layer.
@@ -579,3 +589,4 @@ Customizing the styling on the white modal frame and background, close button, o
579
589
  The `lightning-modal-header` component renders the `label` value in an `<h1>` element. If your modal uses the header, begin any additional heading levels in the modal with `<h2>` for accessibility. Provide accessible structure by using heading levels up to `<h6>` appropriately. For more information, see [Semantic Structure, Headings on WebAim.org](https://webaim.org/techniques/semanticstructure/#headings).
580
590
 
581
591
  To include tagline text or link content for the header section, add it between the `<lightning-modal-header>` tags.
592
+
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <lightning-modal-header label={header}>
3
- Here’s a tagline if you need it. It is allowed to extend across
3
+ Here’s a tagline if you need it. It is allowed to extend across
4
4
  mulitple lines, so I’m making up content to show that to you.
5
5
  It is allowed to have links but this one doesn't have any links.
6
6
  </lightning-modal-header>
@@ -38,7 +38,7 @@
38
38
  data-container
39
39
  >
40
40
  <lightning-button-icon
41
- class="slds-modal__close"
41
+ class={computedCloseButtonCssClass}
42
42
  icon-name="utility:close"
43
43
  variant={computedCloseButtonVariant}
44
44
  alternative-text={closeButtonAltText}
@@ -50,6 +50,7 @@ export default class LightningModalBase extends LightningElement {
50
50
  headerLabelIsPopulated = null;
51
51
  headerTitleRef = null;
52
52
  headerTabElemRef = null;
53
+ headerHeightCheck = null;
53
54
 
54
55
  // modalBody, child
55
56
  bodyRegistered = false;
@@ -71,6 +72,7 @@ export default class LightningModalBase extends LightningElement {
71
72
  footerSlotHasRendered = false;
72
73
  footerDefaultSlotIsPopulated = false;
73
74
  footerTabElemRef = null;
75
+ footerHeightCheck = null;
74
76
 
75
77
  // aria attributes
76
78
  modalLabel = null;
@@ -332,6 +334,25 @@ export default class LightningModalBase extends LightningElement {
332
334
  return classes.toString();
333
335
  }
334
336
 
337
+ /**
338
+ * Compute the correct lightning-button-icon CSS class to use
339
+ * for the size="full" behaviors, based upon the screen size
340
+ * threshold. Two classes are added for full screen behavior
341
+ * to handle edge cases where customers change background of the
342
+ * modal header so the close button maintains visibility for a11y
343
+ * @private
344
+ */
345
+ get computedCloseButtonCssClass() {
346
+ let classes = classSet('slds-modal__close');
347
+ const fullScreenActive =
348
+ this.isSmallScreenSize && this.size === SIZE_FULL;
349
+ classes.add({
350
+ 'slds-button_icon-border-filled': fullScreenActive,
351
+ 'slds-modal_full-close-button': fullScreenActive,
352
+ });
353
+ return classes.toString();
354
+ }
355
+
335
356
  /**
336
357
  * Compute the correct lightning-button-icon variant to use
337
358
  * for the size="full" behaviors, based upon the screen size
@@ -341,9 +362,7 @@ export default class LightningModalBase extends LightningElement {
341
362
  * @private
342
363
  */
343
364
  get computedCloseButtonVariant() {
344
- return this.isSmallScreenSize && this.size === SIZE_FULL
345
- ? 'bare'
346
- : 'bare-inverse';
365
+ return this.shouldModalBeFullScreen() ? 'bare' : 'bare-inverse';
347
366
  }
348
367
 
349
368
  /**
@@ -801,6 +820,14 @@ export default class LightningModalBase extends LightningElement {
801
820
  this.updateAriaLabel();
802
821
  }
803
822
 
823
+ /**
824
+ * update the modalBase tracked header height value
825
+ * @private
826
+ */
827
+ updateHeaderHeight(value) {
828
+ this.headerHeight = !Number.isNaN(value) && value >= 0 ? value : 0;
829
+ }
830
+
804
831
  /**
805
832
  * Registers modalHeader with its parent modal, when present
806
833
  * Sets private tracked state about modalHeader
@@ -813,6 +840,7 @@ export default class LightningModalBase extends LightningElement {
813
840
  firstTabbableElemRef,
814
841
  defaultSlotWrapperId,
815
842
  defaultSlotHasRendered,
843
+ checkHeaderHeightCallback,
816
844
  unRegisterCallback,
817
845
  labelIsPopulated,
818
846
  headerHeight,
@@ -820,7 +848,7 @@ export default class LightningModalBase extends LightningElement {
820
848
  labelId,
821
849
  }) {
822
850
  this.headerRegistered = true;
823
- this.headerHeight = headerHeight || 0;
851
+ this.updateHeaderHeight(headerHeight);
824
852
  this.headerDefaultSlotIsPopulated = defaultSlotIsPopulated;
825
853
  this.headerSlotHasRendered = defaultSlotHasRendered;
826
854
  this.headerSlotWrapperId = defaultSlotWrapperId;
@@ -828,6 +856,7 @@ export default class LightningModalBase extends LightningElement {
828
856
  this.headerLabelIsPopulated = labelIsPopulated;
829
857
  this.headerTitleRef = headerRef;
830
858
  this.headerTabElemRef = firstTabbableElemRef;
859
+ this.headerHeightCheck = checkHeaderHeightCallback;
831
860
  unRegisterCallback(() => {
832
861
  this.unregisterHeader();
833
862
  });
@@ -865,6 +894,7 @@ export default class LightningModalBase extends LightningElement {
865
894
  this.headerLabelIsPopulated = null;
866
895
  this.headerTitleRef = null;
867
896
  this.headerTabElemRef = null;
897
+ this.headerHeightCheck = null;
868
898
  }
869
899
 
870
900
  /**
@@ -875,6 +905,14 @@ export default class LightningModalBase extends LightningElement {
875
905
  this.initFooterState();
876
906
  }
877
907
 
908
+ /**
909
+ * update the modalBase tracked footer height value
910
+ * @private
911
+ */
912
+ updateFooterHeight(value) {
913
+ this.footerHeight = !Number.isNaN(value) && value >= 0 ? value : 0;
914
+ }
915
+
878
916
  /**
879
917
  * Registers modalFooter with its parent modal, when present
880
918
  * Sets private tracked state about modalFooter
@@ -888,12 +926,14 @@ export default class LightningModalBase extends LightningElement {
888
926
  footerHeight,
889
927
  unRegisterCallback,
890
928
  firstTabbableElemRef,
929
+ checkFooterHeightCallback,
891
930
  }) {
892
931
  this.footerRegistered = true;
893
932
  this.footerDefaultSlotIsPopulated = defaultSlotIsPopulated;
894
933
  this.footerSlotHasRendered = defaultSlotHasRendered;
895
- this.footerHeight = footerHeight || 0;
934
+ this.updateFooterHeight(footerHeight);
896
935
  this.footerTabElemRef = firstTabbableElemRef || null;
936
+ this.footerHeightCheck = checkFooterHeightCallback;
897
937
  unRegisterCallback(() => {
898
938
  this.unregisterFooter();
899
939
  });
@@ -926,6 +966,7 @@ export default class LightningModalBase extends LightningElement {
926
966
  this.footerSlotHasRendered = false;
927
967
  this.footerDefaultSlotIsPopulated = false;
928
968
  this.footerTabElemRef = null;
969
+ this.footerHeightCheck = null;
929
970
  }
930
971
 
931
972
  /**
@@ -941,6 +982,63 @@ export default class LightningModalBase extends LightningElement {
941
982
  return this._resizing;
942
983
  }
943
984
 
985
+ /**
986
+ * Test to determine whether modal should display full screen behavior
987
+ * @returns {Boolean}
988
+ * @private
989
+ */
990
+ shouldModalBeFullScreen() {
991
+ return this.size === SIZE_FULL && this.isSmallScreenSize;
992
+ }
993
+
994
+ /**
995
+ * Under the right conditions, update the local tracked header height value
996
+ * @private
997
+ */
998
+ checkAndUpdateHeaderHeight() {
999
+ // important to verify that header is registered,
1000
+ // and not on initial render of modal parent, since
1001
+ // child component may not yet exist
1002
+ const shouldCheckHeaderHeight =
1003
+ !this.initialRender &&
1004
+ this.headerRegistered &&
1005
+ this.shouldModalBeFullScreen();
1006
+ // when screen size changes, variable height content within the
1007
+ // modal header can reflow
1008
+ // for example: 3 rows of content in tagline can become 2 rows
1009
+ if (shouldCheckHeaderHeight) {
1010
+ const { changed: headerChangedHeight, value: newHeaderHeight } =
1011
+ this.headerHeightCheck();
1012
+ if (headerChangedHeight) {
1013
+ this.updateHeaderHeight(newHeaderHeight);
1014
+ }
1015
+ }
1016
+ }
1017
+
1018
+ /**
1019
+ * Under the right conditions, update the local tracked footer height value
1020
+ * @private
1021
+ */
1022
+ checkAndUpdateFooterHeight() {
1023
+ // important to verify that footer is registered,
1024
+ // and not on initial render of modal parent, since
1025
+ // child component may not yet exist
1026
+ const shouldCheckFooterHeight =
1027
+ !this.initialRender &&
1028
+ this.footerRegistered &&
1029
+ this.shouldModalBeFullScreen();
1030
+ // when screen size changes, variable height content within the
1031
+ // modal footer can reflow
1032
+ // for example: 1 row of buttons to 2 rows, or 2 rows to 1 row
1033
+ if (shouldCheckFooterHeight) {
1034
+ const { changed: footerChangedHeight, value: newFooterHeight } =
1035
+ this.footerHeightCheck();
1036
+ if (footerChangedHeight) {
1037
+ this.updateFooterHeight(newFooterHeight);
1038
+ }
1039
+ }
1040
+ }
1041
+
944
1042
  /**
945
1043
  * When the modalBody content height is tall, it requires max-height
946
1044
  * to be set in order to prevent overflow of the modal offscreen
@@ -951,17 +1049,18 @@ export default class LightningModalBase extends LightningElement {
951
1049
  updateModalBodyHeight() {
952
1050
  clearTimeout(this.timeoutId);
953
1051
  this.timeoutId = 0;
954
- const { bodyResizeScheduled, bodyRegistered, baseUpdateBodyCallback } =
955
- this;
956
1052
 
957
- if (bodyRegistered && !bodyResizeScheduled) {
1053
+ if (this.bodyRegistered && !this.bodyResizeScheduled) {
958
1054
  // check, and update isSmallScreenSize before
959
1055
  // callback to modalBody
960
1056
  this.setIsSmallScreenSize();
1057
+ // check for height changes in header or footer
1058
+ this.checkAndUpdateHeaderHeight();
1059
+ this.checkAndUpdateFooterHeight();
961
1060
  // eslint-disable-next-line @lwc/lwc/no-async-operation
962
1061
  requestAnimationFrame(() => {
963
1062
  this.bodyResizeScheduled = false;
964
- if (bodyRegistered && baseUpdateBodyCallback) {
1063
+ if (this.baseUpdateBodyCallback) {
965
1064
  const values = {
966
1065
  footerHeight: this.footerHeight || 0,
967
1066
  headerHeight: this.headerHeight || 0,
@@ -971,7 +1070,7 @@ export default class LightningModalBase extends LightningElement {
971
1070
  sizeSetFull: this.size === SIZE_FULL,
972
1071
  isSmallScreenSize: this.isSmallScreenSize,
973
1072
  };
974
- baseUpdateBodyCallback(values);
1073
+ this.baseUpdateBodyCallback(values);
975
1074
  }
976
1075
  });
977
1076
  this.bodyResizeScheduled = true;
@@ -1031,8 +1130,7 @@ export default class LightningModalBase extends LightningElement {
1031
1130
  * @private
1032
1131
  */
1033
1132
  removeOrientationChangeListener() {
1034
- // TODO START HERE, check for this.windowOrientationEventBound === true
1035
- if (this.portraitMatchMedia) {
1133
+ if (this.windowOrientationEventBound && this.portraitMatchMedia) {
1036
1134
  this.portraitMatchMedia.removeEventListener(
1037
1135
  'change',
1038
1136
  this.screenOrientationChangeHandler
@@ -1053,7 +1151,7 @@ export default class LightningModalBase extends LightningElement {
1053
1151
  this.portraitMatchMedia = window.matchMedia('(orientation: portrait)');
1054
1152
  this.screenOrientationChangeHandler =
1055
1153
  this.handleWindowResizeEvent.bind(this);
1056
- if (typeof this.portraitMatchMedia === 'function') {
1154
+ if (this.portraitMatchMedia) {
1057
1155
  this.portraitMatchMedia.addEventListener(
1058
1156
  'change',
1059
1157
  this.screenOrientationChangeHandler
@@ -9,7 +9,11 @@ const hideClass = 'slds-hide';
9
9
  const footerSelector = `.${footerClass}`;
10
10
  const footerSlotSelector = '[data-footer-slot]';
11
11
  // timeout footer height check
12
+ // checking quickly yields faster resolution of correct
13
+ // footer (not a typo) placement
12
14
  const SIZE_CHECK_TIMER = 50;
15
+ // limited to 4 quick checks totalling 200 ms, which catches
16
+ // any misreported heights based on reflow of content in modalFooter
13
17
  const MAX_HEIGHT_CHECKS = 4;
14
18
 
15
19
  /**
@@ -109,6 +113,20 @@ export default class LightningModalFooter extends LightningElement {
109
113
  return firstElem;
110
114
  }
111
115
 
116
+ /**
117
+ * if not the intial render, check for footer height chnage,
118
+ * when a window resize occurs
119
+ * @returns {Object}
120
+ * @private
121
+ */
122
+ handleModalFooterResizeCheck() {
123
+ // when not intial render, and footer height changed
124
+ // return the tracked value, otherwise indicate no change
125
+ return !this.initialRender && this.hasFooterHeightChanged()
126
+ ? { changed: true, value: this.footerHeightTracked }
127
+ : { changed: false, value: null };
128
+ }
129
+
112
130
  /**
113
131
  * Register modalFooter with modal parent, including callbacks to
114
132
  * unregister the modal footer
@@ -125,6 +143,8 @@ export default class LightningModalFooter extends LightningElement {
125
143
  defaultSlotIsPopulated: this.isDefaultSlotPopulated,
126
144
  defaultSlotHasRendered: !this.initialSlotRender,
127
145
  firstTabbableElemRef: this.firstTabbableElement,
146
+ checkFooterHeightCallback:
147
+ this.handleModalFooterResizeCheck.bind(this),
128
148
  unRegisterCallback: (unregisterCallback) => {
129
149
  this.unregisterCallback = unregisterCallback;
130
150
  },
@@ -165,8 +185,7 @@ export default class LightningModalFooter extends LightningElement {
165
185
  this.footerHeightChecked = 0;
166
186
  } else {
167
187
  this.footerHeightChecked++;
168
- const hasChanged = this.hasFooterHeightChanged();
169
- if (hasChanged) {
188
+ if (this.hasFooterHeightChanged()) {
170
189
  this.registerWithParent();
171
190
  }
172
191
  }
@@ -7,6 +7,12 @@ const modalHeaderSelector = '.slds-modal__header';
7
7
  const labelSelector = '[data-label]';
8
8
  const slotWrapperSelector = '[data-slot-wrapper]';
9
9
  const defaultSlotSelector = '[data-default-slot]';
10
+ // timeout header height check
11
+ // checking quickly yields faster resolution of footer placement
12
+ const SIZE_CHECK_TIMER = 50;
13
+ // limited to 4 quick checks totalling 200 ms, which catches
14
+ // any misreported heights based on reflow of content
15
+ const MAX_HEIGHT_CHECKS = 4;
10
16
 
11
17
  /**
12
18
  * Creates a header to display the heading and tagline at the top of a modal.
@@ -16,6 +22,9 @@ export default class LightningModalHeader extends LightningElement {
16
22
  initialRender = true;
17
23
  initialSlotRender = true;
18
24
  unregisterCallback = null;
25
+ headerHeightTracked = 0;
26
+ headerHeightChecked = 0;
27
+ timeoutId = 0;
19
28
 
20
29
  /**
21
30
  * Text to display as the heading at the top of the modal
@@ -44,7 +53,9 @@ export default class LightningModalHeader extends LightningElement {
44
53
  const divElem = this.template.querySelector(modalHeaderSelector);
45
54
  const headerRect = divElem ? divElem.getBoundingClientRect() : {};
46
55
  const { height } = headerRect;
47
- return height || 0;
56
+ const heightValue = height || 0;
57
+ this.headerHeightTracked = heightValue;
58
+ return heightValue;
48
59
  }
49
60
 
50
61
  /**
@@ -144,6 +155,18 @@ export default class LightningModalHeader extends LightningElement {
144
155
  return (this.label && this.label.trim().length > 0) || false;
145
156
  }
146
157
 
158
+ /**
159
+ * if not the intial render, check for header height chnage,
160
+ * when a window resize occurs
161
+ * @returns {Object}
162
+ * @private
163
+ */
164
+ handleModalHeaderResizeCheck() {
165
+ return !this.initialRender && this.hasHeaderHeightChanged()
166
+ ? { changed: true, value: this.headerHeightTracked }
167
+ : { changed: false, value: null };
168
+ }
169
+
147
170
  /**
148
171
  * Register modalHeader with modal parent, including callbacks to
149
172
  * unregister the modal header
@@ -162,6 +185,8 @@ export default class LightningModalHeader extends LightningElement {
162
185
  defaultSlotWrapperId: this.defaultSlotWrapperId,
163
186
  defaultSlotIsPopulated: this.isDefaultSlotPopulated,
164
187
  defaultSlotHasRendered: !this.initialSlotRender,
188
+ checkHeaderHeightCallback:
189
+ this.handleModalHeaderResizeCheck.bind(this),
165
190
  unRegisterCallback: (unregisterCallback) => {
166
191
  this.unregisterCallback = unregisterCallback;
167
192
  },
@@ -172,6 +197,46 @@ export default class LightningModalHeader extends LightningElement {
172
197
  this.dispatchEvent(evtRegister);
173
198
  }
174
199
 
200
+ /**
201
+ * Provide a means to check whether the tracked header height
202
+ * is different than the current header height to only call modalBase
203
+ * when there is a change in header height
204
+ * @private
205
+ */
206
+ hasHeaderHeightChanged() {
207
+ // note: calling this.headerHeight updates this.headerHeightTracked
208
+ // order of values checked here is required
209
+ const previousRenderedHeight = this.headerHeightTracked;
210
+ const currentRenderedHeight = this.headerHeight;
211
+ return currentRenderedHeight !== previousRenderedHeight;
212
+ }
213
+
214
+ /**
215
+ * On first render, provide a quick means of updating modalBase,
216
+ * if the modalHeader height changes.
217
+ * In rare cases, the height of the header between the
218
+ * normal or full size rendering can change depending on
219
+ * content within the header and the window width
220
+ * @private
221
+ */
222
+ scheduleHeaderHeightCheck() {
223
+ if (this.initialRender && this.timeoutId === 0) {
224
+ // eslint-disable-next-line @lwc/lwc/no-async-operation
225
+ this.timeoutId = setInterval(() => {
226
+ if (this.headerHeightChecked >= MAX_HEIGHT_CHECKS) {
227
+ clearTimeout(this.timeoutId);
228
+ this.timeoutId = 0;
229
+ this.headerHeightChecked = 0;
230
+ } else {
231
+ this.headerHeightChecked++;
232
+ if (this.hasHeaderHeightChanged()) {
233
+ this.registerWithParent();
234
+ }
235
+ }
236
+ }, SIZE_CHECK_TIMER);
237
+ }
238
+ }
239
+
175
240
  /**
176
241
  * When modal header is being created, initialize
177
242
  * private tracked modal header state
@@ -183,6 +248,9 @@ export default class LightningModalHeader extends LightningElement {
183
248
  this.initialRender = true;
184
249
  this.initialSlotRender = true;
185
250
  this.unregisterCallback = null;
251
+ this.headerHeightTracked = 0;
252
+ this.headerHeightChecked = 0;
253
+ this.timeoutId = 0;
186
254
  }
187
255
 
188
256
  connectedCallback() {
@@ -192,6 +260,7 @@ export default class LightningModalHeader extends LightningElement {
192
260
  renderedCallback() {
193
261
  if (this.initialRender) {
194
262
  this.registerWithParent();
263
+ this.scheduleHeaderHeightCheck();
195
264
  this.initialRender = false;
196
265
  }
197
266
  }
@@ -44,19 +44,10 @@ Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */
44
44
  cursor: pointer;
45
45
  }
46
46
 
47
- [type='search'] {
48
- -webkit-appearance: textfield;
49
- outline-offset: -2px;
50
- }
51
- button,
52
- input,
53
- optgroup,
54
- select,
55
- textarea {
56
- color: inherit;
57
- font: inherit;
58
- margin: 0;
59
- }
47
+ [type='search'] {
48
+ appearance: textfield;
49
+ outline-offset: -2px;
50
+ }
60
51
 
61
52
  input:focus,
62
53
  button:focus,
@@ -103,11 +94,3 @@ textarea {
103
94
  iframe {
104
95
  border-style: none;
105
96
  }
106
-
107
- svg:not([fill]) {
108
- fill: currentColor;
109
- }
110
-
111
- abbr[title] {
112
- text-decoration: none;
113
- }
@@ -16,16 +16,6 @@ function isResizeObserverSupported() {
16
16
  return window.ResizeObserver != null;
17
17
  }
18
18
 
19
- function buildResizeObserver(callback) {
20
- if (isResizeObserverSupported()) {
21
- return new ResizeObserver(callback);
22
- }
23
- return {
24
- observe() {},
25
-
26
- unobserve() {},
27
- };
28
- }
29
19
  /**
30
20
  * Shared instance of a primitive bubble used as a tooltip by most components. This was originally
31
21
  * defined in the helptext component which is where the minWidth style came from.
@@ -255,7 +245,7 @@ export class Tooltip {
255
245
 
256
246
  get resizeObserver() {
257
247
  if (!this._resizeObserver) {
258
- this._resizeObserver = buildResizeObserver(() => {
248
+ this._resizeObserver = this._buildResizeObserver(() => {
259
249
  if (this._visible && this._autoPosition) {
260
250
  const tooltip = this._element();
261
251
  /**
@@ -412,4 +402,15 @@ export class Tooltip {
412
402
  this._autoPosition.stop();
413
403
  }
414
404
  }
405
+
406
+ _buildResizeObserver(callback) {
407
+ if (isResizeObserverSupported()) {
408
+ return new ResizeObserver(callback);
409
+ }
410
+ return {
411
+ observe() {},
412
+
413
+ unobserve() {},
414
+ };
415
+ }
415
416
  }