lightning-base-components 1.17.7-alpha → 1.18.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/metadata/raptor.json +5 -0
- package/package.json +43 -1
- package/src/lightning/accordion/accordion.css +12 -0
- package/src/lightning/accordion/accordion.html +3 -1
- package/src/lightning/accordion/accordion.js +4 -2
- package/src/lightning/accordion/accordion.slds.css +671 -0
- package/src/lightning/accordionSection/accordion-section.slds.css +647 -0
- package/src/lightning/accordionSection/accordionSection.css +14 -0
- package/src/lightning/accordionSection/accordionSection.html +23 -19
- package/src/lightning/accordionSection/accordionSection.js +29 -2
- package/src/lightning/ariaObserver/__docs__/ariaObserver.md +21 -9
- package/src/lightning/ariaObserver/ariaObserver.js +185 -154
- package/src/lightning/ariaObserver/polyfill.js +639 -0
- package/src/lightning/avatar/avatar.css +2 -0
- package/src/lightning/avatar/avatar.html +2 -0
- package/src/lightning/avatar/avatar.js +18 -15
- package/src/lightning/avatar/avatar.slds.css +272 -0
- package/src/lightning/baseCombobox/base-combobox.slds.css +1585 -0
- package/src/lightning/baseCombobox/baseCombobox.css +11 -1
- package/src/lightning/baseCombobox/baseCombobox.html +154 -146
- package/src/lightning/baseCombobox/baseCombobox.js +82 -46
- package/src/lightning/baseCombobox/spinner.slds.css +438 -0
- package/src/lightning/baseComboboxItem/baseComboboxItem.js +4 -2
- package/src/lightning/baseComboboxItem/inline.css +2 -0
- package/src/lightning/breadcrumb/breadcrumb.css +2 -2
- package/src/lightning/breadcrumb/breadcrumb.js +4 -2
- package/src/lightning/breadcrumb/breadcrumb.slds.css +2 -7
- package/src/lightning/breadcrumbs/breadcrumbs.css +2 -2
- package/src/lightning/breadcrumbs/breadcrumbs.js +3 -2
- package/src/lightning/breadcrumbs/breadcrumbs.slds.css +7 -1
- package/src/lightning/button/__examples__/inverse/inverse.css +8 -0
- package/src/lightning/button/__examples__/inverse/inverse.html +3 -2
- package/src/lightning/button/button.css +2 -0
- package/src/lightning/button/button.html +4 -2
- package/src/lightning/button/button.js +21 -0
- package/src/lightning/button/button.slds.css +527 -0
- package/src/lightning/buttonGroup/buttonGroup.css +2 -2
- package/src/lightning/buttonGroup/buttonGroup.js +3 -2
- package/src/lightning/buttonIcon/button-icon.slds.css +215 -453
- package/src/lightning/buttonIcon/buttonIcon.css +2 -2
- package/src/lightning/buttonIcon/buttonIcon.js +4 -0
- package/src/lightning/buttonIconStateful/button-icon-stateful.slds.css +215 -453
- package/src/lightning/buttonIconStateful/buttonIconStateful.css +2 -2
- package/src/lightning/buttonMenu/{dropdown.slds.css → button-menu.slds.css} +853 -217
- package/src/lightning/buttonMenu/buttonMenu.css +2 -2
- package/src/lightning/buttonMenu/buttonMenu.html +2 -2
- package/src/lightning/buttonMenu/buttonMenu.js +10 -14
- package/src/lightning/buttonStateful/button-stateful.slds.css +225 -457
- package/src/lightning/buttonStateful/buttonStateful.css +2 -2
- package/src/lightning/buttonStateful/buttonStateful.js +3 -2
- package/src/lightning/calendar/__examples__/basic/basic.html +7 -0
- package/src/lightning/calendar/__examples__/basic/basic.js +3 -0
- package/src/lightning/calendar/calendar.css +3 -0
- package/src/lightning/calendar/calendar.html +12 -9
- package/src/lightning/calendar/calendar.js +15 -1
- package/src/lightning/calendar/calendar.slds.css +2048 -0
- package/src/lightning/card/card.css +2 -2
- package/src/lightning/card/card.js +3 -2
- package/src/lightning/card/card.slds.css +141 -88
- package/src/lightning/colorPickerCustom/colorPickerCustom.css +2 -2
- package/src/lightning/colorPickerCustom/colorPickerCustom.js +3 -2
- package/src/lightning/colorPickerPanel/color-picker-panel.slds.css +11 -38
- package/src/lightning/colorPickerPanel/colorPickerPanel.css +3 -2
- package/src/lightning/colorPickerPanel/colorPickerPanel.js +4 -2
- package/src/lightning/colorPickerPanel/popover.slds.css +121 -0
- package/src/lightning/combobox/combobox.css +4 -0
- package/src/lightning/combobox/combobox.html +31 -29
- package/src/lightning/combobox/combobox.js +21 -4
- package/src/lightning/combobox/combobox.slds.css +13 -0
- package/src/lightning/combobox/form-element.slds.css +281 -0
- package/src/lightning/configProvider/defaultConfig.js +2 -1
- package/src/lightning/datepicker/datepicker.css +3 -0
- package/src/lightning/datepicker/datepicker.html +7 -4
- package/src/lightning/datepicker/datepicker.js +73 -19
- package/src/lightning/datepicker/form-element.slds.css +281 -0
- package/src/lightning/datepicker/input-text.slds.css +398 -0
- package/src/lightning/datetimepicker/datetimepicker.css +3 -0
- package/src/lightning/datetimepicker/datetimepicker.html +9 -3
- package/src/lightning/datetimepicker/datetimepicker.js +39 -35
- package/src/lightning/datetimepicker/form-element.slds.css +281 -0
- package/src/lightning/datetimepicker/input-text.slds.css +398 -0
- package/src/lightning/dualListbox/dualListbox.css +2 -2
- package/src/lightning/dualListbox/dualListbox.html +3 -3
- package/src/lightning/dualListbox/dualListbox.js +31 -6
- package/src/lightning/dualListbox/form-element.slds.css +83 -34
- package/src/lightning/dualListbox/keyboard.js +20 -1
- package/src/lightning/dynamicIcon/dynamicIcon.js +3 -2
- package/src/lightning/dynamicIcon/ellie.css +1 -1
- package/src/lightning/dynamicIcon/eq.css +1 -1
- package/src/lightning/dynamicIcon/score.css +1 -1
- package/src/lightning/dynamicIcon/strength.css +1 -1
- package/src/lightning/dynamicIcon/trend.css +1 -1
- package/src/lightning/dynamicIcon/waffle.css +1 -1
- package/src/lightning/formattedRichText/linkify.js +2 -2
- package/src/lightning/helptext/form-element.slds.css +83 -34
- package/src/lightning/helptext/help-text.slds.css +215 -453
- package/src/lightning/helptext/helptext.css +2 -2
- package/src/lightning/helptext/helptext.js +3 -2
- package/src/lightning/i18nCldrOptions/README.md +5 -0
- package/src/lightning/i18nService/README.md +5 -0
- package/src/lightning/icon/icon.css +2 -2
- package/src/lightning/icon/icon.js +16 -2
- package/src/lightning/icon/icon.slds.css +29 -17
- package/src/lightning/icon/iconColors.js +1 -0
- package/src/lightning/input/__examples__/text/text.html +0 -1
- package/src/lightning/input/form-element.slds.css +281 -0
- package/src/lightning/input/input-checkbox.slds.css +3 -12
- package/src/lightning/input/input-text.slds.css +239 -128
- package/src/lightning/input/input.css +2 -1
- package/src/lightning/input/input.html +8 -8
- package/src/lightning/input/input.js +107 -73
- package/src/lightning/internationalizationLibrary/README.md +24 -0
- package/src/lightning/internationalizationLibrary/utils.js +4 -1
- package/src/lightning/layout/__docs__/layout.md +1 -1
- package/src/lightning/layout/__examples__/simple/simple.css +1 -1
- package/src/lightning/layout/layout.css +5 -1
- package/src/lightning/layout/layout.js +4 -2
- package/src/lightning/layoutItem/__examples__/alignmentBump/alignmentBump.css +1 -1
- package/src/lightning/layoutItem/__examples__/sizePerDevice/sizePerDevice.css +0 -1
- package/src/lightning/layoutItem/layoutItem.css +5 -0
- package/src/lightning/layoutItem/layoutItem.js +4 -2
- package/src/lightning/menuDivider/menu-divider.slds.css +15 -0
- package/src/lightning/menuDivider/menuDivider.css +3 -0
- package/src/lightning/menuDivider/menuDivider.html +1 -1
- package/src/lightning/menuDivider/menuDivider.js +4 -2
- package/src/lightning/menuItem/menu-item.slds.css +140 -0
- package/src/lightning/menuItem/menuItem.css +3 -0
- package/src/lightning/menuItem/menuItem.html +43 -41
- package/src/lightning/menuItem/menuItem.js +4 -4
- package/src/lightning/menuSubheader/menu-subheader.slds.css +22 -0
- package/src/lightning/menuSubheader/menuSubheader.css +3 -0
- package/src/lightning/menuSubheader/menuSubheader.html +3 -1
- package/src/lightning/menuSubheader/menuSubheader.js +4 -6
- package/src/lightning/modal/__docs__/modal.md +3 -1
- package/src/lightning/modal/__modalUtils__/modalContainerTestConstants.js +267 -0
- package/src/lightning/modal/__modalUtils__/modalContainerTestMethods.js +1165 -0
- package/src/lightning/modal/__modalUtils__/modalContainerTestMockData.js +131 -0
- package/src/lightning/modal/modal.js +1 -1
- package/src/lightning/pill/avatar.slds.css +272 -0
- package/src/lightning/pill/link.css +3 -0
- package/src/lightning/pill/link.html +1 -1
- package/src/lightning/pill/pill.js +29 -9
- package/src/lightning/pill/pill.slds.css +168 -0
- package/src/lightning/pill/plain.css +3 -0
- package/src/lightning/pill/plain.html +1 -1
- package/src/lightning/pill/plainLink.css +3 -0
- package/src/lightning/pill/plainLink.html +1 -1
- package/src/lightning/pillContainer/barePillContainer.css +3 -0
- package/src/lightning/pillContainer/barePillContainer.html +1 -2
- package/src/lightning/pillContainer/listbox.slds.css +267 -0
- package/src/lightning/pillContainer/pill-container.slds.css +22 -0
- package/src/lightning/pillContainer/pill.slds.css +168 -0
- package/src/lightning/pillContainer/pillContainer.js +7 -3
- package/src/lightning/pillContainer/standardPillContainer.css +4 -0
- package/src/lightning/pillContainer/standardPillContainer.html +2 -2
- package/src/lightning/popup/popover.slds.css +119 -119
- package/src/lightning/popup/popup.css +1 -2
- package/src/lightning/popup/popup.js +3 -2
- package/src/lightning/positionLibrary/elementProxy.js +7 -2
- package/src/lightning/positionLibrary/util.js +8 -0
- package/src/lightning/primitiveBubble/primitiveBubble.css +2 -2
- package/src/lightning/primitiveBubble/primitiveBubble.js +4 -2
- package/src/lightning/primitiveButton/primitiveButton.js +5 -4
- package/src/lightning/primitiveCellFactory/cellWithStandardLayout.html +29 -21
- package/src/lightning/primitiveCellFactory/primitiveCellFactory.js +4 -0
- package/src/lightning/primitiveColorpickerButton/color-picker-button.slds.css +31 -19
- package/src/lightning/primitiveColorpickerButton/primitiveColorpickerButton.css +2 -2
- package/src/lightning/primitiveColorpickerButton/primitiveColorpickerButton.js +5 -3
- package/src/lightning/primitiveIcon/icon.slds.css +209 -0
- package/src/lightning/primitiveIcon/primitiveIcon.css +2 -1
- package/src/lightning/primitiveIcon/primitiveIcon.html +1 -1
- package/src/lightning/primitiveIcon/primitiveIcon.js +18 -11
- package/src/lightning/progressStep/progressStep.js +10 -13
- package/src/lightning/radioGroup/radioGroup.css +2 -1
- package/src/lightning/radioGroup/radioGroup.js +4 -2
- package/src/lightning/select/form-element.slds.css +83 -34
- package/src/lightning/select/select.css +2 -2
- package/src/lightning/select/select.js +4 -2
- package/src/lightning/select/select.slds.css +86 -34
- package/src/lightning/sldsCommon/sldsCommon.css +135 -75
- package/src/lightning/spinner/spinner.css +2 -2
- package/src/lightning/spinner/spinner.js +4 -2
- package/src/lightning/tabBar/tab-bar.slds.css +334 -0
- package/src/lightning/tabBar/tabBar.css +2 -0
- package/src/lightning/tabBar/tabBar.html +4 -3
- package/src/lightning/tabBar/tabBar.js +30 -3
- package/src/lightning/tabset/tabset.html +5 -4
- package/src/lightning/tabset/tabset.js +29 -11
- package/src/lightning/timepicker/form-element.slds.css +281 -0
- package/src/lightning/timepicker/timepicker.css +3 -0
- package/src/lightning/timepicker/timepicker.html +5 -1
- package/src/lightning/timepicker/timepicker.js +18 -15
- package/src/lightning/timepicker/timepicker.slds.css +18 -0
- package/src/lightning/tooltipLibrary/tooltipLibrary.js +21 -19
- package/src/lightning/utilsPrivate/browser.js +5 -3
- package/src/lightning/utilsPrivate/os.js +6 -4
- package/src/lightning/utilsPrivate/ssr.js +4 -0
- package/src/lightning/utilsPrivate/utilsPrivate.js +2 -0
- package/src/lightning/verticalNavigation/verticalNavigation.css +2 -1
- package/src/lightning/verticalNavigation/verticalNavigation.js +3 -2
- package/src/lightning/verticalNavigationSection/verticalNavigationSection.css +2 -1
- package/src/lightning/verticalNavigationSection/verticalNavigationSection.js +3 -2
- package/src/lightning/accordion/__perf__DISABLED/accordion-perf-utils.js +0 -76
- package/src/lightning/accordion/__perf__DISABLED/accordion10Multiple25SectionEach.perf.js +0 -57
- package/src/lightning/accordion/__perf__DISABLED/accordion10Simple25SectionEach.perf.js +0 -37
- package/src/lightning/accordion/__perf__DISABLED/accordionMultiple50Section.perf.js +0 -45
- package/src/lightning/accordion/__perf__DISABLED/accordionSimple50Section.perf.js +0 -35
- package/src/lightning/accordion/__perf__DISABLED/container/container.html +0 -15
- package/src/lightning/accordion/__perf__DISABLED/container/container.js +0 -7
- package/src/lightning/positionLibrary/__component__/positionLibraryBounding.spec.js +0 -319
- package/src/lightning/positionLibrary/__component__/x/bounding/bounding.css +0 -16
- package/src/lightning/positionLibrary/__component__/x/bounding/bounding.html +0 -36
- package/src/lightning/positionLibrary/__component__/x/bounding/bounding.js +0 -122
- /package/src/lightning/{baseCombobox → baseComboboxItem}/listbox.slds.css +0 -0
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
<
|
|
2
|
+
<div class="slds-accordion__list-item">
|
|
3
|
+
<section class={computedSectionClasses} part="accordion-section">
|
|
4
|
+
<div class="slds-accordion__summary">
|
|
5
|
+
<h2 aria-level={_privateHeadingAriaLevel} onkeydown={handleKeyDown} class="slds-accordion__summary-heading">
|
|
6
|
+
<button
|
|
7
|
+
class="section-control slds-button slds-button_reset slds-accordion__summary-action"
|
|
6
8
|
type="button"
|
|
7
9
|
aria-expanded={computedAriaExpanded}
|
|
8
10
|
aria-controls='lgt-accordion-section'
|
|
9
|
-
onclick={handleSelectSection}
|
|
10
|
-
|
|
11
|
-
icon
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
onclick={handleSelectSection}
|
|
12
|
+
part="button">
|
|
13
|
+
<lightning-primitive-icon
|
|
14
|
+
icon-name={accordionTitleIcon}
|
|
15
|
+
svg-class="slds-button__icon slds-button__icon_left"
|
|
16
|
+
size="x-small">
|
|
17
|
+
</lightning-primitive-icon>
|
|
18
|
+
<span class="slds-accordion__summary-content" title={label}>{label}</span>
|
|
19
|
+
</button>
|
|
20
|
+
</h2>
|
|
21
|
+
<slot name="actions"></slot>
|
|
22
|
+
</div>
|
|
23
|
+
<div id='lgt-accordion-section' hidden={computedHidden} aria-hidden={computedAriaHidden} class="slds-accordion__content">
|
|
24
|
+
<slot></slot>
|
|
25
|
+
</div>
|
|
26
|
+
</section>
|
|
27
|
+
</div>
|
|
24
28
|
</template>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { api, track } from 'lwc';
|
|
2
|
+
import LightningShadowBaseClass from 'lightning/shadowBaseClassPrivate';
|
|
2
3
|
import { generateUniqueId } from 'lightning/inputUtils';
|
|
3
4
|
import { keyCodes, isHeadingLevelValid } from 'lightning/utilsPrivate';
|
|
4
5
|
import { classSet } from 'lightning/utils';
|
|
6
|
+
import DIR from '@salesforce/i18n/dir';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* A single section that is nested in an accordion component.
|
|
@@ -9,7 +11,7 @@ import { classSet } from 'lightning/utils';
|
|
|
9
11
|
* Actions are displayed at the top right corner of the accordion section.
|
|
10
12
|
* @slot default Placeholder for your content in the accordion section.
|
|
11
13
|
*/
|
|
12
|
-
export default class LightningAccordionSection extends
|
|
14
|
+
export default class LightningAccordionSection extends LightningShadowBaseClass {
|
|
13
15
|
/**
|
|
14
16
|
* The unique section name to use with the active-section-name attribute in the accordion component.
|
|
15
17
|
* @type {string}
|
|
@@ -57,6 +59,7 @@ export default class LightningAccordionSection extends LightningElement {
|
|
|
57
59
|
privateUniqueId = generateUniqueId('lgt-accordion-section');
|
|
58
60
|
|
|
59
61
|
connectedCallback() {
|
|
62
|
+
super.connectedCallback();
|
|
60
63
|
this.setAttribute('role', 'listitem');
|
|
61
64
|
this.classList.add('slds-accordion__list-item');
|
|
62
65
|
this.registerSectionWithParent();
|
|
@@ -116,6 +119,30 @@ export default class LightningAccordionSection extends LightningElement {
|
|
|
116
119
|
this.privateAccordionSectionObserver.notifySectionSelect();
|
|
117
120
|
}
|
|
118
121
|
|
|
122
|
+
get hostDirection() {
|
|
123
|
+
// need to use .parentElement as accordionSection needs parentElement accordion to render
|
|
124
|
+
const host = this.template.host;
|
|
125
|
+
if (host.parentElement) {
|
|
126
|
+
return (
|
|
127
|
+
window
|
|
128
|
+
.getComputedStyle(host.parentElement)
|
|
129
|
+
.direction.toLowerCase() || 'ltr'
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// if no parentElement, return direction from @salesforce/i18n; else fallback to 'ltr'
|
|
134
|
+
return DIR || 'ltr';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
get accordionTitleIcon() {
|
|
138
|
+
if (this.privateIsOpen) {
|
|
139
|
+
return 'utility:switch';
|
|
140
|
+
}
|
|
141
|
+
return this.hostDirection === 'ltr'
|
|
142
|
+
? 'utility:chevronright'
|
|
143
|
+
: 'utility:chevronleft';
|
|
144
|
+
}
|
|
145
|
+
|
|
119
146
|
registerSectionWithParent() {
|
|
120
147
|
const detail = {
|
|
121
148
|
targetId: this.privateUniqueId,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
The `lightning/ariaObserver` module provides an easy way for users to write accessible component that works in both synthetic and native shadow.
|
|
2
2
|
|
|
3
3
|
## Aria ID referencing in native shadow
|
|
4
|
-
Use the`
|
|
4
|
+
Use the `AriaObserver` library to write accessible component that works where `ariaLabelledBy` would break native shadow.
|
|
5
5
|
|
|
6
6
|
Here's an example that won't work with native shadow. In the following code, we support attribute `ariaLabelledBy` in our component `c-foo`, so the `input` element is labelled by external elements.
|
|
7
7
|
|
|
@@ -45,9 +45,11 @@ class Foo extends LightningElement {
|
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
Next, use the `connect(options)` method to connect between the internal element and the external reference. It takes an options object with the following keys:
|
|
48
|
+
- `attribute` The name of the aria attribute. Two supported options: `aria-labelledby`, `aria-describedby`, `aria-activedescendant` and `aria-controls`.
|
|
48
49
|
- `targetSelector` The selector to the internal element where the aria attribute should be attached.
|
|
49
|
-
- `
|
|
50
|
-
- `
|
|
50
|
+
- `targetNode` The element where the aria attribute should be attached. If not provided, the `targetSelector` is used.
|
|
51
|
+
- `relatedNodeIds` ID(s) of the external element(s) to which the `targetNode` will be related. Passed as a space separated string `id1 id2 id3` or an Array of strings `['id1', 'id2', 'id3']`. Combined with `relatedNodes` if both are present.
|
|
52
|
+
- `relatedNodes` An Array of HTMLElement element(s) to which the `targetNode` will be related. Combined with `relatedNodeIds` if both are present.
|
|
51
53
|
|
|
52
54
|
This example uses `connect(options)` to display an aria label for the internal `input` element.
|
|
53
55
|
``` js
|
|
@@ -61,12 +63,15 @@ set ariaLabelledBy(refs) {
|
|
|
61
63
|
this.ariaObserver.connect({
|
|
62
64
|
targetSelector: 'input',
|
|
63
65
|
attribute: 'aria-labelledby',
|
|
64
|
-
|
|
66
|
+
relatedNodeIds: refs
|
|
65
67
|
});
|
|
66
68
|
}
|
|
67
69
|
```
|
|
68
70
|
|
|
69
|
-
Then use the `sync()` method to synchronize the ID references when the template is re-rendered.
|
|
71
|
+
Then use the `sync(isNativeShadow)` method to synchronize the ID references when the template is re-rendered.
|
|
72
|
+
- `isNativeShadow` An optional parameter that indicates whether the relationships involve components rendered in native shadow.
|
|
73
|
+
Used when the `targetSelector` or `targetNode` is within a shadow boundary, but the parent component where AriaObserver has been
|
|
74
|
+
instantiated is not. Example: `lightning-primitive-input-simple` may be rendered in native shadow, but `lightning-input` may not be.
|
|
70
75
|
|
|
71
76
|
``` js
|
|
72
77
|
renderedCallback() {
|
|
@@ -74,6 +79,14 @@ renderedCallback() {
|
|
|
74
79
|
}
|
|
75
80
|
```
|
|
76
81
|
|
|
82
|
+
When the containing component is native shadow enabled, AriaObserver observes the component's root node to find and link
|
|
83
|
+
the related elements. This can be overridden in cases where the component's root node does not contain the related elements,
|
|
84
|
+
but one of it's ancestor nodes does. For example, `lightning-base-combobox` is contained within `lightning-combobox` so the root of `lightning-combobox` should be used in place of the root of `lightning-base-combobox`.
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
this.ariaObserver.root = parentRootNode;
|
|
88
|
+
```
|
|
89
|
+
|
|
77
90
|
Finally, disconnect the aria observer and free the resources at the end of the component lifecycle.
|
|
78
91
|
|
|
79
92
|
``` js
|
|
@@ -117,7 +130,7 @@ export default class Foo extends LightningElement {
|
|
|
117
130
|
this.ariaObserver.connect({
|
|
118
131
|
targetSelector: 'input',
|
|
119
132
|
attribute: 'aria-labelledby',
|
|
120
|
-
|
|
133
|
+
relatedNodeIds: refs
|
|
121
134
|
});
|
|
122
135
|
}
|
|
123
136
|
|
|
@@ -134,9 +147,8 @@ export default class Foo extends LightningElement {
|
|
|
134
147
|
}
|
|
135
148
|
```
|
|
136
149
|
|
|
137
|
-
## Limitations
|
|
138
|
-
`AriaObserver` only works with text-only aria ID references.
|
|
139
|
-
|
|
140
150
|
Supported attributes:
|
|
141
151
|
- `aria-labelledby`
|
|
142
152
|
- `aria-describedby`
|
|
153
|
+
- `aria-activedescendant`
|
|
154
|
+
- `aria-controls`
|
|
@@ -4,14 +4,49 @@ import {
|
|
|
4
4
|
isNativeComponent,
|
|
5
5
|
} from 'lightning/utilsPrivate';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
import {
|
|
8
|
+
setAriaActiveDescendant,
|
|
9
|
+
setAriaDescribedBy,
|
|
10
|
+
setAriaLabelledBy,
|
|
11
|
+
setAriaControls,
|
|
12
|
+
} from './polyfill.js';
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
Aria Reflection is used if supported by browser.
|
|
16
|
+
If Aria Reflection is not available, fallback to polyfill
|
|
17
|
+
Aria Reflection: https://wicg.github.io/aom/aria-reflection-explainer.html
|
|
18
|
+
Polyfill: https://git.soma.salesforce.com/lwc/aria-element-reflection
|
|
19
|
+
*/
|
|
20
|
+
const SUPPORTED_ATTRIBUTES = new Map([
|
|
21
|
+
[
|
|
22
|
+
'aria-controls',
|
|
23
|
+
{
|
|
24
|
+
ariaReflection: 'ariaControlsElements',
|
|
25
|
+
polyfill: setAriaControls,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
[
|
|
29
|
+
'aria-labelledby',
|
|
30
|
+
{
|
|
31
|
+
ariaReflection: 'ariaLabelledByElements',
|
|
32
|
+
polyfill: setAriaLabelledBy,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
'aria-describedby',
|
|
37
|
+
{
|
|
38
|
+
ariaReflection: 'ariaDescribedByElements',
|
|
39
|
+
polyfill: setAriaDescribedBy,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
[
|
|
43
|
+
'aria-activedescendant',
|
|
44
|
+
{
|
|
45
|
+
ariaReflection: 'ariaActiveDescendantElement',
|
|
46
|
+
polyfill: setAriaActiveDescendant,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
]);
|
|
15
50
|
|
|
16
51
|
function extractElements(root, ids) {
|
|
17
52
|
if (typeof ids !== 'string' || ids === '') {
|
|
@@ -24,80 +59,15 @@ function extractElements(root, ids) {
|
|
|
24
59
|
.filter((el) => !!el);
|
|
25
60
|
}
|
|
26
61
|
|
|
27
|
-
function extractContent(elements) {
|
|
28
|
-
return elements
|
|
29
|
-
.map((element) => element.textContent)
|
|
30
|
-
.filter((text) => text.length)
|
|
31
|
-
.join(CONTENT_SEPARATOR);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
62
|
function splitIds(ids) {
|
|
35
|
-
return (ids + '').trim().split(/\s+/);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function hashIds(ids) {
|
|
39
|
-
return (ids + '')
|
|
40
|
-
.trim()
|
|
41
|
-
.split(/\s+/)
|
|
42
|
-
.reduce((r, v) => {
|
|
43
|
-
r[v] = 1;
|
|
44
|
-
return r;
|
|
45
|
-
}, {});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// this method should check each individual id from computedIds
|
|
49
|
-
// against the existing value of the attrName on elm, and dupe
|
|
50
|
-
// them, and add the new ones.
|
|
51
|
-
function addAriaRefWhenNeeded(elm, attrName, computedIds) {
|
|
52
|
-
const newIds = splitIds(computedIds);
|
|
53
|
-
const oldIds = getAttr(elm, attrName) || '';
|
|
54
|
-
const oldIdsHash = hashIds(oldIds);
|
|
55
|
-
const suffix = [];
|
|
56
|
-
for (let i = 0; i < newIds.length; i += 1) {
|
|
57
|
-
if (!oldIdsHash[newIds[i]]) {
|
|
58
|
-
suffix.push(newIds[i]);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (suffix.length !== 0) {
|
|
63
|
-
synchronizeAttrs(elm, {
|
|
64
|
-
[attrName]:
|
|
65
|
-
oldIds + (oldIds.length === 0 ? '' : ' ') + suffix.join(' '),
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// this method should check each individual id from computedIds
|
|
71
|
-
// against the existing value of the attrName on elm, and remove
|
|
72
|
-
// them when possible in preparation for some new values.
|
|
73
|
-
function removeAriaRefWhenPossible(elm, attrName, computedIds) {
|
|
74
|
-
const newIds = splitIds(computedIds);
|
|
75
|
-
const oldIds = getAttr(elm, attrName) || '';
|
|
76
|
-
const oldIdsHash = hashIds(oldIds);
|
|
77
|
-
const newValues = [];
|
|
78
|
-
for (let i = 0; i < newIds.length; i += 1) {
|
|
79
|
-
if (!oldIdsHash[newIds[i]]) {
|
|
80
|
-
newValues.push(newIds[i]);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
synchronizeAttrs(elm, {
|
|
85
|
-
[attrName]: newValues.join(' '),
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function createPlaceholderContainer() {
|
|
90
|
-
const container = document.createElement('span');
|
|
91
|
-
container.className = 'slds-assistive-text';
|
|
92
|
-
container.setAttribute('placeholder-container', '');
|
|
93
|
-
return container;
|
|
63
|
+
return ids ? (ids + '').trim().split(/\s+/) : [];
|
|
94
64
|
}
|
|
95
65
|
|
|
96
66
|
export default class AriaObserver {
|
|
97
67
|
constructor(component) {
|
|
98
68
|
this.component = component;
|
|
99
69
|
this.template = component.template;
|
|
100
|
-
this.
|
|
70
|
+
this.isNativeShadow = isNativeComponent(component);
|
|
101
71
|
this.state = {};
|
|
102
72
|
this.liveIds = {};
|
|
103
73
|
this.guid = guid();
|
|
@@ -114,54 +84,139 @@ export default class AriaObserver {
|
|
|
114
84
|
this.liveIds[refs] = liveId;
|
|
115
85
|
}
|
|
116
86
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Connects the internal element and the external reference. It takes an options object with the following keys:
|
|
89
|
+
* @param {String} attribute The name of the aria attribute. Two supported options: `aria-labelledby`, `aria-describedby`, `aria-activedescendant` and `aria-controls`.
|
|
90
|
+
* @param {String} targetSelector The selector to the internal element where the aria attribute should be attached.
|
|
91
|
+
* @param {HTMLElement} targetNode The element where the aria attribute should be attached. If not provided, the `targetSelector` is used.
|
|
92
|
+
* @param {String|Array[String]} relatedNodeIds ID(s) of the external element(s) to which the `targetNode` will be related. Passed as a space separated string `id1 id2 id3`. Combined with `relatedNodes` if both are present.
|
|
93
|
+
* @param {Array[HTMLElement]} relatedNodes an Array of HTMLElement element(s) to which the `targetNode` will be related. Combined with `relatedNodeIds` if both are present.
|
|
94
|
+
*/
|
|
95
|
+
connect({
|
|
96
|
+
attribute,
|
|
97
|
+
targetSelector,
|
|
98
|
+
targetNode,
|
|
99
|
+
relatedNodeIds,
|
|
100
|
+
relatedNodes,
|
|
101
|
+
}) {
|
|
120
102
|
this.state[attribute] = this.state[attribute] || {};
|
|
121
103
|
const attrState = this.state[attribute];
|
|
122
104
|
|
|
123
|
-
|
|
124
|
-
attrState.
|
|
105
|
+
attrState.targetNode = targetNode;
|
|
106
|
+
attrState.targetSelector = targetSelector;
|
|
107
|
+
attrState.relatedNodes = (
|
|
108
|
+
!Array.isArray(relatedNodes) ? [relatedNodes] : relatedNodes
|
|
109
|
+
).filter(Boolean);
|
|
125
110
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (elm) {
|
|
130
|
-
removeAriaRefWhenPossible(elm, attribute, attrState.ids);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
attrState.ids = ids;
|
|
135
|
-
|
|
136
|
-
if (this.isNative && !attrState.placeholder) {
|
|
137
|
-
// create placeholder element for copied content
|
|
138
|
-
attrState.placeholder = document.createElement('span');
|
|
139
|
-
attrState.placeholder.id = `auto-link-${attribute}-${this.guid}`;
|
|
140
|
-
}
|
|
111
|
+
attrState.relatedNodeIds = Array.isArray(relatedNodeIds)
|
|
112
|
+
? relatedNodeIds.join(' ')
|
|
113
|
+
: relatedNodeIds;
|
|
141
114
|
|
|
142
115
|
if (this.component.isConnected) {
|
|
143
116
|
this.privateUpdate(attribute);
|
|
144
117
|
}
|
|
145
118
|
}
|
|
146
119
|
|
|
147
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Connects the MutationObserver when in native shadow mode and connects the
|
|
122
|
+
* appropriate aria attributes to the correct elements
|
|
123
|
+
* @param {Boolean} isNativeShadow - This flag is used when a subcomponent
|
|
124
|
+
* (like lightning-primitive-input-simple) is in native shadow mode and the parent
|
|
125
|
+
* (lightning-input) that was passed on AriaObserver instantiation is not.
|
|
126
|
+
*/
|
|
127
|
+
sync(isNativeShadow) {
|
|
128
|
+
if (isNativeShadow != null) {
|
|
129
|
+
this.isNativeShadow = isNativeShadow;
|
|
130
|
+
}
|
|
131
|
+
|
|
148
132
|
if (!this.component.isConnected) {
|
|
149
133
|
throw new Error(
|
|
150
134
|
`Invalid sync invocation. It can only be invoked during renderedCallback().`
|
|
151
135
|
);
|
|
152
136
|
}
|
|
153
|
-
|
|
154
|
-
|
|
137
|
+
|
|
138
|
+
if (!this.root) {
|
|
139
|
+
this.root =
|
|
140
|
+
this.template && this.template.host
|
|
141
|
+
? this.template.host.getRootNode()
|
|
142
|
+
: null;
|
|
155
143
|
}
|
|
144
|
+
|
|
145
|
+
this.privateUpdateLiveIds();
|
|
146
|
+
|
|
156
147
|
for (const attrName in this.state) {
|
|
157
148
|
if (Object.prototype.hasOwnProperty.call(this.state, attrName)) {
|
|
158
149
|
this.privateUpdate(attrName);
|
|
159
150
|
}
|
|
160
151
|
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
get privateIsMoRequired() {
|
|
155
|
+
return this.isNativeShadow || Object.keys(this.liveIds).length !== 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
get root() {
|
|
159
|
+
return this._root;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Sets the specified root element and observes it. The root element should contain
|
|
164
|
+
* Observes the root element. The root element should contain
|
|
165
|
+
* the related node elements. By default, this is the template host's root node, but can be
|
|
166
|
+
* overridden where required.
|
|
167
|
+
*/
|
|
168
|
+
set root(root) {
|
|
169
|
+
this._root = root;
|
|
170
|
+
if (this._root && this.privateIsMoRequired) {
|
|
171
|
+
this.privateCreateMutationObserver();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
privateUpdate(attrName) {
|
|
176
|
+
const {
|
|
177
|
+
targetSelector,
|
|
178
|
+
targetNode = this.template.querySelector(targetSelector),
|
|
179
|
+
relatedNodeIds,
|
|
180
|
+
relatedNodes,
|
|
181
|
+
} = this.state[attrName];
|
|
182
|
+
|
|
183
|
+
if (!targetNode) {
|
|
184
|
+
return; // nothing to update
|
|
185
|
+
}
|
|
161
186
|
|
|
162
|
-
|
|
163
|
-
if (!
|
|
164
|
-
|
|
187
|
+
const attribute = SUPPORTED_ATTRIBUTES.get(attrName);
|
|
188
|
+
if (!attribute) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`${attrName} is not supported by AriaObserver. Supported attributes: ${Array.from(
|
|
191
|
+
SUPPORTED_ATTRIBUTES.keys()
|
|
192
|
+
)}`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (this.isNativeShadow) {
|
|
197
|
+
const allRelatedNodes = [
|
|
198
|
+
...relatedNodes,
|
|
199
|
+
...extractElements(this.root, relatedNodeIds),
|
|
200
|
+
];
|
|
201
|
+
if (targetNode[attribute.ariaReflection]) {
|
|
202
|
+
/*
|
|
203
|
+
Remove any existing polyfill relationships that might have been created
|
|
204
|
+
before the ariaReflection property was available (initial connection can occur before the property
|
|
205
|
+
exists).
|
|
206
|
+
*/
|
|
207
|
+
attribute.polyfill(targetNode, null, attrName);
|
|
208
|
+
// Use Aria Reflection to manage relationships
|
|
209
|
+
targetNode[attribute.ariaReflection] = relatedNodes;
|
|
210
|
+
} else {
|
|
211
|
+
attribute.polyfill(targetNode, allRelatedNodes, attrName);
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
synchronizeAttrs(targetNode, {
|
|
215
|
+
[attrName]: [
|
|
216
|
+
...splitIds(relatedNodeIds),
|
|
217
|
+
...relatedNodes.map((n) => n.id),
|
|
218
|
+
].join(' '),
|
|
219
|
+
});
|
|
165
220
|
}
|
|
166
221
|
}
|
|
167
222
|
|
|
@@ -175,6 +230,26 @@ export default class AriaObserver {
|
|
|
175
230
|
});
|
|
176
231
|
}
|
|
177
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Observes the root element. The root element should contain
|
|
235
|
+
* the related node elements. By default, this is the template host's root node, but can be
|
|
236
|
+
* overridden where required.
|
|
237
|
+
*/
|
|
238
|
+
privateCreateMutationObserver() {
|
|
239
|
+
this.disconnect();
|
|
240
|
+
this.mo = new MutationObserver(() => {
|
|
241
|
+
if (!this.component.isConnected) {
|
|
242
|
+
return; // do nothing when the template is not connected
|
|
243
|
+
}
|
|
244
|
+
this.sync();
|
|
245
|
+
});
|
|
246
|
+
this.mo.observe(this.root, {
|
|
247
|
+
characterData: true,
|
|
248
|
+
childList: true,
|
|
249
|
+
subtree: true,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
178
253
|
privateExtractIds(elements) {
|
|
179
254
|
return elements
|
|
180
255
|
.map((el) => {
|
|
@@ -184,7 +259,10 @@ export default class AriaObserver {
|
|
|
184
259
|
}
|
|
185
260
|
|
|
186
261
|
privateUpdateLiveIds() {
|
|
187
|
-
const root =
|
|
262
|
+
const root =
|
|
263
|
+
this.template && this.template.host
|
|
264
|
+
? this.template.host.getRootNode()
|
|
265
|
+
: null;
|
|
188
266
|
|
|
189
267
|
// if not connected do nothing
|
|
190
268
|
if (!root) {
|
|
@@ -193,7 +271,7 @@ export default class AriaObserver {
|
|
|
193
271
|
for (const liveId in this.liveIds) {
|
|
194
272
|
if (Object.prototype.hasOwnProperty.call(this.liveIds, liveId)) {
|
|
195
273
|
const thisId = this.liveIds[liveId];
|
|
196
|
-
if (!thisId.elements
|
|
274
|
+
if (!thisId.elements || !thisId.elements.length) {
|
|
197
275
|
const splitRefIds = splitIds(liveId);
|
|
198
276
|
// element refs are cached
|
|
199
277
|
const refElements = [
|
|
@@ -213,13 +291,15 @@ export default class AriaObserver {
|
|
|
213
291
|
);
|
|
214
292
|
});
|
|
215
293
|
}
|
|
294
|
+
|
|
216
295
|
const newThisId = this.privateExtractCorrectElements(
|
|
217
296
|
thisId.refs,
|
|
218
297
|
thisId.elements
|
|
219
298
|
);
|
|
220
299
|
const newIds = this.privateExtractIds(newThisId);
|
|
221
|
-
|
|
222
|
-
if
|
|
300
|
+
|
|
301
|
+
// only fire callback if the value changed and the root node has been rendered
|
|
302
|
+
if (newIds.length && newIds !== thisId.ids) {
|
|
223
303
|
thisId.callback(newIds);
|
|
224
304
|
thisId.ids = newIds;
|
|
225
305
|
}
|
|
@@ -227,55 +307,6 @@ export default class AriaObserver {
|
|
|
227
307
|
}
|
|
228
308
|
}
|
|
229
309
|
|
|
230
|
-
privateUpdate(attrName) {
|
|
231
|
-
const { innerSelector } = this.state[attrName];
|
|
232
|
-
const elm = this.template.querySelector(innerSelector);
|
|
233
|
-
if (!elm) {
|
|
234
|
-
return; // nothing to update
|
|
235
|
-
}
|
|
236
|
-
let computedIds;
|
|
237
|
-
if (this.isNative) {
|
|
238
|
-
const { ids, content, placeholder } = this.state[attrName];
|
|
239
|
-
|
|
240
|
-
const newContent = extractContent(extractElements(this.root, ids));
|
|
241
|
-
if (content !== newContent) {
|
|
242
|
-
this.state[attrName].content = placeholder.textContent =
|
|
243
|
-
newContent;
|
|
244
|
-
}
|
|
245
|
-
if (!placeholder.parentNode) {
|
|
246
|
-
// create placeholder container at template root, if not already exist
|
|
247
|
-
if (!this.placeholderContainer) {
|
|
248
|
-
this.placeholderContainer = createPlaceholderContainer();
|
|
249
|
-
this.template.appendChild(this.placeholderContainer);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// inserting the placeholder once
|
|
253
|
-
this.placeholderContainer.appendChild(placeholder);
|
|
254
|
-
}
|
|
255
|
-
computedIds = placeholder.id;
|
|
256
|
-
} else {
|
|
257
|
-
computedIds = this.state[attrName].ids;
|
|
258
|
-
}
|
|
259
|
-
addAriaRefWhenNeeded(elm, attrName, computedIds);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
privateConnect() {
|
|
263
|
-
// caching root ref
|
|
264
|
-
this.root = this.template.host.getRootNode();
|
|
265
|
-
// creating the observer once
|
|
266
|
-
this.mo = new MutationObserver(() => {
|
|
267
|
-
if (!this.component.isConnected) {
|
|
268
|
-
return; // do nothing when the template is not connected
|
|
269
|
-
}
|
|
270
|
-
this.sync();
|
|
271
|
-
});
|
|
272
|
-
this.mo.observe(this.root, {
|
|
273
|
-
characterData: true,
|
|
274
|
-
childList: true,
|
|
275
|
-
subtree: true,
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
310
|
disconnect() {
|
|
280
311
|
// MutationObservers must be disconnected manually when using @lwc/synthetic-shadow
|
|
281
312
|
// https://lwc.dev/guide/composition#:~:text=memory%20leak
|