lightning-base-components 1.14.1-alpha → 1.14.2-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.14.1-alpha",
3
+ "version": "1.14.2-alpha",
4
4
  "engines": {
5
5
  "node": ">=12.18.3"
6
6
  },
@@ -0,0 +1,103 @@
1
+ import { createElement } from 'lwc';
2
+ import AriaObserverContainer from 'lightningtest/ariaObserverContainer';
3
+
4
+ function createTestElement(props = {}) {
5
+ const element = createElement('lightningtest-aria-observer-container', {
6
+ is: AriaObserverContainer,
7
+ });
8
+ Object.assign(element, props);
9
+ document.body.appendChild(element);
10
+
11
+ return element;
12
+ }
13
+
14
+ describe('AriaObserver', () => {
15
+ if (process.env.NATIVE_SHADOW) {
16
+ describe('native shadow', () => {
17
+ it('should copy the label content over and set aria-labelledby to be the internal label element', () => {
18
+ const container = createTestElement();
19
+ const testElement = container.testElement;
20
+
21
+ expect(container.labelContent).toEqual(
22
+ testElement.labelContent
23
+ );
24
+ });
25
+
26
+ it('should react to label id changes', async () => {
27
+ const container = createTestElement();
28
+ const oldLabelContent = container.labelContent;
29
+
30
+ container.updateAriaLabelledby('alt-label-id');
31
+ await Promise.resolve();
32
+
33
+ const testElement = container.testElement;
34
+
35
+ expect(container.labelContent).not.toEqual(oldLabelContent);
36
+ expect(container.labelContent).toEqual(
37
+ testElement.labelContent
38
+ );
39
+ });
40
+
41
+ it('should work with multiple label ids', async () => {
42
+ const container = createTestElement();
43
+ container.updateAriaLabelledby('id-label alt-label-id');
44
+ await Promise.resolve();
45
+
46
+ const testElement = container.testElement;
47
+ expect(testElement.labelContent).toEqual('Foo\nBar');
48
+ });
49
+
50
+ it('should update the internal label content when external content changes', async () => {
51
+ const container = createTestElement();
52
+ container.updateLabelContent();
53
+
54
+ // wait for component rehydration
55
+ await Promise.resolve();
56
+ // wait for mutation observer callback
57
+ await Promise.resolve();
58
+
59
+ const testElement = container.testElement;
60
+ expect(container.labelContent).toEqual(
61
+ testElement.labelContent
62
+ );
63
+ });
64
+ });
65
+ } else {
66
+ describe('synthetic shadow', () => {
67
+ it('should set aria-labelledby to be the external label id', () => {
68
+ const container = createTestElement();
69
+ const testElement = container.testElement;
70
+
71
+ expect(container.labelId).toEqual(testElement.labelId);
72
+ });
73
+
74
+ it('should react to label id changes', async () => {
75
+ const container = createTestElement();
76
+ const oldLabelId = container.labelId;
77
+
78
+ container.updateAriaLabelledby('alt-label-id');
79
+ await Promise.resolve();
80
+
81
+ const testElement = container.testElement;
82
+
83
+ expect(container.labelId).not.toEqual(oldLabelId);
84
+ expect(container.labelId).toEqual(testElement.labelId);
85
+ });
86
+
87
+ it('should keep the label id unchanged when the label content changes', async () => {
88
+ const container = createTestElement();
89
+ container.updateLabelContent();
90
+ await Promise.resolve();
91
+
92
+ const testElement = container.testElement;
93
+ expect(container.labelId).toEqual(testElement.labelId);
94
+ });
95
+
96
+ it('should keep track of the live id', () => {
97
+ const container = createTestElement();
98
+ const testElement = container.testElement;
99
+ expect(testElement.labelId).toEqual(testElement.liveId);
100
+ });
101
+ });
102
+ }
103
+ });
@@ -0,0 +1,268 @@
1
+ import {
2
+ guid,
3
+ synchronizeAttrs,
4
+ isNativeComponent,
5
+ } from 'lightning/utilsPrivate';
6
+
7
+ const CONTENT_SEPARATOR = '\n';
8
+
9
+ function getAttr(elm, attr) {
10
+ if (elm.tagName.match(/lightning/i)) {
11
+ return elm[attr];
12
+ }
13
+ return elm.getAttribute(attr);
14
+ }
15
+
16
+ function extractElements(root, selector) {
17
+ if (typeof selector !== 'string' || selector === '') {
18
+ return [];
19
+ }
20
+ return [].slice.call(root.querySelectorAll(selector));
21
+ }
22
+
23
+ function extractContent(elements) {
24
+ return elements
25
+ .map((element) => element.textContent)
26
+ .filter((text) => text.length)
27
+ .join(CONTENT_SEPARATOR);
28
+ }
29
+
30
+ function splitIds(ids) {
31
+ return (ids + '').trim().split(/\s+/);
32
+ }
33
+
34
+ function hashIds(ids) {
35
+ return (ids + '')
36
+ .trim()
37
+ .split(/\s+/)
38
+ .reduce((r, v) => {
39
+ r[v] = 1;
40
+ return r;
41
+ }, {});
42
+ }
43
+
44
+ // this method should check each individual id from computedIds
45
+ // against the existing value of the attrName on elm, and dupe
46
+ // them, and add the new ones.
47
+ function addAriaRefWhenNeeded(elm, attrName, computedIds) {
48
+ const newIds = splitIds(computedIds);
49
+ const oldIds = getAttr(elm, attrName) || '';
50
+ const oldIdsHash = hashIds(oldIds);
51
+ const suffix = [];
52
+ for (let i = 0; i < newIds.length; i += 1) {
53
+ if (!oldIdsHash[newIds[i]]) {
54
+ suffix.push(newIds[i]);
55
+ }
56
+ }
57
+
58
+ if (suffix.length !== 0) {
59
+ synchronizeAttrs(elm, {
60
+ [attrName]:
61
+ oldIds + (oldIds.length === 0 ? '' : ' ') + suffix.join(' '),
62
+ });
63
+ }
64
+ }
65
+
66
+ // this method should check each individual id from computedIds
67
+ // against the existing value of the attrName on elm, and remove
68
+ // them when possible in preparation for some new values.
69
+ function removeAriaRefWhenPossible(elm, attrName, computedIds) {
70
+ const newIds = splitIds(computedIds);
71
+ const oldIds = getAttr(elm, attrName) || '';
72
+ const oldIdsHash = hashIds(oldIds);
73
+ const newValues = [];
74
+ for (let i = 0; i < newIds.length; i += 1) {
75
+ if (!oldIdsHash[newIds[i]]) {
76
+ newValues.push(newIds[i]);
77
+ }
78
+ }
79
+
80
+ synchronizeAttrs(elm, {
81
+ [attrName]: newValues.join(' '),
82
+ });
83
+ }
84
+
85
+ function createPlaceholderContainer() {
86
+ const container = document.createElement('span');
87
+ container.style.display = 'none';
88
+ container.setAttribute('placeholder-container', '');
89
+ return container;
90
+ }
91
+
92
+ export default class AriaObserver {
93
+ constructor(component) {
94
+ this.component = component;
95
+ this.template = component.template;
96
+ this.isNative = isNativeComponent(component);
97
+ this.state = {};
98
+ this.liveIds = {};
99
+ this.guid = guid();
100
+ this.placeholderContainer = null;
101
+ }
102
+
103
+ connectLiveIdRef(refs, callback) {
104
+ const selector = (refs + '')
105
+ .trim()
106
+ .split(/\s+/)
107
+ .map((ref) => `[id*="${ref}"]`)
108
+ .join(',');
109
+ const liveId = { selector, callback };
110
+ this.liveIds[refs] = liveId;
111
+ }
112
+
113
+ connect({ targetSelector, attribute, id, ids }) {
114
+ ids = ids || id;
115
+
116
+ let attrState = this.state[attribute];
117
+ if (attrState) {
118
+ // note: we don't support linking to a different targetSelector, attribute
119
+ if (!this.isNative) {
120
+ const elm = this.template.querySelector(
121
+ attrState.innerSelector
122
+ );
123
+ if (elm) {
124
+ // removing the old ids if possible before setting the new ones
125
+ removeAriaRefWhenPossible(elm, attribute, attrState.ids);
126
+ }
127
+ attrState.ids = ids;
128
+ }
129
+ } else {
130
+ attrState = this.state[attribute] = {
131
+ ids,
132
+ innerSelector: targetSelector,
133
+ };
134
+ }
135
+ if (this.isNative) {
136
+ attrState.outerSelector = (ids + '')
137
+ .trim()
138
+ .split(/\s+/)
139
+ .map((ref) => `#${ref}`)
140
+ .join(',');
141
+
142
+ // create placeholder element for copied content
143
+ if (!attrState.placeholder) {
144
+ attrState.placeholder = document.createElement('span');
145
+ attrState.placeholder.id = `auto-link-${attribute}-${this.guid}`;
146
+ }
147
+ }
148
+ if (this.component.isConnected) {
149
+ this.privateUpdate(attribute);
150
+ }
151
+ }
152
+
153
+ sync() {
154
+ if (!this.component.isConnected) {
155
+ throw new Error(
156
+ `Invalid sync invocation. It can only be invoked during renderedCallback().`
157
+ );
158
+ }
159
+ if (this.isNative && !this.mo) {
160
+ this.privateConnect();
161
+ }
162
+ for (const attrName in this.state) {
163
+ if (Object.prototype.hasOwnProperty.call(this.state, attrName)) {
164
+ this.privateUpdate(attrName);
165
+ }
166
+ }
167
+
168
+ // live idRef feature is a no-op in native
169
+ if (!this.isNative) {
170
+ this.privateUpdateLiveIds();
171
+ }
172
+ }
173
+
174
+ privateExtractIds(elements) {
175
+ return elements
176
+ .map((el) => {
177
+ return el.getAttribute('id');
178
+ })
179
+ .join(' ');
180
+ }
181
+
182
+ privateUpdateLiveIds() {
183
+ const root = this.template.host.getRootNode();
184
+
185
+ // if not connected do nothing
186
+ if (!root) {
187
+ return;
188
+ }
189
+ for (const liveId in this.liveIds) {
190
+ if (Object.prototype.hasOwnProperty.call(this.liveIds, liveId)) {
191
+ const thisId = this.liveIds[liveId];
192
+ if (!thisId.elements) {
193
+ // element refs are cached
194
+ thisId.elements = Array.prototype.slice.call(
195
+ root.querySelectorAll(thisId.selector)
196
+ );
197
+ }
198
+ const newIds = this.privateExtractIds(thisId.elements);
199
+ // only fire calback if the value changed
200
+ if (newIds !== thisId.ids) {
201
+ thisId.callback(newIds);
202
+ thisId.ids = newIds;
203
+ }
204
+ }
205
+ }
206
+ }
207
+
208
+ privateUpdate(attrName) {
209
+ const { innerSelector } = this.state[attrName];
210
+ const elm = this.template.querySelector(innerSelector);
211
+ if (!elm) {
212
+ return; // nothing to update
213
+ }
214
+ let computedIds;
215
+ if (this.isNative) {
216
+ const { outerSelector, content, placeholder } =
217
+ this.state[attrName];
218
+
219
+ const newContent = extractContent(
220
+ extractElements(this.root, outerSelector)
221
+ );
222
+ if (content !== newContent) {
223
+ this.state[attrName].content = placeholder.textContent =
224
+ newContent;
225
+ }
226
+ if (!placeholder.parentNode) {
227
+ // create placeholder container at template root, if not already exist
228
+ if (!this.placeholderContainer) {
229
+ this.placeholderContainer = createPlaceholderContainer();
230
+ this.template.appendChild(this.placeholderContainer);
231
+ }
232
+
233
+ // inserting the placeholder once
234
+ this.placeholderContainer.appendChild(placeholder);
235
+ }
236
+ computedIds = placeholder.id;
237
+ } else {
238
+ computedIds = this.state[attrName].ids;
239
+ }
240
+ addAriaRefWhenNeeded(elm, attrName, computedIds);
241
+ }
242
+
243
+ privateConnect() {
244
+ // caching root ref
245
+ this.root = this.template.host.getRootNode();
246
+ // creating the observer once
247
+ this.mo = new MutationObserver(() => {
248
+ if (!this.component.isConnected) {
249
+ return; // do nothing when the template is not connected
250
+ }
251
+ this.sync();
252
+ });
253
+ this.mo.observe(this.root, {
254
+ characterData: true,
255
+ childList: true,
256
+ subtree: true,
257
+ });
258
+ }
259
+
260
+ disconnect() {
261
+ // MutationObservers must be disconnected manually when using @lwc/synthetic-shadow
262
+ // https://lwc.dev/guide/composition#:~:text=memory%20leak
263
+ if (this.mo) {
264
+ this.mo.disconnect();
265
+ this.mo = undefined;
266
+ }
267
+ }
268
+ }
@@ -77,7 +77,7 @@ const i18n = {
77
77
 
78
78
  const ARIA_CONTROLS = 'aria-controls';
79
79
  const ARIA_LABEL = 'aria-label';
80
- const ARIA_LABELEDBY = 'aria-labelledby';
80
+ const ARIA_LABELLEDBY = 'aria-labelledby';
81
81
  const ARIA_DESCRIBEDBY = 'aria-describedby';
82
82
 
83
83
  /*
@@ -524,7 +524,7 @@ export default class LightningInput extends LightningElement {
524
524
  this._ariaControls = references;
525
525
  this.ariaObserver.connect({
526
526
  targetSelector: 'input',
527
- attribute: 'aria-controls',
527
+ attribute: ARIA_CONTROLS,
528
528
  ids: references,
529
529
  });
530
530
  }
@@ -537,8 +537,7 @@ export default class LightningInput extends LightningElement {
537
537
  get ariaLabelledBy() {
538
538
  // native version returns the auto linked value
539
539
  if (this.isNative) {
540
- const ariaValues =
541
- this._inputElement.getAttribute('aria-labelledby');
540
+ const ariaValues = this._inputElement.getAttribute(ARIA_LABELLEDBY);
542
541
  return filterNonAutoLink(ariaValues);
543
542
  }
544
543
  return this._ariaLabelledBy;
@@ -548,7 +547,7 @@ export default class LightningInput extends LightningElement {
548
547
  this._ariaLabelledBy = references;
549
548
  this.ariaObserver.connect({
550
549
  targetSelector: 'input',
551
- attribute: 'aria-labelledby',
550
+ attribute: ARIA_LABELLEDBY,
552
551
  ids: references,
553
552
  });
554
553
  }
@@ -562,7 +561,7 @@ export default class LightningInput extends LightningElement {
562
561
  if (this.isNative) {
563
562
  // in native case return the linked value
564
563
  const ariaValues =
565
- this._inputElement.getAttribute('aria-describedby');
564
+ this._inputElement.getAttribute(ARIA_DESCRIBEDBY);
566
565
  return filterNonAutoLink(ariaValues);
567
566
  }
568
567
  return this._ariaDescribedBy;
@@ -572,7 +571,7 @@ export default class LightningInput extends LightningElement {
572
571
  this._ariaDescribedBy = references;
573
572
  this.ariaObserver.connect({
574
573
  targetSelector: 'input',
575
- attribute: 'aria-describedby',
574
+ attribute: ARIA_DESCRIBEDBY,
576
575
  ids: references,
577
576
  });
578
577
  }
@@ -2033,32 +2032,58 @@ export default class LightningInput extends LightningElement {
2033
2032
  return result;
2034
2033
  }
2035
2034
 
2035
+ _updateInputA11y(elem) {
2036
+ synchronizeAttrs(elem, {
2037
+ [ARIA_LABELLEDBY]: this.computedAriaLabelledBy,
2038
+ [ARIA_DESCRIBEDBY]: this.computedAriaDescribedBy,
2039
+ [ARIA_CONTROLS]: this.computedAriaControls,
2040
+ [ARIA_LABEL]: this.computedAriaLabel,
2041
+ });
2042
+ }
2043
+
2044
+ _updateDateOrTimePickerA11y(elem) {
2045
+ synchronizeAttrs(elem, {
2046
+ ariaLabelledByElement: this.ariaLabelledBy,
2047
+ ariaDescribedByElements: this.ariaDescribedBy,
2048
+ ariaControlsElement: this.ariaControls,
2049
+ [ARIA_LABEL]: this.computedAriaLabel,
2050
+ });
2051
+ }
2052
+
2053
+ _updateDateTimePickerA11y(elem) {
2054
+ synchronizeAttrs(elem, {
2055
+ // datepicker aria attributes
2056
+ dateAriaLabelledBy: this.dateAriaLabelledBy,
2057
+ dateAriaDescribedBy: this.dateAriaDescribedBy,
2058
+ dateAriaControls: this.dateAriaControls,
2059
+ dateAriaLabel: this.dateAriaLabel,
2060
+ // timepicker aria attributes
2061
+ timeAriaLabelledBy: this.timeAriaLabelledBy,
2062
+ timeAriaDescribedBy: this.timeAriaDescribedBy,
2063
+ timeAriaControls: this.timeAriaControls,
2064
+ timeAriaLabel: this.timeAriaLabel,
2065
+ });
2066
+ }
2067
+
2036
2068
  _synchronizeA11y() {
2069
+ // each of these templates are mutually exclusive and are selected
2070
+ // depending on the [type] of input.
2037
2071
  const input = this.template.querySelector('input');
2038
2072
  const datepicker = this.template.querySelector('lightning-datepicker');
2039
2073
  const timepicker = this.template.querySelector('lightning-timepicker');
2040
-
2074
+ const datetimepicker = this.template.querySelector(
2075
+ 'lightning-datetimepicker'
2076
+ );
2077
+ // determine which template type is present,
2078
+ // and update a11y props accordingly
2041
2079
  if (input) {
2042
- synchronizeAttrs(input, {
2043
- [ARIA_LABELEDBY]: this.computedAriaLabelledBy,
2044
- [ARIA_DESCRIBEDBY]: this.computedAriaDescribedBy,
2045
- [ARIA_CONTROLS]: this.computedAriaControls,
2046
- [ARIA_LABEL]: this.computedAriaLabel,
2047
- });
2080
+ this._updateInputA11y(input);
2048
2081
  } else if (datepicker) {
2049
- synchronizeAttrs(datepicker, {
2050
- ariaLabelledByElement: this.ariaLabelledBy,
2051
- ariaDescribedByElements: this.ariaDescribedBy,
2052
- ariaControlsElement: this.ariaControls,
2053
- [ARIA_LABEL]: this.computedAriaLabel,
2054
- });
2082
+ this._updateDateOrTimePickerA11y(datepicker);
2055
2083
  } else if (timepicker) {
2056
- synchronizeAttrs(timepicker, {
2057
- ariaLabelledByElement: this.ariaLabelledBy,
2058
- ariaDescribedByElements: this.ariaDescribedBy,
2059
- ariaControlsElement: this.ariaControls,
2060
- [ARIA_LABEL]: this.computedAriaLabel,
2061
- });
2084
+ this._updateDateOrTimePickerA11y(timepicker);
2085
+ } else if (datetimepicker) {
2086
+ this._updateDateTimePickerA11y(datetimepicker);
2062
2087
  }
2063
2088
  }
2064
2089
  }
@@ -131,38 +131,50 @@ function createRelationship(
131
131
  );
132
132
 
133
133
  const autoShrink = config.autoShrink.height || config.autoShrink.width;
134
- if (config.scrollableParentBound && scrollableParent) {
135
- const parent = normalizeElement(scrollableParent);
136
- const boxConfig = {
137
- element: config.element,
138
- enabled: config.enabled,
139
- target: createProxy(parent),
140
- align: {},
141
- targetAlign: {},
142
- pad: 3,
143
- boxDirections: {
144
- top: true,
145
- bottom: true,
146
- left: true,
147
- right: true,
148
- },
149
- };
150
- if (autoShrink) {
151
- const style = boxConfig.element.getNode().style;
152
- if (!style.minHeight) {
153
- style.minHeight = config.minHeight;
154
- boxConfig.element._removeMinHeight = true;
155
- }
156
-
157
- boxConfig.boxDirections = {
158
- top: !!config.autoShrink.height,
159
- bottom: !!config.autoShrink.height,
160
- left: !!config.autoShrink.width,
161
- right: !!config.autoShrink.width,
134
+ const modal = new OverlayDetector(originalConfig.target);
135
+ if (
136
+ (config.scrollableParentBound && scrollableParent) ||
137
+ (modal.isInsideModal && modal.overlay && autoShrink)
138
+ ) {
139
+ let parent;
140
+ if (config.scrollableParentBound && scrollableParent) {
141
+ parent = normalizeElement(scrollableParent);
142
+ } else if (modal.isInsideModal && modal.overlay) {
143
+ parent = normalizeElement(modal.overlay);
144
+ }
145
+ if (parent) {
146
+ const boxConfig = {
147
+ element: config.element,
148
+ enabled: config.enabled,
149
+ target: createProxy(parent),
150
+ align: {},
151
+ targetAlign: {},
152
+ pad: 3,
153
+ boxDirections: {
154
+ top: true,
155
+ bottom: true,
156
+ left: true,
157
+ right: true,
158
+ },
162
159
  };
163
- constraintList.push(new Constraint('shrinking box', boxConfig));
164
- } else {
165
- constraintList.push(new Constraint('bounding box', boxConfig));
160
+
161
+ if (autoShrink) {
162
+ const style = boxConfig.element.getNode().style;
163
+ if (!style.minHeight) {
164
+ style.minHeight = config.minHeight;
165
+ boxConfig.element._removeMinHeight = true;
166
+ }
167
+
168
+ boxConfig.boxDirections = {
169
+ top: !!config.autoShrink.height,
170
+ bottom: !!config.autoShrink.height,
171
+ left: !!config.autoShrink.width,
172
+ right: !!config.autoShrink.width,
173
+ };
174
+ constraintList.push(new Constraint('shrinking box', boxConfig));
175
+ } else {
176
+ constraintList.push(new Constraint('bounding box', boxConfig));
177
+ }
166
178
  }
167
179
  }
168
180
 
@@ -31,6 +31,8 @@ export default class LightningProgressStep extends LightningElement {
31
31
  */
32
32
  @api value;
33
33
 
34
+ _privateLabel;
35
+
34
36
  @track state = {};
35
37
 
36
38
  updateInternal(newStatus, newType, newIndex, newActive, shouldFocus) {
@@ -47,6 +49,7 @@ export default class LightningProgressStep extends LightningElement {
47
49
  this.state.type = newType;
48
50
  this.state.index = newIndex;
49
51
  this.state.active = newActive;
52
+ this.initializeTooltip();
50
53
  }
51
54
  /**
52
55
  * Text to display as the name or tooltip for the step.
@@ -54,30 +57,12 @@ export default class LightningProgressStep extends LightningElement {
54
57
  */
55
58
  @api
56
59
  set label(value) {
57
- if (this._tooltip) {
58
- this._tooltip.value = value;
59
- } else if (value && !this.isPath) {
60
- // Note that because the tooltip target is a child element it may not be present in the
61
- // dom during initial rendering.
62
- this._tooltip = new Tooltip(value, {
63
- root: this,
64
- target: () => this.template.querySelector('button'),
65
- type: TooltipType.Toggle,
66
- align: {
67
- horizontal: Direction.Center,
68
- vertical: Direction.Bottom,
69
- },
70
- targetAlign: {
71
- horizontal: Direction.Center,
72
- vertical: Direction.Top,
73
- },
74
- });
75
- this._tooltip.initialize();
76
- }
60
+ this._privateLabel = value;
61
+ this.initializeTooltip();
77
62
  }
78
63
 
79
64
  get label() {
80
- return this._tooltip ? this._tooltip.value : undefined;
65
+ return this._privateLabel;
81
66
  }
82
67
 
83
68
  computeClassSet(type, status, isActive) {
@@ -240,4 +225,27 @@ export default class LightningProgressStep extends LightningElement {
240
225
  }
241
226
  return base;
242
227
  }
228
+
229
+ initializeTooltip() {
230
+ if (this._tooltip) {
231
+ this._tooltip.value = this._privateLabel;
232
+ } else if (this._privateLabel && this.state.type && !this.isPath) {
233
+ // Note that because the tooltip target is a child element it may not be present in the
234
+ // dom during initial rendering.
235
+ this._tooltip = new Tooltip(this._privateLabel, {
236
+ root: this,
237
+ target: () => this.template.querySelector('button'),
238
+ type: TooltipType.Toggle,
239
+ align: {
240
+ horizontal: Direction.Center,
241
+ vertical: Direction.Bottom,
242
+ },
243
+ targetAlign: {
244
+ horizontal: Direction.Center,
245
+ vertical: Direction.Top,
246
+ },
247
+ });
248
+ this._tooltip.initialize();
249
+ }
250
+ }
243
251
  }
@@ -218,7 +218,8 @@ export function buttonGroupOrderClass(groupOrder) {
218
218
  * @returns {Boolean} Whether the component is native
219
219
  */
220
220
  export function isNativeComponent(cmp) {
221
- return !String(cmp?.template?.constructor).includes(
222
- 'function SyntheticShadowRoot'
223
- );
221
+ if (cmp && cmp.template && cmp.template.constructor) {
222
+ return !!String(cmp.template.constructor).match(/\[native code\]/);
223
+ }
224
+ return false;
224
225
  }