@justeattakeaway/pie-list 0.4.0 → 0.5.0
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/README.md +108 -7
- package/custom-elements.json +43 -5
- package/dist/{PieElement-CGtUo9tK.js → PieElement-DQQMEr70.js} +1 -1
- package/dist/index-BH08tWcm.js +339 -0
- package/dist/index.js +1 -1
- package/dist/pie-list-item/defs.d.ts +17 -1
- package/dist/pie-list-item/defs.d.ts.map +1 -1
- package/dist/pie-list-item/index.d.ts +24 -14
- package/dist/pie-list-item/index.d.ts.map +1 -1
- package/dist/pie-list-item/index.js +4 -3
- package/dist/pie-list-item/react.js +2 -2
- package/package.json +2 -2
- package/src/pie-list-item/defs.ts +18 -2
- package/src/pie-list-item/index.ts +53 -18
- package/src/pie-list-item/list-item.scss +60 -9
- package/dist/index-BMp2PPeH.js +0 -307
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { ComponentDefaultProps } from '@justeattakeaway/pie-webc-core';
|
|
2
|
-
|
|
2
|
+
type AriaProps = {
|
|
3
|
+
button?: {
|
|
4
|
+
haspopup?: 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog' | 'true';
|
|
5
|
+
};
|
|
6
|
+
};
|
|
7
|
+
export declare const interactionTypes: readonly ["none", "radio", "checkbox", "switch", "link", "button"];
|
|
3
8
|
export type InteractionType = typeof interactionTypes[number];
|
|
4
9
|
export interface ListItemProps {
|
|
5
10
|
/**
|
|
@@ -42,6 +47,10 @@ export interface ListItemProps {
|
|
|
42
47
|
* - `switch` - hosts a `pie-switch`; there is no group, so the item stays `role="listitem"`.
|
|
43
48
|
* - `link` - the whole row is a single navigation link. Slot an empty `<a slot="link" href="...">`;
|
|
44
49
|
* it is stretched over the entire row as the clickable target and named from the item's text.
|
|
50
|
+
* - `button` - the whole row is a single button (for an in-page action rather than navigation).
|
|
51
|
+
* The item renders the button for you (no slotting); it is stretched invisibly over the entire
|
|
52
|
+
* row, named from the item's text, and dispatches a native `click` (pointer and keyboard).
|
|
53
|
+
* Listen for `click` on the `pie-list-item`. It is not a form control.
|
|
45
54
|
*
|
|
46
55
|
* Set this on each interactive row. When radio/checkbox rows sit inside a `pie-radio-group` /
|
|
47
56
|
* `pie-checkbox-group`, the group lays them out as a divided list automatically; this prop
|
|
@@ -55,7 +64,14 @@ export interface ListItemProps {
|
|
|
55
64
|
* (static) item.
|
|
56
65
|
*/
|
|
57
66
|
disabled?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Additional ARIA properties that the item cannot derive from its text props. `button.haspopup`
|
|
69
|
+
* applies when `interactionType="button"`: its value is forwarded to the internal `<button>`.
|
|
70
|
+
* Set it when the button row triggers a popup such as a dialog or menu.
|
|
71
|
+
*/
|
|
72
|
+
aria?: AriaProps;
|
|
58
73
|
}
|
|
59
74
|
export type DefaultProps = ComponentDefaultProps<ListItemProps, keyof Omit<ListItemProps, 'primaryText' | 'secondaryText' | 'metaText'>>;
|
|
60
75
|
export declare const defaultProps: DefaultProps;
|
|
76
|
+
export {};
|
|
61
77
|
//# sourceMappingURL=defs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defs.d.ts","sourceRoot":"","sources":["../../src/pie-list-item/defs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"defs.d.ts","sourceRoot":"","sources":["../../src/pie-list-item/defs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAE5E,KAAK,SAAS,GAAG;IACb,MAAM,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;KACvE,CAAC;CACL,CAAC;AAEF,eAAO,MAAM,gBAAgB,oEAAqE,CAAC;AAEnG,MAAM,MAAM,eAAe,GAAG,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAE9D,MAAM,WAAW,aAAa;IAC1B;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IAEnB;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,eAAe,CAAC,EAAE,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAA;IAEjD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;CACnB;AAED,MAAM,MAAM,YAAY,GAAG,qBAAqB,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC,aAAa,EAAE,aAAa,GAAG,eAAe,GAAG,UAAU,CAAC,CAAC,CAAC;AAEzI,eAAO,MAAM,YAAY,EAAE,YAM1B,CAAC"}
|
|
@@ -15,8 +15,9 @@ export declare class PieListItem extends PieElement implements ListItemProps {
|
|
|
15
15
|
isCompact: boolean;
|
|
16
16
|
isBold: boolean;
|
|
17
17
|
hasMedia: boolean;
|
|
18
|
-
interactionType: "checkbox" | "link" | "none" | "radio" | "switch";
|
|
18
|
+
interactionType: "button" | "checkbox" | "link" | "none" | "radio" | "switch";
|
|
19
19
|
disabled: boolean;
|
|
20
|
+
aria: ListItemProps['aria'];
|
|
20
21
|
private _parentDisabled;
|
|
21
22
|
private _ariaProvider;
|
|
22
23
|
private _abortController;
|
|
@@ -26,6 +27,8 @@ export declare class PieListItem extends PieElement implements ListItemProps {
|
|
|
26
27
|
private _consumerDescribedLink;
|
|
27
28
|
private get _isSelectable();
|
|
28
29
|
private get _isLinkRow();
|
|
30
|
+
private get _isButtonRow();
|
|
31
|
+
private get _isOverlayRow();
|
|
29
32
|
private get _providesAria();
|
|
30
33
|
private get _ownedByGroup();
|
|
31
34
|
private get _isDisabled();
|
|
@@ -35,18 +38,16 @@ export declare class PieListItem extends PieElement implements ListItemProps {
|
|
|
35
38
|
protected updated(): void;
|
|
36
39
|
/**
|
|
37
40
|
* Sets the item's role from `interactionType`: `presentation` for radio/checkbox (so the group
|
|
38
|
-
* owns those controls directly), otherwise `listitem` (static items, switches
|
|
39
|
-
* set explicitly by the consumer is left untouched, even if it is added or
|
|
40
|
-
* after the element connects.
|
|
41
|
+
* owns those controls directly), otherwise `listitem` (static items, switches, links and
|
|
42
|
+
* buttons). A role set explicitly by the consumer is left untouched, even if it is added or
|
|
43
|
+
* removed dynamically after the element connects.
|
|
41
44
|
*/
|
|
42
45
|
private _applyRole;
|
|
43
46
|
/**
|
|
44
|
-
* The accessible name and description this item provides to its
|
|
45
|
-
* its text.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* `aria-label` there would be invalid). Context reaches across the shadow boundary that
|
|
49
|
-
* `aria-labelledby` cannot.
|
|
47
|
+
* The accessible name and description this item provides to its interactive element, stitched
|
|
48
|
+
* from its text. Selection controls consume it via `ariaContext` (each applying it to the
|
|
49
|
+
* element that carries its role); the action button of a `button` row takes it directly as
|
|
50
|
+
* `aria-label`/`aria-description` in the template; a link anchor takes it via `_applyLinkAria`.
|
|
50
51
|
*/
|
|
51
52
|
private get _providedAria();
|
|
52
53
|
/**
|
|
@@ -55,11 +56,11 @@ export declare class PieListItem extends PieElement implements ListItemProps {
|
|
|
55
56
|
*/
|
|
56
57
|
private _handleHostClick;
|
|
57
58
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
59
|
+
* On a link row, names the slotted (empty) anchor from the item's own text: the parent owns the
|
|
60
|
+
* child's aria. The anchor is plain light-DOM, so it cannot reference the shadow-DOM text via
|
|
61
|
+
* `aria-labelledby`; and it is not a PIE control that consumes `ariaContext`, so we set the
|
|
61
62
|
* attributes imperatively. The visible text is `aria-hidden` in the template, so nothing is
|
|
62
|
-
* announced twice.
|
|
63
|
+
* announced twice. (A `button` row names its own rendered element in the template instead.)
|
|
63
64
|
*
|
|
64
65
|
* A consumer-provided name/description always wins: we capture (once) whether the anchor already
|
|
65
66
|
* carries `aria-label`/`aria-labelledby` or `aria-description`/`aria-describedby` and, if so,
|
|
@@ -68,6 +69,15 @@ export declare class PieListItem extends PieElement implements ListItemProps {
|
|
|
68
69
|
private _applyLinkAria;
|
|
69
70
|
_renderSecondaryText(): import('lit-html').TemplateResult<1> | typeof nothing;
|
|
70
71
|
_renderTrailingContent(): import('lit-html').TemplateResult<1>;
|
|
72
|
+
/**
|
|
73
|
+
* The interactive target for a `button` row: an empty, native `<button>` stretched invisibly
|
|
74
|
+
* over the whole row (see the SCSS). The item renders and owns it - rather than the consumer
|
|
75
|
+
* slotting one - so it can name it from its own text and keep its attributes private (it is just
|
|
76
|
+
* an action box, not a configurable control). Being a real button, activation is native: pointer
|
|
77
|
+
* clicks and keyboard (Enter/Space) both fire a `click` that bubbles out, so consumers listen for
|
|
78
|
+
* `click` on the `pie-list-item`. It is `type="button"`, so it never submits a form.
|
|
79
|
+
*/
|
|
80
|
+
_renderActionButton(): import('lit-html').TemplateResult<1>;
|
|
71
81
|
render(): import('lit-html').TemplateResult<1>;
|
|
72
82
|
static styles: import('lit').CSSResult;
|
|
73
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pie-list-item/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACa,OAAO,EAC1B,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pie-list-item/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACa,OAAO,EAC1B,MAAM,KAAK,CAAC;AAYb,OAAO,EAAE,UAAU,EAAE,MAAM,yDAAyD,CAAC;AACrF,OAAO,EAAE,KAAK,aAAa,EAAkC,MAAM,QAAQ,CAAC;AAG5E,QAAA,MAAM,iBAAiB,kBAAkB,CAAC;AAE1C;;;;;GAKG;AACH,qBACa,WAAY,SAAQ,UAAW,YAAW,aAAa;IAE5D,WAAW,EAAG,aAAa,CAAC,aAAa,CAAC,CAAC;IAG3C,aAAa,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAG9C,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAGpC,SAAS,UAA0B;IAGnC,MAAM,UAAuB;IAG7B,QAAQ,UAAyB;IAIjC,eAAe,+DAAgC;IAG/C,QAAQ,UAAyB;IAGjC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAMhC,OAAO,CAAC,eAAe,CAAS;IAYhC,OAAO,CAAC,aAAa,CAA8E;IAEnG,OAAO,CAAC,gBAAgB,CAAmB;IAI3C,OAAO,CAAC,YAAY,CAAuB;IAI3C,OAAO,CAAC,iBAAiB,CAAS;IAElC,OAAO,CAAC,kBAAkB,CAAS;IAEnC,OAAO,CAAC,sBAAsB,CAAS;IAKvC,OAAO,KAAK,aAAa,GAIxB;IAED,OAAO,KAAK,UAAU,GAErB;IAED,OAAO,KAAK,YAAY,GAEvB;IAKD,OAAO,KAAK,aAAa,GAExB;IAID,OAAO,KAAK,aAAa,GAExB;IAKD,OAAO,KAAK,aAAa,GAExB;IAKD,OAAO,KAAK,WAAW,GAEtB;IAID,OAAO,KAAK,QAAQ,GAEnB;IAED,iBAAiB;IAOjB,oBAAoB;IAKpB,SAAS,CAAC,OAAO;IAMjB;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAUlB;;;;;OAKG;IACH,OAAO,KAAK,aAAa,GAMxB;IAED;;;OAGG;IACH,OAAO,CAAC,gBAAgB,CAQtB;IAEF;;;;;;;;;;OAUG;IACH,OAAO,CAAC,cAAc;IA8CtB,oBAAoB;IAUpB,sBAAsB;IAStB;;;;;;;OAOG;IACH,mBAAmB;IAYnB,MAAM;IAgCN,MAAM,CAAC,MAAM,0BAAqB;CACrC;AAED,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC;KACpC;CACJ"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import "lit";
|
|
2
2
|
import "lit/decorators.js";
|
|
3
3
|
import "lit/directives/class-map.js";
|
|
4
|
-
import
|
|
4
|
+
import "lit/directives/if-defined.js";
|
|
5
|
+
import { P } from "../index-BH08tWcm.js";
|
|
5
6
|
import "@justeattakeaway/pie-webc-core";
|
|
6
|
-
import "../PieElement-
|
|
7
|
+
import "../PieElement-DQQMEr70.js";
|
|
7
8
|
export {
|
|
8
|
-
|
|
9
|
+
P as PieListItem
|
|
9
10
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as e from "react";
|
|
2
2
|
import { createComponent as t } from "@lit/react";
|
|
3
|
-
import { P as i } from "../index-
|
|
4
|
-
import { d as n, i as c } from "../index-
|
|
3
|
+
import { P as i } from "../index-BH08tWcm.js";
|
|
4
|
+
import { d as n, i as c } from "../index-BH08tWcm.js";
|
|
5
5
|
const s = t({
|
|
6
6
|
displayName: "PieListItem",
|
|
7
7
|
elementClass: i,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justeattakeaway/pie-list",
|
|
3
3
|
"description": "PIE Design System List built using Web Components",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@justeattakeaway/pie-monorepo-utils": "0.9.8"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@justeattakeaway/pie-webc-core": "16.1.
|
|
45
|
+
"@justeattakeaway/pie-webc-core": "16.1.1",
|
|
46
46
|
"@lit/context": "1.1.6"
|
|
47
47
|
},
|
|
48
48
|
"customElements": "custom-elements.json",
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { type ComponentDefaultProps } from '@justeattakeaway/pie-webc-core';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
type AriaProps = {
|
|
4
|
+
button?: {
|
|
5
|
+
haspopup?: 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog' | 'true';
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const interactionTypes = ['none', 'radio', 'checkbox', 'switch', 'link', 'button'] as const;
|
|
5
10
|
|
|
6
11
|
export type InteractionType = typeof interactionTypes[number];
|
|
7
12
|
|
|
@@ -50,6 +55,10 @@ export interface ListItemProps {
|
|
|
50
55
|
* - `switch` - hosts a `pie-switch`; there is no group, so the item stays `role="listitem"`.
|
|
51
56
|
* - `link` - the whole row is a single navigation link. Slot an empty `<a slot="link" href="...">`;
|
|
52
57
|
* it is stretched over the entire row as the clickable target and named from the item's text.
|
|
58
|
+
* - `button` - the whole row is a single button (for an in-page action rather than navigation).
|
|
59
|
+
* The item renders the button for you (no slotting); it is stretched invisibly over the entire
|
|
60
|
+
* row, named from the item's text, and dispatches a native `click` (pointer and keyboard).
|
|
61
|
+
* Listen for `click` on the `pie-list-item`. It is not a form control.
|
|
53
62
|
*
|
|
54
63
|
* Set this on each interactive row. When radio/checkbox rows sit inside a `pie-radio-group` /
|
|
55
64
|
* `pie-checkbox-group`, the group lays them out as a divided list automatically; this prop
|
|
@@ -64,6 +73,13 @@ export interface ListItemProps {
|
|
|
64
73
|
* (static) item.
|
|
65
74
|
*/
|
|
66
75
|
disabled?: boolean
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Additional ARIA properties that the item cannot derive from its text props. `button.haspopup`
|
|
79
|
+
* applies when `interactionType="button"`: its value is forwarded to the internal `<button>`.
|
|
80
|
+
* Set it when the button row triggers a popup such as a dialog or menu.
|
|
81
|
+
*/
|
|
82
|
+
aria?: AriaProps
|
|
67
83
|
}
|
|
68
84
|
|
|
69
85
|
export type DefaultProps = ComponentDefaultProps<ListItemProps, keyof Omit<ListItemProps, 'primaryText' | 'secondaryText' | 'metaText'>>;
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
} from 'lit';
|
|
4
4
|
import { property, state } from 'lit/decorators.js';
|
|
5
5
|
import { classMap } from 'lit/directives/class-map.js';
|
|
6
|
+
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
6
7
|
import { consume, ContextProvider } from '@lit/context';
|
|
7
8
|
import {
|
|
8
9
|
safeCustomElement,
|
|
@@ -50,6 +51,9 @@ export class PieListItem extends PieElement implements ListItemProps {
|
|
|
50
51
|
@property({ type: Boolean })
|
|
51
52
|
disabled = defaultProps.disabled;
|
|
52
53
|
|
|
54
|
+
@property({ type: Object })
|
|
55
|
+
aria: ListItemProps['aria'];
|
|
56
|
+
|
|
53
57
|
// Whether a disabling ancestor (e.g. `pie-radio-group`) has provided its disabled state.
|
|
54
58
|
// Defaults to false when there is no provider (a standalone item or a static list).
|
|
55
59
|
@consume({ context: parentDisabledContext, subscribe: true })
|
|
@@ -83,8 +87,8 @@ export class PieListItem extends PieElement implements ListItemProps {
|
|
|
83
87
|
private _consumerDescribedLink = false;
|
|
84
88
|
|
|
85
89
|
// A selection row hosts a slotted control (radio/checkbox/switch) and makes the whole row a
|
|
86
|
-
// toggle target. `link`
|
|
87
|
-
// selection
|
|
90
|
+
// toggle target. `link`/`button` are interactive too, but they are single navigation/action
|
|
91
|
+
// targets rather than selection controls, so they are deliberately excluded here.
|
|
88
92
|
private get _isSelectable (): boolean {
|
|
89
93
|
return this.interactionType === 'radio' ||
|
|
90
94
|
this.interactionType === 'checkbox' ||
|
|
@@ -95,10 +99,21 @@ export class PieListItem extends PieElement implements ListItemProps {
|
|
|
95
99
|
return this.interactionType === 'link';
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
private get _isButtonRow (): boolean {
|
|
103
|
+
return this.interactionType === 'button';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// A link and a button row share the same visual mechanic: an empty element stretched over the
|
|
107
|
+
// whole row as the interactive target, named by the item from its own text. They differ in who
|
|
108
|
+
// owns that element - a link is a consumer-slotted anchor, a button is one the item renders.
|
|
109
|
+
private get _isOverlayRow (): boolean {
|
|
110
|
+
return this._isLinkRow || this._isButtonRow;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// True when the item lends its text as an interactive element's accessible name/description:
|
|
114
|
+
// selectable rows (radio/checkbox/switch) and overlay rows (link anchor / action button).
|
|
100
115
|
private get _providesAria (): boolean {
|
|
101
|
-
return this._isSelectable || this.
|
|
116
|
+
return this._isSelectable || this._isOverlayRow;
|
|
102
117
|
}
|
|
103
118
|
|
|
104
119
|
// radio/checkbox are owned by a selection group, which is why the item becomes `presentation`
|
|
@@ -141,9 +156,9 @@ export class PieListItem extends PieElement implements ListItemProps {
|
|
|
141
156
|
|
|
142
157
|
/**
|
|
143
158
|
* Sets the item's role from `interactionType`: `presentation` for radio/checkbox (so the group
|
|
144
|
-
* owns those controls directly), otherwise `listitem` (static items, switches
|
|
145
|
-
* set explicitly by the consumer is left untouched, even if it is added or
|
|
146
|
-
* after the element connects.
|
|
159
|
+
* owns those controls directly), otherwise `listitem` (static items, switches, links and
|
|
160
|
+
* buttons). A role set explicitly by the consumer is left untouched, even if it is added or
|
|
161
|
+
* removed dynamically after the element connects.
|
|
147
162
|
*/
|
|
148
163
|
private _applyRole (): void {
|
|
149
164
|
const currentRole = this.getAttribute('role');
|
|
@@ -156,12 +171,10 @@ export class PieListItem extends PieElement implements ListItemProps {
|
|
|
156
171
|
}
|
|
157
172
|
|
|
158
173
|
/**
|
|
159
|
-
* The accessible name and description this item provides to its
|
|
160
|
-
* its text.
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* `aria-label` there would be invalid). Context reaches across the shadow boundary that
|
|
164
|
-
* `aria-labelledby` cannot.
|
|
174
|
+
* The accessible name and description this item provides to its interactive element, stitched
|
|
175
|
+
* from its text. Selection controls consume it via `ariaContext` (each applying it to the
|
|
176
|
+
* element that carries its role); the action button of a `button` row takes it directly as
|
|
177
|
+
* `aria-label`/`aria-description` in the template; a link anchor takes it via `_applyLinkAria`.
|
|
165
178
|
*/
|
|
166
179
|
private get _providedAria (): ContextualAria | undefined {
|
|
167
180
|
if (!this._providesAria) return undefined;
|
|
@@ -186,11 +199,11 @@ export class PieListItem extends PieElement implements ListItemProps {
|
|
|
186
199
|
};
|
|
187
200
|
|
|
188
201
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
202
|
+
* On a link row, names the slotted (empty) anchor from the item's own text: the parent owns the
|
|
203
|
+
* child's aria. The anchor is plain light-DOM, so it cannot reference the shadow-DOM text via
|
|
204
|
+
* `aria-labelledby`; and it is not a PIE control that consumes `ariaContext`, so we set the
|
|
192
205
|
* attributes imperatively. The visible text is `aria-hidden` in the template, so nothing is
|
|
193
|
-
* announced twice.
|
|
206
|
+
* announced twice. (A `button` row names its own rendered element in the template instead.)
|
|
194
207
|
*
|
|
195
208
|
* A consumer-provided name/description always wins: we capture (once) whether the anchor already
|
|
196
209
|
* carries `aria-label`/`aria-labelledby` or `aria-description`/`aria-describedby` and, if so,
|
|
@@ -261,6 +274,26 @@ export class PieListItem extends PieElement implements ListItemProps {
|
|
|
261
274
|
return html`<div class="c-listItem-trailing"><slot name="trailing"></slot></div>`;
|
|
262
275
|
}
|
|
263
276
|
|
|
277
|
+
/**
|
|
278
|
+
* The interactive target for a `button` row: an empty, native `<button>` stretched invisibly
|
|
279
|
+
* over the whole row (see the SCSS). The item renders and owns it - rather than the consumer
|
|
280
|
+
* slotting one - so it can name it from its own text and keep its attributes private (it is just
|
|
281
|
+
* an action box, not a configurable control). Being a real button, activation is native: pointer
|
|
282
|
+
* clicks and keyboard (Enter/Space) both fire a `click` that bubbles out, so consumers listen for
|
|
283
|
+
* `click` on the `pie-list-item`. It is `type="button"`, so it never submits a form.
|
|
284
|
+
*/
|
|
285
|
+
_renderActionButton () {
|
|
286
|
+
const aria = this._providedAria;
|
|
287
|
+
|
|
288
|
+
return html`<button
|
|
289
|
+
class="c-listItem-action"
|
|
290
|
+
type="button"
|
|
291
|
+
?disabled=${this._isDisabled}
|
|
292
|
+
aria-label=${ifDefined(aria?.label)}
|
|
293
|
+
aria-description=${ifDefined(aria?.description)}
|
|
294
|
+
aria-haspopup=${ifDefined(this.aria?.button?.haspopup)}></button>`;
|
|
295
|
+
}
|
|
296
|
+
|
|
264
297
|
render () {
|
|
265
298
|
const { primaryText } = this;
|
|
266
299
|
|
|
@@ -271,12 +304,14 @@ export class PieListItem extends PieElement implements ListItemProps {
|
|
|
271
304
|
'has-media': this.hasMedia,
|
|
272
305
|
'is-selectable': this._isSelectable,
|
|
273
306
|
'is-link': this._isLinkRow,
|
|
307
|
+
'is-button': this._isButtonRow,
|
|
274
308
|
'is-disabled': this._isDisabled,
|
|
275
309
|
};
|
|
276
310
|
|
|
277
311
|
return html`
|
|
278
312
|
<div class=${classMap(containerClasses)}>
|
|
279
313
|
${this._isLinkRow ? html`<slot name="link"></slot>` : nothing}
|
|
314
|
+
${this._isButtonRow ? this._renderActionButton() : nothing}
|
|
280
315
|
<div class="c-listItem-leading">
|
|
281
316
|
<slot name="leading"></slot>
|
|
282
317
|
</div>
|
|
@@ -49,9 +49,10 @@
|
|
|
49
49
|
min-height: 48px;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
//
|
|
53
|
-
// slotted anchor can stretch over it.
|
|
54
|
-
.c-listItem-container.is-link
|
|
52
|
+
// Overlay rows (link/button): the whole item is a single interactive target. The container is
|
|
53
|
+
// positioned so the slotted anchor/button can stretch over it.
|
|
54
|
+
.c-listItem-container.is-link,
|
|
55
|
+
.c-listItem-container.is-button {
|
|
55
56
|
position: relative;
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -62,7 +63,8 @@
|
|
|
62
63
|
// defaults use the standard PIE interactive overlay tokens; override `--list-item-hover-bg` /
|
|
63
64
|
// `--list-item-active-bg` if design specifies dedicated list tokens.
|
|
64
65
|
.c-listItem-container.is-selectable:not(.is-disabled),
|
|
65
|
-
.c-listItem-container.is-link
|
|
66
|
+
.c-listItem-container.is-link,
|
|
67
|
+
.c-listItem-container.is-button:not(.is-disabled) {
|
|
66
68
|
--list-item-hover-bg: hsl(var(--dt-color-black-h), var(--dt-color-black-s), var(--dt-color-black-l), var(--dt-color-hover-01));
|
|
67
69
|
--list-item-active-bg: hsl(var(--dt-color-black-h), var(--dt-color-black-s), var(--dt-color-black-l), var(--dt-color-active-01));
|
|
68
70
|
|
|
@@ -84,6 +86,9 @@
|
|
|
84
86
|
--interactive-container-tint: var(--list-item-hover-bg);
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
// The pressed tint for a pointer press: the container is an ancestor of the pressed element, so
|
|
90
|
+
// it matches `:active`. (A keyboard press is handled by the `:has()` rule below, because keyboard
|
|
91
|
+
// `:active` does not propagate up to the container.)
|
|
87
92
|
&:active {
|
|
88
93
|
background-color: var(--list-item-active-bg);
|
|
89
94
|
|
|
@@ -91,17 +96,49 @@
|
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
99
|
+
// Pressed tint for a `button` row's keyboard press. A native `<button>` is `:active` while Space is
|
|
100
|
+
// held, but that state does not propagate to the container, so `&:active` above misses it. The
|
|
101
|
+
// action button is a real shadow-DOM descendant (not slotted), so `:has()` can react to its
|
|
102
|
+
// `:active` directly - no JS needed. A pointer press satisfies both this and `&:active` with the
|
|
103
|
+
// same value, so there is no double up.
|
|
104
|
+
.c-listItem-container.is-button:has(.c-listItem-action:active) {
|
|
105
|
+
background-color: var(--list-item-active-bg);
|
|
106
|
+
|
|
107
|
+
--interactive-container-tint: var(--list-item-active-bg);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Stretch the interactive target over the entire row so the whole block is the click/touch target.
|
|
111
|
+
// For a link it is the consumer's slotted anchor; for a button it is the native <button> the item
|
|
112
|
+
// renders. Both anchor to the container (positioned above).
|
|
113
|
+
slot[name="link"]::slotted(a),
|
|
114
|
+
.c-listItem-action {
|
|
97
115
|
position: absolute;
|
|
98
116
|
inset: 0;
|
|
99
117
|
z-index: 1;
|
|
100
118
|
border-radius: inherit; // so the focus ring follows any rounding applied to the row
|
|
101
119
|
}
|
|
102
120
|
|
|
103
|
-
//
|
|
104
|
-
|
|
121
|
+
// The action element of a `button` row is a native <button> the item renders. Strip its chrome so
|
|
122
|
+
// it is invisible (the row provides the styling); it stays a real, focusable button with native
|
|
123
|
+
// pointer/keyboard activation and screen-reader semantics.
|
|
124
|
+
.c-listItem-action {
|
|
125
|
+
padding: 0;
|
|
126
|
+
border: none;
|
|
127
|
+
background: transparent;
|
|
128
|
+
appearance: none;
|
|
129
|
+
font: inherit;
|
|
130
|
+
cursor: pointer;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// A disabled action button shows the default cursor (it already ignores clicks and is unfocusable
|
|
134
|
+
// natively via the `disabled` attribute).
|
|
135
|
+
.c-listItem-action:disabled {
|
|
136
|
+
cursor: default;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Standard PIE focus ring. The anchor/action is row-sized, so keyboard focus rings the whole item.
|
|
140
|
+
slot[name="link"]::slotted(a:focus-visible),
|
|
141
|
+
.c-listItem-action:focus-visible {
|
|
105
142
|
@include p.focus;
|
|
106
143
|
}
|
|
107
144
|
|
|
@@ -147,6 +184,20 @@ slot[name="trailing"]::slotted(*),
|
|
|
147
184
|
--icon-size-override: 24px
|
|
148
185
|
}
|
|
149
186
|
|
|
187
|
+
// Disabled button row: mute text and icons. Icons use `fill: currentColor`, so setting `color`
|
|
188
|
+
// on the host element cascades through the shadow boundary and changes their rendered fill.
|
|
189
|
+
.c-listItem-container.is-button.is-disabled {
|
|
190
|
+
.c-listItem-primaryText,
|
|
191
|
+
.c-listItem-secondaryText,
|
|
192
|
+
.c-listItem-metaText {
|
|
193
|
+
color: var(--dt-color-content-disabled);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.c-listItem-container.is-button.is-disabled ::slotted(.c-pieIcon) {
|
|
198
|
+
color: var(--dt-color-disabled-01);
|
|
199
|
+
}
|
|
200
|
+
|
|
150
201
|
// typography
|
|
151
202
|
|
|
152
203
|
.c-listItem-primaryText {
|