lightning-base-components 1.13.10-alpha → 1.14.4-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 +24 -0
- package/package.json +20 -4
- package/scopedImports/@salesforce-internal-core.appVersion.js +1 -1
- package/scopedImports/@salesforce-label-LightningDualListbox.movedOptionsPlural.js +1 -0
- package/scopedImports/@salesforce-label-LightningDualListbox.movedOptionsSingular.js +1 -0
- package/scopedImports/@salesforce-label-LightningErrorMessage.validitySelectAtleastOne.js +1 -0
- package/scopedImports/@salesforce-label-LightningMap.titleWithAddress.js +1 -0
- package/scopedImports/@salesforce-label-LightningModalBase.cancelandclose.js +1 -0
- package/src/lightning/ariaObserver/__component__/ariaObserver.spec.js +112 -0
- package/src/lightning/ariaObserver/__docs__/ariaObserver.md +142 -0
- package/src/lightning/{utilsPrivate/contentMutation.js → ariaObserver/ariaObserver.js} +60 -98
- package/src/lightning/buttonMenu/keyboard.js +0 -10
- package/src/lightning/card/card.html +6 -0
- package/src/lightning/checkboxGroup/checkboxGroup.html +2 -2
- package/src/lightning/checkboxGroup/checkboxGroup.js +6 -1
- package/src/lightning/colorPickerCustom/colorPickerCustom.js +20 -1
- package/src/lightning/datatable/__docs__/datatable.md +55 -0
- package/src/lightning/datatable/__examples__/basic/basic.html +1 -1
- package/src/lightning/datatable/columns-shared.js +1 -1
- package/src/lightning/datatable/datatable.js +98 -30
- package/src/lightning/datatable/errors.js +20 -9
- package/src/lightning/datatable/headerActions.js +77 -49
- package/src/lightning/datatable/infiniteLoading.js +100 -28
- package/src/lightning/datatable/inlineEdit.js +505 -379
- package/src/lightning/datatable/inlineEditShared.js +24 -0
- package/src/lightning/datatable/keyboard.js +162 -127
- package/src/lightning/datatable/renderManager.js +201 -133
- package/src/lightning/datatable/rowLevelActions.js +17 -13
- package/src/lightning/datatable/rowNumber.js +54 -20
- package/src/lightning/datatable/rowSelection.js +760 -0
- package/src/lightning/datatable/rowSelectionShared.js +79 -0
- package/src/lightning/datatable/rows.js +17 -6
- package/src/lightning/datatable/state.js +16 -2
- package/src/lightning/datatable/templates/div/div.css +4 -0
- package/src/lightning/datatable/templates/div/div.html +6 -0
- package/src/lightning/datatable/templates/table/table.html +5 -0
- package/src/lightning/datatable/utils.js +14 -0
- package/src/lightning/datatable/wrapText.js +77 -47
- package/src/lightning/dualListbox/dualListbox.html +1 -1
- package/src/lightning/dualListbox/dualListbox.js +42 -0
- package/src/lightning/formattedDateTime/__docs__/formattedDateTime.md +36 -3
- package/src/lightning/formattedDateTime/__examples__/datetime/datetime.html +2 -2
- package/src/lightning/formattedDateTime/__examples__/datetime/datetime.js +3 -1
- package/src/lightning/formattedDateTime/__examples__/time/time.html +1 -1
- package/src/lightning/formattedDateTime/__examples__/time/time.js +3 -1
- package/src/lightning/formattedDateTime/formattedDateTime.js +1 -0
- package/src/lightning/input/input.html +1 -5
- package/src/lightning/input/input.js +69 -48
- package/src/lightning/inputUtils/validity.js +12 -1
- package/src/lightning/pillContainer/__docs__/pillContainer.md +45 -1
- package/src/lightning/primitiveCellActions/primitiveCellActions.js +69 -12
- package/src/lightning/primitiveCellFactory/cellWithStandardLayout.html +13 -11
- package/src/lightning/primitiveCellFactory/primitiveCellFactory.js +13 -8
- package/src/lightning/primitiveDatatableIeditPanel/primitiveDatatableIeditPanel.html +17 -14
- package/src/lightning/primitiveDatatableIeditPanel/primitiveDatatableIeditPanel.js +167 -98
- package/src/lightning/primitiveDatatableIeditTypeFactory/primitiveDatatableIeditTypeFactory.js +94 -69
- package/src/lightning/primitiveDatatableStatusBar/primitiveDatatableStatusBar.html +4 -4
- package/src/lightning/primitiveDatatableStatusBar/primitiveDatatableStatusBar.js +4 -4
- package/src/lightning/primitiveHeaderActions/primitiveHeaderActions.js +99 -37
- package/src/lightning/progressIndicator/progressIndicator.js +1 -1
- package/src/lightning/progressStep/progressStep.js +30 -22
- package/src/lightning/staticMap/staticMap.html +1 -0
- package/src/lightning/staticMap/staticMap.js +39 -2
- package/src/lightning/utils/classSet.js +4 -1
- package/src/lightning/utilsPrivate/utilsPrivate.js +12 -1
- package/scopedImports/@salesforce-label-LightningModalBase.close.js +0 -1
- package/src/lightning/datatable/inlineEdit-shared.js +0 -14
- package/src/lightning/datatable/selector-shared.js +0 -38
- package/src/lightning/datatable/selector.js +0 -527
package/metadata/raptor.json
CHANGED
|
@@ -1352,9 +1352,11 @@
|
|
|
1352
1352
|
"industriesCibApi": {},
|
|
1353
1353
|
"industriesDecisionMatrixDesignerApi": {},
|
|
1354
1354
|
"industriesExplainabilityApi": {},
|
|
1355
|
+
"industriesHealthcloudHpiApi": {},
|
|
1355
1356
|
"industriesIdentityVerificationApi": {},
|
|
1356
1357
|
"industriesInterestTaggingApi": {},
|
|
1357
1358
|
"industriesLoyaltyEngineApi": {},
|
|
1359
|
+
"industriesPublicSectorApi": {},
|
|
1358
1360
|
"industriesRcgTenantmanagementApi": {},
|
|
1359
1361
|
"industriesRuleBuilderApi": {},
|
|
1360
1362
|
"industriesSustainabilityRecalculateApi": {},
|
|
@@ -3412,6 +3414,28 @@
|
|
|
3412
3414
|
"uiRelatedListApi": {
|
|
3413
3415
|
"minVersion": "53.0"
|
|
3414
3416
|
},
|
|
3417
|
+
"unstable_analyticsDataServiceApi": {},
|
|
3418
|
+
"unstable_analyticsWaveApi": {},
|
|
3419
|
+
"unstable_cmsAuthoringApi": {},
|
|
3420
|
+
"unstable_cmsDeliveryApi": {},
|
|
3421
|
+
"unstable_cmsTypeApi": {},
|
|
3422
|
+
"unstable_commerceApi": {},
|
|
3423
|
+
"unstable_communityNavigationMenuApi": {},
|
|
3424
|
+
"unstable_communityRecordSeoPropertiesApi": {},
|
|
3425
|
+
"unstable_communitySitesSearchApi": {},
|
|
3426
|
+
"unstable_experienceMarketingIntegrationApi": {},
|
|
3427
|
+
"unstable_industriesCibApi": {},
|
|
3428
|
+
"unstable_industriesDecisionMatrixDesignerApi": {},
|
|
3429
|
+
"unstable_industriesExplainabilityApi": {},
|
|
3430
|
+
"unstable_industriesHealthcloudHpiApi": {},
|
|
3431
|
+
"unstable_industriesInterestTaggingApi": {},
|
|
3432
|
+
"unstable_industriesLoyaltyEngineApi": {},
|
|
3433
|
+
"unstable_industriesPublicSectorApi": {},
|
|
3434
|
+
"unstable_industriesRcgTenantmanagementApi": {},
|
|
3435
|
+
"unstable_industriesRuleBuilderApi": {},
|
|
3436
|
+
"unstable_platformAdminSuccessGuidanceApi": {},
|
|
3437
|
+
"unstable_platformInteractionOrchestratorApi": {},
|
|
3438
|
+
"unstable_platformScaleCenterApi": {},
|
|
3415
3439
|
"unstable_uiActionsApi": {},
|
|
3416
3440
|
"unstable_uiAppsApi": {},
|
|
3417
3441
|
"unstable_uiDuplicatesApi": {},
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightning-base-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.4-alpha",
|
|
4
4
|
"engines": {
|
|
5
|
-
"node": ">=
|
|
5
|
+
"node": ">=14.16.0"
|
|
6
6
|
},
|
|
7
7
|
"files": [
|
|
8
8
|
"external",
|
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
"name": "@salesforce/label/LightningErrorMessage.validityTooShort",
|
|
58
58
|
"path": "scopedImports/@salesforce-label-LightningErrorMessage.validityTooShort.js"
|
|
59
59
|
},
|
|
60
|
+
{
|
|
61
|
+
"name": "@salesforce/label/LightningErrorMessage.validitySelectAtleastOne",
|
|
62
|
+
"path": "scopedImports/@salesforce-label-LightningErrorMessage.validitySelectAtleastOne.js"
|
|
63
|
+
},
|
|
60
64
|
{
|
|
61
65
|
"name": "@salesforce/label/LightningCarousel.tabString",
|
|
62
66
|
"path": "scopedImports/@salesforce-label-LightningCarousel.tabString.js"
|
|
@@ -369,6 +373,14 @@
|
|
|
369
373
|
"name": "@salesforce/label/LightningDualListbox.requiredOptionError",
|
|
370
374
|
"path": "scopedImports/@salesforce-label-LightningDualListbox.requiredOptionError.js"
|
|
371
375
|
},
|
|
376
|
+
{
|
|
377
|
+
"name": "@salesforce/label/LightningDualListbox.movedOptionsSingular",
|
|
378
|
+
"path": "scopedImports/@salesforce-label-LightningDualListbox.movedOptionsSingular.js"
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
"name": "@salesforce/label/LightningDualListbox.movedOptionsPlural",
|
|
382
|
+
"path": "scopedImports/@salesforce-label-LightningDualListbox.movedOptionsPlural.js"
|
|
383
|
+
},
|
|
372
384
|
{
|
|
373
385
|
"name": "@salesforce/label/LightningProgressIndicator.stageComplete",
|
|
374
386
|
"path": "scopedImports/@salesforce-label-LightningProgressIndicator.stageComplete.js"
|
|
@@ -861,6 +873,10 @@
|
|
|
861
873
|
"name": "@salesforce/label/LightningMap.iframeTitle",
|
|
862
874
|
"path": "scopedImports/@salesforce-label-LightningMap.iframeTitle.js"
|
|
863
875
|
},
|
|
876
|
+
{
|
|
877
|
+
"name": "@salesforce/label/LightningMap.titleWithAddress",
|
|
878
|
+
"path": "scopedImports/@salesforce-label-LightningMap.titleWithAddress.js"
|
|
879
|
+
},
|
|
864
880
|
{
|
|
865
881
|
"name": "@salesforce/label/LightningPrimitiveCoordinate.selected",
|
|
866
882
|
"path": "scopedImports/@salesforce-label-LightningPrimitiveCoordinate.selected.js"
|
|
@@ -942,8 +958,8 @@
|
|
|
942
958
|
"path": "scopedImports/@salesforce-label-LightningRating.nStars.js"
|
|
943
959
|
},
|
|
944
960
|
{
|
|
945
|
-
"name": "@salesforce/label/LightningModalBase.
|
|
946
|
-
"path": "scopedImports/@salesforce-label-LightningModalBase.
|
|
961
|
+
"name": "@salesforce/label/LightningModalBase.cancelandclose",
|
|
962
|
+
"path": "scopedImports/@salesforce-label-LightningModalBase.cancelandclose.js"
|
|
947
963
|
},
|
|
948
964
|
{
|
|
949
965
|
"name": "@salesforce/label/LightningModalBase.waitstate",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '
|
|
1
|
+
export default '236';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default 'Items {0} moved to the list {1}';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default 'Item {0} moved to the list {1}';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default 'You must select at least one choice from this set.';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default 'Map of {0}';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default 'Cancel and close';
|
|
@@ -0,0 +1,112 @@
|
|
|
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 work with label ids appearing in the opposite of document order', async () => {
|
|
51
|
+
const container = createTestElement();
|
|
52
|
+
container.updateAriaLabelledby('alt-label-id id-label');
|
|
53
|
+
await Promise.resolve();
|
|
54
|
+
|
|
55
|
+
const testElement = container.testElement;
|
|
56
|
+
expect(testElement.labelContent).toEqual('Bar\nFoo');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should update the internal label content when external content changes', async () => {
|
|
60
|
+
const container = createTestElement();
|
|
61
|
+
container.updateLabelContent();
|
|
62
|
+
|
|
63
|
+
// wait for component rehydration
|
|
64
|
+
await Promise.resolve();
|
|
65
|
+
// wait for mutation observer callback
|
|
66
|
+
await Promise.resolve();
|
|
67
|
+
|
|
68
|
+
const testElement = container.testElement;
|
|
69
|
+
expect(container.labelContent).toEqual(
|
|
70
|
+
testElement.labelContent
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
} else {
|
|
75
|
+
describe('synthetic shadow', () => {
|
|
76
|
+
it('should set aria-labelledby to be the external label id', () => {
|
|
77
|
+
const container = createTestElement();
|
|
78
|
+
const testElement = container.testElement;
|
|
79
|
+
|
|
80
|
+
expect(container.labelId).toEqual(testElement.labelId);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should react to label id changes', async () => {
|
|
84
|
+
const container = createTestElement();
|
|
85
|
+
const oldLabelId = container.labelId;
|
|
86
|
+
|
|
87
|
+
container.updateAriaLabelledby('alt-label-id');
|
|
88
|
+
await Promise.resolve();
|
|
89
|
+
|
|
90
|
+
const testElement = container.testElement;
|
|
91
|
+
|
|
92
|
+
expect(container.labelId).not.toEqual(oldLabelId);
|
|
93
|
+
expect(container.labelId).toEqual(testElement.labelId);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should keep the label id unchanged when the label content changes', async () => {
|
|
97
|
+
const container = createTestElement();
|
|
98
|
+
container.updateLabelContent();
|
|
99
|
+
await Promise.resolve();
|
|
100
|
+
|
|
101
|
+
const testElement = container.testElement;
|
|
102
|
+
expect(container.labelId).toEqual(testElement.labelId);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should keep track of the live id', () => {
|
|
106
|
+
const container = createTestElement();
|
|
107
|
+
const testElement = container.testElement;
|
|
108
|
+
expect(testElement.labelId).toEqual(testElement.liveId);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
The `lightning/ariaObserver` module provides an easy way for users to write accessible component that works in both synthetic and native shadow.
|
|
2
|
+
|
|
3
|
+
## Aria ID referencing in native shadow
|
|
4
|
+
Use the` AriaObserver` library to write accessible component that works where `ariaLabelledBy` would break native shadow.
|
|
5
|
+
|
|
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
|
+
|
|
8
|
+
``` html
|
|
9
|
+
<template>
|
|
10
|
+
<input aria-labelledby={ariaLabelledBy}>
|
|
11
|
+
</template>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
class Foo extends LightningElement {
|
|
16
|
+
@api ariaLabelledBy;
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This example uses the `aria-labelledby` attribute to use the internal input as an external label in `c-foo`.
|
|
21
|
+
|
|
22
|
+
``` html
|
|
23
|
+
<span id="my-label">Input field</span>
|
|
24
|
+
<c-foo aria-labelledby="my-label"></c-foo>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The above example works fine in synthetic shadow, but in native shadow mode, the `aria-labelledby` ID reference is broken. The `input` element is isolated in its own shadow DOM, so the label with id `my-label` isn't in the same shadow boundary.
|
|
28
|
+
|
|
29
|
+
## Creating AriaObserver
|
|
30
|
+
|
|
31
|
+
To use `AriaObserver` in your component, first import it from `lightning/ariaObserver`. Then, instantiate the `AriaObserver` within your component.
|
|
32
|
+
|
|
33
|
+
The `AriaObserver` constructor takes one parameter:
|
|
34
|
+
- `cmpReference` The reference of the current component (`this`).
|
|
35
|
+
|
|
36
|
+
``` js
|
|
37
|
+
import AriaObserver from 'lightning/ariaObserver';
|
|
38
|
+
|
|
39
|
+
class Foo extends LightningElement {
|
|
40
|
+
constructor() {
|
|
41
|
+
super();
|
|
42
|
+
this.ariaObserver = new AriaObserver(this);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
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
|
+
- `targetSelector` The selector to the internal element where the aria attribute should be attached.
|
|
49
|
+
- `attribute` The name of the aria attribute. Two supported options: `aria-labelledby`and `aria-describedby`.
|
|
50
|
+
- `id` The ID of the external element. Alternatively, you can use `ids` for multiple IDs.
|
|
51
|
+
|
|
52
|
+
This example uses `connect(options)` to display an aria label for the internal `input` element.
|
|
53
|
+
``` js
|
|
54
|
+
@api
|
|
55
|
+
get ariaLabelledBy() {
|
|
56
|
+
return this._ariaLabelledBy;
|
|
57
|
+
}
|
|
58
|
+
set ariaLabelledBy(refs) {
|
|
59
|
+
this._ariaLabelledBy = refs;
|
|
60
|
+
|
|
61
|
+
this.ariaObserver.connect({
|
|
62
|
+
targetSelector: 'input',
|
|
63
|
+
attribute: 'aria-labelledby',
|
|
64
|
+
id: refs
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Then use the `sync()` method to synchronize the ID references when the template is re-rendered.
|
|
70
|
+
|
|
71
|
+
``` js
|
|
72
|
+
renderedCallback() {
|
|
73
|
+
this.ariaObserver.sync();
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Finally, disconnect the aria observer and free the resources at the end of the component lifecycle.
|
|
78
|
+
|
|
79
|
+
``` js
|
|
80
|
+
disconnectedCallback() {
|
|
81
|
+
if (this.ariaObserver) {
|
|
82
|
+
this.ariaObserver.disconnect();
|
|
83
|
+
this.ariaObserver = undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Here is all these steps combined into a complete example of a component using `AriaObserver`.
|
|
89
|
+
|
|
90
|
+
``` html
|
|
91
|
+
<template>
|
|
92
|
+
<!-- element where the aria attribute is attached -->
|
|
93
|
+
<input>
|
|
94
|
+
</template>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
``` js
|
|
98
|
+
import {api, LightningElement} from 'lwc';
|
|
99
|
+
import AriaObserver from 'lightning/ariaObserver';
|
|
100
|
+
|
|
101
|
+
export default class Foo extends LightningElement {
|
|
102
|
+
constructor() {
|
|
103
|
+
super();
|
|
104
|
+
this.ariaObserver = new AriaObserver(this);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_ariaLabelledBy = '';
|
|
108
|
+
|
|
109
|
+
@api
|
|
110
|
+
get ariaLabelledBy() {
|
|
111
|
+
return this._ariaLabelledBy;
|
|
112
|
+
}
|
|
113
|
+
set ariaLabelledBy(refs) {
|
|
114
|
+
this._ariaLabelledBy = refs;
|
|
115
|
+
|
|
116
|
+
/* Establish the connection between input and the external label */
|
|
117
|
+
this.ariaObserver.connect({
|
|
118
|
+
targetSelector: 'input',
|
|
119
|
+
attribute: 'aria-labelledby',
|
|
120
|
+
id: refs
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
renderedCallback() {
|
|
125
|
+
this.ariaObserver.sync();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
disconnectedCallback() {
|
|
129
|
+
if (this.ariaObserver) {
|
|
130
|
+
this.ariaObserver.disconnect();
|
|
131
|
+
this.ariaObserver = undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Limitations
|
|
138
|
+
`AriaObserver` only works with text-only aria ID references.
|
|
139
|
+
|
|
140
|
+
Supported attributes:
|
|
141
|
+
- `aria-labelledby`
|
|
142
|
+
- `aria-describedby`
|
|
@@ -1,50 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
guid,
|
|
3
|
+
synchronizeAttrs,
|
|
4
|
+
isNativeComponent,
|
|
5
|
+
} from 'lightning/utilsPrivate';
|
|
3
6
|
|
|
4
7
|
const CONTENT_SEPARATOR = '\n';
|
|
5
8
|
|
|
6
|
-
/**
|
|
7
|
-
<template>
|
|
8
|
-
<span lwc:dom="manual" class="visually-hidden"></span>
|
|
9
|
-
<input>
|
|
10
|
-
</template>
|
|
11
|
-
|
|
12
|
-
class Foo extends LightningElement {
|
|
13
|
-
constructor() {
|
|
14
|
-
super();
|
|
15
|
-
this.ariaObserver = new ContentMutation(this);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
connectedCallback() {
|
|
19
|
-
if (!this.ariaObserver) {
|
|
20
|
-
this.ariaObserver = new ContentMutation(this);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
disconnectedCallback() {
|
|
25
|
-
if (this.ariaObserver) {
|
|
26
|
-
this.ariaObserver.disconnect();
|
|
27
|
-
this.ariaObserver = undefined;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
@track ariaLabeledbyValue = '';
|
|
32
|
-
|
|
33
|
-
@api
|
|
34
|
-
get ariaLabeledby() {
|
|
35
|
-
return this.ariaLabeledbyValue; // whatever they set, is what they get back.
|
|
36
|
-
}
|
|
37
|
-
set ariaLabeledby(refs) {
|
|
38
|
-
this.ariaLabeledbyValue = refs;
|
|
39
|
-
this.ariaObserver.link('input', 'aria-labeledby', refs, 'span.visually-hidden');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
renderedCallback() {
|
|
43
|
-
this.ariaObserver.sync();
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
**/
|
|
47
|
-
|
|
48
9
|
function getAttr(elm, attr) {
|
|
49
10
|
if (elm.tagName.match(/lightning/i)) {
|
|
50
11
|
return elm[attr];
|
|
@@ -52,11 +13,13 @@ function getAttr(elm, attr) {
|
|
|
52
13
|
return elm.getAttribute(attr);
|
|
53
14
|
}
|
|
54
15
|
|
|
55
|
-
function extractElements(root,
|
|
56
|
-
if (typeof
|
|
16
|
+
function extractElements(root, ids) {
|
|
17
|
+
if (typeof ids !== 'string' || ids === '') {
|
|
57
18
|
return [];
|
|
58
19
|
}
|
|
59
|
-
|
|
20
|
+
// We must query the elements in the order of ids, so that
|
|
21
|
+
// the content will be extracted in the correct order.
|
|
22
|
+
return splitIds(ids).map((id) => root.querySelector(`#${id}`));
|
|
60
23
|
}
|
|
61
24
|
|
|
62
25
|
function extractContent(elements) {
|
|
@@ -95,11 +58,10 @@ function addAriaRefWhenNeeded(elm, attrName, computedIds) {
|
|
|
95
58
|
}
|
|
96
59
|
|
|
97
60
|
if (suffix.length !== 0) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
61
|
+
synchronizeAttrs(elm, {
|
|
62
|
+
[attrName]:
|
|
63
|
+
oldIds + (oldIds.length === 0 ? '' : ' ') + suffix.join(' '),
|
|
64
|
+
});
|
|
103
65
|
}
|
|
104
66
|
}
|
|
105
67
|
|
|
@@ -116,19 +78,28 @@ function removeAriaRefWhenPossible(elm, attrName, computedIds) {
|
|
|
116
78
|
newValues.push(newIds[i]);
|
|
117
79
|
}
|
|
118
80
|
}
|
|
119
|
-
|
|
81
|
+
|
|
82
|
+
synchronizeAttrs(elm, {
|
|
83
|
+
[attrName]: newValues.join(' '),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function createPlaceholderContainer() {
|
|
88
|
+
const container = document.createElement('span');
|
|
89
|
+
container.style.display = 'none';
|
|
90
|
+
container.setAttribute('placeholder-container', '');
|
|
91
|
+
return container;
|
|
120
92
|
}
|
|
121
93
|
|
|
122
|
-
export class
|
|
94
|
+
export default class AriaObserver {
|
|
123
95
|
constructor(component) {
|
|
124
96
|
this.component = component;
|
|
125
97
|
this.template = component.template;
|
|
126
|
-
this.isNative =
|
|
127
|
-
.toString()
|
|
128
|
-
.match(/\[native code\]/);
|
|
98
|
+
this.isNative = isNativeComponent(component);
|
|
129
99
|
this.state = {};
|
|
130
100
|
this.liveIds = {};
|
|
131
101
|
this.guid = guid();
|
|
102
|
+
this.placeholderContainer = null;
|
|
132
103
|
}
|
|
133
104
|
|
|
134
105
|
connectLiveIdRef(refs, callback) {
|
|
@@ -141,37 +112,33 @@ export class ContentMutation {
|
|
|
141
112
|
this.liveIds[refs] = liveId;
|
|
142
113
|
}
|
|
143
114
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
115
|
+
connect({ targetSelector, attribute, id, ids }) {
|
|
116
|
+
ids = ids || id;
|
|
117
|
+
|
|
118
|
+
this.state[attribute] = this.state[attribute] || {};
|
|
119
|
+
const attrState = this.state[attribute];
|
|
120
|
+
|
|
121
|
+
// note: we don't support linking to a different targetSelector
|
|
122
|
+
attrState.innerSelector = attrState.innerSelector || targetSelector;
|
|
123
|
+
|
|
124
|
+
// removing the old ids if possible before setting the new ones
|
|
125
|
+
if (!this.isNative && attrState.ids) {
|
|
126
|
+
const elm = this.template.querySelector(attrState.innerSelector);
|
|
127
|
+
if (elm) {
|
|
128
|
+
removeAriaRefWhenPossible(elm, attribute, attrState.ids);
|
|
156
129
|
}
|
|
157
|
-
} else {
|
|
158
|
-
attrState = this.state[attrName] = {
|
|
159
|
-
ids,
|
|
160
|
-
innerSelector,
|
|
161
|
-
placeholderContainerSelector,
|
|
162
|
-
};
|
|
163
130
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
.join(',');
|
|
131
|
+
|
|
132
|
+
attrState.ids = ids;
|
|
133
|
+
|
|
134
|
+
if (this.isNative && !attrState.placeholder) {
|
|
135
|
+
// create placeholder element for copied content
|
|
170
136
|
attrState.placeholder = document.createElement('span');
|
|
171
|
-
attrState.placeholder.id = `auto-link-${
|
|
137
|
+
attrState.placeholder.id = `auto-link-${attribute}-${this.guid}`;
|
|
172
138
|
}
|
|
139
|
+
|
|
173
140
|
if (this.component.isConnected) {
|
|
174
|
-
this.privateUpdate(
|
|
141
|
+
this.privateUpdate(attribute);
|
|
175
142
|
}
|
|
176
143
|
}
|
|
177
144
|
|
|
@@ -238,27 +205,22 @@ export class ContentMutation {
|
|
|
238
205
|
}
|
|
239
206
|
let computedIds;
|
|
240
207
|
if (this.isNative) {
|
|
241
|
-
const {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
placeholder,
|
|
245
|
-
placeholderContainerSelector,
|
|
246
|
-
} = this.state[attrName];
|
|
247
|
-
const newContent = extractContent(
|
|
248
|
-
extractElements(this.root, outerSelector)
|
|
249
|
-
);
|
|
208
|
+
const { ids, content, placeholder } = this.state[attrName];
|
|
209
|
+
|
|
210
|
+
const newContent = extractContent(extractElements(this.root, ids));
|
|
250
211
|
if (content !== newContent) {
|
|
251
212
|
this.state[attrName].content = placeholder.textContent =
|
|
252
213
|
newContent;
|
|
253
214
|
}
|
|
254
215
|
if (!placeholder.parentNode) {
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (container) {
|
|
260
|
-
container.appendChild(placeholder);
|
|
216
|
+
// create placeholder container at template root, if not already exist
|
|
217
|
+
if (!this.placeholderContainer) {
|
|
218
|
+
this.placeholderContainer = createPlaceholderContainer();
|
|
219
|
+
this.template.appendChild(this.placeholderContainer);
|
|
261
220
|
}
|
|
221
|
+
|
|
222
|
+
// inserting the placeholder once
|
|
223
|
+
this.placeholderContainer.appendChild(placeholder);
|
|
262
224
|
}
|
|
263
225
|
computedIds = placeholder.id;
|
|
264
226
|
} else {
|
|
@@ -113,16 +113,6 @@ export function handleKeyDownOnMenuTrigger(event, menuInterface) {
|
|
|
113
113
|
menuInterface.toggleMenuVisibility();
|
|
114
114
|
}
|
|
115
115
|
break;
|
|
116
|
-
// W3: Home and End: If arrow key wrapping is not supported, move focus to first and last item
|
|
117
|
-
// Note: We do support wrapping, but it doesn't hurt to support these keys anyway.
|
|
118
|
-
case keyCodes.home:
|
|
119
|
-
preventDefaultAndStopPropagation(event);
|
|
120
|
-
menuInterface.focusOnIndex(0);
|
|
121
|
-
break;
|
|
122
|
-
case keyCodes.end:
|
|
123
|
-
preventDefaultAndStopPropagation(event);
|
|
124
|
-
menuInterface.focusOnIndex(menuInterface.getTotalMenuItems() - 1);
|
|
125
|
-
break;
|
|
126
116
|
// W3: Escape: Close the menu and return focus to the element or context, e.g., menu button or
|
|
127
117
|
// parent menu item, from which the menu was opened
|
|
128
118
|
case keyCodes.escape:
|
|
@@ -40,5 +40,11 @@
|
|
|
40
40
|
<slot name="footer"></slot>
|
|
41
41
|
</div>
|
|
42
42
|
</template>
|
|
43
|
+
<!-- This dummy placeholder slot is required because
|
|
44
|
+
a slot's presence is verified by assignedElements count
|
|
45
|
+
and not whether the slot itself is present or not -->
|
|
46
|
+
<template if:false={showFooter}>
|
|
47
|
+
<slot name="footer" class="slds-hide"></slot>
|
|
48
|
+
</template>
|
|
43
49
|
</article>
|
|
44
50
|
</template>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<fieldset aria-
|
|
2
|
+
<fieldset aria-describedby={computearaiDescriedBy}>
|
|
3
3
|
|
|
4
4
|
<legend class={computedLegendClass}>
|
|
5
5
|
<template if:true={required}>
|
|
@@ -32,6 +32,6 @@
|
|
|
32
32
|
<template if:true={_helpMessage}>
|
|
33
33
|
<div id="helptext" data-helptext class="slds-form-element__help">{_helpMessage}</div>
|
|
34
34
|
</template>
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
</fieldset>
|
|
37
37
|
</template>
|
|
@@ -295,7 +295,7 @@ export default class LightningCheckboxGroup extends LightningElement {
|
|
|
295
295
|
get _constraint() {
|
|
296
296
|
if (!this._constraintApi) {
|
|
297
297
|
this._constraintApi = new FieldConstraintApi(() => this, {
|
|
298
|
-
|
|
298
|
+
validitySelectAtleastOneValue: () =>
|
|
299
299
|
!this.disabled && this.required && this.value.length === 0,
|
|
300
300
|
});
|
|
301
301
|
}
|
|
@@ -313,4 +313,9 @@ export default class LightningCheckboxGroup extends LightningElement {
|
|
|
313
313
|
})
|
|
314
314
|
.toString();
|
|
315
315
|
}
|
|
316
|
+
|
|
317
|
+
computearaiDescriedBy() {
|
|
318
|
+
const helpMessage = this.template.querySelector('[data-helptext]');
|
|
319
|
+
return getRealDOMId(helpMessage);
|
|
320
|
+
}
|
|
316
321
|
}
|