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 +1 -1
- package/src/lightning/ariaObserver/__examples__/connect/connect.js +0 -1
- package/src/lightning/ariaObserver/__examples__/connectChild/connectChild.js +1 -3
- 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.html +1 -1
- package/src/lightning/modalBase/modalBase.js +111 -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/src/lightning/tooltipLibrary/tooltipLibrary.js +12 -11
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
@@ -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.
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
}
|
|
@@ -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 =
|
|
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
|
}
|