@justeattakeaway/pie-list 0.3.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 CHANGED
@@ -21,7 +21,25 @@
21
21
  - [CSS Variables](#css-variables)
22
22
  - [Events](#events)
23
23
  - [Accessibility](#accessibility)
24
+ - [You never set ARIA on a list item or its slotted content](#you-never-set-aria-on-a-list-item-or-its-slotted-content)
25
+ - [How the naming is derived](#how-the-naming-is-derived)
24
26
  - [Usage Examples](#usage-examples)
27
+ - [Basic list](#basic-list)
28
+ - [Leading and trailing content](#leading-and-trailing-content)
29
+ - [Trailing tag](#trailing-tag)
30
+ - [Meta text](#meta-text)
31
+ - [Bold primary text](#bold-primary-text)
32
+ - [Compact list](#compact-list)
33
+ - [Media](#media)
34
+ - [Overriding alignment and padding](#overriding-alignment-and-padding)
35
+ - [Selectable lists](#selectable-lists)
36
+ - [Single-select (radios)](#single-select-radios)
37
+ - [Multi-select (checkboxes)](#multi-select-checkboxes)
38
+ - [Independent toggles (switches)](#independent-toggles-switches)
39
+ - [Link items](#link-items)
40
+ - [Indicating the current page](#indicating-the-current-page)
41
+ - [Button items](#button-items)
42
+ - [Framework variants](#framework-variants)
25
43
  - [Usage Notes and Rules](#usage-notes-and-rules)
26
44
  - [Questions and Support](#questions-and-support)
27
45
  - [Contributing](#contributing)
@@ -55,8 +73,9 @@ Ideally, you should install the component using the **`@justeattakeaway/pie-webc
55
73
  | `isCompact` | `true`, `false` | Decreases the item height to save vertical space. See the [rules](#usage-notes-and-rules) below. | `false` |
56
74
  | `isBold` | `true`, `false` | Sets the primary text to a bold font-weight. | `false` |
57
75
  | `hasMedia` | `true`, `false` | **Required whenever you slot a media element (e.g. `pie-thumbnail`) into the item.** Reduces the block padding so single-line media sits correctly (this padding adjustment has no effect when `secondaryText` is set, but you should still set `hasMedia`). | `false` |
58
- | `selectionType` | `"none"`, `"radio"`, `"checkbox"`, `"switch"` | Declares that the item hosts an interactive control in its `leading`/`trailing` slot, making the **whole row** a selectable target. Set it on each selectable row. Inside a `pie-radio-group`, the group lays the rows out as a divided list automatically. See [Selectable lists](#selectable-lists). | `"none"` |
76
+ | `interactionType` | `"none"`, `"radio"`, `"checkbox"`, `"switch"`, `"link"`, `"button"` | Declares how the **whole row** behaves, and drives its role, accessible naming, click forwarding and interactive states from this one prop. `"none"` is a static item; `"radio"`/`"checkbox"`/`"switch"` host the matching control in the `leading`/`trailing` slot; `"link"` turns the row into a single navigation link (slot an empty `<a slot="link" href="...">`); `"button"` turns the row into a single button for an in-page action (the item renders the button for you - listen for `click`). See [Selectable lists](#selectable-lists), [Link items](#link-items) and [Button items](#button-items). | `"none"` |
59
77
  | `disabled` | `true`, `false` | Marks the row as disabled: it takes the disabled styling and stops forwarding row clicks to its control. Set it alongside the slotted control's own `disabled` (the control still governs its own interactivity). No visible effect on a non-selectable (static) item. | `false` |
78
+ | `aria` | `{ button?: { haspopup?: "menu" \| "listbox" \| "tree" \| "grid" \| "dialog" \| "true" } }` | Additional ARIA properties that the item cannot derive from its text props. `button.haspopup` only applies when `interactionType="button"` and is forwarded to the internal `<button>` element. Set it when the button row triggers a popup such as a dialog or menu. | `undefined` |
60
79
 
61
80
  ### Slots
62
81
 
@@ -66,6 +85,7 @@ Slots are provided by `pie-list-item`.
66
85
  |---|---|
67
86
  | `leading` | Content displayed at the start of the item, before the text. Intended for a small icon or a media element (e.g. `pie-thumbnail`). If slotting `pie-thumbnail`, it MUST use `size="40"`; this is the only size that fits the list-item layout correctly. |
68
87
  | `trailing` | Content displayed at the end of the item, after the text. Intended for a small icon, a `pie-tag`, etc. Not rendered when `metaText` is set. |
88
+ | `link` | Only rendered when `interactionType="link"`. Slot a single **empty** anchor (`<a slot="link" href="...">`) here. It is stretched over the whole row so the entire item becomes the navigation target. The item supplies the anchor's accessible name and description from its text, so the anchor must contain no text of its own; apply any native anchor attributes you need (`href`, `target`, `rel`, and so on). See [Link items](#link-items). |
69
89
 
70
90
  The permitted slotted elements are: a PIE WEBC icon, `pie-tag`, `pie-thumbnail`, `pie-avatar`*, `pie-switch`, and native HTML radio/checkbox inputs.
71
91
  Some slotted content is designed with specific properties being used. So please read the entire readme to understand correct slot usage.
@@ -116,9 +136,36 @@ This component does not emit any custom events. To listen for interactions, trea
116
136
  </pie-list>
117
137
  ```
118
138
 
139
+ ### You never set ARIA on a list item or its slotted content
140
+
141
+ > [!IMPORTANT]
142
+ > You do **not** add any ARIA attributes to a `pie-list-item`, nor to the control, anchor, icon or media you slot into it. The item manages all of that for you, driven entirely by the `primaryText`, `secondaryText` and `metaText` props (and the `interactionType`). Just provide the text and slot the element in.
143
+
144
+ Specifically, for **every** interaction type the item takes care of:
145
+
146
+ - the **role** on the item itself (`listitem`, or `presentation` for radio/checkbox rows so the group owns the control directly) - you never write `role="..."`;
147
+ - the **accessible name and description** of whatever it hosts - you never write `aria-label`, `aria-labelledby`, `aria-description` or `aria-describedby` on the control, anchor or button;
148
+ - **hiding the duplicated visible text** from assistive technology (the item `aria-hidden`s its own text once the name/description have been mirrored onto the interactive element), so nothing is announced twice.
149
+
150
+ The one accessibility attribute you **do** own is the accessible name of the `pie-list` container itself (`aria-label` / `aria-labelledby`), because only you know what the list represents. See the note above.
151
+
152
+ ### How the naming is derived
153
+
154
+ When a `pie-list-item` hosts an interactive element (a slotted control set via `interactionType="radio" | "checkbox" | "switch"`, a slotted link anchor via `interactionType="link"`, or the button it renders for `interactionType="button"`) it generates that element's accessibility naming from its own text:
155
+
156
+ - `primaryText` becomes the element's **accessible name**;
157
+ - `secondaryText` and `metaText` become its **accessible description** (combined with a full stop when both are present);
158
+ - the visible text rendered inside the item is `aria-hidden`, so a screen reader announces the name and description once (via the interactive element) rather than twice.
159
+
160
+ How the naming reaches the element differs by type, but this is entirely internal - you never do it yourself. Selection controls receive it through a shared context and each applies it to the element that carries its role: `pie-radio` names its host, while `pie-checkbox` and `pie-switch` name their internal `input` (their host is role-less). A link anchor is a plain light-DOM element you slot (not a PIE control), so `pie-list-item` sets `aria-label` and `aria-description` on it directly. A `button` row's button is one the item renders itself, so it is named the same way from within the item.
161
+
162
+ **For slotted elements, anything you set yourself wins.** If a slotted control already has its own name (its `label` or `aria` prop), or the slotted link anchor already has an `aria-label`/`aria-labelledby` (or `aria-description`/`aria-describedby`), the item leaves it untouched and does not overwrite it. This is an escape hatch for the rare case that needs it, not something you normally reach for. (A `button` row's button is rendered and named by the item, so there is nothing for you to set.)
163
+
119
164
  ## Usage Examples
120
165
 
121
- **For HTML:**
166
+ The examples below use HTML. For framework-specific syntax, see [Framework variants](#framework-variants).
167
+
168
+ ### Basic list
122
169
 
123
170
  ```js
124
171
  // import as modules into a js file e.g. main.js
@@ -142,7 +189,7 @@ A basic list with primary, secondary and meta text (note the required `aria-labe
142
189
  <script type="module" src="/main.js"></script>
143
190
  ```
144
191
 
145
- **Leading and trailing content (icons):**
192
+ ### Leading and trailing content
146
193
 
147
194
  ```js
148
195
  import '@justeattakeaway/pie-icons-webc/dist/IconChevronRight.js';
@@ -157,7 +204,7 @@ import '@justeattakeaway/pie-icons-webc/dist/IconChevronRight.js';
157
204
  </pie-list>
158
205
  ```
159
206
 
160
- **Trailing `pie-tag`:**
207
+ ### Trailing tag
161
208
 
162
209
  ```js
163
210
  import '@justeattakeaway/pie-webc/components/tag.js';
@@ -171,7 +218,9 @@ import '@justeattakeaway/pie-webc/components/tag.js';
171
218
  </pie-list>
172
219
  ```
173
220
 
174
- **Meta text** (renders as trailing text; do not combine with the `trailing` slot):
221
+ ### Meta text
222
+
223
+ Renders as trailing text; do not combine with the `trailing` slot:
175
224
 
176
225
  ```html
177
226
  <pie-list>
@@ -182,7 +231,7 @@ import '@justeattakeaway/pie-webc/components/tag.js';
182
231
  </pie-list>
183
232
  ```
184
233
 
185
- **Bold primary text:**
234
+ ### Bold primary text
186
235
 
187
236
  ```html
188
237
  <pie-list>
@@ -190,7 +239,9 @@ import '@justeattakeaway/pie-webc/components/tag.js';
190
239
  </pie-list>
191
240
  ```
192
241
 
193
- **Compact list** (reduced height, see [rules](#usage-notes-and-rules)):
242
+ ### Compact list
243
+
244
+ Reduced height, see [rules](#usage-notes-and-rules):
194
245
 
195
246
  ```html
196
247
  <pie-list>
@@ -203,7 +254,9 @@ import '@justeattakeaway/pie-webc/components/tag.js';
203
254
  </pie-list>
204
255
  ```
205
256
 
206
- **Media (`pie-thumbnail`) in the leading slot:**
257
+ ### Media
258
+
259
+ Slot larger media such as `pie-thumbnail` into the `leading` slot:
207
260
 
208
261
  ```js
209
262
  import '@justeattakeaway/pie-webc/components/thumbnail.js';
@@ -227,7 +280,9 @@ import '@justeattakeaway/pie-webc/components/thumbnail.js';
227
280
  </pie-list>
228
281
  ```
229
282
 
230
- **Overriding alignment and padding** (via CSS variables):
283
+ ### Overriding alignment and padding
284
+
285
+ Via CSS variables:
231
286
 
232
287
  ```html
233
288
  <!-- Vertically centre the content of the items. `--list-item-alignment` must be set on the
@@ -248,20 +303,22 @@ import '@justeattakeaway/pie-webc/components/thumbnail.js';
248
303
 
249
304
  ### Selectable lists
250
305
 
251
- `pie-list` itself is a static container with no selection or keyboard behaviour. To build a **selectable** list, do **not** put the controls in `pie-list`. Instead, place `pie-list-item`s inside a [`pie-radio-group`](https://webc.pie.design/?path=/docs/components-radio-group--overview) (single-select) or a [`pie-checkbox-group`](https://webc.pie.design/?path=/docs/components-checkbox-group--overview) (multi-select), set each item's `selectionType`, and slot the control into the item's `leading` (or `trailing`) slot.
306
+ `pie-list` itself is a static container with no selection or keyboard behaviour. To build a **selectable** list, do **not** put the controls in `pie-list`. Instead, place `pie-list-item`s inside a [`pie-radio-group`](https://webc.pie.design/?path=/docs/components-radio-group--overview) (single-select) or a [`pie-checkbox-group`](https://webc.pie.design/?path=/docs/components-checkbox-group--overview) (multi-select), set each item's `interactionType`, and slot the control into the item's `leading` (or `trailing`) slot. Switches have no group, so `interactionType="switch"` rows go directly in a `pie-list`.
252
307
 
253
- #### The `selectionType` prop
308
+ #### The `interactionType` prop
254
309
 
255
- `selectionType` is what makes a row selectable. Set it to `radio` or `checkbox` and it drives the item to:
310
+ `interactionType` (set to `"radio"`, `"checkbox"` or `"switch"`) is what makes a row selectable. Setting it drives the item to:
256
311
 
257
312
  - take the correct **role** — `presentation` for `radio`/`checkbox` (so the group owns the controls directly), `listitem` for `switch` and `none`;
258
313
  - provide its `primaryText`, `secondaryText` and `metaText` as the slotted control's **accessible name and description** (and `aria-hidden` the now-duplicated visible text);
259
314
  - **forward a click** anywhere on the row to the control, so the whole row is a hit target;
260
- - show **hover and active** states on the row (suppressed when the control, or the group, is disabled).
315
+ - show **hover and active** states on the row (suppressed when the control is disabled, or when its group is disabled; switches have no group, so only per-row and per-switch disable applies).
261
316
 
262
- Provide the label through the item's `primaryText` — not as the control's own content. The group still owns the group-level semantics (its own role, shared `name`, selection coordination and group-disable); `selectionType` only governs the item. See the [`pie-radio-group`](https://webc.pie.design/?path=/docs/components-radio-group--overview) and [`pie-checkbox-group`](https://webc.pie.design/?path=/docs/components-checkbox-group--overview) docs for the group behaviour.
317
+ Provide the label through the item's `primaryText` — not as the control's own content. The group still owns the group-level semantics (its own role, shared `name`, selection coordination and group-disable); `interactionType` only governs the item. See the [`pie-radio-group`](https://webc.pie.design/?path=/docs/components-radio-group--overview) and [`pie-checkbox-group`](https://webc.pie.design/?path=/docs/components-checkbox-group--overview) docs for the group behaviour.
263
318
 
264
- Single-select (radios):
319
+ You do not add any ARIA to the item or the slotted control - `pie-list-item` derives the control's accessible name and description from your `primaryText`, `secondaryText` and `metaText`. See [Accessibility](#accessibility).
320
+
321
+ #### Single-select (radios)
265
322
 
266
323
  ```js
267
324
  import '@justeattakeaway/pie-webc/components/radio-group.js';
@@ -270,19 +327,19 @@ import '@justeattakeaway/pie-webc/components/list-item.js';
270
327
  ```
271
328
 
272
329
  ```html
273
- <!-- `selectionType="radio"` makes each row selectable and the group lays them out as a divided
330
+ <!-- `interactionType="radio"` makes each row selectable and the group lays them out as a divided
274
331
  list. `value` on the group selects the matching radio (here, Express). -->
275
332
  <pie-radio-group name="delivery" value="express">
276
- <pie-list-item selectionType="radio" primaryText="Standard delivery" secondaryText="3 to 5 working days" metaText="Free">
333
+ <pie-list-item interactionType="radio" primaryText="Standard delivery" secondaryText="3 to 5 working days" metaText="Free">
277
334
  <pie-radio slot="leading" value="standard"></pie-radio>
278
335
  </pie-list-item>
279
- <pie-list-item selectionType="radio" primaryText="Express delivery" secondaryText="Next working day" metaText="£4.99">
336
+ <pie-list-item interactionType="radio" primaryText="Express delivery" secondaryText="Next working day" metaText="£4.99">
280
337
  <pie-radio slot="leading" value="express"></pie-radio>
281
338
  </pie-list-item>
282
339
  </pie-radio-group>
283
340
  ```
284
341
 
285
- Multi-select (checkboxes):
342
+ #### Multi-select (checkboxes)
286
343
 
287
344
  ```js
288
345
  import '@justeattakeaway/pie-webc/components/checkbox-group.js';
@@ -292,15 +349,175 @@ import '@justeattakeaway/pie-webc/components/list-item.js';
292
349
 
293
350
  ```html
294
351
  <pie-checkbox-group>
295
- <pie-list-item selectionType="checkbox" primaryText="Cheese" secondaryText="Extra mature" metaText="Free">
352
+ <pie-list-item interactionType="checkbox" primaryText="Cheese" secondaryText="Extra mature" metaText="Free">
296
353
  <pie-checkbox slot="leading" name="cheese"></pie-checkbox>
297
354
  </pie-list-item>
298
- <pie-list-item selectionType="checkbox" primaryText="Pepperoni" secondaryText="Spicy">
355
+ <pie-list-item interactionType="checkbox" primaryText="Pepperoni" secondaryText="Spicy">
299
356
  <pie-checkbox slot="leading" name="pepperoni"></pie-checkbox>
300
357
  </pie-list-item>
301
358
  </pie-checkbox-group>
302
359
  ```
303
360
 
361
+ #### Independent toggles (switches)
362
+
363
+ There is no group, so the rows go in a `pie-list` (which needs its own accessible name), and the switch sits in the `trailing` slot. Set `disabled` on the `pie-list-item` alongside any disabled `pie-switch`:
364
+
365
+ ```js
366
+ import '@justeattakeaway/pie-webc/components/list.js';
367
+ import '@justeattakeaway/pie-webc/components/list-item.js';
368
+ import '@justeattakeaway/pie-webc/components/switch.js';
369
+ ```
370
+
371
+ ```html
372
+ <pie-list aria-label="Notification settings">
373
+ <pie-list-item interactionType="switch" primaryText="Email" secondaryText="Order updates and receipts">
374
+ <pie-switch slot="trailing"></pie-switch>
375
+ </pie-list-item>
376
+ <pie-list-item interactionType="switch" primaryText="Push notifications">
377
+ <pie-switch slot="trailing"></pie-switch>
378
+ </pie-list-item>
379
+ <pie-list-item interactionType="switch" primaryText="SMS" disabled>
380
+ <pie-switch slot="trailing" disabled></pie-switch>
381
+ </pie-list-item>
382
+ </pie-list>
383
+ ```
384
+
385
+ Each `pie-switch` manages its own state and emits a native `change` event when toggled. Listen on the switch itself, or on the `pie-list` since the event bubbles. Set a switch's initial state with its `checked` prop. There is no group coordinating them, so each switch is independent.
386
+
387
+ ### Link items
388
+
389
+ Set `interactionType="link"` and slot a single **empty** anchor into the `link` slot to turn the whole row into one navigation link. The anchor is stretched over the entire item (`position: absolute; inset: 0`), so a click or tap anywhere on the row follows the link, and keyboard focus lands on the row as a whole (the focus ring hugs the row).
390
+
391
+ Because the anchor is empty, `pie-list-item` names it from its own text: `primaryText` becomes the anchor's `aria-label` and `secondaryText`/`metaText` its `aria-description`, and the visible text is hidden from assistive technology so nothing is announced twice (see [You never set ARIA on a list item or its slotted content](#you-never-set-aria-on-a-list-item-or-its-slotted-content)). If you set your own `aria-label`/`aria-labelledby` (or `aria-description`/`aria-describedby`) on the anchor, that wins and the item leaves it untouched. Apply whatever native anchor attributes you need (`href`, `target`, `rel`, `download`, and so on); the only requirement is that the anchor **contains no text** (it must stay empty for the naming to work). Give the `pie-list` its own accessible name (e.g. `aria-label`).
392
+
393
+ ```js
394
+ import '@justeattakeaway/pie-webc/components/list.js';
395
+ import '@justeattakeaway/pie-webc/components/list-item.js';
396
+ ```
397
+
398
+ ```html
399
+ <pie-list aria-label="Manage your restaurant">
400
+ <pie-list-item interactionType="link" primaryText="Orders" secondaryText="View and manage live orders" metaText="12 active">
401
+ <a slot="link" href="/orders"></a>
402
+ </pie-list-item>
403
+ <pie-list-item interactionType="link" primaryText="Menu" secondaryText="Edit items, prices and photos">
404
+ <a slot="link" href="/menu"></a>
405
+ </pie-list-item>
406
+ </pie-list>
407
+ ```
408
+
409
+ You are not limited to a raw `<a>`. Any component that renders an anchor and forwards the `slot` attribute works the same way, so in **Next.js you can slot the `next/link` `<Link>` component** (and in Nuxt, `<NuxtLink>`). Slot it into `link`, keep it empty, and `pie-list-item` names it exactly as it names a raw anchor:
410
+
411
+ ```jsx
412
+ // Next.js
413
+ import Link from 'next/link';
414
+ import { PieListItem } from '@justeattakeaway/pie-webc/react/list-item.js';
415
+
416
+ <PieListItem interactionType="link" primaryText="Orders" secondaryText="View and manage live orders">
417
+ <Link slot="link" href="/orders" />
418
+ </PieListItem>
419
+ ```
420
+
421
+ #### Indicating the current page
422
+
423
+ To highlight the active link in a navigation list, combine `isBold` on the `pie-list-item` with `aria-current="page"` on the slotted anchor. `isBold` gives the active item a heavier weight visually; `aria-current="page"` communicates the current page to assistive technology. `pie-list-item` only manages the anchor's `aria-label` and `aria-description`, so `aria-current` does not conflict with the item's naming logic and is safe to set yourself.
424
+
425
+ You can also slot a tick icon into the `trailing` slot of the active item as an additional visual indicator. PIE icons render with `role="presentation"` on their SVG, so they are already hidden from assistive technology and no extra `aria-hidden` is needed.
426
+
427
+ ```js
428
+ import '@justeattakeaway/pie-icons-webc/dist/IconCheck.js';
429
+ ```
430
+
431
+ ```html
432
+ <pie-list aria-label="Account navigation">
433
+ <pie-list-item interactionType="link" primaryText="Overview">
434
+ <a slot="link" href="/account"></a>
435
+ </pie-list-item>
436
+ <pie-list-item interactionType="link" isBold primaryText="Orders">
437
+ <a slot="link" href="/account/orders" aria-current="page"></a>
438
+ <icon-check slot="trailing"></icon-check>
439
+ </pie-list-item>
440
+ <pie-list-item interactionType="link" primaryText="Payment methods">
441
+ <a slot="link" href="/account/payment"></a>
442
+ </pie-list-item>
443
+ <pie-list-item interactionType="link" primaryText="Addresses">
444
+ <a slot="link" href="/account/addresses"></a>
445
+ </pie-list-item>
446
+ </pie-list>
447
+ ```
448
+
449
+ In React, pass `isBold` as a prop and `aria-current="page"` directly to the `<Link>` component (or raw anchor):
450
+
451
+ ```jsx
452
+ import { IconCheck } from '@justeattakeaway/pie-icons-webc/dist/IconCheck.js';
453
+
454
+ <PieListItem interactionType="link" isBold primaryText="Orders">
455
+ <Link slot="link" href="/account/orders" aria-current="page" />
456
+ <IconCheck slot="trailing" />
457
+ </PieListItem>
458
+ ```
459
+
460
+ ### Button items
461
+
462
+ Set `interactionType="button"` to turn the whole row into one button. Use this for an **in-page action** (opening a dialog, running a command); use [`link`](#link-items) when the row navigates to another page. Unlike a link, **you do not slot anything** - the item renders the interactive element for you: an invisible, row-sized native `<button type="button">` stretched over the row. A click or tap anywhere on the row activates it, and keyboard focus lands on the row as a whole (the focus ring hugs the row). It is just an action trigger, **not a form control** - it is `type="button"`, so it never submits a form, and it carries no value.
463
+
464
+ The item names the button from its own text: `primaryText` becomes the button's `aria-label` and `secondaryText`/`metaText` its `aria-description`, and the visible text is hidden from assistive technology so nothing is announced twice (see [You never set ARIA on a list item or its slotted content](#you-never-set-aria-on-a-list-item-or-its-slotted-content)).
465
+
466
+ **Handle activation by listening for `click`** on the `pie-list-item` (or on the `pie-list`, since the event bubbles). Because it is a real `<button>`, activation is native: both pointer taps and keyboard (Enter, and Space) fire a `click`, so a single `click` listener covers every input. The row shows a pressed tint while it is pressed - by pointer, or while Space is held on the focused row - and the focus ring on keyboard focus. Give the `pie-list` its own accessible name (e.g. `aria-label`).
467
+
468
+ **When the button opens a popup (dialog, menu, etc.)**, pass the `aria` prop with `button.haspopup` set to the popup type. This is forwarded to the internal `<button>` so assistive technology announces it correctly:
469
+
470
+ ```html
471
+ <!-- A row that opens a dialog -->
472
+ <pie-list-item
473
+ interactionType="button"
474
+ primaryText="Delete account"
475
+ secondaryText="Permanently remove your account"
476
+ .aria=${{ button: { haspopup: 'dialog' } }}>
477
+ </pie-list-item>
478
+ ```
479
+
480
+ In React:
481
+
482
+ ```jsx
483
+ <PieListItem
484
+ interactionType="button"
485
+ primaryText="Delete account"
486
+ secondaryText="Permanently remove your account"
487
+ aria={{ button: { haspopup: 'dialog' } }}
488
+ onClick={openConfirmDialog}
489
+ />
490
+ ```
491
+
492
+ ```js
493
+ import '@justeattakeaway/pie-webc/components/list.js';
494
+ import '@justeattakeaway/pie-webc/components/list-item.js';
495
+ ```
496
+
497
+ ```html
498
+ <pie-list aria-label="Account actions">
499
+ <pie-list-item interactionType="button" primaryText="Edit profile" secondaryText="Update your name and photo"></pie-list-item>
500
+ <pie-list-item interactionType="button" primaryText="Sign out" secondaryText="End your session on this device"></pie-list-item>
501
+ </pie-list>
502
+
503
+ <script type="module">
504
+ document.querySelector('pie-list').addEventListener('click', (event) => {
505
+ const item = event.target.closest('pie-list-item');
506
+ if (item) runActionFor(item.primaryText);
507
+ });
508
+ </script>
509
+ ```
510
+
511
+ In React, attach an `onClick` to the item:
512
+
513
+ ```jsx
514
+ import { PieListItem } from '@justeattakeaway/pie-webc/react/list-item.js';
515
+
516
+ <PieListItem interactionType="button" primaryText="Edit profile" secondaryText="Update your name and photo" onClick={openProfileEditor} />
517
+ ```
518
+
519
+ ### Framework variants
520
+
304
521
  **For Native JS Applications, Vue, Angular, Svelte etc.:**
305
522
 
306
523
  ```js
@@ -68,11 +68,11 @@
68
68
  "declarations": [
69
69
  {
70
70
  "kind": "variable",
71
- "name": "selectionTypes",
71
+ "name": "interactionTypes",
72
72
  "type": {
73
- "text": "['none', 'radio', 'checkbox', 'switch']"
73
+ "text": "['none', 'radio', 'checkbox', 'switch', 'link', 'button']"
74
74
  },
75
- "default": "['none', 'radio', 'checkbox', 'switch']"
75
+ "default": "['none', 'radio', 'checkbox', 'switch', 'link', 'button']"
76
76
  },
77
77
  {
78
78
  "kind": "variable",
@@ -80,15 +80,15 @@
80
80
  "type": {
81
81
  "text": "DefaultProps"
82
82
  },
83
- "default": "{ isCompact: false, isBold: false, hasMedia: false, selectionType: 'none', disabled: false, }"
83
+ "default": "{ isCompact: false, isBold: false, hasMedia: false, interactionType: 'none', disabled: false, }"
84
84
  }
85
85
  ],
86
86
  "exports": [
87
87
  {
88
88
  "kind": "js",
89
- "name": "selectionTypes",
89
+ "name": "interactionTypes",
90
90
  "declaration": {
91
- "name": "selectionTypes",
91
+ "name": "interactionTypes",
92
92
  "module": "src/pie-list-item/defs.js"
93
93
  }
94
94
  },
@@ -118,6 +118,10 @@
118
118
  {
119
119
  "description": "Content shown at the end of the item. Mutually exclusive with `metaText`.",
120
120
  "name": "trailing"
121
+ },
122
+ {
123
+ "description": "Rendered only when `interactionType=\"link\"`. Slot one empty anchor (e.g. `<a slot=\"link\" href=\"/x\">`) stretched over the whole row as the link target. Must contain no text.",
124
+ "name": "link"
121
125
  }
122
126
  ],
123
127
  "members": [
@@ -162,14 +166,22 @@
162
166
  },
163
167
  {
164
168
  "kind": "field",
165
- "name": "selectionType",
166
- "attribute": "selectionType"
169
+ "name": "interactionType",
170
+ "attribute": "interactionType"
167
171
  },
168
172
  {
169
173
  "kind": "field",
170
174
  "name": "disabled",
171
175
  "attribute": "disabled"
172
176
  },
177
+ {
178
+ "kind": "field",
179
+ "name": "aria",
180
+ "type": {
181
+ "text": "ListItemProps['aria']"
182
+ },
183
+ "attribute": "aria"
184
+ },
173
185
  {
174
186
  "kind": "field",
175
187
  "name": "_parentDisabled",
@@ -194,7 +206,34 @@
194
206
  },
195
207
  {
196
208
  "kind": "field",
197
- "name": "_hasExplicitRole",
209
+ "name": "_managedRole",
210
+ "type": {
211
+ "text": "string | null"
212
+ },
213
+ "privacy": "private",
214
+ "default": "null"
215
+ },
216
+ {
217
+ "kind": "field",
218
+ "name": "_linkAriaCaptured",
219
+ "type": {
220
+ "text": "boolean"
221
+ },
222
+ "privacy": "private",
223
+ "default": "false"
224
+ },
225
+ {
226
+ "kind": "field",
227
+ "name": "_consumerNamedLink",
228
+ "type": {
229
+ "text": "boolean"
230
+ },
231
+ "privacy": "private",
232
+ "default": "false"
233
+ },
234
+ {
235
+ "kind": "field",
236
+ "name": "_consumerDescribedLink",
198
237
  "type": {
199
238
  "text": "boolean"
200
239
  },
@@ -210,6 +249,42 @@
210
249
  "privacy": "private",
211
250
  "readonly": true
212
251
  },
252
+ {
253
+ "kind": "field",
254
+ "name": "_isLinkRow",
255
+ "type": {
256
+ "text": "boolean"
257
+ },
258
+ "privacy": "private",
259
+ "readonly": true
260
+ },
261
+ {
262
+ "kind": "field",
263
+ "name": "_isButtonRow",
264
+ "type": {
265
+ "text": "boolean"
266
+ },
267
+ "privacy": "private",
268
+ "readonly": true
269
+ },
270
+ {
271
+ "kind": "field",
272
+ "name": "_isOverlayRow",
273
+ "type": {
274
+ "text": "boolean"
275
+ },
276
+ "privacy": "private",
277
+ "readonly": true
278
+ },
279
+ {
280
+ "kind": "field",
281
+ "name": "_providesAria",
282
+ "type": {
283
+ "text": "boolean"
284
+ },
285
+ "privacy": "private",
286
+ "readonly": true
287
+ },
213
288
  {
214
289
  "kind": "field",
215
290
  "name": "_ownedByGroup",
@@ -246,7 +321,7 @@
246
321
  "text": "void"
247
322
  }
248
323
  },
249
- "description": "Sets the item's role from `selectionType`: `presentation` for radio/checkbox (so the group\nowns those controls directly), otherwise `listitem` (static items and switches). A role set\nexplicitly by the consumer is left untouched."
324
+ "description": "Sets the item's role from `interactionType`: `presentation` for radio/checkbox (so the group\nowns those controls directly), otherwise `listitem` (static items, switches, links and\nbuttons). A role set explicitly by the consumer is left untouched, even if it is added or\nremoved dynamically after the element connects."
250
325
  },
251
326
  {
252
327
  "kind": "field",
@@ -255,7 +330,7 @@
255
330
  "text": "ContextualAria | undefined"
256
331
  },
257
332
  "privacy": "private",
258
- "description": "The accessible name and description this item provides to its slotted control, stitched from\nits text. Only provided in a selectable list. Each control consumes this via `ariaContext`\nand decides how to apply it: `pie-radio` names its host (which carries `role=\"radio\"`),\nwhile `pie-checkbox` / `pie-switch` name their internal input (their host is role-less, so\n`aria-label` there would be invalid). Context reaches across the shadow boundary that\n`aria-labelledby` cannot.",
333
+ "description": "The accessible name and description this item provides to its interactive element, stitched\nfrom its text. Selection controls consume it via `ariaContext` (each applying it to the\nelement that carries its role); the action button of a `button` row takes it directly as\n`aria-label`/`aria-description` in the template; a link anchor takes it via `_applyLinkAria`.",
259
334
  "readonly": true
260
335
  },
261
336
  {
@@ -264,6 +339,17 @@
264
339
  "privacy": "private",
265
340
  "description": "Forwards a click anywhere on the row to the slotted control, so the whole item is a hit\ntarget. Skips clicks that already landed on the control, to avoid a double toggle."
266
341
  },
342
+ {
343
+ "kind": "method",
344
+ "name": "_applyLinkAria",
345
+ "privacy": "private",
346
+ "return": {
347
+ "type": {
348
+ "text": "void"
349
+ }
350
+ },
351
+ "description": "On a link row, names the slotted (empty) anchor from the item's own text: the parent owns the\nchild's aria. The anchor is plain light-DOM, so it cannot reference the shadow-DOM text via\n`aria-labelledby`; and it is not a PIE control that consumes `ariaContext`, so we set the\nattributes imperatively. The visible text is `aria-hidden` in the template, so nothing is\nannounced twice. (A `button` row names its own rendered element in the template instead.)\n\nA consumer-provided name/description always wins: we capture (once) whether the anchor already\ncarries `aria-label`/`aria-labelledby` or `aria-description`/`aria-describedby` and, if so,\nleave that attribute untouched. Otherwise we keep our generated value in sync with the text."
352
+ },
267
353
  {
268
354
  "kind": "method",
269
355
  "name": "_renderSecondaryText"
@@ -271,6 +357,11 @@
271
357
  {
272
358
  "kind": "method",
273
359
  "name": "_renderTrailingContent"
360
+ },
361
+ {
362
+ "kind": "method",
363
+ "name": "_renderActionButton",
364
+ "description": "The interactive target for a `button` row: an empty, native `<button>` stretched invisibly\nover the whole row (see the SCSS). The item renders and owns it - rather than the consumer\nslotting one - so it can name it from its own text and keep its attributes private (it is just\nan action box, not a configurable control). Being a real button, activation is native: pointer\nclicks and keyboard (Enter/Space) both fire a `click` that bubbles out, so consumers listen for\n`click` on the `pie-list-item`. It is `type=\"button\"`, so it never submits a form."
274
365
  }
275
366
  ],
276
367
  "attributes": [
@@ -308,12 +399,19 @@
308
399
  "fieldName": "hasMedia"
309
400
  },
310
401
  {
311
- "name": "selectionType",
312
- "fieldName": "selectionType"
402
+ "name": "interactionType",
403
+ "fieldName": "interactionType"
313
404
  },
314
405
  {
315
406
  "name": "disabled",
316
407
  "fieldName": "disabled"
408
+ },
409
+ {
410
+ "name": "aria",
411
+ "type": {
412
+ "text": "ListItemProps['aria']"
413
+ },
414
+ "fieldName": "aria"
317
415
  }
318
416
  ],
319
417
  "superclass": {
@@ -4,7 +4,7 @@ const t = class t extends i {
4
4
  this.getAttribute("v") || this.setAttribute("v", t.v);
5
5
  }
6
6
  };
7
- t.v = "0.3.0";
7
+ t.v = "0.5.0";
8
8
  let s = t;
9
9
  export {
10
10
  s as P