@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
package/README.md
CHANGED
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
- [Multi-select (checkboxes)](#multi-select-checkboxes)
|
|
38
38
|
- [Independent toggles (switches)](#independent-toggles-switches)
|
|
39
39
|
- [Link items](#link-items)
|
|
40
|
+
- [Indicating the current page](#indicating-the-current-page)
|
|
41
|
+
- [Button items](#button-items)
|
|
40
42
|
- [Framework variants](#framework-variants)
|
|
41
43
|
- [Usage Notes and Rules](#usage-notes-and-rules)
|
|
42
44
|
- [Questions and Support](#questions-and-support)
|
|
@@ -71,8 +73,9 @@ Ideally, you should install the component using the **`@justeattakeaway/pie-webc
|
|
|
71
73
|
| `isCompact` | `true`, `false` | Decreases the item height to save vertical space. See the [rules](#usage-notes-and-rules) below. | `false` |
|
|
72
74
|
| `isBold` | `true`, `false` | Sets the primary text to a bold font-weight. | `false` |
|
|
73
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` |
|
|
74
|
-
| `interactionType` | `"none"`, `"radio"`, `"checkbox"`, `"switch"`, `"link"` | 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="...">`). See [Selectable lists](#selectable-lists)
|
|
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"` |
|
|
75
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` |
|
|
76
79
|
|
|
77
80
|
### Slots
|
|
78
81
|
|
|
@@ -141,22 +144,22 @@ This component does not emit any custom events. To listen for interactions, trea
|
|
|
141
144
|
Specifically, for **every** interaction type the item takes care of:
|
|
142
145
|
|
|
143
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="..."`;
|
|
144
|
-
- the **accessible name and description** of whatever it hosts - you never write `aria-label`, `aria-labelledby`, `aria-description` or `aria-describedby` on the control or
|
|
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;
|
|
145
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.
|
|
146
149
|
|
|
147
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.
|
|
148
151
|
|
|
149
152
|
### How the naming is derived
|
|
150
153
|
|
|
151
|
-
When a `pie-list-item` hosts an interactive element (a slotted control set via `interactionType="radio" | "checkbox" | "switch"`,
|
|
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:
|
|
152
155
|
|
|
153
|
-
- `primaryText` becomes the
|
|
156
|
+
- `primaryText` becomes the element's **accessible name**;
|
|
154
157
|
- `secondaryText` and `metaText` become its **accessible description** (combined with a full stop when both are present);
|
|
155
|
-
- the visible text rendered inside the item is `aria-hidden`, so a screen reader announces the name and description once (via the
|
|
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.
|
|
156
159
|
|
|
157
|
-
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 (not a PIE control), so `pie-list-item` sets `aria-label` and `aria-description` on it directly.
|
|
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.
|
|
158
161
|
|
|
159
|
-
**
|
|
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.)
|
|
160
163
|
|
|
161
164
|
## Usage Examples
|
|
162
165
|
|
|
@@ -415,6 +418,104 @@ import { PieListItem } from '@justeattakeaway/pie-webc/react/list-item.js';
|
|
|
415
418
|
</PieListItem>
|
|
416
419
|
```
|
|
417
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
|
+
|
|
418
519
|
### Framework variants
|
|
419
520
|
|
|
420
521
|
**For Native JS Applications, Vue, Angular, Svelte etc.:**
|
package/custom-elements.json
CHANGED
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
"kind": "variable",
|
|
71
71
|
"name": "interactionTypes",
|
|
72
72
|
"type": {
|
|
73
|
-
"text": "['none', 'radio', 'checkbox', 'switch', 'link']"
|
|
73
|
+
"text": "['none', 'radio', 'checkbox', 'switch', 'link', 'button']"
|
|
74
74
|
},
|
|
75
|
-
"default": "['none', 'radio', 'checkbox', 'switch', 'link']"
|
|
75
|
+
"default": "['none', 'radio', 'checkbox', 'switch', 'link', 'button']"
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
"kind": "variable",
|
|
@@ -174,6 +174,14 @@
|
|
|
174
174
|
"name": "disabled",
|
|
175
175
|
"attribute": "disabled"
|
|
176
176
|
},
|
|
177
|
+
{
|
|
178
|
+
"kind": "field",
|
|
179
|
+
"name": "aria",
|
|
180
|
+
"type": {
|
|
181
|
+
"text": "ListItemProps['aria']"
|
|
182
|
+
},
|
|
183
|
+
"attribute": "aria"
|
|
184
|
+
},
|
|
177
185
|
{
|
|
178
186
|
"kind": "field",
|
|
179
187
|
"name": "_parentDisabled",
|
|
@@ -250,6 +258,24 @@
|
|
|
250
258
|
"privacy": "private",
|
|
251
259
|
"readonly": true
|
|
252
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
|
+
},
|
|
253
279
|
{
|
|
254
280
|
"kind": "field",
|
|
255
281
|
"name": "_providesAria",
|
|
@@ -295,7 +321,7 @@
|
|
|
295
321
|
"text": "void"
|
|
296
322
|
}
|
|
297
323
|
},
|
|
298
|
-
"description": "Sets the item's role from `interactionType`: `presentation` for radio/checkbox (so the group\nowns those controls directly), otherwise `listitem` (static items, switches and
|
|
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."
|
|
299
325
|
},
|
|
300
326
|
{
|
|
301
327
|
"kind": "field",
|
|
@@ -304,7 +330,7 @@
|
|
|
304
330
|
"text": "ContextualAria | undefined"
|
|
305
331
|
},
|
|
306
332
|
"privacy": "private",
|
|
307
|
-
"description": "The accessible name and description this item provides to its
|
|
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`.",
|
|
308
334
|
"readonly": true
|
|
309
335
|
},
|
|
310
336
|
{
|
|
@@ -322,7 +348,7 @@
|
|
|
322
348
|
"text": "void"
|
|
323
349
|
}
|
|
324
350
|
},
|
|
325
|
-
"description": "
|
|
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."
|
|
326
352
|
},
|
|
327
353
|
{
|
|
328
354
|
"kind": "method",
|
|
@@ -331,6 +357,11 @@
|
|
|
331
357
|
{
|
|
332
358
|
"kind": "method",
|
|
333
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."
|
|
334
365
|
}
|
|
335
366
|
],
|
|
336
367
|
"attributes": [
|
|
@@ -374,6 +405,13 @@
|
|
|
374
405
|
{
|
|
375
406
|
"name": "disabled",
|
|
376
407
|
"fieldName": "disabled"
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"name": "aria",
|
|
411
|
+
"type": {
|
|
412
|
+
"text": "ListItemProps['aria']"
|
|
413
|
+
},
|
|
414
|
+
"fieldName": "aria"
|
|
377
415
|
}
|
|
378
416
|
],
|
|
379
417
|
"superclass": {
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { unsafeCSS as x, isServer as y, html as d, 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-DQQMEr70.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 P 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 S = ["none", "radio", "checkbox", "switch", "link", "button"], h = {
|
|
125
|
+
isCompact: !1,
|
|
126
|
+
isBold: !1,
|
|
127
|
+
hasMedia: !1,
|
|
128
|
+
interactionType: "none",
|
|
129
|
+
disabled: !1
|
|
130
|
+
}, D = "*,*:after,*:before{box-sizing:inherit}:host{--list-item-inline-padding: var(--dt-spacing-d);--list-item-alignment: flex-start;--list-item-border: none;display:block}.c-listItem-container{--list-item-block-padding: var(--dt-spacing-d);--list-item-block-end-padding: calc(var(--list-item-block-padding) - var(--list-item-size-modifier));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-end-padding, var(--list-item-block-padding));display:flex;align-items:var(--list-item-alignment);gap:var(--dt-spacing-c);min-height:56px;border-bottom:var(--list-item-border)}.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)}";
|
|
131
|
+
var $ = Object.defineProperty, z = Object.getOwnPropertyDescriptor, l = (i, t, e, o) => {
|
|
132
|
+
for (var s = o > 1 ? void 0 : o ? z(t, e) : t, r = i.length - 1, n; r >= 0; r--)
|
|
133
|
+
(n = i[r]) && (s = (o ? n(t, e, s) : n(s)) || s);
|
|
134
|
+
return o && s && $(t, e, s), s;
|
|
135
|
+
};
|
|
136
|
+
const q = "pie-list-item";
|
|
137
|
+
let a = class extends A {
|
|
138
|
+
constructor() {
|
|
139
|
+
super(...arguments), this.isCompact = h.isCompact, this.isBold = h.isBold, this.hasMedia = h.hasMedia, this.interactionType = h.interactionType, this.disabled = h.disabled, this._parentDisabled = !1, this._ariaProvider = y ? void 0 : new P(this, { context: w }), this._managedRole = null, this._linkAriaCaptured = !1, this._consumerNamedLink = !1, this._consumerDescribedLink = !1, this._handleHostClick = (i) => {
|
|
140
|
+
if (!this._isSelectable || this._isDisabled) return;
|
|
141
|
+
const t = this._control;
|
|
142
|
+
!t || i.composedPath().includes(t) || (t.click(), t.focus());
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// A selection row hosts a slotted control (radio/checkbox/switch) and makes the whole row a
|
|
146
|
+
// toggle target. `link`/`button` are interactive too, but they are single navigation/action
|
|
147
|
+
// targets rather than selection controls, so they are deliberately excluded here.
|
|
148
|
+
get _isSelectable() {
|
|
149
|
+
return this.interactionType === "radio" || this.interactionType === "checkbox" || this.interactionType === "switch";
|
|
150
|
+
}
|
|
151
|
+
get _isLinkRow() {
|
|
152
|
+
return this.interactionType === "link";
|
|
153
|
+
}
|
|
154
|
+
get _isButtonRow() {
|
|
155
|
+
return this.interactionType === "button";
|
|
156
|
+
}
|
|
157
|
+
// A link and a button row share the same visual mechanic: an empty element stretched over the
|
|
158
|
+
// whole row as the interactive target, named by the item from its own text. They differ in who
|
|
159
|
+
// owns that element - a link is a consumer-slotted anchor, a button is one the item renders.
|
|
160
|
+
get _isOverlayRow() {
|
|
161
|
+
return this._isLinkRow || this._isButtonRow;
|
|
162
|
+
}
|
|
163
|
+
// True when the item lends its text as an interactive element's accessible name/description:
|
|
164
|
+
// selectable rows (radio/checkbox/switch) and overlay rows (link anchor / action button).
|
|
165
|
+
get _providesAria() {
|
|
166
|
+
return this._isSelectable || this._isOverlayRow;
|
|
167
|
+
}
|
|
168
|
+
// radio/checkbox are owned by a selection group, which is why the item becomes `presentation`
|
|
169
|
+
// (so the group owns the controls directly) and the radio is named on its host. A switch has
|
|
170
|
+
// no group, so the item stays a `listitem`.
|
|
171
|
+
get _ownedByGroup() {
|
|
172
|
+
return this.interactionType === "radio" || this.interactionType === "checkbox";
|
|
173
|
+
}
|
|
174
|
+
// True when the row should be treated as disabled: either its own `disabled` prop is set, or the
|
|
175
|
+
// containing group is disabled (provided via context). Declarative and reactive, so the styling
|
|
176
|
+
// and row-click guard react without observing the slotted control.
|
|
177
|
+
get _isDisabled() {
|
|
178
|
+
return this.disabled || this._parentDisabled;
|
|
179
|
+
}
|
|
180
|
+
// The interactive control (radio/checkbox/switch) slotted into this item, if any. Used only by
|
|
181
|
+
// the client-side row-click handler.
|
|
182
|
+
get _control() {
|
|
183
|
+
return this.querySelector("pie-radio, pie-checkbox, pie-switch");
|
|
184
|
+
}
|
|
185
|
+
connectedCallback() {
|
|
186
|
+
super.connectedCallback(), this._abortController = new AbortController(), this.addEventListener("click", this._handleHostClick, { signal: this._abortController.signal });
|
|
187
|
+
}
|
|
188
|
+
disconnectedCallback() {
|
|
189
|
+
super.disconnectedCallback(), this._abortController.abort();
|
|
190
|
+
}
|
|
191
|
+
updated() {
|
|
192
|
+
var i;
|
|
193
|
+
this._applyRole(), (i = this._ariaProvider) == null || i.setValue(this._providedAria), this._applyLinkAria();
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Sets the item's role from `interactionType`: `presentation` for radio/checkbox (so the group
|
|
197
|
+
* owns those controls directly), otherwise `listitem` (static items, switches, links and
|
|
198
|
+
* buttons). A role set explicitly by the consumer is left untouched, even if it is added or
|
|
199
|
+
* removed dynamically after the element connects.
|
|
200
|
+
*/
|
|
201
|
+
_applyRole() {
|
|
202
|
+
const i = this.getAttribute("role");
|
|
203
|
+
if (i !== null && i !== this._managedRole) return;
|
|
204
|
+
const t = this._ownedByGroup ? "presentation" : "listitem";
|
|
205
|
+
this._managedRole = t, this.setAttribute("role", t);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The accessible name and description this item provides to its interactive element, stitched
|
|
209
|
+
* from its text. Selection controls consume it via `ariaContext` (each applying it to the
|
|
210
|
+
* element that carries its role); the action button of a `button` row takes it directly as
|
|
211
|
+
* `aria-label`/`aria-description` in the template; a link anchor takes it via `_applyLinkAria`.
|
|
212
|
+
*/
|
|
213
|
+
get _providedAria() {
|
|
214
|
+
if (!this._providesAria) return;
|
|
215
|
+
const i = [this.secondaryText, this.metaText].filter(Boolean).join(". ");
|
|
216
|
+
return { label: this.primaryText, description: i || void 0 };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* On a link row, names the slotted (empty) anchor from the item's own text: the parent owns the
|
|
220
|
+
* child's aria. The anchor is plain light-DOM, so it cannot reference the shadow-DOM text via
|
|
221
|
+
* `aria-labelledby`; and it is not a PIE control that consumes `ariaContext`, so we set the
|
|
222
|
+
* attributes imperatively. The visible text is `aria-hidden` in the template, so nothing is
|
|
223
|
+
* announced twice. (A `button` row names its own rendered element in the template instead.)
|
|
224
|
+
*
|
|
225
|
+
* A consumer-provided name/description always wins: we capture (once) whether the anchor already
|
|
226
|
+
* carries `aria-label`/`aria-labelledby` or `aria-description`/`aria-describedby` and, if so,
|
|
227
|
+
* leave that attribute untouched. Otherwise we keep our generated value in sync with the text.
|
|
228
|
+
*/
|
|
229
|
+
_applyLinkAria() {
|
|
230
|
+
if (!this._isLinkRow) {
|
|
231
|
+
if (this._linkAriaCaptured) {
|
|
232
|
+
const e = this.querySelector('a[slot="link"]');
|
|
233
|
+
e && (this._consumerNamedLink || e.removeAttribute("aria-label"), this._consumerDescribedLink || e.removeAttribute("aria-description")), this._linkAriaCaptured = !1, this._consumerNamedLink = !1, this._consumerDescribedLink = !1;
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const i = this.querySelector('a[slot="link"]');
|
|
238
|
+
if (!i) return;
|
|
239
|
+
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);
|
|
240
|
+
const t = this._providedAria;
|
|
241
|
+
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"));
|
|
242
|
+
}
|
|
243
|
+
_renderSecondaryText() {
|
|
244
|
+
const { secondaryText: i } = this;
|
|
245
|
+
return i ? d`<span class="c-listItem-secondaryText">${i}</span>` : p;
|
|
246
|
+
}
|
|
247
|
+
// metaText is another form of trailing content. The component can only ever display either slotted trailing content or the metaText string
|
|
248
|
+
_renderTrailingContent() {
|
|
249
|
+
const { metaText: i } = this;
|
|
250
|
+
return i ? d`<span class="c-listItem-metaText c-listItem-trailing" aria-hidden=${this._providesAria ? "true" : p}>${i}</span>` : d`<div class="c-listItem-trailing"><slot name="trailing"></slot></div>`;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The interactive target for a `button` row: an empty, native `<button>` stretched invisibly
|
|
254
|
+
* over the whole row (see the SCSS). The item renders and owns it - rather than the consumer
|
|
255
|
+
* slotting one - so it can name it from its own text and keep its attributes private (it is just
|
|
256
|
+
* an action box, not a configurable control). Being a real button, activation is native: pointer
|
|
257
|
+
* clicks and keyboard (Enter/Space) both fire a `click` that bubbles out, so consumers listen for
|
|
258
|
+
* `click` on the `pie-list-item`. It is `type="button"`, so it never submits a form.
|
|
259
|
+
*/
|
|
260
|
+
_renderActionButton() {
|
|
261
|
+
var t, e;
|
|
262
|
+
const i = this._providedAria;
|
|
263
|
+
return d`<button
|
|
264
|
+
class="c-listItem-action"
|
|
265
|
+
type="button"
|
|
266
|
+
?disabled=${this._isDisabled}
|
|
267
|
+
aria-label=${m(i == null ? void 0 : i.label)}
|
|
268
|
+
aria-description=${m(i == null ? void 0 : i.description)}
|
|
269
|
+
aria-haspopup=${m((e = (t = this.aria) == null ? void 0 : t.button) == null ? void 0 : e.haspopup)}></button>`;
|
|
270
|
+
}
|
|
271
|
+
render() {
|
|
272
|
+
const { primaryText: i } = this, t = {
|
|
273
|
+
"c-listItem-container": !0,
|
|
274
|
+
"is-compact": this.isCompact,
|
|
275
|
+
"is-bold": this.isBold,
|
|
276
|
+
"has-media": this.hasMedia,
|
|
277
|
+
"is-selectable": this._isSelectable,
|
|
278
|
+
"is-link": this._isLinkRow,
|
|
279
|
+
"is-button": this._isButtonRow,
|
|
280
|
+
"is-disabled": this._isDisabled
|
|
281
|
+
};
|
|
282
|
+
return d`
|
|
283
|
+
<div class=${_(t)}>
|
|
284
|
+
${this._isLinkRow ? d`<slot name="link"></slot>` : p}
|
|
285
|
+
${this._isButtonRow ? this._renderActionButton() : p}
|
|
286
|
+
<div class="c-listItem-leading">
|
|
287
|
+
<slot name="leading"></slot>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
<div class="c-listItem-text" aria-hidden=${this._providesAria ? "true" : p}>
|
|
291
|
+
<span class="c-listItem-primaryText">${i}</span>
|
|
292
|
+
${this._renderSecondaryText()}
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
${this._renderTrailingContent()}
|
|
296
|
+
</div>`;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
a.styles = x(D);
|
|
300
|
+
l([
|
|
301
|
+
c({ type: String })
|
|
302
|
+
], a.prototype, "primaryText", 2);
|
|
303
|
+
l([
|
|
304
|
+
c({ type: String })
|
|
305
|
+
], a.prototype, "secondaryText", 2);
|
|
306
|
+
l([
|
|
307
|
+
c({ type: String })
|
|
308
|
+
], a.prototype, "metaText", 2);
|
|
309
|
+
l([
|
|
310
|
+
c({ type: Boolean })
|
|
311
|
+
], a.prototype, "isCompact", 2);
|
|
312
|
+
l([
|
|
313
|
+
c({ type: Boolean })
|
|
314
|
+
], a.prototype, "isBold", 2);
|
|
315
|
+
l([
|
|
316
|
+
c({ type: Boolean })
|
|
317
|
+
], a.prototype, "hasMedia", 2);
|
|
318
|
+
l([
|
|
319
|
+
c({ type: String }),
|
|
320
|
+
I(q, S, h.interactionType)
|
|
321
|
+
], a.prototype, "interactionType", 2);
|
|
322
|
+
l([
|
|
323
|
+
c({ type: Boolean })
|
|
324
|
+
], a.prototype, "disabled", 2);
|
|
325
|
+
l([
|
|
326
|
+
c({ type: Object })
|
|
327
|
+
], a.prototype, "aria", 2);
|
|
328
|
+
l([
|
|
329
|
+
B({ context: C, subscribe: !0 }),
|
|
330
|
+
k()
|
|
331
|
+
], a.prototype, "_parentDisabled", 2);
|
|
332
|
+
a = l([
|
|
333
|
+
T("pie-list-item")
|
|
334
|
+
], a);
|
|
335
|
+
export {
|
|
336
|
+
a as P,
|
|
337
|
+
h as d,
|
|
338
|
+
S as i
|
|
339
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { html as n, unsafeCSS as m } from "lit";
|
|
2
|
-
import { P as f } from "./PieElement-
|
|
2
|
+
import { P as f } from "./PieElement-DQQMEr70.js";
|
|
3
3
|
import { RtlMixin as d, safeCustomElement as p } from "@justeattakeaway/pie-webc-core";
|
|
4
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
5
|
var b = Object.getOwnPropertyDescriptor, u = (s, r, a, l) => {
|