@fusion.dev/helix-headless 0.1.0 → 0.3.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,58 +7,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { CommandController, customElement, HelixElement, MixinControllerOpen, onUnlessFalse, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary A panel that slides in from an edge.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* Takes `MixinControllerOpen`, so `open`, `defaultOpen` and the
|
|
15
|
-
* `helix-before-open` and `helix-before-close` requests behave exactly as they
|
|
16
|
-
* do on an accordion item, and a consumer already knows the API.
|
|
17
|
-
*
|
|
18
|
-
* Modal by default, which is three things together and not one: focus is
|
|
19
|
-
* trapped inside, Escape and an outside press close it, and the page behind is
|
|
20
|
-
* covered. A panel with the look of a modal and none of its behavior is worse
|
|
21
|
-
* than an inline one, because it invites the reader to treat it as blocking.
|
|
22
|
-
*
|
|
23
|
-
* Content stays mounted when closed, so it is in the server's markup and can
|
|
24
|
-
* animate. A closed `<dialog>` is already out of the tab order and out of the
|
|
25
|
-
* accessibility tree, so nothing here writes `inert`. The `role`, the
|
|
26
|
-
* `aria-modal` and the scrim come from the element too, and
|
|
27
|
-
* `drawer-root.ssr.spec.ts` asserts the panel carries no hand-written `inert`
|
|
28
|
-
* and no hand-written `role`.
|
|
29
|
-
*
|
|
30
|
-
* Opened declaratively with the platform's invoker attributes:
|
|
31
|
-
*
|
|
32
|
-
* ```html
|
|
33
|
-
* <helix-button commandfor="nav" command="--toggle">Menu</helix-button>
|
|
34
|
-
* <helix-drawer id="nav">…</helix-drawer>
|
|
35
|
-
* ```
|
|
36
|
-
*
|
|
37
|
-
* @since 0.1.0
|
|
38
|
-
* @status experimental
|
|
39
|
-
* @category overlays
|
|
40
|
-
* @aliases Sheet, Sidebar, Panel, Off-canvas
|
|
41
|
-
* @documentation https://helix.fusion.dev/components/drawer
|
|
42
|
-
*
|
|
43
|
-
* @slot - The drawer's content.
|
|
44
|
-
* @slot header - Content pinned above the scrolling area.
|
|
45
|
-
*
|
|
46
|
-
* @event {Event} close - Relayed, because the platform's own does not escape.
|
|
47
|
-
* @event {HelixBeforeCloseEvent} helix-before-close - Fired before the panel closes, and cancelable.
|
|
48
|
-
* @event {HelixBeforeOpenEvent} helix-before-open - Fired before the panel opens. Cancelable: refusing it takes the decision and nothing moves.
|
|
49
|
-
* @event {Event} toggle - Says the state moved, carrying `newState` and `oldState`.
|
|
50
|
-
*
|
|
51
|
-
* @csspart content - The scrolling content region.
|
|
52
|
-
* @csspart header - The header region.
|
|
53
|
-
* @csspart panel - The sliding panel.
|
|
54
|
-
*/
|
|
55
10
|
let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(HelixElement) {
|
|
56
11
|
constructor() {
|
|
57
12
|
super(...arguments);
|
|
58
|
-
/* Opened and closed from anywhere on the page, without a line of script:
|
|
59
|
-
`<helix-button commandfor="…" command="--show">`. The same three commands
|
|
60
|
-
the dialog answers, because they are the same act: a drawer and a dialog
|
|
61
|
-
differ in where they sit, not in what opening one means. */
|
|
62
13
|
this.commands = new CommandController(this, {
|
|
63
14
|
commands: {
|
|
64
15
|
'--close': () => this.requestOpen(false),
|
|
@@ -66,82 +17,25 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
|
|
|
66
17
|
'--toggle': () => this.toggleOpen(),
|
|
67
18
|
},
|
|
68
19
|
});
|
|
69
|
-
/**
|
|
70
|
-
* Accessible name for the panel.
|
|
71
|
-
*/
|
|
72
20
|
this.label = '';
|
|
73
|
-
/**
|
|
74
|
-
* Whether the drawer blocks the page behind it.
|
|
75
|
-
*
|
|
76
|
-
* Turning this off removes the focus trap and the scrim together, because
|
|
77
|
-
* they are one decision: a panel that traps focus without covering the page
|
|
78
|
-
* strands the reader, and one that covers without trapping lets them tab
|
|
79
|
-
* behind the cover.
|
|
80
|
-
*
|
|
81
|
-
* Not reflected. It stays `true` by default, since a drawer that silently
|
|
82
|
-
* lost its focus trap because a consumer forgot a word would be worse than an
|
|
83
|
-
* attribute on the host, and nothing selects on it: the scrim is rendered or
|
|
84
|
-
* not, rather than shown or hidden by CSS.
|
|
85
|
-
*/
|
|
86
21
|
this.modal = true;
|
|
87
|
-
/** What a value set before the element existed is kept in. */
|
|
88
22
|
this.returned = '';
|
|
89
|
-
/**
|
|
90
|
-
* Escape, routed through the request so a listener can refuse it.
|
|
91
|
-
*
|
|
92
|
-
* A native dialog closes itself on Escape, which would shut the drawer while
|
|
93
|
-
* the consumer's state still said open: the bug that makes a modal impossible
|
|
94
|
-
* to reopen. `Dialog` intercepts it for the same reason.
|
|
95
|
-
*/
|
|
96
23
|
this.handleCancel = (event) => {
|
|
97
24
|
event.preventDefault();
|
|
98
25
|
this.requestOpen(false);
|
|
99
26
|
};
|
|
100
|
-
/** A close that happened anyway is reflected back rather than argued with. */
|
|
101
27
|
this.handleClose = (event) => {
|
|
102
28
|
if (this.open) {
|
|
103
29
|
this.requestOpen(false);
|
|
104
30
|
}
|
|
105
|
-
/* And under the platform's own name, from the host. `close` on a `<dialog>`
|
|
106
|
-
neither bubbles nor is composed, so the one the browser just fired reaches
|
|
107
|
-
nobody outside this shadow root, which is the same reason `Avatar` relays
|
|
108
|
-
`load`. Announced after the state has moved, so a consumer binding
|
|
109
|
-
`onClose` reads a dialog that is already shut.
|
|
110
|
-
|
|
111
|
-
`cancel` is deliberately not relayed. `handleCancel` prevents it so that
|
|
112
|
-
Escape is a request rather than an act, so the native one never completes:
|
|
113
|
-
relaying it would announce a dismissal that was refused. */
|
|
114
31
|
this.relayNativeEvent(event);
|
|
115
32
|
};
|
|
116
|
-
/**
|
|
117
|
-
* A press on the backdrop, which lands on the dialog element itself.
|
|
118
|
-
*
|
|
119
|
-
* The panel fills only part of the dialog's box, so a press beside it hits
|
|
120
|
-
* the dialog and not a child. A native `<dialog>` does not close on that, and
|
|
121
|
-
* it is the gesture that most obviously means close.
|
|
122
|
-
*/
|
|
123
33
|
this.handleBackdrop = (event) => {
|
|
124
34
|
if (event.target === this.panel && this.modal) {
|
|
125
35
|
this.requestOpen(false);
|
|
126
36
|
}
|
|
127
37
|
};
|
|
128
38
|
}
|
|
129
|
-
/**
|
|
130
|
-
* What the dialog closed with, the way `<dialog>` reports it.
|
|
131
|
-
*
|
|
132
|
-
* **The whole point of a `<dialog>` returning a value is lost behind a shadow
|
|
133
|
-
* boundary**, which is where this one is: `querySelector` does not reach the
|
|
134
|
-
* element, so a consumer could neither set the value nor read it back. A
|
|
135
|
-
* drawer asking which of its actions dismissed it is the ordinary case and had
|
|
136
|
-
* no answer.
|
|
137
|
-
*
|
|
138
|
-
* Written before `open` is set to false rather than passed to a `close(value)`
|
|
139
|
-
* call, because this component has no such method: it is driven by `open`
|
|
140
|
-
* alone, and `Dialog`'s `close()` is the one that takes the value directly.
|
|
141
|
-
*
|
|
142
|
-
* Held here as well as on the element, so a value set before the first render
|
|
143
|
-
* survives to it and a consumer reading one back gets what they wrote.
|
|
144
|
-
*/
|
|
145
39
|
get returnValue() {
|
|
146
40
|
return this.panel?.returnValue ?? this.returned;
|
|
147
41
|
}
|
|
@@ -152,18 +46,11 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
|
|
|
152
46
|
panel.returnValue = value;
|
|
153
47
|
}
|
|
154
48
|
}
|
|
155
|
-
/** The panel, which is a real `<dialog>`. */
|
|
156
49
|
get panel() {
|
|
157
50
|
return this.renderRoot?.querySelector('[part="panel"]') ?? null;
|
|
158
51
|
}
|
|
159
52
|
updated(changed) {
|
|
160
53
|
super.updated(changed);
|
|
161
|
-
/* After render, and only on a difference: `showModal()` on an open dialog
|
|
162
|
-
throws and `close()` on a shut one fires a second `close` event.
|
|
163
|
-
*
|
|
164
|
-
* `showModal` when modal and `show` when not, which is the whole of what
|
|
165
|
-
* `modal` means: both put the panel in the top layer, and only the first
|
|
166
|
-
* brings a backdrop, a focus trap and `inert` behind it. */
|
|
167
54
|
const panel = this.panel;
|
|
168
55
|
if (!panel) {
|
|
169
56
|
return;
|
|
@@ -181,9 +68,6 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
|
|
|
181
68
|
}
|
|
182
69
|
}
|
|
183
70
|
render() {
|
|
184
|
-
/* No `role`, no `aria-modal`, no `inert`, no `tabindex`: a `<dialog>` opened
|
|
185
|
-
with `showModal()` carries all four, and each hand-written version was a
|
|
186
|
-
thing that could drift from what the element actually did. */
|
|
187
71
|
return html `<dialog
|
|
188
72
|
part="panel"
|
|
189
73
|
closedby=${this.closedBy ?? nothing}
|
|
@@ -7,88 +7,19 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, HelixElement, HelixSelectEvent } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary One row of a drill-down, which may hold a page of its own.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* A row that opens a page *contains* that page: its rows are this row's own
|
|
15
|
-
* children, assigned to the `page` slot. The tree is the structure, so there
|
|
16
|
-
* are no ids to wire and a consumer who moves a row has moved its page with it.
|
|
17
|
-
*
|
|
18
|
-
* **It renders one of three things**, and which one is the stack's business
|
|
19
|
-
* rather than its own. On the page showing, it is a row. Showing its own page,
|
|
20
|
-
* it contributes that page's rows and nothing else, because its row belongs to
|
|
21
|
-
* the page above. On neither, it renders nothing at all: the rows are in the
|
|
22
|
-
* light DOM and come back untouched, so there is no state to keep and nothing
|
|
23
|
-
* to hide with an attribute this element would have to write onto itself.
|
|
24
|
-
*
|
|
25
|
-
* Choosing is announced, never applied. The row dispatches and the stack
|
|
26
|
-
* decides whether that means drilling in or choosing a command.
|
|
27
|
-
*
|
|
28
|
-
* @since 0.1.0
|
|
29
|
-
* @status experimental
|
|
30
|
-
* @aliases Panel item, Drill row
|
|
31
|
-
* @documentation https://helix.fusion.dev/components/drilldown
|
|
32
|
-
*
|
|
33
|
-
* @slot - The words naming this row.
|
|
34
|
-
* @slot end - After the words, usually a mark or a count.
|
|
35
|
-
* @slot page - The rows of the page this one opens.
|
|
36
|
-
* @slot start - Before the words, usually an icon.
|
|
37
|
-
*
|
|
38
|
-
* @event {HelixSelectEvent} helix-select - Fired when the row is chosen. Cancelable: refusing it takes the decision and nothing moves.
|
|
39
|
-
*
|
|
40
|
-
* @csspart item - The row itself.
|
|
41
|
-
* @csspart label - The words.
|
|
42
|
-
*/
|
|
43
10
|
let HelixHeadlessDrilldownItem = class HelixHeadlessDrilldownItem extends HelixElement {
|
|
44
11
|
constructor() {
|
|
45
12
|
super(...arguments);
|
|
46
|
-
/**
|
|
47
|
-
* Whether the row can be reached at all.
|
|
48
|
-
*/
|
|
49
13
|
this.disabled = false;
|
|
50
|
-
/**
|
|
51
|
-
* Whether this row holds the region's tab stop. Written by the stack.
|
|
52
|
-
*
|
|
53
|
-
* A drill-down keeps one stop and moves within it on the arrows, so every
|
|
54
|
-
* other row is at `-1`. Leave it off every row, including the first, and
|
|
55
|
-
* nothing in the region is tab-reachable: the keyboard cannot get in without
|
|
56
|
-
* first clicking something with a pointer.
|
|
57
|
-
*
|
|
58
|
-
* No attribute, the same as `Button`'s: this is coordination between
|
|
59
|
-
* elements, and writing `tabindex` onto a host the consumer rendered is a
|
|
60
|
-
* change a React consumer's hydration reports and does not patch up. The stop
|
|
61
|
-
* lands inside this shadow root instead.
|
|
62
|
-
*/
|
|
63
14
|
this.focusable = false;
|
|
64
|
-
/**
|
|
65
|
-
* @internal Read by a parent to identify its children across the light DOM,
|
|
66
|
-
* which is cheaper than `instanceof` and is why this is public rather than
|
|
67
|
-
* protected. Not a consumer's to read or set.
|
|
68
|
-
*/
|
|
69
15
|
this.isDrilldownItem = true;
|
|
70
|
-
/**
|
|
71
|
-
* Whether the page showing is the one this row is on.
|
|
72
|
-
*
|
|
73
|
-
* Written by the stack.
|
|
74
|
-
*/
|
|
75
16
|
this.onPage = true;
|
|
76
|
-
/**
|
|
77
|
-
* Whether this row's own page is the one showing.
|
|
78
|
-
*
|
|
79
|
-
* Written by the stack.
|
|
80
|
-
*/
|
|
81
17
|
this.showingPage = false;
|
|
82
|
-
/**
|
|
83
|
-
* Identifies the row to whatever is listening.
|
|
84
|
-
*/
|
|
85
18
|
this.value = '';
|
|
86
19
|
}
|
|
87
|
-
/** Moves focus onto the row itself. */
|
|
88
20
|
focusItem() {
|
|
89
21
|
this.renderRoot.querySelector('[part="item"]')?.focus();
|
|
90
22
|
}
|
|
91
|
-
/** Announces that this row was chosen. */
|
|
92
23
|
select() {
|
|
93
24
|
if (this.disabled) {
|
|
94
25
|
return;
|
|
@@ -102,19 +33,6 @@ let HelixHeadlessDrilldownItem = class HelixHeadlessDrilldownItem extends HelixE
|
|
|
102
33
|
if (!this.onPage) {
|
|
103
34
|
return nothing;
|
|
104
35
|
}
|
|
105
|
-
/* A real `<button>`, and `role="menuitem"` on it.
|
|
106
|
-
*
|
|
107
|
-
* A `<div role="button">` with a click handler cannot be activated from the
|
|
108
|
-
* keyboard: Enter and Space are behavior the platform gives a real button
|
|
109
|
-
* and gives nothing else. Same argument `Button` makes for rendering one
|
|
110
|
-
* rather than reproducing it.
|
|
111
|
-
*
|
|
112
|
-
* `menuitem` because the stack says `role="menu"`, and a menu whose
|
|
113
|
-
* children are buttons is not a menu: a reader would be told the count of
|
|
114
|
-
* items by one role and the kind of them by the other.
|
|
115
|
-
*
|
|
116
|
-
* `aria-disabled` rather than `disabled`, so a row that cannot be chosen is
|
|
117
|
-
* still reachable and still announces why. `select()` is what refuses. */
|
|
118
36
|
return html `<button
|
|
119
37
|
part="item"
|
|
120
38
|
type="button"
|
|
@@ -10,67 +10,16 @@ import { html, nothing } from 'lit';
|
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
11
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
12
|
import { HelixHeadlessDrilldownItem } from '../item/drilldown-item.js';
|
|
13
|
-
/**
|
|
14
|
-
* @summary A stack of pages, one showing at a time, with a way back.
|
|
15
|
-
*
|
|
16
|
-
* @description
|
|
17
|
-
* One panel that changes rather than panels opening beside each other. That is
|
|
18
|
-
* the right shape wherever there is no room for a second column: a phone has
|
|
19
|
-
* none, and a touch screen has no hover to open one with.
|
|
20
|
-
*
|
|
21
|
-
* **Not a `Menu`, and the difference is what it is for rather than how it
|
|
22
|
-
* looks.** A menu is a thing that opens from a trigger and closes when it is
|
|
23
|
-
* done; this is a region that sits where it is put, inside a drawer or a
|
|
24
|
-
* sidebar or a settings panel. `Menu` drills too, through the same controller,
|
|
25
|
-
* because a menu deep enough to need pages wants exactly this behavior without
|
|
26
|
-
* being this component.
|
|
27
|
-
*
|
|
28
|
-
* **Navigation, never a value.** Rows are places to go and commands to run, so
|
|
29
|
-
* nothing here is form-associated and nothing is submitted. A drill-down that
|
|
30
|
-
* reported a value would be a `Listbox` whose options happen to nest, and that
|
|
31
|
-
* is the component to reach for instead.
|
|
32
|
-
*
|
|
33
|
-
* **The tree is the state.** A row that opens a page contains that page, so the
|
|
34
|
-
* stack holds one reference and reads the rest from the DOM. See
|
|
35
|
-
* `DrilldownController`, which is where this behavior lives and which `Menu`
|
|
36
|
-
* shares.
|
|
37
|
-
*
|
|
38
|
-
* @since 0.1.0
|
|
39
|
-
* @status experimental
|
|
40
|
-
* @category navigation
|
|
41
|
-
* @aliases Panel, Nested list, Drill-in
|
|
42
|
-
* @documentation https://helix.fusion.dev/components/drilldown
|
|
43
|
-
*
|
|
44
|
-
* @slot - The rows of the top page.
|
|
45
|
-
*
|
|
46
|
-
* @event {HelixSelectEvent} helix-select - Fired when a row with no page is chosen. Cancelable: refusing it takes the decision and nothing moves.
|
|
47
|
-
*
|
|
48
|
-
* @csspart back - The control that returns one page.
|
|
49
|
-
* @csspart back-mark - The glyph on the back control.
|
|
50
|
-
* @csspart empty - Shown when there are no entries to show.
|
|
51
|
-
* @csspart header - The bar above a page, holding the way back and its name.
|
|
52
|
-
* @csspart loading - Shown while the entries are still on their way.
|
|
53
|
-
* @csspart page - The region the rows are in.
|
|
54
|
-
* @csspart title - The name of the page showing.
|
|
55
|
-
*/
|
|
56
13
|
let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollection(HelixElement) {
|
|
57
14
|
constructor() {
|
|
58
15
|
super(...arguments);
|
|
59
|
-
/**
|
|
60
|
-
* Accessible name for the region.
|
|
61
|
-
*/
|
|
62
16
|
this.label = 'Navigation';
|
|
63
|
-
/**
|
|
64
|
-
* What the control returning from a page reads.
|
|
65
|
-
*/
|
|
66
17
|
this.backLabel = 'Back';
|
|
67
18
|
this.drilldown = new DrilldownController(this, {
|
|
68
19
|
isRow: (element) => 'isDrilldownItem' in element && element.isDrilldownItem === true,
|
|
69
20
|
root: () => [...this.children],
|
|
70
21
|
slot: 'page',
|
|
71
22
|
});
|
|
72
|
-
/* The stop follows the reader. Arrowing to a row and tabbing away and back
|
|
73
|
-
should return to where they were, not to the top of the page. */
|
|
74
23
|
this.handleFocusIn = (event) => {
|
|
75
24
|
const item = this.drilldown.rows.find((row) => row.contains(event.target));
|
|
76
25
|
if (item && !item.disabled) {
|
|
@@ -84,10 +33,6 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
84
33
|
if (!(item instanceof HelixHeadlessDrilldownItem) || !this.items.includes(item)) {
|
|
85
34
|
return;
|
|
86
35
|
}
|
|
87
|
-
/* A row with a page drills rather than announcing. The event is stopped so
|
|
88
|
-
a consumer listening for a choice does not hear navigation as one: going
|
|
89
|
-
somewhere and choosing something are different acts, and a consumer that
|
|
90
|
-
had to tell them apart would be doing the stack's job. */
|
|
91
36
|
if (this.drilldown.hasPage(item)) {
|
|
92
37
|
event.stopPropagation();
|
|
93
38
|
this.enter(item);
|
|
@@ -108,8 +53,6 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
108
53
|
(event.key === 'Home' ? items[0] : items[items.length - 1])?.focusItem();
|
|
109
54
|
return;
|
|
110
55
|
}
|
|
111
|
-
/* Left and Escape both go back, and neither closes anything: this is a
|
|
112
|
-
region rather than an overlay, so there is nothing above it to close. */
|
|
113
56
|
if (event.key === 'ArrowLeft' || event.key === 'Escape') {
|
|
114
57
|
if (this.back()) {
|
|
115
58
|
event.preventDefault();
|
|
@@ -126,21 +69,9 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
126
69
|
}
|
|
127
70
|
};
|
|
128
71
|
}
|
|
129
|
-
/** The rows on the page showing, which is what the keys move over. */
|
|
130
72
|
get items() {
|
|
131
73
|
return this.drilldown.rows.filter((item) => !item.disabled);
|
|
132
74
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Puts the region's one tab stop on the first row of the page showing.
|
|
135
|
-
*
|
|
136
|
-
* One row holds the stop and the arrows move within, which is the roving
|
|
137
|
-
* pattern the toolbar uses and the one a `menu` wants. Every row at `-1` is
|
|
138
|
-
* a region the keyboard cannot get into at all without a pointer first.
|
|
139
|
-
*
|
|
140
|
-
* Called after every update, because the set changes with the page: drilling
|
|
141
|
-
* in replaces the rows outright, and the stop has to land on rows that are
|
|
142
|
-
* actually showing.
|
|
143
|
-
*/
|
|
144
75
|
placeTabStop() {
|
|
145
76
|
const items = this.items;
|
|
146
77
|
const held = items.find((item) => item.focusable);
|
|
@@ -149,7 +80,6 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
149
80
|
item.focusable = item === stop;
|
|
150
81
|
}
|
|
151
82
|
}
|
|
152
|
-
/** Shows a row's page, and puts focus on the first row of it. */
|
|
153
83
|
enter(item) {
|
|
154
84
|
if (!this.drilldown.enter(item)) {
|
|
155
85
|
return;
|
|
@@ -157,14 +87,6 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
157
87
|
this.direction = 'forward';
|
|
158
88
|
this.updateComplete.then(() => this.items[0]?.focusItem());
|
|
159
89
|
}
|
|
160
|
-
/**
|
|
161
|
-
* Returns one page, and reports whether there was one to return to.
|
|
162
|
-
*
|
|
163
|
-
* Focus lands on the row that opened the page being left, which is why the
|
|
164
|
-
* row is read before going back rather than after: a reader who drills in and
|
|
165
|
-
* comes straight out should be on the row they pressed, not at the top of a
|
|
166
|
-
* list they have already read.
|
|
167
|
-
*/
|
|
168
90
|
back() {
|
|
169
91
|
const leaving = this.drilldown.page;
|
|
170
92
|
if (!this.drilldown.back()) {
|
|
@@ -176,12 +98,6 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
176
98
|
}
|
|
177
99
|
connectedCallback() {
|
|
178
100
|
super.connectedCallback();
|
|
179
|
-
/* Capture, and that is not a detail. A consumer listens on this host too,
|
|
180
|
-
so a bubble-phase listener here is a sibling of theirs: `stopPropagation`
|
|
181
|
-
does nothing to another listener on the same node, and whether the
|
|
182
|
-
navigation is swallowed comes down to which was registered first.
|
|
183
|
-
Capture reaches the host before the row it came from, so stopping there
|
|
184
|
-
stops it for everyone, whatever order anybody bound in. */
|
|
185
101
|
this.addEventListener('helix-select', this.handleSelect, true);
|
|
186
102
|
this.addEventListener('keydown', this.handleKeydown);
|
|
187
103
|
this.addEventListener('focusin', this.handleFocusIn);
|
|
@@ -198,9 +114,6 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
198
114
|
}
|
|
199
115
|
updated(changed) {
|
|
200
116
|
super.updated(changed);
|
|
201
|
-
/* Which rows are on the page showing, written onto the rows themselves
|
|
202
|
-
because only the stack knows: a row cannot see its own depth. Client
|
|
203
|
-
only, since the server has no tree to walk. */
|
|
204
117
|
const page = this.drilldown.page;
|
|
205
118
|
for (const child of this.children) {
|
|
206
119
|
if (!(child instanceof HelixHeadlessDrilldownItem)) {
|
|
@@ -217,7 +130,6 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
217
130
|
this.setAttribute('data-direction', this.direction);
|
|
218
131
|
}
|
|
219
132
|
}
|
|
220
|
-
/** Walks the tree marking which rows are on the page showing. */
|
|
221
133
|
mark(item, page, onPage) {
|
|
222
134
|
item.onPage = onPage;
|
|
223
135
|
item.showingPage = item === page;
|
|
@@ -246,9 +158,6 @@ let HelixHeadlessDrilldown = class HelixHeadlessDrilldown extends MixinCollectio
|
|
|
246
158
|
};
|
|
247
159
|
__decorate([
|
|
248
160
|
property()
|
|
249
|
-
/* Not "Menu". This component's own description is built around not being one,
|
|
250
|
-
and a default accessible name is the one place that claim reaches a
|
|
251
|
-
reader. */
|
|
252
161
|
], HelixHeadlessDrilldown.prototype, "label", void 0);
|
|
253
162
|
__decorate([
|
|
254
163
|
property({ attribute: 'back-label' })
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/* Generated by `bun run gen:element-events` from this package's manifest. Do not edit.
|
|
2
|
-
*
|
|
3
|
-
* Which events each tag dispatches, registered so the React prop audit can tell
|
|
4
|
-
* a handler on the wrong element from one on the right one. See
|
|
5
|
-
* `helix/core/src/events/element-events.ts`. */
|
|
6
1
|
import { registerElementEvents } from '@fusion.dev/helix-core';
|
|
7
2
|
registerElementEvents({
|
|
8
3
|
'helix-headless-accordion-item': ['helix-before-close', 'helix-before-open', 'toggle'],
|
|
@@ -7,44 +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 Says that a region has nothing in it, and what to do about it.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* An empty region with no explanation is indistinguishable from one that failed
|
|
15
|
-
* to load, so this exists to tell the difference: what would be here, and the
|
|
16
|
-
* one action that would put something here.
|
|
17
|
-
*
|
|
18
|
-
* `role="status"` on a polite live region, because an empty view usually
|
|
19
|
-
* arrives in place of content that was being fetched. Announcing it is the only
|
|
20
|
-
* way a screen-reader user learns the search returned nothing, rather than
|
|
21
|
-
* being left waiting.
|
|
22
|
-
*
|
|
23
|
-
* Every region is a bare slot. The reference ships an `empty-state-heading` and
|
|
24
|
-
* an `empty-state-actions` whose only job is to assign themselves a slot on
|
|
25
|
-
* connect, which is a component for something the consumer can write.
|
|
26
|
-
*
|
|
27
|
-
* @since 0.1.0
|
|
28
|
-
* @status stable
|
|
29
|
-
* @category feedback
|
|
30
|
-
* @aliases Placeholder, NoResults, Blank
|
|
31
|
-
* @documentation https://helix.fusion.dev/components/empty
|
|
32
|
-
*
|
|
33
|
-
* @slot - Why, and what to do about it.
|
|
34
|
-
* @slot actions - The way out.
|
|
35
|
-
* @slot heading - What is missing.
|
|
36
|
-
* @slot icon - A mark above the heading.
|
|
37
|
-
*/
|
|
38
10
|
let HelixHeadlessEmpty = class HelixHeadlessEmpty extends HelixElement {
|
|
39
11
|
constructor() {
|
|
40
12
|
super(...arguments);
|
|
41
|
-
/**
|
|
42
|
-
* Whether the state is announced when it appears.
|
|
43
|
-
*
|
|
44
|
-
* On by default: an empty view almost always replaces something that was
|
|
45
|
-
* loading. Turn it off for one that was on the page from the start, which
|
|
46
|
-
* has nothing to announce because nothing changed.
|
|
47
|
-
*/
|
|
48
13
|
this.noAnnounce = false;
|
|
49
14
|
}
|
|
50
15
|
willUpdate(changed) {
|