@fusion.dev/helix-headless 0.2.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,49 +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 something indeterminate is in progress.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* Two concentric circles rather than one arc: the track is what makes the
|
|
15
|
-
* moving part read as a proportion of a whole rather than as a shape drifting
|
|
16
|
-
* on its own, and it costs one element.
|
|
17
|
-
*
|
|
18
|
-
* **The center is a slot and is empty by default.** A loader with a mark in the
|
|
19
|
-
* middle is a brand's loader, and this library does not know whose. The slot is
|
|
20
|
-
* the capability; the logo is the application's, put in once wherever the app
|
|
21
|
-
* wraps it. Nothing is rendered when nothing is slotted, so an unbranded loader
|
|
22
|
-
* is still two circles and no extra box.
|
|
23
|
-
*
|
|
24
|
-
* What is slotted does not turn. Only the arc moves, because a mark rotating
|
|
25
|
-
* with it stops reading as a mark and becomes part of the animation, and a
|
|
26
|
-
* wordmark upside down twice a second is worse than no wordmark.
|
|
27
|
-
*
|
|
28
|
-
* The semantics are on the host through `ElementInternals`, not on an inner
|
|
29
|
-
* span. A `role` written into the shadow root announces correctly but leaves
|
|
30
|
-
* the host itself anonymous to anything walking the light DOM, and a `role`
|
|
31
|
-
* written onto the host as an attribute is one a React consumer's server markup
|
|
32
|
-
* never had.
|
|
33
|
-
*
|
|
34
|
-
* @since 0.1.0
|
|
35
|
-
* @status stable
|
|
36
|
-
* @category feedback
|
|
37
|
-
* @aliases Spinner, Busy, Activity
|
|
38
|
-
* @documentation https://helix.fusion.dev/components/loader
|
|
39
|
-
*
|
|
40
|
-
* @slot - A mark to sit still at the center, usually a logo. Empty by default.
|
|
41
|
-
*
|
|
42
|
-
* @csspart indicator - The arc that turns.
|
|
43
|
-
* @csspart mark - The region holding what was slotted at the center.
|
|
44
|
-
* @csspart svg - The rotating circle pair.
|
|
45
|
-
* @csspart track - The full circle behind the indicator.
|
|
46
|
-
*/
|
|
47
10
|
let HelixHeadlessLoader = class HelixHeadlessLoader extends HelixElement {
|
|
48
11
|
constructor() {
|
|
49
12
|
super(...arguments);
|
|
50
|
-
/**
|
|
51
|
-
* Accessible name, announced while the work is outstanding.
|
|
52
|
-
*/
|
|
53
13
|
this.label = 'Loading';
|
|
54
14
|
}
|
|
55
15
|
willUpdate(changed) {
|
|
@@ -8,123 +8,12 @@ var HelixHeadlessMegamenu_1;
|
|
|
8
8
|
import { anchorElement, customElement, HelixElement, MixinControllerOpen, PopoverController, tellDisclosure, } from '@fusion.dev/helix-core';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
|
-
/**
|
|
12
|
-
* The panel showing now, so opening one closes the last.
|
|
13
|
-
*
|
|
14
|
-
* A page has one of these open at a time: a nav bar whose Products panel is
|
|
15
|
-
* still down while Solutions opens is a nav bar with two panels over the page.
|
|
16
|
-
* Module scope rather than a coordinating parent, because there is nothing to
|
|
17
|
-
* coordinate through: these sit in whatever container the consumer used, which
|
|
18
|
-
* is usually `helix-navigation` and is not required to be. `Video` holds the
|
|
19
|
-
* media session the same way and for the same reason.
|
|
20
|
-
*/
|
|
21
11
|
let showing;
|
|
22
|
-
/**
|
|
23
|
-
* @summary A section of a navigation that opens a panel of destinations.
|
|
24
|
-
*
|
|
25
|
-
* @description
|
|
26
|
-
* **A disclosure, not a menu, and that is the whole design.** `role="menu"` and
|
|
27
|
-
* `menuitem` are for an application's commands: they take over the arrow keys,
|
|
28
|
-
* announce "menu" to a reader who asked for navigation, and put every link
|
|
29
|
-
* inside a role that is not a link. What this is, is a button that shows and
|
|
30
|
-
* hides a region of the consumer's own anchors, which is the pattern the APG
|
|
31
|
-
* gives for site navigation and the one that leaves a middle click, a modified
|
|
32
|
-
* click and the status bar working. `Menu` remains right for a row of commands.
|
|
33
|
-
*
|
|
34
|
-
* **It renders no links**, for the reason `Navigation` gives: a router renders
|
|
35
|
-
* its own anchor and cannot render one inside this shadow root. The panel is a
|
|
36
|
-
* slot, and what goes in it is lists and headings the consumer wrote.
|
|
37
|
-
*
|
|
38
|
-
* **It opens on hover, and a press is always the fallback.** `open-on` says
|
|
39
|
-
* which, and hover is the resting value because a nav bar is the one place a
|
|
40
|
-
* reader expects to sweep along a row and see each section: making them click,
|
|
41
|
-
* read, click again to compare two is the interaction hover exists to save.
|
|
42
|
-
*
|
|
43
|
-
* Hover is not the only way in, and the code is mostly about that. There is no
|
|
44
|
-
* hover on a touch screen, so a tap opens the panel there whatever `open-on`
|
|
45
|
-
* says, which is why the pointer type is checked rather than assumed. Focus
|
|
46
|
-
* still opens nothing, for the same reason a tab through a nav bar should not
|
|
47
|
-
* unfold it, but Enter on the trigger does, and a keyboard press arrives as a
|
|
48
|
-
* click with no pointer behind it.
|
|
49
|
-
*
|
|
50
|
-
* A mouse click on a trigger the pointer is already sitting on is the case
|
|
51
|
-
* worth naming: toggling there would close the panel and the pointer, still on
|
|
52
|
-
* the trigger, would open it again, so the trigger would flicker and never
|
|
53
|
-
* close. In hover mode the mouse's own clicks are left alone and hover governs
|
|
54
|
-
* on its own. Escape and a click away still close it.
|
|
55
|
-
*
|
|
56
|
-
* The delays are intent, not decoration. Opening waits, so a pointer crossing
|
|
57
|
-
* the bar on its way somewhere else opens nothing; closing waits longer, so the
|
|
58
|
-
* diagonal from the trigger down into the panel does not pass through a gap and
|
|
59
|
-
* shut it. Moving along a bar where a panel is already down opens the next one
|
|
60
|
-
* at once, because at that point the reader is reading the bar, not crossing it.
|
|
61
|
-
*
|
|
62
|
-
* **Focus is not trapped and not moved.** A disclosure leaves the reader on the
|
|
63
|
-
* trigger and the panel follows in the tab order, which is what the markup
|
|
64
|
-
* already says, so Tab walks into the links and out the far side without
|
|
65
|
-
* anything managing it. Escape closes and puts focus back, because Escape is
|
|
66
|
-
* the one key that has to return the reader to where they were.
|
|
67
|
-
*
|
|
68
|
-
* The custom property below is written by this component and read by whoever
|
|
69
|
-
* draws it. A consumer never sets it: it carries a number this element computed
|
|
70
|
-
* and CSS cannot, which is what lets a stylesheet on this tier draw the thing
|
|
71
|
-
* without measuring anything itself.
|
|
72
|
-
*
|
|
73
|
-
* @since 0.1.0
|
|
74
|
-
* @status experimental
|
|
75
|
-
* @category navigation
|
|
76
|
-
* @aliases Mega menu, Flyout, Nav panel
|
|
77
|
-
* @documentation https://helix.fusion.dev/components/megamenu
|
|
78
|
-
*
|
|
79
|
-
* @slot - The panel's contents: the consumer's own lists and headings.
|
|
80
|
-
* @slot trigger - The control that opens it. Falls back to a button named by `label`.
|
|
81
|
-
*
|
|
82
|
-
* @event {HelixBeforeCloseEvent} helix-before-close - Fired before it closes, and cancelable.
|
|
83
|
-
* @event {HelixBeforeOpenEvent} helix-before-open - Fired before it opens, and cancelable.
|
|
84
|
-
* @event {Event} toggle - Says the state moved, carrying `newState` and `oldState`.
|
|
85
|
-
*
|
|
86
|
-
* @csspart surface - The floating panel, which is what opens.
|
|
87
|
-
* @csspart trigger - The default trigger, where none was slotted.
|
|
88
|
-
*
|
|
89
|
-
* @cssproperty --megamenu-top - Where the panel starts, measured from the bar above it.
|
|
90
|
-
*/
|
|
91
12
|
let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerOpen(HelixElement) {
|
|
92
13
|
constructor() {
|
|
93
14
|
super(...arguments);
|
|
94
|
-
/**
|
|
95
|
-
* The width, in pixels, under which a card becomes a band. Zero never does.
|
|
96
|
-
*
|
|
97
|
-
* A card is a panel hung off one word, and there is no room to hang anything
|
|
98
|
-
* off a word on a phone: the columns do not shrink past
|
|
99
|
-
* `--megamenu-column-size`, so four of them stay four of them and the panel
|
|
100
|
-
* runs off the side of the screen rather than reflowing. Below this it takes
|
|
101
|
-
* the whole width and its columns stack, which is the only arrangement that
|
|
102
|
-
* fits.
|
|
103
|
-
*
|
|
104
|
-
* **In script rather than a media query, and only once.** Whether the panel is
|
|
105
|
-
* a band is not a question of paint: a band is not placed against the trigger,
|
|
106
|
-
* so the controller has to be told not to measure, and that decision is here
|
|
107
|
-
* whatever the stylesheet says. Writing the width in both places would be one
|
|
108
|
-
* number in two languages, drifting the first time either moved. This sets
|
|
109
|
-
* `data-narrow` on the host and the stylesheet arranges itself off that.
|
|
110
|
-
*/
|
|
111
15
|
this.fullBelow = 640;
|
|
112
|
-
/**
|
|
113
|
-
* The section's name, which names the default trigger and the panel.
|
|
114
|
-
*/
|
|
115
16
|
this.label = '';
|
|
116
|
-
/**
|
|
117
|
-
* Whether hovering the trigger opens the panel, or only a press does.
|
|
118
|
-
*
|
|
119
|
-
* A press always works. This says whether a mouse also opens it by resting on
|
|
120
|
-
* the trigger, and it is the default because that is what a navigation bar
|
|
121
|
-
* does. `click` is for a trigger that does something else as well, or a panel
|
|
122
|
-
* heavy enough that opening it on the way past is rude.
|
|
123
|
-
*/
|
|
124
|
-
/* Read, never written back. It has a default, and a reflected default puts an
|
|
125
|
-
attribute on the host that the consumer did not write, which the server
|
|
126
|
-
then ships: the SSR spec for this element checks exactly that. Nothing
|
|
127
|
-
selects on it, so there is nothing to reflect for. */
|
|
128
17
|
this.openOn = 'hover';
|
|
129
18
|
this.narrow = false;
|
|
130
19
|
this.handleWidth = (event) => {
|
|
@@ -132,34 +21,12 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
|
|
|
132
21
|
};
|
|
133
22
|
this.panel = new PopoverController(this, {
|
|
134
23
|
anchor: () => this.trigger,
|
|
135
|
-
/* The host, not the panel. A press on the trigger is a press on this
|
|
136
|
-
element, and counting it as outside would close the panel on the way to
|
|
137
|
-
the click that toggles it. `Menu` says the same about its own. */
|
|
138
24
|
boundary: () => this,
|
|
139
25
|
onDismiss: () => this.close(),
|
|
140
26
|
place: () => !this.banded,
|
|
141
27
|
placement: () => 'bottom-start',
|
|
142
28
|
surface: () => this.renderRoot?.querySelector('[part="surface"]'),
|
|
143
29
|
});
|
|
144
|
-
/**
|
|
145
|
-
* Keeps a band under its bar while the page moves, and closes it when the bar
|
|
146
|
-
* goes.
|
|
147
|
-
*
|
|
148
|
-
* The band is fixed and its offset is measured once, when it opens. That is
|
|
149
|
-
* fine until the page scrolls: a header that scrolls away leaves the band
|
|
150
|
-
* pinned where the header used to be, open, in the middle of the page with
|
|
151
|
-
* nothing above it. Measured at a scroll of 300: header bottom at -243 and the
|
|
152
|
-
* band still sitting at 57.
|
|
153
|
-
*
|
|
154
|
-
* The card has none of this, because the floating controller follows its
|
|
155
|
-
* anchor. The band turns that controller off, having nothing to be placed
|
|
156
|
-
* against, and this is the half of it still wanted.
|
|
157
|
-
*
|
|
158
|
-
* A sticky bar does not move, so re-measuring returns the same number and the
|
|
159
|
-
* band stays where it is. `requestOpen` rather than `close`, because closing
|
|
160
|
-
* puts focus back on the trigger and a reader who has scrolled away should not
|
|
161
|
-
* be dragged back up to it.
|
|
162
|
-
*/
|
|
163
30
|
this.handleScroll = () => {
|
|
164
31
|
if (!this.open || !this.banded) {
|
|
165
32
|
return;
|
|
@@ -174,14 +41,9 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
|
|
|
174
41
|
this.pointer = event.pointerType;
|
|
175
42
|
};
|
|
176
43
|
this.handlePointerEnter = (event) => {
|
|
177
|
-
/* Mouse only. A touch screen synthesizes an enter on tap, and acting on it
|
|
178
|
-
would open the panel and then let the click that follows close it again,
|
|
179
|
-
so a tap would do nothing at all. */
|
|
180
44
|
if (this.openOn !== 'hover' || event.pointerType !== 'mouse') {
|
|
181
45
|
return;
|
|
182
46
|
}
|
|
183
|
-
/* No wait when a panel is already down: the bar is open, and the reader
|
|
184
|
-
moving along it is choosing, not passing through. */
|
|
185
47
|
this.schedule(showing && showing !== this ? 0 : HelixHeadlessMegamenu_1.OPEN_DELAY, () => {
|
|
186
48
|
this.requestOpen(true);
|
|
187
49
|
});
|
|
@@ -191,9 +53,6 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
|
|
|
191
53
|
return;
|
|
192
54
|
}
|
|
193
55
|
this.schedule(HelixHeadlessMegamenu_1.CLOSE_DELAY, () => {
|
|
194
|
-
/* Not `close()`: leaving with the pointer should not pull focus back to
|
|
195
|
-
the trigger, which would scroll the page to a nav bar the reader has
|
|
196
|
-
moved on from. Escape is the one that returns them. */
|
|
197
56
|
this.requestOpen(false);
|
|
198
57
|
});
|
|
199
58
|
};
|
|
@@ -203,39 +62,10 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
|
|
|
203
62
|
this.requestOpen(!this.open);
|
|
204
63
|
return;
|
|
205
64
|
}
|
|
206
|
-
/* In hover mode the mouse's own clicks are hover's business. Toggling here
|
|
207
|
-
would close a panel the pointer is still standing on, and the pointer
|
|
208
|
-
would reopen it. A keyboard press arrives with no clicks behind it, and a
|
|
209
|
-
tap arrives with a pointer that is not a mouse: both are the only way in
|
|
210
|
-
for the people making them, so both toggle. */
|
|
211
65
|
if (event.detail === 0 || (this.pointer !== undefined && this.pointer !== 'mouse')) {
|
|
212
66
|
this.requestOpen(!this.open);
|
|
213
67
|
}
|
|
214
68
|
};
|
|
215
|
-
/**
|
|
216
|
-
* Shuts the panel when a reader takes one of the destinations in it.
|
|
217
|
-
*
|
|
218
|
-
* Without this a click navigates and leaves the panel hanging over wherever
|
|
219
|
-
* the reader landed. With a router that navigates in place, that is a panel
|
|
220
|
-
* covering the page it was asked to go to.
|
|
221
|
-
*
|
|
222
|
-
* **A modified click leaves it open**, which is the same position this
|
|
223
|
-
* element already takes on middle clicks and the status bar: `cmd` on a link
|
|
224
|
-
* opens a tab behind this page rather than navigating it, so the reader is
|
|
225
|
-
* still standing in the menu and collecting, and shutting it under them would
|
|
226
|
-
* make the second choice cost a second open.
|
|
227
|
-
*
|
|
228
|
-
* `requestOpen(false)` rather than `close()`, for the reason `pointerleave`
|
|
229
|
-
* gives: the trigger is not where the reader is going, and pulling focus back
|
|
230
|
-
* to a nav bar they have just left is a scroll they did not ask for.
|
|
231
|
-
*
|
|
232
|
-
* **On the host rather than on the panel**, which is the same reason the
|
|
233
|
-
* pointer handlers give and one more. A row is slotted light DOM and the
|
|
234
|
-
* panel is in the shadow root, so a click only reaches the panel through the
|
|
235
|
-
* flattened tree: real ones do, and a DOM that does not implement slot
|
|
236
|
-
* propagation never delivers it, which makes every case here pass by never
|
|
237
|
-
* running this at all. The host is the row's own ancestor in both.
|
|
238
|
-
*/
|
|
239
69
|
this.handleContentClick = (event) => {
|
|
240
70
|
if (!this.open || event.defaultPrevented || event.button !== 0) {
|
|
241
71
|
return;
|
|
@@ -243,14 +73,8 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
|
|
|
243
73
|
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
|
244
74
|
return;
|
|
245
75
|
}
|
|
246
|
-
/* Asking for the destination rather than for any click, so the headings,
|
|
247
|
-
the gaps between rows and a reader's stray click on the panel's own
|
|
248
|
-
padding leave it where it is. */
|
|
249
76
|
const target = event.target;
|
|
250
77
|
const chosen = target?.closest?.('a[href], button:not([disabled]), [role="menuitem"]');
|
|
251
|
-
/* Inside this element and not the trigger. A press on the trigger is the
|
|
252
|
-
trigger's to answer, and a shadow-rendered one retargets to the host, so
|
|
253
|
-
`closest` there climbs past this element entirely. */
|
|
254
78
|
if (!chosen || !this.contains(chosen) || chosen.closest('[slot="trigger"]')) {
|
|
255
79
|
return;
|
|
256
80
|
}
|
|
@@ -258,12 +82,9 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
|
|
|
258
82
|
};
|
|
259
83
|
}
|
|
260
84
|
static { HelixHeadlessMegamenu_1 = this; }
|
|
261
|
-
/** A band, whether because it was asked for or because there is no room. */
|
|
262
85
|
get banded() {
|
|
263
86
|
return this.variant === 'full' || this.narrow;
|
|
264
87
|
}
|
|
265
|
-
/* Rebuilt rather than kept, because the width is a property and a query
|
|
266
|
-
cannot be edited once it is made. */
|
|
267
88
|
watchWidth() {
|
|
268
89
|
this.query?.removeEventListener('change', this.handleWidth);
|
|
269
90
|
this.query = undefined;
|
|
@@ -275,69 +96,31 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
|
|
|
275
96
|
this.query.addEventListener('change', this.handleWidth);
|
|
276
97
|
this.handleWidth(this.query);
|
|
277
98
|
}
|
|
278
|
-
/** Tells a slotted trigger the disclosure state, by property. See `tellDisclosure`. */
|
|
279
99
|
tellTrigger() {
|
|
280
100
|
const slotted = this.slotted;
|
|
281
101
|
if (slotted) {
|
|
282
102
|
tellDisclosure(slotted, this.open);
|
|
283
103
|
}
|
|
284
104
|
}
|
|
285
|
-
/** What the consumer slotted, if they slotted anything. */
|
|
286
105
|
get slotted() {
|
|
287
106
|
const slot = this.renderRoot?.querySelector('slot[name="trigger"]');
|
|
288
107
|
return slot?.assignedElements({ flatten: false })[0];
|
|
289
108
|
}
|
|
290
|
-
/** Whatever the consumer slotted, or the button rendered in its place. */
|
|
291
109
|
get trigger() {
|
|
292
110
|
return anchorElement(this.renderRoot?.querySelector('slot[name="trigger"]'), this.renderRoot?.querySelector('[part="trigger"]') ?? this);
|
|
293
111
|
}
|
|
294
|
-
/** Shuts it and puts the reader back where they were. */
|
|
295
112
|
close() {
|
|
296
113
|
if (!this.requestOpen(false)) {
|
|
297
114
|
return;
|
|
298
115
|
}
|
|
299
116
|
this.trigger.focus();
|
|
300
117
|
}
|
|
301
|
-
/**
|
|
302
|
-
* Where a full-bleed band starts, which is the only number it needs.
|
|
303
|
-
*
|
|
304
|
-
* The placing pass is off for that appearance, so nothing else is measuring:
|
|
305
|
-
* a band spans the viewport and has only one degree of freedom, how far down
|
|
306
|
-
* it sits, and that is the bottom of the trigger in viewport coordinates
|
|
307
|
-
* because the panel is fixed. A header that scrolls takes the band with it,
|
|
308
|
-
* which is what a panel hanging off a header should do.
|
|
309
|
-
*
|
|
310
|
-
* Written as a custom property rather than an inline `top`, so the stylesheet
|
|
311
|
-
* keeps the arrangement and this contributes the one measurement it cannot
|
|
312
|
-
* make. Nothing to do for the card, which the controller places.
|
|
313
|
-
*/
|
|
314
118
|
band() {
|
|
315
119
|
if (!this.banded) {
|
|
316
120
|
return;
|
|
317
121
|
}
|
|
318
122
|
this.style.setProperty('--megamenu-top', `${Math.round(this.bar())}px`);
|
|
319
123
|
}
|
|
320
|
-
/**
|
|
321
|
-
* The bottom edge of the bar this hangs from.
|
|
322
|
-
*
|
|
323
|
-
* The trigger's own bottom is the obvious answer and is the wrong one: a
|
|
324
|
-
* header is taller than the word inside it, so a band starting at the word
|
|
325
|
-
* starts partway up the header, over its lower padding and its border.
|
|
326
|
-
* Measured: trigger bottom 44, header bottom 57, thirteen pixels of header
|
|
327
|
-
* with a band drawn across it.
|
|
328
|
-
*
|
|
329
|
-
* **The first ancestor as wide as the window, which is a fact and not a tag
|
|
330
|
-
* name.** A band spans the viewport, so what it should hang from is whatever
|
|
331
|
-
* else does: that is what makes something a bar. Asking for `helix-app-header`
|
|
332
|
-
* by name would make this element know about that one, which is the coupling
|
|
333
|
-
* `Menu` is careful not to have with `Drilldown`, and would leave a consumer
|
|
334
|
-
* who wrote their own header with a band across it.
|
|
335
|
-
*
|
|
336
|
-
* `body` ends the walk. It is as wide as the window and is not a bar, and its
|
|
337
|
-
* bottom is the foot of the page: without the guard a megamenu on a plain page
|
|
338
|
-
* would put its band down there. Nothing found means nothing to hang from, and
|
|
339
|
-
* the trigger is the honest answer again.
|
|
340
|
-
*/
|
|
341
124
|
bar() {
|
|
342
125
|
const wide = document.documentElement.clientWidth;
|
|
343
126
|
let node = this.parentElement;
|
|
@@ -350,14 +133,6 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
|
|
|
350
133
|
}
|
|
351
134
|
return this.trigger.getBoundingClientRect().bottom;
|
|
352
135
|
}
|
|
353
|
-
/**
|
|
354
|
-
* How long a pointer has to mean it, in milliseconds.
|
|
355
|
-
*
|
|
356
|
-
* Opening is the shorter wait: too long and the panel feels stuck, too short
|
|
357
|
-
* and crossing the bar opens things. Closing is longer because the pointer
|
|
358
|
-
* has to get from the trigger to the panel, and the straight line between
|
|
359
|
-
* them is not the line anybody moves along.
|
|
360
|
-
*/
|
|
361
136
|
static { this.OPEN_DELAY = 120; }
|
|
362
137
|
static { this.CLOSE_DELAY = 220; }
|
|
363
138
|
schedule(delay, then) {
|
|
@@ -370,15 +145,9 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
|
|
|
370
145
|
this.timer = undefined;
|
|
371
146
|
}
|
|
372
147
|
}
|
|
373
|
-
/* On the host, and here rather than in the template, because Lit templates
|
|
374
|
-
bind inside a shadow root and these have to hear the panel too. Not the
|
|
375
|
-
constructor: the server runs that and has no events. */
|
|
376
148
|
connectedCallback() {
|
|
377
149
|
super.connectedCallback();
|
|
378
150
|
this.watchWidth();
|
|
379
|
-
/* On the window and capturing, so a band over a scrolling panel hears that
|
|
380
|
-
too rather than only the page's own scrolling. Passive: this measures and
|
|
381
|
-
never prevents. */
|
|
382
151
|
addEventListener('scroll', this.handleScroll, { capture: true, passive: true });
|
|
383
152
|
this.addEventListener('pointerdown', this.handlePointerDown);
|
|
384
153
|
this.addEventListener('click', this.handleContentClick);
|
|
@@ -404,51 +173,9 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
|
|
|
404
173
|
if (changed.has('fullBelow')) {
|
|
405
174
|
this.watchWidth();
|
|
406
175
|
}
|
|
407
|
-
/* The one thing the stylesheet cannot work out for itself, since the
|
|
408
|
-
breakpoint is declared up here. */
|
|
409
176
|
this.toggleAttribute('data-narrow', this.narrow);
|
|
410
|
-
/* A slotted trigger is told the disclosure state, by property.
|
|
411
|
-
|
|
412
|
-
`render` writes `aria-expanded` onto the button it renders itself, and a
|
|
413
|
-
consumer who takes the documented `trigger` slot replaces that button, so
|
|
414
|
-
without this the panel opens and closes and nothing announces it. Silent,
|
|
415
|
-
because the slot works in every other respect and the failure is only
|
|
416
|
-
audible.
|
|
417
|
-
|
|
418
|
-
**Never `setAttribute` here: that is a hydration mismatch.** Measured
|
|
419
|
-
against React 19.2.8, four families server-rendered and hydrated with the
|
|
420
|
-
definitions loaded either side of `hydrateRoot`: this one and `SpeedDial`
|
|
421
|
-
produced "some attributes of the server rendered HTML didn't match the
|
|
422
|
-
client properties. This won't be patched up" in both orders, and
|
|
423
|
-
`Popover` and `Menu`, which write nothing, were clean in both. React
|
|
424
|
-
answers a mismatch by discarding the server's HTML for that subtree and
|
|
425
|
-
rebuilding it, which is the expensive half rather than the console line.
|
|
426
|
-
|
|
427
|
-
**The property is the way through**, as `Menu` already does for its items.
|
|
428
|
-
React diffs the attributes it rendered and never looks at a property, so
|
|
429
|
-
`expanded` travels to the trigger, lands as `aria-expanded` inside that
|
|
430
|
-
element's shadow root, and the consumer's markup is left as they served
|
|
431
|
-
it.
|
|
432
|
-
|
|
433
|
-
**What it cannot reach is a trigger that is not a Helix element.** A plain
|
|
434
|
-
`<button slot="trigger">` has no shadow root to hide the state in and no
|
|
435
|
-
property to take it, and a React consumer cannot mirror it themselves
|
|
436
|
-
because the open state is this element's. That case is warned about rather
|
|
437
|
-
than written to, on the same argument `Tooltip` makes about a link it
|
|
438
|
-
will not write for the consumer.
|
|
439
|
-
|
|
440
|
-
**`aria-controls` is not written here and cannot be.** It would name
|
|
441
|
-
`panel`, an id inside this shadow root, and an IDREF resolves only within
|
|
442
|
-
the tree that holds it: from a light-DOM trigger looking in, the reference
|
|
443
|
-
is to nothing. Measured, `document.querySelector('#panel')` came back
|
|
444
|
-
`null` while the same id resolved inside `megamenu.shadowRoot`. An inert
|
|
445
|
-
`aria-controls` is worse than none, an audit tool reporting it satisfied
|
|
446
|
-
and a screen reader following it nowhere. */
|
|
447
177
|
this.tellTrigger();
|
|
448
178
|
if (this.open) {
|
|
449
|
-
/* Before showing this one, so the two are never both up for a frame. The
|
|
450
|
-
other closes through `requestOpen`, so a consumer holding it open is
|
|
451
|
-
asked rather than overruled. */
|
|
452
179
|
if (showing && showing !== this) {
|
|
453
180
|
showing.requestOpen(false);
|
|
454
181
|
}
|
|
@@ -463,13 +190,6 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
|
|
|
463
190
|
this.panel.hide();
|
|
464
191
|
}
|
|
465
192
|
}
|
|
466
|
-
/**
|
|
467
|
-
* The control that opens it, behind a seam the styled tier can move.
|
|
468
|
-
*
|
|
469
|
-
* The same case `SpeedDial` records: a bare `<button>` is what this tier can
|
|
470
|
-
* write, `helix-button` lives a tier above, and a render method is how a tier
|
|
471
|
-
* that has one reaches this without reimplementing the panel below it.
|
|
472
|
-
*/
|
|
473
193
|
renderTrigger() {
|
|
474
194
|
return html `<slot name="trigger" @click=${this.handleTriggerClick}>
|
|
475
195
|
<button
|
|
@@ -483,10 +203,6 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
|
|
|
483
203
|
</slot>`;
|
|
484
204
|
}
|
|
485
205
|
render() {
|
|
486
|
-
/* The listeners sit on the host rather than the trigger, so the panel counts
|
|
487
|
-
as inside it: the top layer changes where the panel is painted and not
|
|
488
|
-
where it is in the tree, so a pointer moving off the trigger and onto the
|
|
489
|
-
panel has not left this element and fires no leave. */
|
|
490
206
|
return html `${this.renderTrigger()}
|
|
491
207
|
<div
|
|
492
208
|
part="surface"
|