@justeattakeaway/pie-list 0.4.0 → 0.6.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.
@@ -0,0 +1,344 @@
1
+ import { unsafeCSS as x, isServer as y, html as h, nothing as p } from "lit";
2
+ import { property as c, state as k } from "lit/decorators.js";
3
+ import { classMap as _ } from "lit/directives/class-map.js";
4
+ import { ifDefined as m } from "lit/directives/if-defined.js";
5
+ import { validPropertyValues as I, safeCustomElement as T, ariaContext as w, parentDisabledContext as C } from "@justeattakeaway/pie-webc-core";
6
+ import { P as A } from "./PieElement-wRsAkkYC.js";
7
+ /**
8
+ * @license
9
+ * Copyright 2021 Google LLC
10
+ * SPDX-License-Identifier: BSD-3-Clause
11
+ */
12
+ let f = class extends Event {
13
+ constructor(t, e, o, s) {
14
+ super("context-request", { bubbles: !0, composed: !0 }), this.context = t, this.contextTarget = e, this.callback = o, this.subscribe = s ?? !1;
15
+ }
16
+ };
17
+ /**
18
+ * @license
19
+ * Copyright 2021 Google LLC
20
+ * SPDX-License-Identifier: BSD-3-Clause
21
+ */
22
+ let g = class {
23
+ constructor(t, e, o, s) {
24
+ if (this.subscribe = !1, this.provided = !1, this.value = void 0, this.t = (r, n) => {
25
+ this.unsubscribe && (this.unsubscribe !== n && (this.provided = !1, this.unsubscribe()), this.subscribe || this.unsubscribe()), this.value = r, this.host.requestUpdate(), this.provided && !this.subscribe || (this.provided = !0, this.callback && this.callback(r, n)), this.unsubscribe = n;
26
+ }, this.host = t, e.context !== void 0) {
27
+ const r = e;
28
+ this.context = r.context, this.callback = r.callback, this.subscribe = r.subscribe ?? !1;
29
+ } else this.context = e, this.callback = o, this.subscribe = s ?? !1;
30
+ this.host.addController(this);
31
+ }
32
+ hostConnected() {
33
+ this.dispatchRequest();
34
+ }
35
+ hostDisconnected() {
36
+ this.unsubscribe && (this.unsubscribe(), this.unsubscribe = void 0);
37
+ }
38
+ dispatchRequest() {
39
+ this.host.dispatchEvent(new f(this.context, this.host, this.t, this.subscribe));
40
+ }
41
+ };
42
+ /**
43
+ * @license
44
+ * Copyright 2021 Google LLC
45
+ * SPDX-License-Identifier: BSD-3-Clause
46
+ */
47
+ class R {
48
+ get value() {
49
+ return this.o;
50
+ }
51
+ set value(t) {
52
+ this.setValue(t);
53
+ }
54
+ setValue(t, e = !1) {
55
+ const o = e || !Object.is(t, this.o);
56
+ this.o = t, o && this.updateObservers();
57
+ }
58
+ constructor(t) {
59
+ this.subscriptions = /* @__PURE__ */ new Map(), this.updateObservers = () => {
60
+ for (const [e, { disposer: o }] of this.subscriptions) e(this.o, o);
61
+ }, t !== void 0 && (this.value = t);
62
+ }
63
+ addCallback(t, e, o) {
64
+ if (!o) return void t(this.value);
65
+ this.subscriptions.has(t) || this.subscriptions.set(t, { disposer: () => {
66
+ this.subscriptions.delete(t);
67
+ }, consumerHost: e });
68
+ const { disposer: s } = this.subscriptions.get(t);
69
+ t(this.value, s);
70
+ }
71
+ clearCallbacks() {
72
+ this.subscriptions.clear();
73
+ }
74
+ }
75
+ /**
76
+ * @license
77
+ * Copyright 2021 Google LLC
78
+ * SPDX-License-Identifier: BSD-3-Clause
79
+ */
80
+ class L extends Event {
81
+ constructor(t, e) {
82
+ super("context-provider", { bubbles: !0, composed: !0 }), this.context = t, this.contextTarget = e;
83
+ }
84
+ }
85
+ class D extends R {
86
+ constructor(t, e, o) {
87
+ var s, r;
88
+ super(e.context !== void 0 ? e.initialValue : o), this.onContextRequest = (n) => {
89
+ if (n.context !== this.context) return;
90
+ const b = n.contextTarget ?? n.composedPath()[0];
91
+ b !== this.host && (n.stopPropagation(), this.addCallback(n.callback, b, n.subscribe));
92
+ }, this.onProviderRequest = (n) => {
93
+ if (n.context !== this.context || (n.contextTarget ?? n.composedPath()[0]) === this.host) return;
94
+ const b = /* @__PURE__ */ new Set();
95
+ for (const [u, { consumerHost: v }] of this.subscriptions) b.has(u) || (b.add(u), v.dispatchEvent(new f(this.context, v, u, !0)));
96
+ n.stopPropagation();
97
+ }, this.host = t, e.context !== void 0 ? this.context = e.context : this.context = e, this.attachListeners(), (r = (s = this.host).addController) == null || r.call(s, this);
98
+ }
99
+ attachListeners() {
100
+ this.host.addEventListener("context-request", this.onContextRequest), this.host.addEventListener("context-provider", this.onProviderRequest);
101
+ }
102
+ hostConnected() {
103
+ this.host.dispatchEvent(new L(this.context, this.host));
104
+ }
105
+ }
106
+ /**
107
+ * @license
108
+ * Copyright 2022 Google LLC
109
+ * SPDX-License-Identifier: BSD-3-Clause
110
+ */
111
+ function B({ context: i, subscribe: t }) {
112
+ return (e, o) => {
113
+ typeof o == "object" ? o.addInitializer((function() {
114
+ new g(this, { context: i, callback: (s) => {
115
+ e.set.call(this, s);
116
+ }, subscribe: t });
117
+ })) : e.constructor.addInitializer(((s) => {
118
+ new g(s, { context: i, callback: (r) => {
119
+ s[o] = r;
120
+ }, subscribe: t });
121
+ }));
122
+ };
123
+ }
124
+ const P = ["none", "radio", "checkbox", "switch", "link", "button"], d = {
125
+ isCompact: !1,
126
+ isBold: !1,
127
+ hasMedia: !1,
128
+ interactionType: "none",
129
+ disabled: !1,
130
+ hasDivider: !1
131
+ }, S = "*,*:after,*:before{box-sizing:inherit}:host{--list-item-inline-padding: var(--dt-spacing-d);--list-item-alignment: flex-start;display:block}.c-listItem-container{--list-item-block-padding: var(--dt-spacing-d);padding-inline-start:var(--list-item-inline-padding);padding-inline-end:var(--list-item-inline-padding);padding-block-start:var(--list-item-block-padding);padding-block-end:var(--list-item-block-padding);display:flex;align-items:var(--list-item-alignment);gap:var(--dt-spacing-c);min-height:56px}.c-listItem-container.has-divider{padding-block-end:calc(var(--list-item-block-padding) - 1px);border-bottom:1px solid var(--dt-color-divider-default)}.c-listItem-container:has(.c-listItem-secondaryText){align-items:var(--list-item-alignment);min-height:76px}.c-listItem-container.is-compact{--list-item-block-padding: var(--dt-spacing-c);--list-item-alignment: center;align-items:var(--list-item-alignment);min-height:48px}.c-listItem-container.is-link,.c-listItem-container.is-button{position:relative}.c-listItem-container.is-selectable:not(.is-disabled),.c-listItem-container.is-link,.c-listItem-container.is-button:not(.is-disabled){--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));--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))}@supports (background-color: color-mix(in srgb,black,white)){.c-listItem-container.is-selectable:not(.is-disabled),.c-listItem-container.is-link,.c-listItem-container.is-button:not(.is-disabled){--list-item-hover-bg: color-mix(in srgb, var(--dt-color-hover-01-bg) var(--dt-color-hover-01), transparent);--list-item-active-bg: color-mix(in srgb, var(--dt-color-active-01-bg) var(--dt-color-active-01), transparent)}}.c-listItem-container.is-selectable:not(.is-disabled),.c-listItem-container.is-link,.c-listItem-container.is-button:not(.is-disabled){cursor:pointer}.c-listItem-container.is-selectable:not(.is-disabled):hover,.c-listItem-container.is-link:hover,.c-listItem-container.is-button:not(.is-disabled):hover{background-color:var(--list-item-hover-bg);--interactive-container-tint: var(--list-item-hover-bg)}.c-listItem-container.is-selectable:not(.is-disabled):active,.c-listItem-container.is-link:active,.c-listItem-container.is-button:not(.is-disabled):active{background-color:var(--list-item-active-bg);--interactive-container-tint: var(--list-item-active-bg)}.c-listItem-container.is-button:has(.c-listItem-action:active){background-color:var(--list-item-active-bg);--interactive-container-tint: var(--list-item-active-bg)}slot[name=link]::slotted(a),.c-listItem-action{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;border-radius:inherit}.c-listItem-action{padding:0;border:none;background:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none;font:inherit;cursor:pointer}.c-listItem-action:disabled{cursor:default}slot[name=link]::slotted(a:focus-visible),.c-listItem-action:focus-visible{box-shadow:0 0 0 2px var(--dt-color-focus-inner),0 0 0 4px var(--dt-color-focus-outer);outline:none}.c-listItem-container.has-media:has(.c-listItem-primaryText:only-child){--list-item-block-padding: var(--dt-spacing-b)}.c-listItem-text{flex:1}slot[name=trailing]::slotted(*),.c-listItem-metaText{padding-inline-start:var(--dt-spacing-a)}.c-listItem-leading,slot[name=leading],.c-listItem-trailing,slot[name=trailing]{display:contents}.c-listItem-text>*{display:block}.c-listItem-trailing{margin-inline-start:auto}.c-listItem-metaText{display:block;max-width:33.3333333333%}::slotted(.c-pieIcon){--icon-size-override: 24px }.c-listItem-container.is-button.is-disabled .c-listItem-primaryText,.c-listItem-container.is-button.is-disabled .c-listItem-secondaryText,.c-listItem-container.is-button.is-disabled .c-listItem-metaText{color:var(--dt-color-content-disabled)}.c-listItem-container.is-button.is-disabled ::slotted(.c-pieIcon){color:var(--dt-color-disabled-01)}.c-listItem-primaryText{font-family:var(--dt-font-body-l-family);font-weight:var(--dt-font-body-l-weight);font-size:calc(var(--dt-font-body-l-size) * 1px);line-height:calc(var(--dt-font-body-l-line-height) * 1px);color:var(--dt-color-content-default)}.c-listItem-container.is-bold .c-listItem-primaryText{font-family:var(--dt-font-body-strong-l-family);font-weight:var(--dt-font-body-strong-l-weight);font-size:calc(var(--dt-font-body-strong-l-size) * 1px);line-height:calc(var(--dt-font-body-strong-l-line-height) * 1px)}.c-listItem-secondaryText,.c-listItem-metaText{font-family:var(--dt-font-body-s-family);font-weight:var(--dt-font-body-s-weight);font-size:calc(var(--dt-font-body-s-size) * 1px);line-height:calc(var(--dt-font-body-s-line-height) * 1px);color:var(--dt-color-content-subdued)}";
132
+ var $ = Object.defineProperty, q = Object.getOwnPropertyDescriptor, l = (i, t, e, o) => {
133
+ for (var s = o > 1 ? void 0 : o ? q(t, e) : t, r = i.length - 1, n; r >= 0; r--)
134
+ (n = i[r]) && (s = (o ? n(t, e, s) : n(s)) || s);
135
+ return o && s && $(t, e, s), s;
136
+ };
137
+ const z = "pie-list-item";
138
+ let a = class extends A {
139
+ constructor() {
140
+ super(...arguments), this.isCompact = d.isCompact, this.isBold = d.isBold, this.hasMedia = d.hasMedia, this.interactionType = d.interactionType, this.disabled = d.disabled, this.hasDivider = d.hasDivider, this._parentDisabled = !1, this._ariaProvider = y ? void 0 : new D(this, { context: w }), this._managedRole = null, this._linkAriaCaptured = !1, this._consumerNamedLink = !1, this._consumerDescribedLink = !1, this._handleHostClick = (i) => {
141
+ if (!this._isSelectable || this._isDisabled) return;
142
+ const t = this._control;
143
+ !t || i.composedPath().includes(t) || (t.click(), t.focus());
144
+ };
145
+ }
146
+ // A selection row hosts a slotted control (radio/checkbox/switch) and makes the whole row a
147
+ // toggle target. `link`/`button` are interactive too, but they are single navigation/action
148
+ // targets rather than selection controls, so they are deliberately excluded here.
149
+ get _isSelectable() {
150
+ return this.interactionType === "radio" || this.interactionType === "checkbox" || this.interactionType === "switch";
151
+ }
152
+ get _isLinkRow() {
153
+ return this.interactionType === "link";
154
+ }
155
+ get _isButtonRow() {
156
+ return this.interactionType === "button";
157
+ }
158
+ // A link and a button row share the same visual mechanic: an empty element stretched over the
159
+ // whole row as the interactive target, named by the item from its own text. They differ in who
160
+ // owns that element - a link is a consumer-slotted anchor, a button is one the item renders.
161
+ get _isOverlayRow() {
162
+ return this._isLinkRow || this._isButtonRow;
163
+ }
164
+ // True when the item lends its text as an interactive element's accessible name/description:
165
+ // selectable rows (radio/checkbox/switch) and overlay rows (link anchor / action button).
166
+ get _providesAria() {
167
+ return this._isSelectable || this._isOverlayRow;
168
+ }
169
+ // radio/checkbox are owned by a selection group, which is why the item becomes `presentation`
170
+ // (so the group owns the controls directly) and the radio is named on its host. A switch has
171
+ // no group, so the item stays a `listitem`.
172
+ get _ownedByGroup() {
173
+ return this.interactionType === "radio" || this.interactionType === "checkbox";
174
+ }
175
+ // True when the row should be treated as disabled: either its own `disabled` prop is set, or the
176
+ // containing group is disabled (provided via context). Declarative and reactive, so the styling
177
+ // and row-click guard react without observing the slotted control.
178
+ get _isDisabled() {
179
+ return this.disabled || this._parentDisabled;
180
+ }
181
+ // The interactive control (radio/checkbox/switch) slotted into this item, if any. Used only by
182
+ // the client-side row-click handler.
183
+ get _control() {
184
+ return this.querySelector("pie-radio, pie-checkbox, pie-switch");
185
+ }
186
+ connectedCallback() {
187
+ super.connectedCallback(), this._abortController = new AbortController(), this.addEventListener("click", this._handleHostClick, { signal: this._abortController.signal });
188
+ }
189
+ disconnectedCallback() {
190
+ super.disconnectedCallback(), this._abortController.abort();
191
+ }
192
+ updated() {
193
+ var i;
194
+ this._applyRole(), (i = this._ariaProvider) == null || i.setValue(this._providedAria), this._applyLinkAria();
195
+ }
196
+ /**
197
+ * Sets the item's role from `interactionType`: `presentation` for radio/checkbox (so the group
198
+ * owns those controls directly), otherwise `listitem` (static items, switches, links and
199
+ * buttons). A role set explicitly by the consumer is left untouched, even if it is added or
200
+ * removed dynamically after the element connects.
201
+ */
202
+ _applyRole() {
203
+ const i = this.getAttribute("role");
204
+ if (i !== null && i !== this._managedRole) return;
205
+ const t = this._ownedByGroup ? "presentation" : "listitem";
206
+ this._managedRole = t, this.setAttribute("role", t);
207
+ }
208
+ /**
209
+ * The accessible name and description this item provides to its interactive element, stitched
210
+ * from its text. Selection controls consume it via `ariaContext` (each applying it to the
211
+ * element that carries its role); the action button of a `button` row takes it directly as
212
+ * `aria-label`/`aria-description` in the template; a link anchor takes it via `_applyLinkAria`.
213
+ */
214
+ get _providedAria() {
215
+ if (!this._providesAria) return;
216
+ const i = [this.secondaryText, this.metaText].filter(Boolean).join(". ");
217
+ return { label: this.primaryText, description: i || void 0 };
218
+ }
219
+ /**
220
+ * On a link row, names the slotted (empty) anchor from the item's own text: the parent owns the
221
+ * child's aria. The anchor is plain light-DOM, so it cannot reference the shadow-DOM text via
222
+ * `aria-labelledby`; and it is not a PIE control that consumes `ariaContext`, so we set the
223
+ * attributes imperatively. The visible text is `aria-hidden` in the template, so nothing is
224
+ * announced twice. (A `button` row names its own rendered element in the template instead.)
225
+ *
226
+ * A consumer-provided name/description always wins: we capture (once) whether the anchor already
227
+ * carries `aria-label`/`aria-labelledby` or `aria-description`/`aria-describedby` and, if so,
228
+ * leave that attribute untouched. Otherwise we keep our generated value in sync with the text.
229
+ */
230
+ _applyLinkAria() {
231
+ if (!this._isLinkRow) {
232
+ if (this._linkAriaCaptured) {
233
+ const e = this.querySelector('a[slot="link"]');
234
+ e && (this._consumerNamedLink || e.removeAttribute("aria-label"), this._consumerDescribedLink || e.removeAttribute("aria-description")), this._linkAriaCaptured = !1, this._consumerNamedLink = !1, this._consumerDescribedLink = !1;
235
+ }
236
+ return;
237
+ }
238
+ const i = this.querySelector('a[slot="link"]');
239
+ if (!i) return;
240
+ this._linkAriaCaptured || (this._consumerNamedLink = i.hasAttribute("aria-label") || i.hasAttribute("aria-labelledby"), this._consumerDescribedLink = i.hasAttribute("aria-description") || i.hasAttribute("aria-describedby"), this._linkAriaCaptured = !0);
241
+ const t = this._providedAria;
242
+ this._consumerNamedLink || (t != null && t.label ? i.setAttribute("aria-label", t.label) : i.removeAttribute("aria-label")), this._consumerDescribedLink || (t != null && t.description ? i.setAttribute("aria-description", t.description) : i.removeAttribute("aria-description"));
243
+ }
244
+ _renderSecondaryText() {
245
+ const { secondaryText: i } = this;
246
+ return i ? h`<span class="c-listItem-secondaryText">${i}</span>` : p;
247
+ }
248
+ // metaText is another form of trailing content. The component can only ever display either slotted trailing content or the metaText string
249
+ _renderTrailingContent() {
250
+ const { metaText: i } = this;
251
+ return i ? h`<span class="c-listItem-metaText c-listItem-trailing" aria-hidden=${this._providesAria ? "true" : p}>${i}</span>` : h`<div class="c-listItem-trailing"><slot name="trailing"></slot></div>`;
252
+ }
253
+ /**
254
+ * The interactive target for a `button` row: an empty, native `<button>` stretched invisibly
255
+ * over the whole row (see the SCSS). The item renders and owns it - rather than the consumer
256
+ * slotting one - so it can name it from its own text and keep its attributes private (it is just
257
+ * an action box, not a configurable control). Being a real button, activation is native: pointer
258
+ * clicks and keyboard (Enter/Space) both fire a `click` that bubbles out, so consumers listen for
259
+ * `click` on the `pie-list-item`. It is `type="button"`, so it never submits a form.
260
+ */
261
+ _renderActionButton() {
262
+ var t, e;
263
+ const i = this._providedAria;
264
+ return h`<button
265
+ class="c-listItem-action"
266
+ type="button"
267
+ ?disabled=${this._isDisabled}
268
+ aria-label=${m(i == null ? void 0 : i.label)}
269
+ aria-description=${m(i == null ? void 0 : i.description)}
270
+ aria-haspopup=${m((e = (t = this.aria) == null ? void 0 : t.button) == null ? void 0 : e.haspopup)}></button>`;
271
+ }
272
+ render() {
273
+ const { primaryText: i } = this, t = {
274
+ "c-listItem-container": !0,
275
+ "is-compact": this.isCompact,
276
+ "is-bold": this.isBold,
277
+ "has-media": this.hasMedia,
278
+ "has-divider": this.hasDivider,
279
+ "is-selectable": this._isSelectable,
280
+ "is-link": this._isLinkRow,
281
+ "is-button": this._isButtonRow,
282
+ "is-disabled": this._isDisabled
283
+ };
284
+ return h`
285
+ <div class=${_(t)}>
286
+ ${this._isLinkRow ? h`<slot name="link"></slot>` : p}
287
+ ${this._isButtonRow ? this._renderActionButton() : p}
288
+ <div class="c-listItem-leading">
289
+ <slot name="leading"></slot>
290
+ </div>
291
+
292
+ <div class="c-listItem-text" aria-hidden=${this._providesAria ? "true" : p}>
293
+ <span class="c-listItem-primaryText">${i}</span>
294
+ ${this._renderSecondaryText()}
295
+ </div>
296
+
297
+ ${this._renderTrailingContent()}
298
+ </div>`;
299
+ }
300
+ };
301
+ a.styles = x(S);
302
+ l([
303
+ c({ type: String })
304
+ ], a.prototype, "primaryText", 2);
305
+ l([
306
+ c({ type: String })
307
+ ], a.prototype, "secondaryText", 2);
308
+ l([
309
+ c({ type: String })
310
+ ], a.prototype, "metaText", 2);
311
+ l([
312
+ c({ type: Boolean })
313
+ ], a.prototype, "isCompact", 2);
314
+ l([
315
+ c({ type: Boolean })
316
+ ], a.prototype, "isBold", 2);
317
+ l([
318
+ c({ type: Boolean })
319
+ ], a.prototype, "hasMedia", 2);
320
+ l([
321
+ c({ type: String }),
322
+ I(z, P, d.interactionType)
323
+ ], a.prototype, "interactionType", 2);
324
+ l([
325
+ c({ type: Boolean })
326
+ ], a.prototype, "disabled", 2);
327
+ l([
328
+ c({ type: Boolean })
329
+ ], a.prototype, "hasDivider", 2);
330
+ l([
331
+ c({ type: Object })
332
+ ], a.prototype, "aria", 2);
333
+ l([
334
+ B({ context: C, subscribe: !0 }),
335
+ k()
336
+ ], a.prototype, "_parentDisabled", 2);
337
+ a = l([
338
+ T("pie-list-item")
339
+ ], a);
340
+ export {
341
+ a as P,
342
+ d,
343
+ P as i
344
+ };
package/dist/index.js CHANGED
@@ -1,24 +1,24 @@
1
- import { html as n, unsafeCSS as m } from "lit";
2
- import { P as f } from "./PieElement-CGtUo9tK.js";
3
- import { RtlMixin as d, safeCustomElement as p } from "@justeattakeaway/pie-webc-core";
4
- const c = "*,*:after,*:before{box-sizing:inherit}:host{display:block}:host ::slotted(pie-list-item:not(:last-of-type)){--list-item-size-modifier: 1px;--list-item-border: var(--list-item-size-modifier) solid var(--dt-color-divider-default)}";
5
- var b = Object.getOwnPropertyDescriptor, u = (s, r, a, l) => {
6
- for (var t = l > 1 ? void 0 : l ? b(r, a) : r, e = s.length - 1, o; e >= 0; e--)
7
- (o = s[e]) && (t = o(t) || t);
8
- return t;
1
+ import { html as a, unsafeCSS as c } from "lit";
2
+ import { P as f } from "./PieElement-wRsAkkYC.js";
3
+ import { RtlMixin as m, safeCustomElement as p } from "@justeattakeaway/pie-webc-core";
4
+ const b = "*,*:after,*:before{box-sizing:inherit}:host{display:block}";
5
+ var u = Object.getOwnPropertyDescriptor, h = (r, l, n, i) => {
6
+ for (var e = i > 1 ? void 0 : i ? u(l, n) : l, t = r.length - 1, o; t >= 0; t--)
7
+ (o = r[t]) && (e = o(e) || e);
8
+ return e;
9
9
  };
10
- let i = class extends d(f) {
10
+ let s = class extends m(f) {
11
11
  connectedCallback() {
12
12
  super.connectedCallback(), this.hasAttribute("role") || this.setAttribute("role", "list");
13
13
  }
14
14
  render() {
15
- return n`<slot></slot>`;
15
+ return a`<slot></slot>`;
16
16
  }
17
17
  };
18
- i.styles = m(c);
19
- i = u([
18
+ s.styles = c(b);
19
+ s = h([
20
20
  p("pie-list")
21
- ], i);
21
+ ], s);
22
22
  export {
23
- i as PieList
23
+ s as PieList
24
24
  };
@@ -1,5 +1,10 @@
1
1
  import { ComponentDefaultProps } from '@justeattakeaway/pie-webc-core';
2
- export declare const interactionTypes: readonly ["none", "radio", "checkbox", "switch", "link"];
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,18 @@ export interface ListItemProps {
55
64
  * (static) item.
56
65
  */
57
66
  disabled?: boolean;
67
+ /**
68
+ * When `true`, renders a bottom divider line on the item. Do not set this to true for the last item in a list.
69
+ */
70
+ hasDivider?: boolean;
71
+ /**
72
+ * Additional ARIA properties that the item cannot derive from its text props. `button.haspopup`
73
+ * applies when `interactionType="button"`: its value is forwarded to the internal `<button>`.
74
+ * Set it when the button row triggers a popup such as a dialog or menu.
75
+ */
76
+ aria?: AriaProps;
58
77
  }
59
78
  export type DefaultProps = ComponentDefaultProps<ListItemProps, keyof Omit<ListItemProps, 'primaryText' | 'secondaryText' | 'metaText'>>;
60
79
  export declare const defaultProps: DefaultProps;
80
+ export {};
61
81
  //# 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;AAG5E,eAAO,MAAM,gBAAgB,0DAA2D,CAAC;AAEzF,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;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,CAAC,EAAE,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAA;IAEjD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;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"}
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;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IAEpB;;;;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,YAO1B,CAAC"}
@@ -15,8 +15,10 @@ 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
+ hasDivider: boolean;
21
+ aria: ListItemProps['aria'];
20
22
  private _parentDisabled;
21
23
  private _ariaProvider;
22
24
  private _abortController;
@@ -26,6 +28,8 @@ export declare class PieListItem extends PieElement implements ListItemProps {
26
28
  private _consumerDescribedLink;
27
29
  private get _isSelectable();
28
30
  private get _isLinkRow();
31
+ private get _isButtonRow();
32
+ private get _isOverlayRow();
29
33
  private get _providesAria();
30
34
  private get _ownedByGroup();
31
35
  private get _isDisabled();
@@ -35,18 +39,16 @@ export declare class PieListItem extends PieElement implements ListItemProps {
35
39
  protected updated(): void;
36
40
  /**
37
41
  * Sets the item's role from `interactionType`: `presentation` for radio/checkbox (so the group
38
- * owns those controls directly), otherwise `listitem` (static items, switches and links). A role
39
- * set explicitly by the consumer is left untouched, even if it is added or removed dynamically
40
- * after the element connects.
42
+ * owns those controls directly), otherwise `listitem` (static items, switches, links and
43
+ * buttons). A role set explicitly by the consumer is left untouched, even if it is added or
44
+ * removed dynamically after the element connects.
41
45
  */
42
46
  private _applyRole;
43
47
  /**
44
- * The accessible name and description this item provides to its slotted control, stitched from
45
- * its text. Only provided in a selectable list. Each control consumes this via `ariaContext`
46
- * and decides how to apply it: `pie-radio` names its host (which carries `role="radio"`),
47
- * while `pie-checkbox` / `pie-switch` name their internal input (their host is role-less, so
48
- * `aria-label` there would be invalid). Context reaches across the shadow boundary that
49
- * `aria-labelledby` cannot.
48
+ * The accessible name and description this item provides to its interactive element, stitched
49
+ * from its text. Selection controls consume it via `ariaContext` (each applying it to the
50
+ * element that carries its role); the action button of a `button` row takes it directly as
51
+ * `aria-label`/`aria-description` in the template; a link anchor takes it via `_applyLinkAria`.
50
52
  */
51
53
  private get _providedAria();
52
54
  /**
@@ -55,11 +57,11 @@ export declare class PieListItem extends PieElement implements ListItemProps {
55
57
  */
56
58
  private _handleHostClick;
57
59
  /**
58
- * When the item is a link, names the slotted (empty) anchor from the item's own text: the parent
59
- * owns the child's aria. The anchor is plain light-DOM, so it cannot reference the shadow-DOM text
60
- * via `aria-labelledby`; and it is not a PIE control that consumes `ariaContext`, so we set the
60
+ * On a link row, names the slotted (empty) anchor from the item's own text: the parent owns the
61
+ * child's aria. The anchor is plain light-DOM, so it cannot reference the shadow-DOM text via
62
+ * `aria-labelledby`; and it is not a PIE control that consumes `ariaContext`, so we set the
61
63
  * attributes imperatively. The visible text is `aria-hidden` in the template, so nothing is
62
- * announced twice.
64
+ * announced twice. (A `button` row names its own rendered element in the template instead.)
63
65
  *
64
66
  * A consumer-provided name/description always wins: we capture (once) whether the anchor already
65
67
  * carries `aria-label`/`aria-labelledby` or `aria-description`/`aria-describedby` and, if so,
@@ -68,6 +70,15 @@ export declare class PieListItem extends PieElement implements ListItemProps {
68
70
  private _applyLinkAria;
69
71
  _renderSecondaryText(): import('lit-html').TemplateResult<1> | typeof nothing;
70
72
  _renderTrailingContent(): import('lit-html').TemplateResult<1>;
73
+ /**
74
+ * The interactive target for a `button` row: an empty, native `<button>` stretched invisibly
75
+ * over the whole row (see the SCSS). The item renders and owns it - rather than the consumer
76
+ * slotting one - so it can name it from its own text and keep its attributes private (it is just
77
+ * an action box, not a configurable control). Being a real button, activation is native: pointer
78
+ * clicks and keyboard (Enter/Space) both fire a `click` that bubbles out, so consumers listen for
79
+ * `click` on the `pie-list-item`. It is `type="button"`, so it never submits a form.
80
+ */
81
+ _renderActionButton(): import('lit-html').TemplateResult<1>;
71
82
  render(): import('lit-html').TemplateResult<1>;
72
83
  static styles: import('lit').CSSResult;
73
84
  }
@@ -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;AAWb,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,oDAAgC;IAG/C,QAAQ,UAAyB;IAMrC,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;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;;;;;;;OAOG;IACH,OAAO,KAAK,aAAa,GAMxB;IAED;;;OAGG;IACH,OAAO,CAAC,gBAAgB,CAQtB;IAEF;;;;;;;;;;OAUG;IACH,OAAO,CAAC,cAAc;IA8CtB,oBAAoB;IAUpB,sBAAsB;IAStB,MAAM;IA8BN,MAAM,CAAC,MAAM,0BAAqB;CACrC;AAED,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC;KACpC;CACJ"}
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,UAAU,UAA2B;IAGrC,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;IAiCN,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 { P as s } from "../index-BMp2PPeH.js";
4
+ import "lit/directives/if-defined.js";
5
+ import { P } from "../index-DpLh8onF.js";
5
6
  import "@justeattakeaway/pie-webc-core";
6
- import "../PieElement-CGtUo9tK.js";
7
+ import "../PieElement-wRsAkkYC.js";
7
8
  export {
8
- s as PieListItem
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-BMp2PPeH.js";
4
- import { d as n, i as c } from "../index-BMp2PPeH.js";
3
+ import { P as i } from "../index-DpLh8onF.js";
4
+ import { d as n, i as c } from "../index-DpLh8onF.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.0",
4
+ "version": "0.6.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -38,11 +38,11 @@
38
38
  "license": "Apache-2.0",
39
39
  "devDependencies": {
40
40
  "@justeattakeaway/pie-components-config": "0.21.3",
41
- "@justeattakeaway/pie-css": "1.3.2",
41
+ "@justeattakeaway/pie-css": "1.3.3",
42
42
  "@justeattakeaway/pie-monorepo-utils": "0.9.8"
43
43
  },
44
44
  "dependencies": {
45
- "@justeattakeaway/pie-webc-core": "16.1.0",
45
+ "@justeattakeaway/pie-webc-core": "16.1.1",
46
46
  "@lit/context": "1.1.6"
47
47
  },
48
48
  "customElements": "custom-elements.json",
package/src/list.scss CHANGED
@@ -2,6 +2,4 @@
2
2
 
3
3
  :host {
4
4
  display: block;
5
-
6
- @include p.list-item-divider;
7
5
  }
@@ -1,7 +1,12 @@
1
1
  import { type ComponentDefaultProps } from '@justeattakeaway/pie-webc-core';
2
2
 
3
- // `button` is reserved for future work and is intentionally not yet an accepted value.
4
- export const interactionTypes = ['none', 'radio', 'checkbox', 'switch', 'link'] as const;
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,18 @@ export interface ListItemProps {
64
73
  * (static) item.
65
74
  */
66
75
  disabled?: boolean
76
+
77
+ /**
78
+ * When `true`, renders a bottom divider line on the item. Do not set this to true for the last item in a list.
79
+ */
80
+ hasDivider?: boolean
81
+
82
+ /**
83
+ * Additional ARIA properties that the item cannot derive from its text props. `button.haspopup`
84
+ * applies when `interactionType="button"`: its value is forwarded to the internal `<button>`.
85
+ * Set it when the button row triggers a popup such as a dialog or menu.
86
+ */
87
+ aria?: AriaProps
67
88
  }
68
89
 
69
90
  export type DefaultProps = ComponentDefaultProps<ListItemProps, keyof Omit<ListItemProps, 'primaryText' | 'secondaryText' | 'metaText'>>;
@@ -74,4 +95,5 @@ export const defaultProps: DefaultProps = {
74
95
  hasMedia: false,
75
96
  interactionType: 'none',
76
97
  disabled: false,
98
+ hasDivider: false,
77
99
  };