@fusion.dev/helix-headless 0.2.0 → 0.4.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/custom-elements.json +234 -234
- package/dist/accordion/item/accordion-item.js +0 -108
- package/dist/accordion/root/accordion-root.js +0 -33
- package/dist/announcement/root/announcement-root.js +1 -76
- package/dist/app-header/root/app-header-root.js +0 -84
- package/dist/avatar/group/avatar-group.js +0 -77
- package/dist/avatar/root/avatar-root.js +0 -72
- package/dist/badge/root/badge-root.js +0 -67
- package/dist/bar-chart/root/bar-chart-root.js +3 -263
- package/dist/blockquote/root/blockquote-root.js +0 -46
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +0 -72
- package/dist/breadcrumbs/root/breadcrumbs-root.js +0 -52
- package/dist/button/root/button-root.js +0 -215
- package/dist/button-group/root/button-group-root.js +0 -30
- package/dist/callout/root/callout-root.js +0 -52
- package/dist/card/root/card-root.js +0 -41
- package/dist/carousel/root/carousel-root.d.ts +2 -2
- package/dist/carousel/root/carousel-root.js +2 -362
- package/dist/chat/composer/chat-composer.js +0 -131
- package/dist/chat/message/chat-message.js +0 -137
- package/dist/chat/root/chat-root.js +0 -193
- package/dist/checkbox/root/checkbox-root.js +0 -113
- package/dist/checkbox-group/root/checkbox-group-root.js +1 -157
- package/dist/code/root/code-root.js +0 -26
- package/dist/code-block/root/code-block-root.js +0 -100
- package/dist/color-input/root/color-input-root.js +0 -217
- package/dist/color-picker/color.js +0 -69
- package/dist/color-picker/root/color-picker-root.js +0 -272
- package/dist/combobox/root/combobox-root.js +0 -428
- package/dist/command/root/command-root.js +0 -177
- package/dist/copy-button/root/copy-button-root.js +0 -134
- package/dist/data-table/root/data-table-root.js +0 -336
- package/dist/data-table/root/order.js +0 -100
- package/dist/date-input/root/date-input-root.js +1 -398
- package/dist/date-picker/root/date-picker-root.js +0 -358
- package/dist/dialog/root/dialog-root.js +0 -189
- package/dist/divider/root/divider-root.js +0 -71
- package/dist/donut-chart/root/donut-chart-root.js +0 -214
- package/dist/drawer/root/drawer-root.js +0 -116
- package/dist/drilldown/item/drilldown-item.js +0 -82
- package/dist/drilldown/root/drilldown-root.js +0 -91
- package/dist/element-events.generated.js +0 -5
- package/dist/empty/root/empty-root.js +0 -35
- package/dist/file-input/root/file-input-root.js +0 -232
- package/dist/format-date/root/format-date-root.js +0 -95
- package/dist/heatmap/root/buckets.js +0 -57
- package/dist/heatmap/root/heatmap-root.js +0 -313
- package/dist/icon/root/icon-root.js +0 -48
- package/dist/icon-button/root/icon-button-root.js +0 -30
- package/dist/image/root/image-root.js +0 -174
- package/dist/index.js +0 -9
- package/dist/input/root/input-root.js +0 -228
- package/dist/kbd/root/kbd-root.js +0 -117
- package/dist/legend/root/legend-root.js +0 -73
- package/dist/line-chart/root/line-chart-root.js +0 -172
- package/dist/list/item/list-item.js +0 -92
- package/dist/list/root/list-root.js +0 -26
- package/dist/listbox/root/listbox-root.js +2 -429
- package/dist/loader/root/loader-root.js +0 -40
- package/dist/megamenu/root/megamenu-root.js +0 -284
- package/dist/menu/item/menu-item.js +1 -221
- package/dist/menu/root/menu-root.js +1 -171
- package/dist/meter/root/meter-root.js +1 -152
- package/dist/navigation/root/navigation-root.js +0 -56
- package/dist/notifications/notification/notification.js +0 -159
- package/dist/notifications/root/notifications-root.js +0 -148
- package/dist/number-input/root/number-input-root.js +1 -263
- package/dist/option/group/option-group.js +0 -26
- package/dist/option/root/option-root.js +0 -157
- package/dist/outline/root/outline-root.js +0 -292
- package/dist/pagination/root/pagination-root.js +0 -361
- package/dist/pagination/size/pagination-size.js +0 -104
- package/dist/pagination/summary/pagination-summary.js +0 -90
- package/dist/pin-input/root/pin-input-root.js +0 -276
- package/dist/popover/root/popover-root.js +0 -103
- package/dist/progress/root/progress-root.js +1 -133
- package/dist/qr-code/root/qr-code-root.js +0 -96
- package/dist/quote/root/quote-root.js +0 -25
- package/dist/radio/group/radio-group.js +0 -81
- package/dist/radio/root/radio-root.js +0 -85
- package/dist/rating/root/rating-root.js +0 -220
- package/dist/scroll-area/root/scroll-area-root.js +0 -100
- package/dist/select/root/select-root.js +2 -218
- package/dist/skeleton/root/skeleton-root.js +0 -20
- package/dist/slider/root/slider-root.js +1 -267
- package/dist/sparkline/root/sparkline-root.js +3 -164
- package/dist/speed-dial/item/speed-dial-item.js +0 -52
- package/dist/speed-dial/root/speed-dial-root.js +1 -116
- package/dist/splitter/root/splitter-root.js +0 -489
- package/dist/stat/group/stat-group.js +0 -18
- package/dist/stat/root/stat-root.js +0 -23
- package/dist/steps/root/steps-root.js +0 -62
- package/dist/steps/step/step.js +0 -131
- package/dist/switch/root/switch-root.js +0 -80
- package/dist/table/cell/table-cell.js +0 -23
- package/dist/table/column/table-column.js +0 -158
- package/dist/table/root/table-root.js +0 -82
- package/dist/table/row/table-row.js +0 -21
- package/dist/tabs/panel/tab-panel.js +0 -49
- package/dist/tabs/root/tabs-root.js +0 -98
- package/dist/tabs/tab/tab.js +0 -59
- package/dist/tag/root/tag-root.js +0 -31
- package/dist/textarea/root/textarea-root.js +0 -162
- package/dist/time-input/root/time-input-root.js +0 -283
- package/dist/time-picker/root/time-picker-root.js +0 -326
- package/dist/timeline/item/timeline-item.js +0 -66
- package/dist/timeline/root/timeline-root.js +0 -41
- package/dist/toast/item/toast-item.js +0 -165
- package/dist/toast/root/toast-root.js +0 -51
- package/dist/toggle/group/toggle-group.js +0 -150
- package/dist/toggle/root/toggle-root.js +0 -78
- package/dist/toolbar/root/toolbar-root.js +0 -116
- package/dist/tooltip/root/tooltip-root.js +0 -427
- package/dist/tour/root/tour-root.js +0 -130
- package/dist/tour/stop/tour-stop.js +1 -127
- package/dist/tree/item/tree-item.js +0 -198
- package/dist/tree/root/tree-root.js +0 -97
- package/dist/trend/root/trend-root.js +0 -53
- package/dist/video/root/video-root.d.ts +8 -12
- package/dist/video/root/video-root.js +5 -756
- package/dist/video-playlist/item/video-playlist-item.js +1 -90
- package/dist/video-playlist/menu/video-playlist-menu.js +1 -132
- package/dist/video-playlist/root/video-playlist-root.js +0 -113
- package/dist/virtual-list/root/virtual-list-root.js +0 -105
- package/jsx.d.ts +1408 -2576
- package/package.json +6 -6
- package/vue.d.ts +1 -1
|
@@ -7,27 +7,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary A row of related measurements.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* A group rather than a list, because the figures are read together and their
|
|
15
|
-
* order carries no meaning: a list would announce "1 of 4" for a set where the
|
|
16
|
-
* position is not information.
|
|
17
|
-
*
|
|
18
|
-
* @since 0.1.0
|
|
19
|
-
* @status stable
|
|
20
|
-
* @aliases Metrics, KPIs, Figures
|
|
21
|
-
* @documentation https://helix.fusion.dev/components/stat
|
|
22
|
-
*
|
|
23
|
-
* @slot - The stats.
|
|
24
|
-
*/
|
|
25
10
|
let HelixHeadlessStatGroup = class HelixHeadlessStatGroup extends HelixElement {
|
|
26
11
|
constructor() {
|
|
27
12
|
super(...arguments);
|
|
28
|
-
/**
|
|
29
|
-
* Accessible name for the set.
|
|
30
|
-
*/
|
|
31
13
|
this.label = '';
|
|
32
14
|
}
|
|
33
15
|
willUpdate(changed) {
|
|
@@ -7,29 +7,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, HelixElement } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary One measurement, with the word for what it measures.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* Label first in the markup and the value after it, which is the reading order
|
|
15
|
-
* a screen reader takes and the order a caption sits above a figure. The eye
|
|
16
|
-
* lands on the number either way, because the number is the loud thing; a
|
|
17
|
-
* reader hearing it needs to know what it counts before hearing how many.
|
|
18
|
-
*
|
|
19
|
-
* Both are bare slots. An element whose only job is to assign itself a slot on
|
|
20
|
-
* connect gives its own host an attribute after upgrade, which a React
|
|
21
|
-
* consumer's server markup never had.
|
|
22
|
-
*
|
|
23
|
-
* @since 0.1.0
|
|
24
|
-
* @status stable
|
|
25
|
-
* @category data
|
|
26
|
-
* @aliases Metric, KPI, Figure
|
|
27
|
-
* @documentation https://helix.fusion.dev/components/stat
|
|
28
|
-
*
|
|
29
|
-
* @slot - The value.
|
|
30
|
-
* @slot label - What the value measures.
|
|
31
|
-
* @slot unit - The unit, set beside the value.
|
|
32
|
-
*/
|
|
33
10
|
let HelixHeadlessStat = class HelixHeadlessStat extends HelixElement {
|
|
34
11
|
render() {
|
|
35
12
|
return html `<slot name="label"></slot>
|
|
@@ -7,59 +7,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, HelixElement, hostAria, MixinControllerActive, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary A sequence of stages, and where the reader is in it.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* **`active` names where the reader is. `complete` stays on each step.** They
|
|
15
|
-
* are two different facts and only one of them is positional. Deriving both
|
|
16
|
-
* from a position is the obvious design and it is wrong the first time anybody
|
|
17
|
-
* steps back: return to stage one of three and stages one and two are still
|
|
18
|
-
* finished, while "everything before the active one" says only the first is.
|
|
19
|
-
* So this element drives `current` and never touches `complete`.
|
|
20
|
-
*
|
|
21
|
-
* **`active` is optional, and a sequence without it still renders.** Left
|
|
22
|
-
* unset, each step's own `current` stands and nothing here overwrites it, which
|
|
23
|
-
* is what lets the whole sequence be right in the server's markup: this element
|
|
24
|
-
* would have to count its slotted children to know which one is third, and the
|
|
25
|
-
* server has no children to count. Set it, and it wins, because it is a
|
|
26
|
-
* statement about the sequence where the attribute on a step is a statement
|
|
27
|
-
* about one step.
|
|
28
|
-
*
|
|
29
|
-
* That is also why the numbering is not JavaScript. It is a CSS counter in the
|
|
30
|
-
* tier above, which the browser resolves at layout with no script at all, so
|
|
31
|
-
* the third step reads "3" in the server's own markup.
|
|
32
|
-
*
|
|
33
|
-
* **The connector belongs to the step, not to the list.** Each one draws the
|
|
34
|
-
* line that arrives at it and hides it when it is first, the same arrangement
|
|
35
|
-
* Breadcrumbs uses for its separator and for the same reason: a line drawn
|
|
36
|
-
* between items needs either an index or a parent that counts, and drawing it
|
|
37
|
-
* on the item needs neither.
|
|
38
|
-
*
|
|
39
|
-
* `role="list"` on the host rather than an `<ol>` in the shadow root. The steps
|
|
40
|
-
* are the consumer's elements in the light DOM, so an `<ol>` here would be a
|
|
41
|
-
* list whose items are somewhere else.
|
|
42
|
-
*
|
|
43
|
-
* @since 0.1.0
|
|
44
|
-
* @status experimental
|
|
45
|
-
* @category navigation
|
|
46
|
-
* @aliases Stepper, Wizard, Progress steps
|
|
47
|
-
* @documentation https://helix.fusion.dev/components/steps
|
|
48
|
-
*
|
|
49
|
-
* @slot - The steps, which are `helix-step` elements.
|
|
50
|
-
*
|
|
51
|
-
* @event {HelixActivateEvent} helix-activate - Fired once the current one has moved, so the element already holds it.
|
|
52
|
-
* @event {HelixBeforeActivateEvent} helix-before-activate - Fired before the active step changes. Cancelable: refusing it takes the decision and nothing moves.
|
|
53
|
-
*/
|
|
54
10
|
let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(HelixElement) {
|
|
55
11
|
constructor() {
|
|
56
12
|
super(...arguments);
|
|
57
|
-
/**
|
|
58
|
-
* Accessible name for the sequence.
|
|
59
|
-
*
|
|
60
|
-
* A list of stages is a landmark a reader arrives at out of order, so it
|
|
61
|
-
* needs a name of its own rather than borrowing the heading above it.
|
|
62
|
-
*/
|
|
63
13
|
this.label = 'Steps';
|
|
64
14
|
this.handleSelection = (event) => {
|
|
65
15
|
this.requestActive(event.detail.value);
|
|
@@ -68,21 +18,9 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
68
18
|
this.sync();
|
|
69
19
|
};
|
|
70
20
|
}
|
|
71
|
-
/** The steps, in the order the consumer wrote them. */
|
|
72
21
|
get steps() {
|
|
73
22
|
return [...this.querySelectorAll(':scope > *')].filter((one) => 'current' in one);
|
|
74
23
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Puts `active` on whichever step owns it.
|
|
77
|
-
*
|
|
78
|
-
* Returns without touching anything where nothing has set `active`, which is
|
|
79
|
-
* the case a server renders: the steps' own `current` is the state then, and
|
|
80
|
-
* overwriting it on hydration is exactly the flash this design exists to
|
|
81
|
-
* avoid.
|
|
82
|
-
*
|
|
83
|
-
* `current` only. `complete` is the consumer's on each step, because a reader
|
|
84
|
-
* who steps back has finished more stages than they are standing in front of.
|
|
85
|
-
*/
|
|
86
24
|
sync() {
|
|
87
25
|
if (this.active === '') {
|
|
88
26
|
return;
|
package/dist/steps/step/step.js
CHANGED
|
@@ -9,131 +9,21 @@ import { check, circle, record } from '@fusion.dev/helix-icons';
|
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
11
11
|
import { html } from 'lit/static-html.js';
|
|
12
|
-
/* Side-effect import, because this renders a button and a custom element
|
|
13
|
-
registers on import. The path is relative, so this registers the base button
|
|
14
|
-
and the styled file's identical import registers the styled one; `tier` picks
|
|
15
|
-
which name the template asks for. */
|
|
16
12
|
import '../../button/index.js';
|
|
17
|
-
/**
|
|
18
|
-
* @summary One stage in a sequence.
|
|
19
|
-
*
|
|
20
|
-
* @description
|
|
21
|
-
* **The name is a button, and only the name.** A stepper is something a reader
|
|
22
|
-
* goes back through, and the thing they reach for is the name of the stage. The
|
|
23
|
-
* marker is `aria-hidden` decoration, so wrapping it in the button would add
|
|
24
|
-
* hit area and no meaning: the accessible name would be the text either way,
|
|
25
|
-
* and what is pressable would stop being the same shape as what is readable.
|
|
26
|
-
*
|
|
27
|
-
* It is `helix-button`, not a `<button>` this element resets and dresses: the
|
|
28
|
-
* hover wash, the focus ring and the disabled treatment are decisions the
|
|
29
|
-
* button already carries. `ghost` is the quiet shape, transparent until it is
|
|
30
|
-
* pointed at.
|
|
31
|
-
*
|
|
32
|
-
* It says nothing about where a reader may go. Every step is pressable and
|
|
33
|
-
* announces itself with `helix-select`; refusing the ones ahead is `disabled`,
|
|
34
|
-
* which the consumer sets because only the consumer knows whether a form can be
|
|
35
|
-
* skipped. A component that guessed would be wrong on the first wizard that
|
|
36
|
-
* lets you jump to the end.
|
|
37
|
-
*
|
|
38
|
-
* **The marker is a slot whose fallback is the state.** A finished stage shows
|
|
39
|
-
* a check, the current one a target, and one not yet reached an empty ring:
|
|
40
|
-
* three marks that say what a reader wants to know without a legend. A consumer
|
|
41
|
-
* who wants their own fills the slot instead.
|
|
42
|
-
*
|
|
43
|
-
* The step renders exactly one of the three, chosen from its own two booleans.
|
|
44
|
-
* Rendering all three and hiding two in CSS would work and is worse: the
|
|
45
|
-
* accessibility tree would carry marks that are not there, and the markup would
|
|
46
|
-
* say three things about a stage that is one.
|
|
47
|
-
*
|
|
48
|
-
* Numbering belongs to the tier above. `numeric` hides the mark and shows a CSS
|
|
49
|
-
* counter, which the browser resolves at layout, so the digit costs no script
|
|
50
|
-
* and is right in the server's markup however the steps are ordered. Nothing
|
|
51
|
-
* here knows which appearance is in use.
|
|
52
|
-
*
|
|
53
|
-
* **`complete` and `current` are separate, and both can be false.** A stage
|
|
54
|
-
* that is neither is one the reader has not reached, which is the majority of
|
|
55
|
-
* any sequence and therefore the state that needs no attribute. They can also
|
|
56
|
-
* both be true: a reader who steps back into a finished stage is in it and has
|
|
57
|
-
* finished it, and a stepper that forgets the second is one that loses its
|
|
58
|
-
* checks the moment somebody reviews their answers.
|
|
59
|
-
*
|
|
60
|
-
* **The `content` slot opens on the current step, and only down a column.** A
|
|
61
|
-
* row has no room under a step to put a form in, and stretching one to fit
|
|
62
|
-
* would set every step's height by the tallest. `current` is what opens it, so
|
|
63
|
-
* the state that says where the reader is is also the state that shows them the
|
|
64
|
-
* stage: one attribute, no second flag to keep in step with it, and it renders
|
|
65
|
-
* open on the server rather than after hydration.
|
|
66
|
-
*
|
|
67
|
-
* @since 0.1.0
|
|
68
|
-
* @status experimental
|
|
69
|
-
* @aliases Stage
|
|
70
|
-
* @documentation https://helix.fusion.dev/components/steps
|
|
71
|
-
*
|
|
72
|
-
* @slot - The step's name.
|
|
73
|
-
* @slot content - What the stage holds, shown while it is the current one.
|
|
74
|
-
* @slot description - A line under the name, for what the stage is for.
|
|
75
|
-
* @slot marker - Replaces the number, or the check a complete step shows.
|
|
76
|
-
*
|
|
77
|
-
* @event {HelixSelectEvent} helix-select - Fired when the step is pressed. Cancelable: refusing it takes the decision and nothing moves.
|
|
78
|
-
*
|
|
79
|
-
* @csspart body - The name and its description.
|
|
80
|
-
* @csspart content - What the stage holds, while it is open.
|
|
81
|
-
* @csspart description - The line under it.
|
|
82
|
-
* @csspart label - The step's name, which is a `helix-button`.
|
|
83
|
-
* @csspart label-button - The inner control.
|
|
84
|
-
* @csspart lead - The half line arriving from the step before.
|
|
85
|
-
* @csspart mark - The glyph inside the marker: a tick, a dot, or an empty ring.
|
|
86
|
-
* @csspart marker - The shape carrying the number or the check.
|
|
87
|
-
* @csspart trail - The half line running on to the next.
|
|
88
|
-
*/
|
|
89
13
|
let HelixHeadlessStep = class HelixHeadlessStep extends HelixElement {
|
|
90
14
|
constructor() {
|
|
91
15
|
super(...arguments);
|
|
92
|
-
/**
|
|
93
|
-
* Whether the reader has finished this stage.
|
|
94
|
-
*/
|
|
95
16
|
this.complete = false;
|
|
96
|
-
/**
|
|
97
|
-
* Whether this is the stage the reader is in.
|
|
98
|
-
*/
|
|
99
17
|
this.current = false;
|
|
100
|
-
/**
|
|
101
|
-
* Whether the reader may go to this stage.
|
|
102
|
-
*/
|
|
103
18
|
this.disabled = false;
|
|
104
|
-
/**
|
|
105
|
-
* What this stage is called when it announces itself.
|
|
106
|
-
*/
|
|
107
19
|
this.value = '';
|
|
108
20
|
}
|
|
109
|
-
/** Announces the step, unless it is refusing. */
|
|
110
21
|
select() {
|
|
111
22
|
if (this.disabled) {
|
|
112
23
|
return;
|
|
113
24
|
}
|
|
114
25
|
this.dispatchEvent(new HelixSelectEvent({ value: this.value }));
|
|
115
26
|
}
|
|
116
|
-
/**
|
|
117
|
-
* The mark this stage's state calls for.
|
|
118
|
-
*
|
|
119
|
-
* `complete` first, so a stage a reader has stepped back into keeps its check
|
|
120
|
-
* rather than losing it to the target: they did finish it, and the name and
|
|
121
|
-
* the rule below still say which one is open.
|
|
122
|
-
*
|
|
123
|
-
* **Three shapes, and the reader can tell them apart with the color off.**
|
|
124
|
-
* An outline circle for a stage nobody has reached, the same circle with a
|
|
125
|
-
* center for the one they are standing on, a bare check for one that is done.
|
|
126
|
-
* The first two are one shape filling in, which is the progression, and the
|
|
127
|
-
* check is the only mark that leaves the family because finishing is the only
|
|
128
|
-
* state that is not a position in the sequence. A coin around the check would
|
|
129
|
-
* put two of the three inside the same container and leave hue doing the work
|
|
130
|
-
* of telling them apart.
|
|
131
|
-
*
|
|
132
|
-
* The bare check matches `Timeline`, deliberately: the two components report
|
|
133
|
-
* the same kind of thing, a sequence with marks down it, and a check on a
|
|
134
|
-
* hairline in one beside a check inside a coin in the other is two answers to
|
|
135
|
-
* one question.
|
|
136
|
-
*/
|
|
137
27
|
renderMarker() {
|
|
138
28
|
const icon = this.complete ? check : this.current ? record : circle;
|
|
139
29
|
return html `<slot name="marker"
|
|
@@ -147,15 +37,6 @@ let HelixHeadlessStep = class HelixHeadlessStep extends HelixElement {
|
|
|
147
37
|
hostAria(this.internals, { role: 'listitem' });
|
|
148
38
|
this.internals.ariaCurrent = this.current ? 'step' : null;
|
|
149
39
|
}
|
|
150
|
-
/**
|
|
151
|
-
* The step's own label, which is a control because a step can be gone to, behind a seam the styled tier can move.
|
|
152
|
-
*
|
|
153
|
-
* A method rather than a fragment inline, so a tier above can render its own,
|
|
154
|
-
* wrap this one, or leave it out, without reimplementing the whole template.
|
|
155
|
-
* The tag is interpolated rather than written, so `tier` resolves it: this
|
|
156
|
-
* renders the headless element, and a styled subclass that sets `tier`
|
|
157
|
-
* renders the styled one from the same template.
|
|
158
|
-
*/
|
|
159
40
|
renderLabel() {
|
|
160
41
|
return html `<${this.tag('button')}
|
|
161
42
|
part="label"
|
|
@@ -168,18 +49,6 @@ let HelixHeadlessStep = class HelixHeadlessStep extends HelixElement {
|
|
|
168
49
|
</${this.tag('button')}>`;
|
|
169
50
|
}
|
|
170
51
|
render() {
|
|
171
|
-
/* Two half lines, one either side of the mark, rather than one line running
|
|
172
|
-
the whole way to the next step.
|
|
173
|
-
A single line drawn to the next marker has to know how far away that is,
|
|
174
|
-
which is the next step's size, and a step knows only its own; drawn
|
|
175
|
-
across the gap alone it is a stub. Two halves need neither: each reaches
|
|
176
|
-
from the mark to its own step's edge and half the gap beyond, so it meets
|
|
177
|
-
the one coming the other way in the middle of the gap and the rule reads
|
|
178
|
-
as continuous however wide the steps are or how differently they are
|
|
179
|
-
filled.
|
|
180
|
-
The first has nothing on its leading side and the last nothing on its
|
|
181
|
-
trailing one, which `:host(:first-child)` and `:host(:last-child)` say
|
|
182
|
-
with no index and no parent counting its children. */
|
|
183
52
|
return html `<span part="lead" aria-hidden="true"></span>
|
|
184
53
|
<span part="trail" aria-hidden="true"></span>
|
|
185
54
|
<span part="marker" aria-hidden="true">
|
|
@@ -7,74 +7,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { AriaForwardController, customElement, HelixFormElement, MixinControllerChecked, MixinField, slotFilled, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary A control that turns something on or off.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* **A switch is not a checkbox with a different shape.** A checkbox is an
|
|
15
|
-
* answer to a question the form asks when it is submitted; a switch is a thing
|
|
16
|
-
* happening now. That is why `role="switch"` exists and why a switch belongs
|
|
17
|
-
* outside a form as often as in one: it says "on" and "off" rather than
|
|
18
|
-
* "ticked" and "unticked", and a reader expects the effect immediately rather
|
|
19
|
-
* than on Save.
|
|
20
|
-
*
|
|
21
|
-
* Rendered as a real `<input type="checkbox">` carrying `role="switch"`, which
|
|
22
|
-
* is the pairing ARIA describes: the input is what a form submits and what a
|
|
23
|
-
* label forwards a press to, and the role is what a screen reader announces.
|
|
24
|
-
* Neither half can be got from the other.
|
|
25
|
-
*
|
|
26
|
-
* The label sits beside it, inside the `<label>` that forwards the press, the
|
|
27
|
-
* way a checkbox's does. The hint and the message go under the row, because
|
|
28
|
-
* there is no above: the switch and the words naming it are one line.
|
|
29
|
-
*
|
|
30
|
-
* @since 0.1.0
|
|
31
|
-
* @status stable
|
|
32
|
-
* @category forms
|
|
33
|
-
* @aliases Toggle switch, On/off
|
|
34
|
-
* @documentation https://helix.fusion.dev/components/switch
|
|
35
|
-
*
|
|
36
|
-
* @slot - The words naming what this turns on.
|
|
37
|
-
*
|
|
38
|
-
* @event {Event} change - Relayed from the input once the state has changed.
|
|
39
|
-
* @event {HelixBeforeToggleEvent} helix-before-toggle - Fired before the state changes, and cancelable.
|
|
40
|
-
* @event {Event} input - Announced once the state has moved.
|
|
41
|
-
*
|
|
42
|
-
* @csspart control - The input.
|
|
43
|
-
* @csspart feedback - What is wrong with it.
|
|
44
|
-
* @csspart field - The row: the track and its label.
|
|
45
|
-
* @csspart hint - Help under the control, while nothing is wrong.
|
|
46
|
-
* @csspart label - The words beside it.
|
|
47
|
-
* @csspart thumb - The part that moves.
|
|
48
|
-
* @csspart track - The groove the thumb travels in.
|
|
49
|
-
*/
|
|
50
10
|
let HelixHeadlessSwitch = class HelixHeadlessSwitch extends MixinControllerChecked(MixinField(HelixFormElement)) {
|
|
51
11
|
constructor() {
|
|
52
12
|
super(...arguments);
|
|
53
|
-
/* A consumer's `aria-describedby` names ids in their own tree and the control
|
|
54
|
-
is in this shadow root, so the reference has to be carried down as an
|
|
55
|
-
element rather than copied as an id. See `AriaForwardController`: measured
|
|
56
|
-
in Chrome's own tree, the host held the description and the control the
|
|
57
|
-
reader is actually on was exposed without it. */
|
|
58
13
|
this.aria = new AriaForwardController(this, {
|
|
59
14
|
target: () => this.renderRoot?.querySelector('[part="control"]') ?? null,
|
|
60
15
|
});
|
|
61
|
-
/**
|
|
62
|
-
* Value submitted when the switch is on.
|
|
63
|
-
*/
|
|
64
16
|
this.value = 'on';
|
|
65
17
|
this.handleChange = (event) => {
|
|
66
18
|
event.stopPropagation();
|
|
67
19
|
const control = event.target;
|
|
68
20
|
const taken = this.requestChecked(control.checked);
|
|
69
|
-
/* The input follows the element, always, not only where the change was
|
|
70
|
-
refused.
|
|
71
|
-
*
|
|
72
|
-
* A controlled switch takes the request and applies nothing: it has
|
|
73
|
-
announced where it would go and is waiting to be told. The input had
|
|
74
|
-
already flipped itself, so until the consumer answered, the switch showed
|
|
75
|
-
one state and held the other, and a consumer who never answers showed the
|
|
76
|
-
wrong one for good. Putting it back costs nothing where the change was
|
|
77
|
-
taken, since `this.checked` is then the new value anyway. */
|
|
78
21
|
control.checked = this.checked;
|
|
79
22
|
if (!taken) {
|
|
80
23
|
return;
|
|
@@ -82,17 +25,11 @@ let HelixHeadlessSwitch = class HelixHeadlessSwitch extends MixinControllerCheck
|
|
|
82
25
|
this.dispatchEvent(new Event('change', { bubbles: true, composed: false }));
|
|
83
26
|
};
|
|
84
27
|
}
|
|
85
|
-
/** Reports what the form would submit, and the verdict on it. */
|
|
86
28
|
sync() {
|
|
87
|
-
// `null` rather than the value when it is off, which is how a checkbox
|
|
88
|
-
// behaves: an unticked box submits nothing rather than a blank.
|
|
89
29
|
this.internals.setFormValue(this.checked ? this.value : null);
|
|
90
30
|
this.reportRequired(!this.checked, 'Turn this on to continue.', 'control');
|
|
91
31
|
}
|
|
92
|
-
/** Returns the switch to the state its markup declared. */
|
|
93
32
|
formResetCallback() {
|
|
94
|
-
// `resetChecked` drops both halves, the consumer's and the element's, so
|
|
95
|
-
// there is nothing to clear before it.
|
|
96
33
|
this.resetChecked();
|
|
97
34
|
}
|
|
98
35
|
updated(changed) {
|
|
@@ -100,30 +37,13 @@ let HelixHeadlessSwitch = class HelixHeadlessSwitch extends MixinControllerCheck
|
|
|
100
37
|
this.markEmpty();
|
|
101
38
|
this.sync();
|
|
102
39
|
}
|
|
103
|
-
/**
|
|
104
|
-
* Says on the `label` region whether anything is in it.
|
|
105
|
-
*
|
|
106
|
-
* An empty region is not free: it is still a flex item, so it still takes the
|
|
107
|
-
* container's gap. There is no selector that can ask, `slot:empty` being
|
|
108
|
-
* about a slot's fallback rather than its assigned nodes, so the component
|
|
109
|
-
* asks and says so, on the region inside this shadow root rather than on the
|
|
110
|
-
* host: an attribute written onto the host is a change to markup the consumer
|
|
111
|
-
* rendered, which React reports on hydration and does not patch up.
|
|
112
|
-
*/
|
|
113
40
|
markEmpty() {
|
|
114
41
|
const region = this.renderRoot?.querySelector('[part="label"]');
|
|
115
42
|
const empty = !slotFilled(this.renderRoot?.querySelector('[part="label"] slot'));
|
|
116
43
|
region?.toggleAttribute('data-empty', empty);
|
|
117
|
-
/* `hidden` beside the mirror, because `data-empty` is a hook the styled
|
|
118
|
-
sheet reads and this tier has no sheet: an empty region left in the box
|
|
119
|
-
tree is a gap a consumer cannot close without knowing to look for it. The
|
|
120
|
-
styled rule stays, because that sheet sets `display` on this part and so
|
|
121
|
-
outranks the UA one. */
|
|
122
44
|
region?.toggleAttribute('hidden', empty);
|
|
123
45
|
}
|
|
124
46
|
render() {
|
|
125
|
-
/* A real `<label>` around both, so pressing the words works with no click
|
|
126
|
-
handler: the browser does the forwarding. */
|
|
127
47
|
return html `<label part="field">
|
|
128
48
|
<span part="track">
|
|
129
49
|
<input
|
|
@@ -7,36 +7,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary One cell of a table.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* Carries its own role, because a grid of divs is not a table to anything
|
|
15
|
-
* reading it. `header` makes the cell a `columnheader`, which is what lets a
|
|
16
|
-
* screen reader announce "Attribute, appearance" rather than just the value.
|
|
17
|
-
*
|
|
18
|
-
* The role goes on the host rather than inside the shadow root, because the
|
|
19
|
-
* cell has to be a child of its row in the flat tree for the table's structure
|
|
20
|
-
* to hold together.
|
|
21
|
-
*
|
|
22
|
-
* @since 0.1.0
|
|
23
|
-
* @status experimental
|
|
24
|
-
* @aliases Cell, TableCell
|
|
25
|
-
* @documentation https://helix.fusion.dev/components/table
|
|
26
|
-
*
|
|
27
|
-
* @slot - The cell's content.
|
|
28
|
-
*/
|
|
29
10
|
let HelixHeadlessTableCell = class HelixHeadlessTableCell extends HelixElement {
|
|
30
11
|
constructor() {
|
|
31
12
|
super(...arguments);
|
|
32
|
-
/**
|
|
33
|
-
* Whether the cell heads its column.
|
|
34
|
-
*/
|
|
35
13
|
this.header = false;
|
|
36
14
|
}
|
|
37
15
|
willUpdate(changed) {
|
|
38
16
|
super.willUpdate(changed);
|
|
39
|
-
// In `willUpdate`, which the server does call, unlike `connectedCallback`.
|
|
40
17
|
hostAria(this.internals, { role: this.header ? 'columnheader' : 'cell' });
|
|
41
18
|
}
|
|
42
19
|
render() {
|
|
@@ -7,15 +7,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property, state } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* A dot path resolved against a row.
|
|
12
|
-
*
|
|
13
|
-
* Written here rather than taken from a library, because the whole of what is
|
|
14
|
-
* needed is "walk the keys and stop at the first thing that is not an object".
|
|
15
|
-
* Returns `undefined` for a path that does not resolve, which the table renders
|
|
16
|
-
* as an empty cell: a missing key is a hole in the data and drawing `undefined`
|
|
17
|
-
* into a table tells a reader the software is broken rather than the row is.
|
|
18
|
-
*/
|
|
19
10
|
const at = (row, path) => {
|
|
20
11
|
if (path === '') {
|
|
21
12
|
return undefined;
|
|
@@ -32,189 +23,40 @@ const at = (row, path) => {
|
|
|
32
23
|
}
|
|
33
24
|
return cursor;
|
|
34
25
|
};
|
|
35
|
-
/**
|
|
36
|
-
* @summary One column of a table: its heading, how wide it is, and what it reads.
|
|
37
|
-
*
|
|
38
|
-
* @description
|
|
39
|
-
* **The column is the source of both its heading and its track.** Add one and
|
|
40
|
-
* the table has another track; remove one and it does not. Declaring the widths
|
|
41
|
-
* separately, as a CSS track list on the table, leaves nothing tying the two
|
|
42
|
-
* together: a table with three headings and a two-track list renders without
|
|
43
|
-
* complaint, and a track list that is accidentally the headings is invalid CSS
|
|
44
|
-
* the grid silently ignores, collapsing the whole table into one column.
|
|
45
|
-
*
|
|
46
|
-
* **It is also where every per-column fact goes**, which is the reason
|
|
47
|
-
* `helix-data-table` needs no column config object. `field` names the key the
|
|
48
|
-
* cell reads, `sortable` says a reader may reorder by it, `align` says which
|
|
49
|
-
* edge the values line up against, and `cell` takes a function for anything the
|
|
50
|
-
* field alone cannot express. The schema is markup and the data is a property:
|
|
51
|
-
* a schema is small, declarative, and something a reader should be able to see
|
|
52
|
-
* in the HTML, and data is large and windowed.
|
|
53
|
-
*
|
|
54
|
-
* **The same element serves both tables**, the markup one and the data one. It
|
|
55
|
-
* is identified by a flag rather than by `instanceof`, so a `helix-table-column`
|
|
56
|
-
* works inside either without the two roots sharing anything else.
|
|
57
|
-
*
|
|
58
|
-
* **It generates no box of its own**, the same as a row: the heading is a grid
|
|
59
|
-
* item of the table, so it lines up with the cells under it rather than sitting
|
|
60
|
-
* in a wrapper that has to be kept the same width.
|
|
61
|
-
*
|
|
62
|
-
* @since 0.1.0
|
|
63
|
-
* @status experimental
|
|
64
|
-
* @aliases Column header, Th
|
|
65
|
-
* @documentation https://helix.fusion.dev/components/table
|
|
66
|
-
*
|
|
67
|
-
* @slot - The heading.
|
|
68
|
-
*
|
|
69
|
-
* @csspart column - The heading cell.
|
|
70
|
-
*/
|
|
71
26
|
let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixElement {
|
|
72
27
|
constructor() {
|
|
73
28
|
super(...arguments);
|
|
74
|
-
/**
|
|
75
|
-
* Which edge the values line up against.
|
|
76
|
-
*
|
|
77
|
-
* `end` is what a column of numbers wants, so digits of the same magnitude sit
|
|
78
|
-
* above each other. Left unset the cell inherits the table's own alignment,
|
|
79
|
-
* which is `start` for text in either writing direction.
|
|
80
|
-
*/
|
|
81
29
|
this.align = '';
|
|
82
|
-
/**
|
|
83
|
-
* How to read the cell, where the field alone will not do.
|
|
84
|
-
*
|
|
85
|
-
* A property and not an attribute, because it is a function. Set it from a Lit
|
|
86
|
-
* template with `.cell=${...}`, or assign it in script. A column with neither
|
|
87
|
-
* this nor a `field` renders an empty cell rather than the whole row stringified,
|
|
88
|
-
* which is the failure mode of guessing.
|
|
89
|
-
*/
|
|
90
30
|
this.cell = undefined;
|
|
91
|
-
/**
|
|
92
|
-
* How to read the value that gets sorted and filtered, where it is not the cell.
|
|
93
|
-
*
|
|
94
|
-
* **The one that is easy to leave out and expensive to add later.** A cell
|
|
95
|
-
* showing `2.4 MB` sorts as a string unless something says the underlying value
|
|
96
|
-
* is a number, so `12 kB` sorts above `2.4 MB` and the column looks broken
|
|
97
|
-
* while every value in it is right. Left unset this is the field, which is the
|
|
98
|
-
* correct default: the raw value is what an API sorted by.
|
|
99
|
-
*/
|
|
100
31
|
this.compare = undefined;
|
|
101
|
-
/**
|
|
102
|
-
* The key this column reads out of a row.
|
|
103
|
-
*
|
|
104
|
-
* Dot paths resolve, so `field="owner.name"` reads a nested value: a result
|
|
105
|
-
* from an API arrives shaped the way the API shaped it, and requiring a flat
|
|
106
|
-
* row would push a mapping pass onto every consumer.
|
|
107
|
-
*
|
|
108
|
-
* **It is also the sort and filter identity**, which is why sorting is by
|
|
109
|
-
* field and not by column index: a reordered or hidden column would otherwise
|
|
110
|
-
* change what `sort="2:asc"` means, and a sort that means something different
|
|
111
|
-
* after a column moves is a sort a consumer cannot store in a URL.
|
|
112
|
-
*
|
|
113
|
-
* Empty on a column that is purely presentational, an actions column say, and
|
|
114
|
-
* such a column is never sortable however it is marked.
|
|
115
|
-
*/
|
|
116
32
|
this.field = '';
|
|
117
|
-
/**
|
|
118
|
-
* Whether a reader may reorder the set by this column.
|
|
119
|
-
*
|
|
120
|
-
* Ignored without a `field`, and silently: a column with nothing to sort by
|
|
121
|
-
* has no order to offer, and refusing loudly would mean an actions column
|
|
122
|
-
* carrying `sortable` breaks a table rather than doing nothing.
|
|
123
|
-
*/
|
|
124
33
|
this.sortable = false;
|
|
125
|
-
/**
|
|
126
|
-
* The track this column takes, as CSS.
|
|
127
|
-
*
|
|
128
|
-
* A single track value, so anything valid in `grid-template-columns` works:
|
|
129
|
-
* `16rem`, `auto`, `minmax(0, 2fr)`. Left unset the column shares what is
|
|
130
|
-
* left over equally, as `minmax(max-content, 1fr)`.
|
|
131
|
-
*
|
|
132
|
-
* **The floor is `max-content`, never `0` and never `min-content`.** A zero
|
|
133
|
-
* floor lets a track shrink under its own content: at 375 the columns squeeze
|
|
134
|
-
* to 75px and `eu-central-1` wraps across lines, which reads as corrupt data
|
|
135
|
-
* rather than as a table that ran out of room. A `min-content` floor still
|
|
136
|
-
* breaks it, a hyphen being a wrap opportunity.
|
|
137
|
-
*
|
|
138
|
-
* The overflow has somewhere to go: `@fusion.dev/helix` scrolls the table's
|
|
139
|
-
* own box, so a long value pushes past the container and the table scrolls
|
|
140
|
-
* instead of the value being destroyed. Measured at 375: tracks of 192, 89 and
|
|
141
|
-
* 102 against a 343 box, so the table scrolls 384 and the page does not move.
|
|
142
|
-
*
|
|
143
|
-
* The cost is that a column of prose asks for its longest paragraph on one
|
|
144
|
-
* line, so a column holding sentences rather than values wants
|
|
145
|
-
* `width="minmax(0, 1fr)"` to opt back into wrapping. A headless consumer owns
|
|
146
|
-
* the overflow either way, that being paint, and gets the honest track list.
|
|
147
|
-
*/
|
|
148
34
|
this.width = '';
|
|
149
|
-
/**
|
|
150
|
-
* @internal Read by a parent to identify its children across the light DOM,
|
|
151
|
-
* which is cheaper than `instanceof` and is why this is public rather than
|
|
152
|
-
* protected. Not a consumer's to read or set.
|
|
153
|
-
*/
|
|
154
35
|
this.isTableColumn = true;
|
|
155
|
-
/**
|
|
156
|
-
* @internal Which way this column is currently ordered, written by the table
|
|
157
|
-
* that owns the sort. Not a consumer's to set: the sort lives on the table,
|
|
158
|
-
* and a column that disagreed with it would announce an order the rows are
|
|
159
|
-
* not in.
|
|
160
|
-
*/
|
|
161
36
|
this.sorted = undefined;
|
|
162
37
|
}
|
|
163
|
-
/** The track this column contributes, resolved. */
|
|
164
38
|
get track() {
|
|
165
39
|
return this.width || 'minmax(max-content, 1fr)';
|
|
166
40
|
}
|
|
167
|
-
/** Whether a reader can actually reorder by this column. */
|
|
168
41
|
get canSort() {
|
|
169
42
|
return this.sortable && this.field !== '';
|
|
170
43
|
}
|
|
171
|
-
/**
|
|
172
|
-
* The value this column shows for a row.
|
|
173
|
-
*
|
|
174
|
-
* `cell` first, then the field. An explicit reader that returns `undefined` is
|
|
175
|
-
* treated as declining rather than as an answer, so a formatter can hand the
|
|
176
|
-
* default back for the rows it has nothing to say about.
|
|
177
|
-
*/
|
|
178
44
|
read(row) {
|
|
179
45
|
const rendered = this.cell?.(row);
|
|
180
46
|
return rendered === undefined ? at(row, this.field) : rendered;
|
|
181
47
|
}
|
|
182
|
-
/** The value this column sorts and filters by, which is not always what it shows. */
|
|
183
48
|
order(row) {
|
|
184
49
|
const compared = this.compare?.(row);
|
|
185
50
|
return compared === undefined ? at(row, this.field) : compared;
|
|
186
51
|
}
|
|
187
52
|
willUpdate(changed) {
|
|
188
53
|
super.willUpdate(changed);
|
|
189
|
-
/* **`aria-sort` is not decoration.** A `columnheader` a reader can reorder
|
|
190
|
-
by has to say which way it is ordered and which headers are orderable at
|
|
191
|
-
all, or the whole affordance exists only for a reader who can see the rows
|
|
192
|
-
move. `none` on the sortable ones that are not currently the sort is the
|
|
193
|
-
part that carries the second half: it is what tells a screen reader this
|
|
194
|
-
column can be sorted, where an absent attribute says nothing.
|
|
195
|
-
|
|
196
|
-
Off the internals rather than the attribute, for the same reason the role
|
|
197
|
-
is: it is this element's own semantics rather than markup a consumer
|
|
198
|
-
wrote, and an attribute here is one more thing hydration can disagree
|
|
199
|
-
about. */
|
|
200
54
|
hostAria(this.internals, { role: 'columnheader' });
|
|
201
|
-
/* Set here rather than through `hostAria`, which carries the four semantics
|
|
202
|
-
every family needs and not this one. */
|
|
203
55
|
this.internals.ariaSort = this.canSort ? (this.sorted ?? 'none') : null;
|
|
204
|
-
/* And the same fact as a custom state, because ARIA is not a style hook.
|
|
205
|
-
Whoever draws this column draws the caret, and had nothing to draw it
|
|
206
|
-
from: no state and no part told the sorted column from the rest, so the
|
|
207
|
-
only way to it was listening for `helix-sort` and reading the order back a
|
|
208
|
-
microtask later, which is a lot of machinery for one glyph. */
|
|
209
56
|
for (const [name, on] of [
|
|
210
57
|
['sorted-ascending', this.sorted === 'ascending'],
|
|
211
58
|
['sorted-descending', this.sorted === 'descending'],
|
|
212
59
|
]) {
|
|
213
|
-
/* Through `toggleState`, which carries the feature test, the way
|
|
214
|
-
`pagination-root.ts` does in this same hook. Safari below 17.4 and
|
|
215
|
-
Firefox below 126 have no `CustomStateSet`, and there
|
|
216
|
-
`internals.states.delete` is a `TypeError` thrown from `willUpdate`:
|
|
217
|
-
the column would fail to render rather than render without a caret. */
|
|
218
60
|
this.toggleState(name, on);
|
|
219
61
|
}
|
|
220
62
|
}
|