@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,99 +7,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, HelixBeforeActivateEvent, HelixBeforeCloseEvent, HelixCompleteEvent, HelixElement, hostAria, requestChange, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property, queryAssignedElements, state } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary A guided run through an interface, one stop at a time.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* The root owns the order and nothing else. Which stop is current, what Next
|
|
15
|
-
* and Back mean, when the run is finished; the stops own where they point, and
|
|
16
|
-
* the consumer owns every word.
|
|
17
|
-
*
|
|
18
|
-
* **The spotlight is a shadow, not a hole.** The scrim is a box the size of the
|
|
19
|
-
* current target with an enormous spread on its `box-shadow`, so the darkness
|
|
20
|
-
* is what the shadow paints and the target is simply where the box is. A
|
|
21
|
-
* `clip-path` on a full-screen scrim is the other way to do it and is worse
|
|
22
|
-
* here: it needs the viewport in the same coordinate space as the cut, which
|
|
23
|
-
* stops being true the moment anything between them is transformed.
|
|
24
|
-
*
|
|
25
|
-
* **It measures, and this is a case where measuring is the only option.** The
|
|
26
|
-
* target is an element somewhere else on the page whose position nothing can
|
|
27
|
-
* know statically. That is different from a trail folding by width, where the
|
|
28
|
-
* design rule was expressible and the measurement was answering a question
|
|
29
|
-
* nobody had asked. Here the question is "where is that button", and there is
|
|
30
|
-
* no other way to ask it.
|
|
31
|
-
*
|
|
32
|
-
* **Nothing is measured on the server and nothing needs to be.** A tour starts
|
|
33
|
-
* closed and a closed tour draws nothing, so the server renders the stops as
|
|
34
|
-
* inert markup and the first frame is correct. This is also what makes it
|
|
35
|
-
* acceptable for the root to write `current` onto stops it does not own: there
|
|
36
|
-
* is no server state for the write to disagree with.
|
|
37
|
-
*
|
|
38
|
-
* **It cannot point inside a modal, and nothing can fix that here.**
|
|
39
|
-
* `showModal()` makes everything outside the dialog's own subtree inert, so a
|
|
40
|
-
* panel anywhere else is unpressable however the top layer is ordered: this is
|
|
41
|
-
* what modal means rather than a stacking accident. Measured with a modal
|
|
42
|
-
* `Drawer` open, `elementsFromPoint` over the tour's own Done button returned
|
|
43
|
-
* the drawer, the press light-dismissed the drawer, and the tour heard nothing
|
|
44
|
-
* at all. Anything a tour points into has to stay reachable, which for `Drawer`
|
|
45
|
-
* and `Dialog` means not modal while the tour is running.
|
|
46
|
-
*
|
|
47
|
-
* **Focus is not trapped.** A reader has to be able to look at, and reach, the
|
|
48
|
-
* thing being pointed at. `Escape` leaves, which is the one binding a tour
|
|
49
|
-
* needs and the one every reader already tries.
|
|
50
|
-
*
|
|
51
|
-
* @since 0.1.0
|
|
52
|
-
* @status experimental
|
|
53
|
-
* @category overlays
|
|
54
|
-
* @aliases Coachmark, Product tour, Walkthrough, Onboarding
|
|
55
|
-
* @documentation https://helix.fusion.dev/components/tour
|
|
56
|
-
*
|
|
57
|
-
* @slot - The stops, in the order they should be visited.
|
|
58
|
-
*
|
|
59
|
-
* @event {HelixBeforeActivateEvent} helix-before-activate - Fired before the current stop moves, carrying the new index. Cancelable: refusing it takes the decision and nothing moves.
|
|
60
|
-
* @event {HelixBeforeCloseEvent} helix-before-close - Fired before the reader leaves early. Cancelable: refusing it takes the decision and nothing moves.
|
|
61
|
-
* @event {HelixCompleteEvent} helix-complete - Fired when the last stop is
|
|
62
|
-
* finished.
|
|
63
|
-
* @event {Event} toggle - Says the state moved, carrying `newState` and `oldState`.
|
|
64
|
-
*
|
|
65
|
-
* @csspart spotlight - The box over the current target, which paints the scrim.
|
|
66
|
-
*/
|
|
67
10
|
let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
68
11
|
constructor() {
|
|
69
12
|
super(...arguments);
|
|
70
|
-
/**
|
|
71
|
-
* Label for the control that goes back, handed to every stop.
|
|
72
|
-
*/
|
|
73
13
|
this.backLabel = '';
|
|
74
|
-
/**
|
|
75
|
-
* Stop to start on, where nothing declared one.
|
|
76
|
-
*/
|
|
77
14
|
this.defaultIndex = 0;
|
|
78
|
-
/** The stop being shown, whoever set it, and `undefined` until somebody has. */
|
|
79
15
|
this.indexState = undefined;
|
|
80
|
-
/**
|
|
81
|
-
* Label for the control that goes on from the last stop.
|
|
82
|
-
*/
|
|
83
16
|
this.doneLabel = '';
|
|
84
|
-
/**
|
|
85
|
-
* Accessible name for the run, such as "Getting started".
|
|
86
|
-
*/
|
|
87
17
|
this.label = '';
|
|
88
|
-
/**
|
|
89
|
-
* Label for the control that goes on.
|
|
90
|
-
*/
|
|
91
18
|
this.nextLabel = '';
|
|
92
|
-
/**
|
|
93
|
-
* Whether the tour is running.
|
|
94
|
-
*/
|
|
95
19
|
this.open = false;
|
|
96
|
-
/**
|
|
97
|
-
* Label for the control that leaves the tour.
|
|
98
|
-
*/
|
|
99
20
|
this.skipLabel = '';
|
|
100
|
-
/**
|
|
101
|
-
* Space left around the target when it is lit, in pixels.
|
|
102
|
-
*/
|
|
103
21
|
this.spotlightPadding = 8;
|
|
104
22
|
this.handleAsk = (event) => {
|
|
105
23
|
const what = event.detail?.what;
|
|
@@ -119,24 +37,10 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
|
119
37
|
this.skip();
|
|
120
38
|
}
|
|
121
39
|
};
|
|
122
|
-
/* On the window, because the scrim is fixed and a scroll or a resize moves
|
|
123
|
-
the target out from under it. Passive: neither handler cancels anything. */
|
|
124
40
|
this.handleViewport = () => {
|
|
125
41
|
this.light();
|
|
126
42
|
};
|
|
127
43
|
}
|
|
128
|
-
/**
|
|
129
|
-
* Which stop the reader is on.
|
|
130
|
-
*
|
|
131
|
-
* **Moved through `requestChange`, never by assignment from `move`.** That
|
|
132
|
-
* announces first and applies only if nobody refused, and refusing is how a
|
|
133
|
-
* consumer takes the decision; writing the property and then announcing puts
|
|
134
|
-
* `preventDefault` after the move it is refusing.
|
|
135
|
-
*
|
|
136
|
-
* The shape is `Carousel`'s, which is the nearest thing in the library: a
|
|
137
|
-
* `default-index` for markup that only seeds and a held state that is
|
|
138
|
-
* `undefined` until somebody sets one.
|
|
139
|
-
*/
|
|
140
44
|
get index() {
|
|
141
45
|
return this.indexState ?? this.defaultIndex;
|
|
142
46
|
}
|
|
@@ -145,15 +49,12 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
|
145
49
|
this.indexState = next;
|
|
146
50
|
this.requestUpdate('index', previous);
|
|
147
51
|
}
|
|
148
|
-
/** The stops, in the order they were written. */
|
|
149
52
|
get stops() {
|
|
150
53
|
return (this.assigned ?? []).filter((one) => one.current !== undefined);
|
|
151
54
|
}
|
|
152
|
-
/** How many stops there are. */
|
|
153
55
|
get length() {
|
|
154
56
|
return this.stops.length;
|
|
155
57
|
}
|
|
156
|
-
/** Goes on, and finishes the run on the last stop. */
|
|
157
58
|
next() {
|
|
158
59
|
if (this.index >= this.length - 1) {
|
|
159
60
|
this.finish();
|
|
@@ -161,20 +62,12 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
|
161
62
|
}
|
|
162
63
|
this.move(this.index + 1);
|
|
163
64
|
}
|
|
164
|
-
/** Goes back, and does nothing on the first stop. */
|
|
165
65
|
back() {
|
|
166
66
|
if (this.index <= 0) {
|
|
167
67
|
return;
|
|
168
68
|
}
|
|
169
69
|
this.move(this.index - 1);
|
|
170
70
|
}
|
|
171
|
-
/**
|
|
172
|
-
* Leaves the run early, which is a close rather than a completion.
|
|
173
|
-
*
|
|
174
|
-
* Announced before it is applied, and cancelable because of it: writing
|
|
175
|
-
* `open` first and dispatching after leaves a consumer calling
|
|
176
|
-
* `preventDefault` refusing something that has already happened.
|
|
177
|
-
*/
|
|
178
71
|
skip() {
|
|
179
72
|
const event = new HelixBeforeCloseEvent();
|
|
180
73
|
this.dispatchEvent(event);
|
|
@@ -183,17 +76,10 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
|
183
76
|
}
|
|
184
77
|
this.open = false;
|
|
185
78
|
}
|
|
186
|
-
/* Announced before it closes, and not cancelable, which is the difference
|
|
187
|
-
from `skip`. `HelixCompleteEvent` is `cancelable: false` by construction:
|
|
188
|
-
leaving early is a request a consumer may refuse, and having reached the
|
|
189
|
-
end is something that has already happened. What ordering still buys is a
|
|
190
|
-
listener that reads `open` and sees the tour as it was when it finished. */
|
|
191
79
|
finish() {
|
|
192
80
|
this.dispatchEvent(new HelixCompleteEvent({ value: String(this.index) }));
|
|
193
81
|
this.open = false;
|
|
194
82
|
}
|
|
195
|
-
/* The one path every stateful change in the library goes through: it
|
|
196
|
-
announces, then applies where nothing refused. */
|
|
197
83
|
move(to) {
|
|
198
84
|
requestChange({
|
|
199
85
|
apply: (value) => {
|
|
@@ -206,13 +92,6 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
|
206
92
|
}
|
|
207
93
|
connectedCallback() {
|
|
208
94
|
super.connectedCallback();
|
|
209
|
-
/* On the host, not on the slot the stops are assigned to.
|
|
210
|
-
The event is composed and bubbles, so in a browser it reaches either. It
|
|
211
|
-
does not reach the slot under `bun test`: happy-dom does not propagate
|
|
212
|
-
events through slot assignment, so a listener bound there hears nothing
|
|
213
|
-
and every spec about pressing Next passes for the wrong reason or fails
|
|
214
|
-
for a reason that is not the component's. `Megamenu` moved its own
|
|
215
|
-
listener to the host for exactly this. */
|
|
216
95
|
this.addEventListener('helix-tour-ask', this.handleAsk);
|
|
217
96
|
this.ownerDocument.addEventListener('keydown', this.handleKeydown);
|
|
218
97
|
globalThis.addEventListener('resize', this.handleViewport, { passive: true });
|
|
@@ -234,14 +113,6 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
|
234
113
|
this.settle();
|
|
235
114
|
this.light();
|
|
236
115
|
}
|
|
237
|
-
/**
|
|
238
|
-
* Tells each stop where it is in the run.
|
|
239
|
-
*
|
|
240
|
-
* Written onto the stops, which is the one outward write in this family and
|
|
241
|
-
* is argued for on the class above. The labels go with it, and only where the
|
|
242
|
-
* root was given one, so a stop keeps its own default unless somebody has
|
|
243
|
-
* localised the tour.
|
|
244
|
-
*/
|
|
245
116
|
settle() {
|
|
246
117
|
const stops = this.stops;
|
|
247
118
|
const at = Math.max(0, Math.min(this.index, stops.length - 1));
|
|
@@ -263,7 +134,6 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
|
|
|
263
134
|
}
|
|
264
135
|
});
|
|
265
136
|
}
|
|
266
|
-
/** Reads the current target's box, which is where the light falls. */
|
|
267
137
|
light() {
|
|
268
138
|
const stops = this.stops;
|
|
269
139
|
const target = this.open
|
|
@@ -8,139 +8,36 @@ import { customElement, HelixElement, HelixTourAskEvent, hostAria, PopoverContro
|
|
|
8
8
|
import { property } from 'lit/decorators.js';
|
|
9
9
|
import { html } from 'lit/static-html.js';
|
|
10
10
|
import '../../button/index.js';
|
|
11
|
-
/**
|
|
12
|
-
* @summary One stop on a tour: what it points at, and a panel to say it in.
|
|
13
|
-
*
|
|
14
|
-
* @description
|
|
15
|
-
* A stop names a target with `for` and holds whatever the consumer puts in it.
|
|
16
|
-
* It owns no content of its own, which is the whole shape of this family: the
|
|
17
|
-
* tour abstracts the three things that are genuinely hard, and the words are
|
|
18
|
-
* the consumer's.
|
|
19
|
-
*
|
|
20
|
-
* What it does own:
|
|
21
|
-
*
|
|
22
|
-
* - **Where the panel goes.** Anchored to the target through the same floating
|
|
23
|
-
* pass every other overlay here uses.
|
|
24
|
-
* - **That it is on top.** A popover, so it is in the top layer. A tour points
|
|
25
|
-
* at things inside dialogs, inside scrollers, inside anything with a stacking
|
|
26
|
-
* context, and a panel in the ordinary flow loses to all three.
|
|
27
|
-
* - **When it shows.** Which is `current`, and which the root decides.
|
|
28
|
-
*
|
|
29
|
-
* What it does not own is the heading, the paragraph, the picture and the
|
|
30
|
-
* layout. Slot a `helix-card`, or a heading and a line of prose, or anything
|
|
31
|
-
* else. Fixed `title` and `body` parts would be a component deciding what a
|
|
32
|
-
* tour is allowed to say.
|
|
33
|
-
*
|
|
34
|
-
* **The buttons are slot fallback, so the common case costs nothing and the
|
|
35
|
-
* uncommon one is possible.** Write nothing and get Back, Next and Skip already
|
|
36
|
-
* wired. Slot `actions` and they are yours, driven by `next()`, `back()` and
|
|
37
|
-
* `skip()` on the root, which are public for exactly that.
|
|
38
|
-
*
|
|
39
|
-
* **`for` is an id, the same as `Tooltip`'s.** The target is elsewhere on the
|
|
40
|
-
* page, and an id is the one reference a consumer can write in markup without
|
|
41
|
-
* holding an element. Resolved against the root node, so a stop inside a shadow
|
|
42
|
-
* tree points at a target in that tree.
|
|
43
|
-
*
|
|
44
|
-
* @since 0.1.0
|
|
45
|
-
* @status experimental
|
|
46
|
-
* @category overlays
|
|
47
|
-
* @documentation https://helix.fusion.dev/components/tour
|
|
48
|
-
*
|
|
49
|
-
* @slot - What this stop says. A card, a heading and a line, anything.
|
|
50
|
-
* @slot actions - Replaces the controls, which are Back, Next and Skip.
|
|
51
|
-
*
|
|
52
|
-
* @event {HelixTourAskEvent} helix-tour-ask - A stop asking the tour to move, from its own controls.
|
|
53
|
-
*
|
|
54
|
-
* @csspart actions - The row the controls sit in.
|
|
55
|
-
* @csspart arrow - The point, which the floating pass places against the target.
|
|
56
|
-
* @csspart back - The control that goes to the previous stop.
|
|
57
|
-
* @csspart back-button - The inner control.
|
|
58
|
-
* @csspart bubble - The panel that points at the target.
|
|
59
|
-
* @csspart content - The region holding the slotted content.
|
|
60
|
-
* @csspart next - The control that goes on, and finishes on the last stop.
|
|
61
|
-
* @csspart next-button - The inner control.
|
|
62
|
-
* @csspart skip - The control that leaves the tour.
|
|
63
|
-
* @csspart skip-button - The inner control.
|
|
64
|
-
*/
|
|
65
11
|
let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
|
|
66
12
|
constructor() {
|
|
67
13
|
super(...arguments);
|
|
68
|
-
/**
|
|
69
|
-
* Label for the control that goes back.
|
|
70
|
-
*/
|
|
71
14
|
this.backLabel = 'Back';
|
|
72
|
-
/**
|
|
73
|
-
* Whether this is the stop the reader is on.
|
|
74
|
-
*
|
|
75
|
-
* Written by the root rather than by a consumer, which is the one place this
|
|
76
|
-
* family writes onto markup it does not own. It is acceptable here for a
|
|
77
|
-
* reason that did not hold for the trail or the avatar group: a tour starts
|
|
78
|
-
* closed, so there is no server state to get wrong. The server renders a
|
|
79
|
-
* closed tour, a closed tour shows nothing, and nothing can disagree.
|
|
80
|
-
*/
|
|
81
15
|
this.current = false;
|
|
82
|
-
/**
|
|
83
|
-
* Label for the control that goes on from the last stop.
|
|
84
|
-
*/
|
|
85
16
|
this.doneLabel = 'Done';
|
|
86
|
-
/**
|
|
87
|
-
* Whether this is the first stop, so there is nowhere back to.
|
|
88
|
-
*/
|
|
89
17
|
this.first = false;
|
|
90
|
-
/**
|
|
91
|
-
* The id of the element this stop is about.
|
|
92
|
-
*/
|
|
93
18
|
this.for = '';
|
|
94
|
-
/**
|
|
95
|
-
* Whether this is the last stop, so the control says Done.
|
|
96
|
-
*/
|
|
97
19
|
this.last = false;
|
|
98
|
-
/**
|
|
99
|
-
* Label for the control that goes on.
|
|
100
|
-
*/
|
|
101
20
|
this.nextLabel = 'Next';
|
|
102
|
-
/**
|
|
103
|
-
* Which side of the target the panel prefers.
|
|
104
|
-
*/
|
|
105
21
|
this.placement = 'bottom';
|
|
106
|
-
/**
|
|
107
|
-
* Label for the control that leaves the tour.
|
|
108
|
-
*/
|
|
109
22
|
this.skipLabel = 'Skip';
|
|
110
23
|
this.floating = new PopoverController(this, {
|
|
111
24
|
anchor: () => this.target,
|
|
112
25
|
arrow: () => this.renderRoot?.querySelector('[part="arrow"]'),
|
|
113
|
-
/* A press outside does not end a tour, and this is the decision rather than
|
|
114
|
-
a stub. Everywhere else a surface closes on an outside press because
|
|
115
|
-
pressing elsewhere means the reader is finished with it. Here elsewhere
|
|
116
|
-
is the thing being pointed at: the scrim lets the pointer through on
|
|
117
|
-
purpose, so the first press a reader makes on the button the tour is
|
|
118
|
-
describing would be the press that ends the tour. Leaving is the skip
|
|
119
|
-
control and `Escape`, both of which say so. */
|
|
120
26
|
onDismiss: () => { },
|
|
121
27
|
placement: () => this.placement,
|
|
122
28
|
surface: () => this.renderRoot?.querySelector('[part="bubble"]'),
|
|
123
29
|
});
|
|
124
30
|
}
|
|
125
|
-
/** The element this stop is about, or null where the id resolves to nothing. */
|
|
126
31
|
get target() {
|
|
127
32
|
const scope = this.getRootNode();
|
|
128
33
|
return this.for === '' ? null : (scope.getElementById?.(this.for) ?? null);
|
|
129
34
|
}
|
|
130
35
|
willUpdate(changed) {
|
|
131
36
|
super.willUpdate(changed);
|
|
132
|
-
/* `group`, not `dialog`, and this is a decision rather than a shortcut. A
|
|
133
|
-
tour must not trap focus: the reader has to be able to look at the thing
|
|
134
|
-
being pointed at, and an overlay that will not let them has hidden its
|
|
135
|
-
own subject. */
|
|
136
37
|
hostAria(this.internals, { role: 'group' });
|
|
137
38
|
}
|
|
138
39
|
updated(changed) {
|
|
139
40
|
super.updated(changed);
|
|
140
|
-
/* Shown and hidden from here rather than by a controller reading a
|
|
141
|
-
property, because `current` is written onto this element by the root and
|
|
142
|
-
is not a property this element decides. The controller does the top layer
|
|
143
|
-
and the placement; when is ours. */
|
|
144
41
|
if (this.current) {
|
|
145
42
|
this.floating.show();
|
|
146
43
|
}
|
|
@@ -148,15 +45,6 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
|
|
|
148
45
|
this.floating.hide();
|
|
149
46
|
}
|
|
150
47
|
}
|
|
151
|
-
/**
|
|
152
|
-
* Skip, back and next, behind a seam the styled tier can move.
|
|
153
|
-
*
|
|
154
|
-
* They are slot fallback rather than markup a consumer has to write, so a
|
|
155
|
-
* tour with nothing slotted still has the three controls a tour needs; and
|
|
156
|
-
* they are a method as well, so a tier above can render its own three without
|
|
157
|
-
* reimplementing the panel around them. Both are needed: the slot is for the
|
|
158
|
-
* consumer and the method is for the tier.
|
|
159
|
-
*/
|
|
160
48
|
renderActions() {
|
|
161
49
|
return html `<slot name="actions">
|
|
162
50
|
<${this.tag('button')}
|
|
@@ -187,14 +75,7 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
|
|
|
187
75
|
}
|
|
188
76
|
render() {
|
|
189
77
|
return html `<div part="bubble" popover="manual" ?hidden=${!this.current}>
|
|
190
|
-
${
|
|
191
|
-
/* The one surface in this library that draws an arrow, and every other
|
|
192
|
-
one deliberately does not. A menu or a select hangs off its trigger
|
|
193
|
-
and adjacency says what it belongs to; a tour panel is anchored to
|
|
194
|
-
something it did not open, on a page full of other things, and
|
|
195
|
-
collision may flip it to the far side of its target. Leaving arrows
|
|
196
|
-
off elsewhere is house style rather than a fact about the system. */
|
|
197
|
-
''}
|
|
78
|
+
${''}
|
|
198
79
|
<div part="arrow" aria-hidden="true"></div>
|
|
199
80
|
<div part="content"><slot></slot></div>
|
|
200
81
|
<div part="actions">
|
|
@@ -202,13 +83,6 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
|
|
|
202
83
|
</div>
|
|
203
84
|
</div>`;
|
|
204
85
|
}
|
|
205
|
-
/**
|
|
206
|
-
* Says what the reader pressed and lets the root decide.
|
|
207
|
-
*
|
|
208
|
-
* A stop cannot move a tour: it does not know what is after it, and a stop
|
|
209
|
-
* that reached up to its parent to find out would be a stop that only works
|
|
210
|
-
* inside one. Composed, so the root hears it through its own slot.
|
|
211
|
-
*/
|
|
212
86
|
ask(what) {
|
|
213
87
|
this.dispatchEvent(new HelixTourAskEvent({ what }));
|
|
214
88
|
}
|
|
@@ -9,141 +9,23 @@ import { caretDown } from '@fusion.dev/helix-icons';
|
|
|
9
9
|
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
|
-
/**
|
|
13
|
-
* @summary One node in a tree.
|
|
14
|
-
*
|
|
15
|
-
* @description
|
|
16
|
-
* **`open`, on `MixinControllerOpen`, rather than a bare boolean of its own.** A
|
|
17
|
-
* branch showing its children is the same state a disclosure holds, and
|
|
18
|
-
* `AccordionItem` is on that mixin too. A hand-written toggle announces nothing,
|
|
19
|
-
* so a consumer can neither know a branch opened nor own the state.
|
|
20
|
-
* `aria-expanded` is still what the row renders, because that is the attribute a
|
|
21
|
-
* tree uses; the property is what differs, not the semantics.
|
|
22
|
-
*
|
|
23
|
-
* An element rather than a slot because it owns state: whether it is open,
|
|
24
|
-
* whether it is selected, and whether it is the one holding focus.
|
|
25
|
-
*
|
|
26
|
-
* A branch is any item with something slotted into `children`, detected in
|
|
27
|
-
* `firstUpdated` rather than declared by the consumer. That is a client-side
|
|
28
|
-
* enhancement by necessity, since the server cannot inspect children, so the
|
|
29
|
-
* server renders the node as a leaf and the twisty appears on hydration. It
|
|
30
|
-
* appears rather than disappearing, which is the harmless direction.
|
|
31
|
-
*
|
|
32
|
-
* **`href` makes the row itself the link.** A tree used as navigation is a list
|
|
33
|
-
* of links, and the row is what a reader presses: with the anchor slotted into
|
|
34
|
-
* the name instead, everything outside the words is dead, Enter does nothing,
|
|
35
|
-
* and there is no middle-click, no status bar and no open-in-new-tab. Given an
|
|
36
|
-
* `href` the row *is* an `<a>`, so the platform provides all of that and the
|
|
37
|
-
* keyboard needs no handler at all.
|
|
38
|
-
*
|
|
39
|
-
* It changes the shape of the ARIA, which is why it is a property and not the
|
|
40
|
-
* default. An anchor cannot contain the group, since the group holds more
|
|
41
|
-
* anchors and nesting them is invalid, so the row carries `treeitem` and the
|
|
42
|
-
* group becomes its sibling under a presentational wrapper. That is exactly the
|
|
43
|
-
* navigation flavor of the tree pattern, and the arrangement without an `href`
|
|
44
|
-
* stays as it was: `treeitem` containing `group`, which is what an `<li>`
|
|
45
|
-
* holding a `<ul>` is.
|
|
46
|
-
*
|
|
47
|
-
* A consumer whose framework owns routing slots its own link instead and leaves
|
|
48
|
-
* `href` unset. The row follows that one too, so the whole row works either
|
|
49
|
-
* way; what the anchor adds is what only a real anchor can give.
|
|
50
|
-
*
|
|
51
|
-
* The properties below are written by this component and read by whoever draws
|
|
52
|
-
* it. A consumer sets none of them: they carry a number this element computed
|
|
53
|
-
* and CSS cannot, which is what lets a stylesheet on this tier draw the thing
|
|
54
|
-
* without measuring anything itself.
|
|
55
|
-
*
|
|
56
|
-
* @since 0.1.0
|
|
57
|
-
* @status experimental
|
|
58
|
-
* @aliases TreeNode, Branch, Leaf
|
|
59
|
-
* @documentation https://helix.fusion.dev/components/tree
|
|
60
|
-
*
|
|
61
|
-
* @slot - The item's label.
|
|
62
|
-
* @slot children - Nested items, which make this a branch.
|
|
63
|
-
* @slot end - After the label, at the row's far side. A count, or a state.
|
|
64
|
-
* @slot start - Before the label, after the chevron. A mark about the item.
|
|
65
|
-
*
|
|
66
|
-
* @event {HelixBeforeCloseEvent} helix-before-close - Fired before a branch closes, and cancelable.
|
|
67
|
-
* @event {HelixBeforeOpenEvent} helix-before-open - Fired before a branch opens, and cancelable.
|
|
68
|
-
* @event {HelixSelectEvent} helix-select - Fired when the item is chosen. Cancelable: refusing it takes the decision and nothing moves.
|
|
69
|
-
* @event {Event} toggle - Says the state moved, carrying `newState` and `oldState`.
|
|
70
|
-
*
|
|
71
|
-
* @csspart group - The container for nested items.
|
|
72
|
-
* @csspart item - The node, and the focus target unless `href` moves it to the row.
|
|
73
|
-
* @csspart label - The label text.
|
|
74
|
-
* @csspart row - The clickable label row, which is an `<a>` where `href` is set.
|
|
75
|
-
* @csspart toggle - The expand and collapse control.
|
|
76
|
-
*
|
|
77
|
-
* @cssproperty --tree-level - How deep this row sits, counting from zero, for the indent.
|
|
78
|
-
*/
|
|
79
12
|
let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerOpen(HelixElement) {
|
|
80
13
|
constructor() {
|
|
81
14
|
super(...arguments);
|
|
82
|
-
/**
|
|
83
|
-
* Where this node goes, which makes its row a real link.
|
|
84
|
-
*
|
|
85
|
-
* Unset by default, and unreflected: a tree of choosable nodes is not a tree
|
|
86
|
-
* of links, and an empty `href` written onto every row would be a link to the
|
|
87
|
-
* current page on every item a consumer never asked to be one.
|
|
88
|
-
*/
|
|
89
15
|
this.href = '';
|
|
90
|
-
/**
|
|
91
|
-
* What the link declares about where it goes.
|
|
92
|
-
*
|
|
93
|
-
* Set by the consumer rather than derived from `target`: `noreferrer` is a
|
|
94
|
-
* decision about their own analytics and a component should not make it for
|
|
95
|
-
* them. `noopener` is not needed as a default either, since every browser
|
|
96
|
-
* this library supports implies it for `target="_blank"` already.
|
|
97
|
-
*/
|
|
98
16
|
this.rel = '';
|
|
99
|
-
/**
|
|
100
|
-
* Whether this item is the current one.
|
|
101
|
-
*
|
|
102
|
-
* Not reflected, for the reason `level` below gives in full: the tree assigns
|
|
103
|
-
* this after it upgrades, so the attribute would be a change to markup the
|
|
104
|
-
* consumer rendered and hydration would report it. Mirroring to
|
|
105
|
-
* `data-selected` on the host instead is the same fault under another name.
|
|
106
|
-
*
|
|
107
|
-
* The stylesheet reads it from the row inside this shadow root, because the
|
|
108
|
-
* row is not part of the tree React hydrates.
|
|
109
|
-
*/
|
|
110
17
|
this.selected = false;
|
|
111
|
-
/**
|
|
112
|
-
* Where the link opens, for the rare row that leaves the site.
|
|
113
|
-
*/
|
|
114
18
|
this.target = '';
|
|
115
|
-
/**
|
|
116
|
-
* Identifies the item to the consumer, carried in `helix-select`.
|
|
117
|
-
*/
|
|
118
19
|
this.value = '';
|
|
119
|
-
/**
|
|
120
|
-
* Whether this item holds the tree's single tab stop.
|
|
121
|
-
*/
|
|
122
20
|
this.focusable = false;
|
|
123
|
-
/**
|
|
124
|
-
* Whether anything is slotted into `children`.
|
|
125
|
-
*
|
|
126
|
-
* Read after first render.
|
|
127
|
-
*/
|
|
128
21
|
this.branch = false;
|
|
129
22
|
this.handleToggleClick = (event) => {
|
|
130
|
-
/* Only where the row is a link, and then it has to stop: the row would
|
|
131
|
-
navigate away from the branch the reader was trying to open. Without an
|
|
132
|
-
`href` the whole row toggles, which is what a tree of folders wants, so
|
|
133
|
-
there is nothing here to intercept. */
|
|
134
23
|
event.preventDefault();
|
|
135
24
|
event.stopPropagation();
|
|
136
25
|
this.toggleOpen();
|
|
137
26
|
};
|
|
138
27
|
this.handleRowClick = (event) => {
|
|
139
|
-
/* A press that landed on a link is that link's, whichever branch of this
|
|
140
|
-
runs, and the test has to be made on both sides. A row can hold a link in
|
|
141
|
-
its `end` slot, and asked on one side only, pressing that link collapses
|
|
142
|
-
the branch on the way out: one press, `open` false and the route
|
|
143
|
-
changed. */
|
|
144
28
|
const onLink = event.composedPath().some((node) => node instanceof HTMLAnchorElement);
|
|
145
|
-
/* A branch that is also a link goes where it points, and its chevron is
|
|
146
|
-
what opens it. A branch that is not toggles from anywhere in the row. */
|
|
147
29
|
if (this.branch && this.href === '') {
|
|
148
30
|
if (!onLink) {
|
|
149
31
|
this.toggleOpen();
|
|
@@ -151,8 +33,6 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
|
|
|
151
33
|
return;
|
|
152
34
|
}
|
|
153
35
|
this.select();
|
|
154
|
-
/* Following it here as well would follow it twice and, where a consumer's
|
|
155
|
-
router is listening, route twice. */
|
|
156
36
|
if (!onLink) {
|
|
157
37
|
this.follow();
|
|
158
38
|
}
|
|
@@ -166,74 +46,22 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
|
|
|
166
46
|
this.branch = slot.assignedElements().length > 0;
|
|
167
47
|
});
|
|
168
48
|
}
|
|
169
|
-
/**
|
|
170
|
-
* @internal Read by the tree to identify a branch across the light DOM,
|
|
171
|
-
* which is cheaper than `instanceof` and is why this is public rather than
|
|
172
|
-
* protected. A consumer asks `branch`, which is the property they set.
|
|
173
|
-
*/
|
|
174
49
|
get isBranch() {
|
|
175
50
|
return this.branch;
|
|
176
51
|
}
|
|
177
|
-
/**
|
|
178
|
-
* Moves focus to this node.
|
|
179
|
-
*
|
|
180
|
-
* Which element that is moves with `href`: the thing carrying `treeitem` is
|
|
181
|
-
* the thing the tree's roving tab stop lives on, and with a link that is the
|
|
182
|
-
* row rather than the wrapper around it.
|
|
183
|
-
*/
|
|
184
52
|
focusItem() {
|
|
185
|
-
/* `preventScroll`, because the browser's own answer to a focus is to walk
|
|
186
|
-
every scrollable ancestor and reveal the element, which is
|
|
187
|
-
`scrollIntoView` by another route and takes the page with it. The root
|
|
188
|
-
calls `scrollItemIntoView` on the next line precisely to avoid that, and
|
|
189
|
-
without this the careful call arrives after the thing it exists to
|
|
190
|
-
prevent has already happened. */
|
|
191
53
|
this.renderRoot
|
|
192
54
|
.querySelector(this.href === '' ? '[part="item"]' : '[part="row"]')
|
|
193
55
|
?.focus({ preventScroll: true });
|
|
194
56
|
}
|
|
195
|
-
/** Selects this node and announces it. */
|
|
196
57
|
select() {
|
|
197
58
|
this.dispatchEvent(new HelixSelectEvent({ value: this.value }));
|
|
198
59
|
}
|
|
199
|
-
/**
|
|
200
|
-
* Chooses this node the way a reader does, from a press or from Enter.
|
|
201
|
-
*
|
|
202
|
-
* Selecting and following are two things and this is where they meet, so the
|
|
203
|
-
* pointer and the keyboard take one path to the same end. The tree calls this
|
|
204
|
-
* rather than `select` for the keyboard, or a nav tree whose rows are links
|
|
205
|
-
* could be walked with the arrows and never entered: choosing announces a
|
|
206
|
-
* value and nothing listens for it.
|
|
207
|
-
*/
|
|
208
60
|
activate() {
|
|
209
61
|
this.select();
|
|
210
62
|
this.follow();
|
|
211
63
|
}
|
|
212
|
-
/**
|
|
213
|
-
* Follows a link slotted into the name, if there is one.
|
|
214
|
-
*
|
|
215
|
-
* A row is wider than the words in it: the indent, the padding, and with the
|
|
216
|
-
* chevron at the far end most of the row's width are all outside the anchor,
|
|
217
|
-
* so a reader who pressed the row anywhere but on the text got nothing. The
|
|
218
|
-
* words are the consumer's own content and this does not rewrite them, so
|
|
219
|
-
* what it does instead is press the link they put there.
|
|
220
|
-
*
|
|
221
|
-
* The consumer's anchor rather than an `href` of this element's own,
|
|
222
|
-
* deliberately: a framework's link component is what carries prefetching and
|
|
223
|
-
* client-side routing, and a plain `<a href>` rendered in here would be a
|
|
224
|
-
* full page load on a site that had neither. A synthetic click is what those
|
|
225
|
-
* components listen for, so both keep working.
|
|
226
|
-
*
|
|
227
|
-
* `querySelector` on the light DOM, not the render root: the link is the
|
|
228
|
-
* consumer's, and it is scoped to this item's own name rather than to the
|
|
229
|
-
* whole subtree, or a branch would follow the first link belonging to one of
|
|
230
|
-
* its children.
|
|
231
|
-
*/
|
|
232
64
|
follow() {
|
|
233
|
-
/* Its own row first, where `href` made one. Pressed with a pointer the
|
|
234
|
-
anchor has already navigated and this never runs; from the keyboard it
|
|
235
|
-
has not, because the tree calls `preventDefault` on every key it handles
|
|
236
|
-
and that is what would otherwise swallow Enter on a link. */
|
|
237
65
|
const own = this.renderRoot?.querySelector('a[part="row"]');
|
|
238
66
|
if (own) {
|
|
239
67
|
own.click();
|
|
@@ -250,7 +78,6 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
|
|
|
250
78
|
}
|
|
251
79
|
}
|
|
252
80
|
}
|
|
253
|
-
/** Everything inside the row, which is the same whichever element it is. */
|
|
254
81
|
renderRow() {
|
|
255
82
|
return html `${this.branch
|
|
256
83
|
? html `<svg
|
|
@@ -267,36 +94,11 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
|
|
|
267
94
|
<span part="label"><slot></slot></span>
|
|
268
95
|
<slot name="end"></slot>`;
|
|
269
96
|
}
|
|
270
|
-
/**
|
|
271
|
-
* Mirrors the chosen state where a stylesheet can select on it.
|
|
272
|
-
*
|
|
273
|
-
* **A custom state, never a reflected `selected` attribute.** The parent
|
|
274
|
-
* writes this: a listbox choosing a row, a tab strip moving the tab, a radio
|
|
275
|
-
* group answering for its set. Reflected, that puts the element's own
|
|
276
|
-
* bookkeeping onto markup a consumer wrote and a consumer's server never sent,
|
|
277
|
-
* which React reconciles and reports as a mismatch, and which a serialized
|
|
278
|
-
* element pasted back declares as a choice nobody made. `ElementInternals`
|
|
279
|
-
* holds it instead, where it is not in the DOM at all.
|
|
280
|
-
*
|
|
281
|
-
* Reading `selected` is unaffected and so is writing it: the attribute still
|
|
282
|
-
* seeds the property, which is what `<helix-tree-item selected>` in a consumer's
|
|
283
|
-
* markup means. What stops is the write back.
|
|
284
|
-
*/
|
|
285
97
|
willUpdate(changed) {
|
|
286
98
|
super.willUpdate(changed);
|
|
287
99
|
this.toggleState('selected', this.selected);
|
|
288
100
|
}
|
|
289
101
|
render() {
|
|
290
|
-
/* Where the semantics sit, which `href` decides.
|
|
291
|
-
*
|
|
292
|
-
* A link row carries them itself and the wrapper drops to `none`, because
|
|
293
|
-
* an anchor cannot contain the group: the group holds more anchors and
|
|
294
|
-
* nesting them is invalid markup that no assistive technology reads the way
|
|
295
|
-
* it looks. So the row becomes the `treeitem` and the group becomes its
|
|
296
|
-
* sibling, which is the navigation flavor of the pattern.
|
|
297
|
-
*
|
|
298
|
-
* Without one, nothing changes: the wrapper is the `treeitem` and holds the
|
|
299
|
-
* group, which is what an `<li>` holding a `<ul>` is. */
|
|
300
102
|
const linked = this.href !== '';
|
|
301
103
|
const level = this.level ?? 1;
|
|
302
104
|
const expanded = this.branch ? (this.open ? 'true' : 'false') : nothing;
|