lightning-base-components 1.17.6-alpha → 1.18.1-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 +1 -1
- package/scopedImports/@salesforce-internal-core.appVersion.js +1 -1
- package/src/lightning/ariaObserver/ariaObserver.js +19 -5
- package/src/lightning/buttonIcon/buttonIcon.js +7 -1
- package/src/lightning/formattedAddress/formattedAddress.html +1 -1
- package/src/lightning/helptext/helptext.js +2 -1
- package/src/lightning/modal/__docs__/modal.md +11 -0
- package/src/lightning/modal/__examples__disabled/demofootless/demofootless.html +1 -1
- package/src/lightning/modalBase/modalBase.js +92 -13
- package/src/lightning/modalFooter/modalFooter.js +21 -2
- package/src/lightning/modalHeader/modalHeader.js +70 -1
- package/src/lightning/sldsCommon/sldsCommon.css +4 -21
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '
|
|
1
|
+
export default '244';
|
|
@@ -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
|
-
|
|
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
|
|
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>
|
|
@@ -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;
|
|
@@ -360,9 +362,7 @@ export default class LightningModalBase extends LightningElement {
|
|
|
360
362
|
* @private
|
|
361
363
|
*/
|
|
362
364
|
get computedCloseButtonVariant() {
|
|
363
|
-
return this.
|
|
364
|
-
? 'bare'
|
|
365
|
-
: 'bare-inverse';
|
|
365
|
+
return this.shouldModalBeFullScreen() ? 'bare' : 'bare-inverse';
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
/**
|
|
@@ -820,6 +820,14 @@ export default class LightningModalBase extends LightningElement {
|
|
|
820
820
|
this.updateAriaLabel();
|
|
821
821
|
}
|
|
822
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
|
+
|
|
823
831
|
/**
|
|
824
832
|
* Registers modalHeader with its parent modal, when present
|
|
825
833
|
* Sets private tracked state about modalHeader
|
|
@@ -832,6 +840,7 @@ export default class LightningModalBase extends LightningElement {
|
|
|
832
840
|
firstTabbableElemRef,
|
|
833
841
|
defaultSlotWrapperId,
|
|
834
842
|
defaultSlotHasRendered,
|
|
843
|
+
checkHeaderHeightCallback,
|
|
835
844
|
unRegisterCallback,
|
|
836
845
|
labelIsPopulated,
|
|
837
846
|
headerHeight,
|
|
@@ -839,7 +848,7 @@ export default class LightningModalBase extends LightningElement {
|
|
|
839
848
|
labelId,
|
|
840
849
|
}) {
|
|
841
850
|
this.headerRegistered = true;
|
|
842
|
-
this.headerHeight
|
|
851
|
+
this.updateHeaderHeight(headerHeight);
|
|
843
852
|
this.headerDefaultSlotIsPopulated = defaultSlotIsPopulated;
|
|
844
853
|
this.headerSlotHasRendered = defaultSlotHasRendered;
|
|
845
854
|
this.headerSlotWrapperId = defaultSlotWrapperId;
|
|
@@ -847,6 +856,7 @@ export default class LightningModalBase extends LightningElement {
|
|
|
847
856
|
this.headerLabelIsPopulated = labelIsPopulated;
|
|
848
857
|
this.headerTitleRef = headerRef;
|
|
849
858
|
this.headerTabElemRef = firstTabbableElemRef;
|
|
859
|
+
this.headerHeightCheck = checkHeaderHeightCallback;
|
|
850
860
|
unRegisterCallback(() => {
|
|
851
861
|
this.unregisterHeader();
|
|
852
862
|
});
|
|
@@ -884,6 +894,7 @@ export default class LightningModalBase extends LightningElement {
|
|
|
884
894
|
this.headerLabelIsPopulated = null;
|
|
885
895
|
this.headerTitleRef = null;
|
|
886
896
|
this.headerTabElemRef = null;
|
|
897
|
+
this.headerHeightCheck = null;
|
|
887
898
|
}
|
|
888
899
|
|
|
889
900
|
/**
|
|
@@ -894,6 +905,14 @@ export default class LightningModalBase extends LightningElement {
|
|
|
894
905
|
this.initFooterState();
|
|
895
906
|
}
|
|
896
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
|
+
|
|
897
916
|
/**
|
|
898
917
|
* Registers modalFooter with its parent modal, when present
|
|
899
918
|
* Sets private tracked state about modalFooter
|
|
@@ -907,12 +926,14 @@ export default class LightningModalBase extends LightningElement {
|
|
|
907
926
|
footerHeight,
|
|
908
927
|
unRegisterCallback,
|
|
909
928
|
firstTabbableElemRef,
|
|
929
|
+
checkFooterHeightCallback,
|
|
910
930
|
}) {
|
|
911
931
|
this.footerRegistered = true;
|
|
912
932
|
this.footerDefaultSlotIsPopulated = defaultSlotIsPopulated;
|
|
913
933
|
this.footerSlotHasRendered = defaultSlotHasRendered;
|
|
914
|
-
this.footerHeight
|
|
934
|
+
this.updateFooterHeight(footerHeight);
|
|
915
935
|
this.footerTabElemRef = firstTabbableElemRef || null;
|
|
936
|
+
this.footerHeightCheck = checkFooterHeightCallback;
|
|
916
937
|
unRegisterCallback(() => {
|
|
917
938
|
this.unregisterFooter();
|
|
918
939
|
});
|
|
@@ -945,6 +966,7 @@ export default class LightningModalBase extends LightningElement {
|
|
|
945
966
|
this.footerSlotHasRendered = false;
|
|
946
967
|
this.footerDefaultSlotIsPopulated = false;
|
|
947
968
|
this.footerTabElemRef = null;
|
|
969
|
+
this.footerHeightCheck = null;
|
|
948
970
|
}
|
|
949
971
|
|
|
950
972
|
/**
|
|
@@ -960,6 +982,63 @@ export default class LightningModalBase extends LightningElement {
|
|
|
960
982
|
return this._resizing;
|
|
961
983
|
}
|
|
962
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
|
+
|
|
963
1042
|
/**
|
|
964
1043
|
* When the modalBody content height is tall, it requires max-height
|
|
965
1044
|
* to be set in order to prevent overflow of the modal offscreen
|
|
@@ -970,17 +1049,18 @@ export default class LightningModalBase extends LightningElement {
|
|
|
970
1049
|
updateModalBodyHeight() {
|
|
971
1050
|
clearTimeout(this.timeoutId);
|
|
972
1051
|
this.timeoutId = 0;
|
|
973
|
-
const { bodyResizeScheduled, bodyRegistered, baseUpdateBodyCallback } =
|
|
974
|
-
this;
|
|
975
1052
|
|
|
976
|
-
if (bodyRegistered && !bodyResizeScheduled) {
|
|
1053
|
+
if (this.bodyRegistered && !this.bodyResizeScheduled) {
|
|
977
1054
|
// check, and update isSmallScreenSize before
|
|
978
1055
|
// callback to modalBody
|
|
979
1056
|
this.setIsSmallScreenSize();
|
|
1057
|
+
// check for height changes in header or footer
|
|
1058
|
+
this.checkAndUpdateHeaderHeight();
|
|
1059
|
+
this.checkAndUpdateFooterHeight();
|
|
980
1060
|
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
981
1061
|
requestAnimationFrame(() => {
|
|
982
1062
|
this.bodyResizeScheduled = false;
|
|
983
|
-
if (
|
|
1063
|
+
if (this.baseUpdateBodyCallback) {
|
|
984
1064
|
const values = {
|
|
985
1065
|
footerHeight: this.footerHeight || 0,
|
|
986
1066
|
headerHeight: this.headerHeight || 0,
|
|
@@ -990,7 +1070,7 @@ export default class LightningModalBase extends LightningElement {
|
|
|
990
1070
|
sizeSetFull: this.size === SIZE_FULL,
|
|
991
1071
|
isSmallScreenSize: this.isSmallScreenSize,
|
|
992
1072
|
};
|
|
993
|
-
baseUpdateBodyCallback(values);
|
|
1073
|
+
this.baseUpdateBodyCallback(values);
|
|
994
1074
|
}
|
|
995
1075
|
});
|
|
996
1076
|
this.bodyResizeScheduled = true;
|
|
@@ -1050,8 +1130,7 @@ export default class LightningModalBase extends LightningElement {
|
|
|
1050
1130
|
* @private
|
|
1051
1131
|
*/
|
|
1052
1132
|
removeOrientationChangeListener() {
|
|
1053
|
-
|
|
1054
|
-
if (this.portraitMatchMedia) {
|
|
1133
|
+
if (this.windowOrientationEventBound && this.portraitMatchMedia) {
|
|
1055
1134
|
this.portraitMatchMedia.removeEventListener(
|
|
1056
1135
|
'change',
|
|
1057
1136
|
this.screenOrientationChangeHandler
|
|
@@ -1072,7 +1151,7 @@ export default class LightningModalBase extends LightningElement {
|
|
|
1072
1151
|
this.portraitMatchMedia = window.matchMedia('(orientation: portrait)');
|
|
1073
1152
|
this.screenOrientationChangeHandler =
|
|
1074
1153
|
this.handleWindowResizeEvent.bind(this);
|
|
1075
|
-
if (
|
|
1154
|
+
if (this.portraitMatchMedia) {
|
|
1076
1155
|
this.portraitMatchMedia.addEventListener(
|
|
1077
1156
|
'change',
|
|
1078
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
}
|