@fusion.dev/helix-headless 0.151.0 → 0.153.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 +1332 -37
- package/dist/app-header/root/app-header-root.d.ts +75 -0
- package/dist/app-header/root/app-header-root.js +140 -5
- package/dist/area-chart/root/area-chart-root.d.ts +16 -1
- package/dist/area-chart/root/area-chart-root.js +3 -3
- package/dist/bar-chart/root/bar-chart-root.d.ts +16 -1
- package/dist/bar-chart/root/bar-chart-root.js +3 -3
- package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +11 -2
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +11 -2
- package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +14 -2
- package/dist/breadcrumbs/root/breadcrumbs-root.js +16 -3
- package/dist/dialog/description.d.ts +10 -0
- package/dist/dialog/description.js +22 -8
- package/dist/dialog/root/dialog-root.d.ts +17 -1
- package/dist/dialog/root/dialog-root.js +2 -2
- package/dist/drawer/root/drawer-root.d.ts +38 -2
- package/dist/drawer/root/drawer-root.js +33 -6
- package/dist/heatmap/root/heatmap-root.d.ts +3 -0
- package/dist/heatmap/root/heatmap-root.js +2 -8
- package/dist/lightbox/root/lightbox-root.d.ts +44 -6
- package/dist/lightbox/root/lightbox-root.js +59 -11
- package/dist/lightbox/zoom-controller.d.ts +5 -0
- package/dist/lightbox/zoom-controller.js +8 -0
- package/dist/line-chart/root/line-chart-root.d.ts +16 -1
- package/dist/line-chart/root/line-chart-root.js +3 -3
- package/dist/loader/root/loader-root.d.ts +5 -0
- package/dist/menu/root/menu-root.js +2 -0
- package/dist/navigation/root/navigation-root.d.ts +11 -1
- package/dist/navigation/root/navigation-root.js +10 -5
- package/dist/notifications/root/notifications-root.d.ts +17 -0
- package/dist/notifications/root/notifications-root.js +21 -1
- package/dist/popover/root/popover-root.d.ts +18 -7
- package/dist/popover/root/popover-root.js +8 -1
- package/dist/progress/root/progress-root.d.ts +16 -1
- package/dist/progress/root/progress-root.js +12 -4
- package/dist/scatter-chart/root/scatter-chart-root.d.ts +16 -1
- package/dist/scatter-chart/root/scatter-chart-root.js +3 -3
- package/dist/splitter/root/splitter-root.d.ts +41 -0
- package/dist/splitter/root/splitter-root.js +77 -0
- package/dist/tabs/root/tabs-root.d.ts +25 -0
- package/dist/tabs/root/tabs-root.js +28 -1
- package/dist/tour/naming.d.ts +9 -3
- package/dist/tour/naming.js +9 -3
- package/dist/tour/root/tour-root.d.ts +29 -5
- package/dist/tour/root/tour-root.js +13 -1
- package/dist/tour/stop/tour-stop.d.ts +2 -1
- package/dist/tour/stop/tour-stop.js +4 -1
- package/dist/tree/root/tree-root.d.ts +13 -0
- package/dist/tree/root/tree-root.js +42 -2
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +1456 -36
- package/types/vue.d.ts +277 -24
- package/types/words.d.ts +64 -0
|
@@ -7,6 +7,11 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
7
7
|
* Draws two concentric circles: a track and a moving arc. The track shows the
|
|
8
8
|
* arc as a proportion of a whole, and costs one element.
|
|
9
9
|
*
|
|
10
|
+
* **For work whose length is known, use `Progress` with `shape="ring"`.** It
|
|
11
|
+
* draws the same ring filled to the value, says the value as a progress bar
|
|
12
|
+
* does, and says when the work is complete. A loader claims no proportion, so
|
|
13
|
+
* it takes no value.
|
|
14
|
+
*
|
|
10
15
|
* **The center is a slot, empty by default.** Put your logo there once, where
|
|
11
16
|
* your app wraps the loader. When nothing is slotted, nothing extra is
|
|
12
17
|
* rendered.
|
|
@@ -19,6 +19,13 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
19
19
|
* one page can mark the section in view with `aria-current="location"`. Where
|
|
20
20
|
* several are marked, `page` wins.
|
|
21
21
|
*
|
|
22
|
+
* **The destinations can be a list.** Slot a `<ul>` or an `<ol>` of `<li>`
|
|
23
|
+
* elements, each holding a link, and a screen reader says how many there are
|
|
24
|
+
* and where each one is, as in Radix's `NavigationMenu.List`. The marker and
|
|
25
|
+
* the arrow keys work on the list items as they do on bare links. Give the
|
|
26
|
+
* list `role="list"` as well, since Safari drops the semantics of a list drawn
|
|
27
|
+
* without bullets.
|
|
28
|
+
*
|
|
22
29
|
* Set `orientation="vertical"` to stack the destinations in a column, for a
|
|
23
30
|
* sidebar.
|
|
24
31
|
*
|
|
@@ -73,7 +80,10 @@ export declare class HelixHeadlessNavigation extends HelixElement {
|
|
|
73
80
|
*/
|
|
74
81
|
orientation?: 'vertical';
|
|
75
82
|
private current;
|
|
76
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* The destinations the consumer slotted, and the items of a slotted list in
|
|
85
|
+
* place of the list.
|
|
86
|
+
*/
|
|
77
87
|
private get items();
|
|
78
88
|
/** Re-measures when the links change or hand over the current page. */
|
|
79
89
|
private watch;
|
|
@@ -8,6 +8,10 @@ import { AriaForwardController, customElement, focusableElements, focusItemOf, H
|
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
const FOCUSABLE = 'a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
11
|
+
const isList = (element) => element.localName === 'ul' ||
|
|
12
|
+
element.localName === 'ol' ||
|
|
13
|
+
element.getAttribute('role') === 'list';
|
|
14
|
+
const markOf = (item) => item.hasAttribute('aria-current') ? item : item.querySelector('[aria-current]');
|
|
11
15
|
const navigable = (item) => {
|
|
12
16
|
const control = () => item.navigationControl ??
|
|
13
17
|
(item.matches(FOCUSABLE) ? item : focusableElements(item)[0]);
|
|
@@ -26,10 +30,11 @@ let HelixHeadlessNavigation = class HelixHeadlessNavigation extends HelixElement
|
|
|
26
30
|
active: () => {
|
|
27
31
|
const items = this.items;
|
|
28
32
|
const marked = (item) => {
|
|
29
|
-
const current = item
|
|
30
|
-
return current !== null && current !== 'false';
|
|
33
|
+
const current = markOf(item)?.getAttribute('aria-current');
|
|
34
|
+
return current !== null && current !== undefined && current !== 'false';
|
|
31
35
|
};
|
|
32
|
-
return (items.find((item) => item
|
|
36
|
+
return (items.find((item) => markOf(item)?.getAttribute('aria-current') === 'page') ??
|
|
37
|
+
items.find(marked));
|
|
33
38
|
},
|
|
34
39
|
indicator: () => this.partNamed('indicator'),
|
|
35
40
|
observe: () => this.items,
|
|
@@ -52,14 +57,14 @@ let HelixHeadlessNavigation = class HelixHeadlessNavigation extends HelixElement
|
|
|
52
57
|
static { this.words = 'helix-navigation'; }
|
|
53
58
|
get items() {
|
|
54
59
|
const slot = this.renderRoot.querySelector('slot:not([name])');
|
|
55
|
-
return (slot?.assignedElements() ?? []);
|
|
60
|
+
return (slot?.assignedElements() ?? []).flatMap((one) => isList(one) ? [...one.children] : [one]);
|
|
56
61
|
}
|
|
57
62
|
watch() {
|
|
58
63
|
this.current?.disconnect();
|
|
59
64
|
this.indicator.place();
|
|
60
65
|
this.current = new MutationObserver(() => this.indicator.place());
|
|
61
66
|
for (const item of this.items) {
|
|
62
|
-
this.current.observe(item, { attributeFilter: ['aria-current'] });
|
|
67
|
+
this.current.observe(item, { attributeFilter: ['aria-current'], subtree: true });
|
|
63
68
|
}
|
|
64
69
|
}
|
|
65
70
|
connectedCallback() {
|
|
@@ -29,6 +29,17 @@ declare const HelixHeadlessNotifications_base: (new (...args: any[]) => import("
|
|
|
29
29
|
* the feed holds one page of a longer list, and each row says its place in the
|
|
30
30
|
* whole of it.
|
|
31
31
|
*
|
|
32
|
+
* **Keyboard.**
|
|
33
|
+
*
|
|
34
|
+
* | Key | What it does |
|
|
35
|
+
* |---|---|
|
|
36
|
+
* | Down, Up | On a row, moves to the next or the previous row. |
|
|
37
|
+
* | Home, End | On a row, moves to the first or the last row. |
|
|
38
|
+
* | Page Down, Page Up | From anywhere in a row, moves to the next or the previous row. |
|
|
39
|
+
* | Control+End | From anywhere in the feed, moves to the first control after it on the page. |
|
|
40
|
+
* | Control+Home | From anywhere in the feed, moves to the nearest control before it on the page. |
|
|
41
|
+
* | Tab | Moves through the controls in a row, and on out of the feed. |
|
|
42
|
+
*
|
|
32
43
|
* **`load-more` adds a button after the list** that sends `helix-load-more`,
|
|
33
44
|
* for a feed that holds one page of a longer list. Append the next page, set
|
|
34
45
|
* `loading` while it is on its way, and take `load-more` off once there is
|
|
@@ -246,6 +257,12 @@ export declare class HelixHeadlessNotifications extends HelixHeadlessNotificatio
|
|
|
246
257
|
* is how the APG feed pattern moves on from inside an article.
|
|
247
258
|
*/
|
|
248
259
|
private readonly handleKeydown;
|
|
260
|
+
/**
|
|
261
|
+
* `Control+End` and `Control+Home`, which leave the feed for the first
|
|
262
|
+
* control after it or the nearest one before it, as the APG feed pattern
|
|
263
|
+
* does. A feed of many rows is otherwise a long walk to get past.
|
|
264
|
+
*/
|
|
265
|
+
private leave;
|
|
249
266
|
/**
|
|
250
267
|
* Tells each row whether it is the stop, and where it sits in the feed.
|
|
251
268
|
*
|
|
@@ -34,7 +34,11 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
36
|
this.handleKeydown = (event) => {
|
|
37
|
-
if (event.defaultPrevented || event.altKey || event.
|
|
37
|
+
if (event.defaultPrevented || event.altKey || event.metaKey) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (event.ctrlKey) {
|
|
41
|
+
this.leave(event);
|
|
38
42
|
return;
|
|
39
43
|
}
|
|
40
44
|
const rows = this.reachable;
|
|
@@ -151,6 +155,22 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
151
155
|
rowFocused(target) {
|
|
152
156
|
return this.reachable.find((row) => rowBox(row) === target);
|
|
153
157
|
}
|
|
158
|
+
leave(event) {
|
|
159
|
+
if (event.key !== 'End' && event.key !== 'Home') {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const after = event.key === 'End';
|
|
163
|
+
const outside = focusableElements(document.body).filter((one) => one.tabIndex >= 0 &&
|
|
164
|
+
!deepContains(this, one) &&
|
|
165
|
+
(this.compareDocumentPosition(one) &
|
|
166
|
+
(after ? Node.DOCUMENT_POSITION_FOLLOWING : Node.DOCUMENT_POSITION_PRECEDING)) !==
|
|
167
|
+
0);
|
|
168
|
+
const next = after ? outside[0] : outside.at(-1);
|
|
169
|
+
if (next) {
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
next.focus();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
154
174
|
rove() {
|
|
155
175
|
const rows = this.reachable;
|
|
156
176
|
const stop = rovingStop(rows, this.stopRow);
|
|
@@ -11,9 +11,10 @@ declare const HelixHeadlessPopover_base: (new (...args: any[]) => import("@fusio
|
|
|
11
11
|
*
|
|
12
12
|
* The trigger is slotted and stays your own element. A Helix trigger is told
|
|
13
13
|
* whether the popover is open inside its own shadow root. On a plain element,
|
|
14
|
-
* such as a `<button>`, the popover writes `aria-expanded
|
|
15
|
-
*
|
|
16
|
-
* `aria-expanded` yourself
|
|
14
|
+
* such as a `<button>`, the popover writes `aria-expanded`, from a frame after
|
|
15
|
+
* its own first render, or from the first press where that comes sooner. It
|
|
16
|
+
* never writes it where you wrote `aria-expanded` yourself, and writes nothing
|
|
17
|
+
* else onto your markup.
|
|
17
18
|
*
|
|
18
19
|
* The panel is in the top layer through the popover API, so it needs no
|
|
19
20
|
* `z-index`, and an ancestor with `overflow: hidden`, `transform`, `filter`, or
|
|
@@ -36,6 +37,17 @@ declare const HelixHeadlessPopover_base: (new (...args: any[]) => import("@fusio
|
|
|
36
37
|
*
|
|
37
38
|
* The panel's name is `label` when you set it. Otherwise it is the first
|
|
38
39
|
* heading in the panel, or the elements an `aria-labelledby` on the host names.
|
|
40
|
+
* A development build warns when a popover opens with nothing but
|
|
41
|
+
* `fallback-label` to name it.
|
|
42
|
+
*
|
|
43
|
+
* **Keyboard.** Opening moves focus to the first control in the panel, or to
|
|
44
|
+
* the panel where it holds none.
|
|
45
|
+
*
|
|
46
|
+
* | Key | What it does |
|
|
47
|
+
* |---|---|
|
|
48
|
+
* | Enter, Space | On the trigger, opens the panel, or closes it while it is open. |
|
|
49
|
+
* | Escape | Closes the panel and puts focus back on the trigger. |
|
|
50
|
+
* | Tab, Shift+Tab | Moves through the controls in the panel. Leaving the panel and the trigger closes it. With `modal`, moves from the last control round to the first and from the first round to the last. |
|
|
39
51
|
*
|
|
40
52
|
* `arrow` draws a point on the panel's edge facing the trigger. The side the
|
|
41
53
|
* panel landed on is on the host as `:state(placed-top)` and its kin, and the
|
|
@@ -198,7 +210,7 @@ export declare class HelixHeadlessPopover extends HelixHeadlessPopover_base {
|
|
|
198
210
|
private readonly notePress;
|
|
199
211
|
disconnectedCallback(): void;
|
|
200
212
|
private readonly handleTriggerClick;
|
|
201
|
-
/** Whether
|
|
213
|
+
/** Whether the trigger may be told, from a frame after the first render or a reader's press. */
|
|
202
214
|
private pressed;
|
|
203
215
|
/** The plain trigger this popover writes `aria-expanded` on, once it may. */
|
|
204
216
|
private told;
|
|
@@ -207,9 +219,8 @@ export declare class HelixHeadlessPopover extends HelixHeadlessPopover_base {
|
|
|
207
219
|
*
|
|
208
220
|
* A Helix trigger by property, through `tellDisclosure`. A plain element,
|
|
209
221
|
* which has no shadow root to keep the state out of the markup, by
|
|
210
|
-
* `aria-expanded`,
|
|
211
|
-
*
|
|
212
|
-
* be a hydration mismatch. One that already carries `aria-expanded` is the
|
|
222
|
+
* `aria-expanded`, from a frame after the first render or a reader's first
|
|
223
|
+
* press on, so a closed panel is said to be closed before anyone presses. One that already carries `aria-expanded` is the
|
|
213
224
|
* consumer's to keep, and is left alone.
|
|
214
225
|
*/
|
|
215
226
|
private tellTrigger;
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, anchorElement, arrowReach, CommandController, customElement, deepActiveElement, FocusScopeController, HelixElement, headingIn, MixinControllerOpen, OpenPhaseController, PopoverController, reflectElements, sideOf, tellDisclosure, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, anchorElement, arrowReach, auditName, CommandController, customElement, deepActiveElement, FocusScopeController, HelixElement, headingIn, MixinControllerOpen, OpenPhaseController, PopoverController, reflectElements, sideOf, tellDisclosure, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property, state } from 'lit/decorators.js';
|
|
10
10
|
import { hideOutside } from '../hide-outside.js';
|
|
@@ -144,6 +144,10 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
|
|
|
144
144
|
super.connectedCallback();
|
|
145
145
|
this.addEventListener('focusout', this.handleFocusOut);
|
|
146
146
|
this.addEventListener('click', this.notePress, { capture: true });
|
|
147
|
+
void this.whenSettled.then(() => {
|
|
148
|
+
this.pressed = true;
|
|
149
|
+
this.tellTrigger();
|
|
150
|
+
});
|
|
147
151
|
}
|
|
148
152
|
disconnectedCallback() {
|
|
149
153
|
super.disconnectedCallback();
|
|
@@ -180,6 +184,9 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
|
|
|
180
184
|
super.updated(changed);
|
|
181
185
|
this.tellTrigger();
|
|
182
186
|
this.nameByHeading();
|
|
187
|
+
if (changed.has('open') && this.open) {
|
|
188
|
+
auditName(this, () => null, 'Give it a `label`, or a heading in the panel.', () => this.label !== '' || this.namedByHeading || headingIn(this.slotted) != null);
|
|
189
|
+
}
|
|
183
190
|
if (this.settled) {
|
|
184
191
|
this.hideAround();
|
|
185
192
|
}
|
|
@@ -15,6 +15,15 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
15
15
|
*
|
|
16
16
|
* The fill is a fraction of the track, written as a custom property.
|
|
17
17
|
*
|
|
18
|
+
* **The reading is said in the words the bar would show**: a whole percentage
|
|
19
|
+
* of the range in `locale`, the reading `format-options` writes, or
|
|
20
|
+
* `value-text`, as `aria-valuetext` beside the number.
|
|
21
|
+
*
|
|
22
|
+
* **Words set in the default slot are drawn on the bar**, or in the middle of a
|
|
23
|
+
* ring, such as `62%` or `3 of 8`, and are what the bar says as its reading
|
|
24
|
+
* where `value-text` is not set. While there are any, the bar carries
|
|
25
|
+
* `:state(content)`, so a sheet can make room for them.
|
|
26
|
+
*
|
|
18
27
|
* **Reaching `max` is said and sent.** When the value arrives at `max` from
|
|
19
28
|
* below, the bar says `complete-label`, "Complete" by default, in a polite
|
|
20
29
|
* `live` region and sends `helix-complete`, so a reader hears that the work is
|
|
@@ -28,10 +37,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
28
37
|
* @aliases Progress bar, Loading bar
|
|
29
38
|
* @documentation https://fusion.dev/helix/components/progress
|
|
30
39
|
*
|
|
40
|
+
* @slot - Words drawn on the bar, or in the middle of a ring, such as the reading. Said as the reading where `value-text` is not set.
|
|
31
41
|
* @slot label - A visible label. Its words name the progress bar in place of `label`.
|
|
32
42
|
*
|
|
33
43
|
* @event {HelixCompleteEvent} helix-complete - The value reached `max` from below. Its `value` is `max`. Not sent for a bar that starts full.
|
|
34
44
|
*
|
|
45
|
+
* @csspart content - The box holding the words of the default slot, over the track or in the middle of the ring. Carries `--progress-fraction`.
|
|
35
46
|
* @csspart groove - The unfilled circle under the ring's filled arc.
|
|
36
47
|
* @csspart indicator - The filled part that shows the work done.
|
|
37
48
|
* @csspart live - A polite region that says `complete-label` when the work is done, rendered from that moment. Hide it visually.
|
|
@@ -41,6 +52,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
41
52
|
* @cssproperty --progress-fraction - The value as a fraction of the range, 0 to 1. Pinned to 1 while indeterminate.
|
|
42
53
|
*
|
|
43
54
|
* @cssstate complete - The value is at `max` and the bar is not indeterminate.
|
|
55
|
+
* @cssstate content - Words are set in the default slot.
|
|
44
56
|
*/
|
|
45
57
|
export declare class HelixHeadlessProgress extends HelixElement {
|
|
46
58
|
/**
|
|
@@ -67,7 +79,8 @@ export declare class HelixHeadlessProgress extends HelixElement {
|
|
|
67
79
|
*
|
|
68
80
|
* A `percent` style writes the share of the range, and any other the value
|
|
69
81
|
* itself, so the example says "18 GB". Absent is a whole percentage. The
|
|
70
|
-
* words are announced
|
|
82
|
+
* words are announced whether or not `show-value` shows them. `value-text`
|
|
83
|
+
* still wins.
|
|
71
84
|
*/
|
|
72
85
|
formatOptions?: Intl.NumberFormatOptions;
|
|
73
86
|
/**
|
|
@@ -173,6 +186,8 @@ export declare class HelixHeadlessProgress extends HelixElement {
|
|
|
173
186
|
private get text();
|
|
174
187
|
/** A label's words changed, so the name is written again. */
|
|
175
188
|
private readonly renamed;
|
|
189
|
+
/** The words on the bar changed, so the reading is said again and the state set. */
|
|
190
|
+
private readonly reworded;
|
|
176
191
|
protected render(): TemplateResult;
|
|
177
192
|
}
|
|
178
193
|
declare global {
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, drawStatus, HelixCompleteEvent, HelixElement, hostAria, readingText, StatusController, slottedWords, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, drawStatus, HelixCompleteEvent, HelixElement, hostAria, readingText, StatusController, slotFilled, slotNamed, slottedWords, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
|
|
@@ -23,6 +23,10 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
|
|
|
23
23
|
this.renamed = () => {
|
|
24
24
|
this.requestUpdate();
|
|
25
25
|
};
|
|
26
|
+
this.reworded = () => {
|
|
27
|
+
this.toggleState('content', slotFilled(slotNamed(this.renderRoot, '')));
|
|
28
|
+
this.requestUpdate();
|
|
29
|
+
};
|
|
26
30
|
}
|
|
27
31
|
static { this.words = 'helix-progress'; }
|
|
28
32
|
get complete() {
|
|
@@ -48,8 +52,8 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
|
|
|
48
52
|
this.internals.ariaValueNow = this.indeterminate
|
|
49
53
|
? null
|
|
50
54
|
: String(Math.min(Math.max(this.value || min, min), max));
|
|
51
|
-
this.
|
|
52
|
-
|
|
55
|
+
const shown = this.valueText || slottedWords(this, ['']);
|
|
56
|
+
this.internals.ariaValueText = this.indeterminate ? shown || null : shown || this.text;
|
|
53
57
|
this.toggleState('complete', this.complete);
|
|
54
58
|
}
|
|
55
59
|
updated(changed) {
|
|
@@ -75,10 +79,14 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
|
|
|
75
79
|
<circle part="groove" />
|
|
76
80
|
<circle part="indicator" pathLength="100" style="--progress-fraction: ${fraction}" />
|
|
77
81
|
</svg>`;
|
|
82
|
+
const content = html `<span part="content" aria-hidden="true" style="--progress-fraction: ${fraction}"
|
|
83
|
+
><slot @slotchange=${this.reworded}></slot
|
|
84
|
+
></span>`;
|
|
78
85
|
return html `<slot name="label" @slotchange=${this.renamed}></slot>${this.shape === 'ring'
|
|
79
|
-
? ring
|
|
86
|
+
? html `${ring}${content}`
|
|
80
87
|
: html `<div part="track">
|
|
81
88
|
<div part="indicator" style="--progress-fraction: ${fraction}"></div>
|
|
89
|
+
${content}
|
|
82
90
|
</div>`}
|
|
83
91
|
${this.showValue && !this.indeterminate
|
|
84
92
|
? html `<span part="value" aria-hidden="true"
|
|
@@ -24,7 +24,7 @@ export interface ScatterField {
|
|
|
24
24
|
y: [number, number];
|
|
25
25
|
yNames: string[];
|
|
26
26
|
}
|
|
27
|
-
declare const HelixHeadlessScatterChart_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface);
|
|
27
|
+
declare const HelixHeadlessScatterChart_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartStatusInterface);
|
|
28
28
|
/**
|
|
29
29
|
* @summary A chart that shows whether two measures move together, with one point per row.
|
|
30
30
|
*
|
|
@@ -71,17 +71,32 @@ declare const HelixHeadlessScatterChart_base: typeof HelixElement & (new (...arg
|
|
|
71
71
|
* tap or key, and `helix-reading-press` says a reading was clicked, tapped, or
|
|
72
72
|
* pressed with Enter or Space, for a page that opens what a reading stands for.
|
|
73
73
|
*
|
|
74
|
+
* **`data` takes the readings as data** where the page has no table to point
|
|
75
|
+
* `for` at, in a table's shape: the column headings, then one row per reading
|
|
76
|
+
* with its label first. A table is still the better source where the page can
|
|
77
|
+
* show one, since it reads before any script runs.
|
|
78
|
+
*
|
|
79
|
+
* **A chart with nothing to draw says so.** A table with no readings shows
|
|
80
|
+
* `empty-text`, "No readings", in the plot, and `loading` shows `loading-text`
|
|
81
|
+
* there until readings arrive. The `empty` and `loading` slots take markup in
|
|
82
|
+
* their place, such as a link to add the first reading.
|
|
83
|
+
*
|
|
74
84
|
* @since 0.1.0
|
|
75
85
|
* @status stable
|
|
76
86
|
* @category charts
|
|
77
87
|
* @aliases Scatter Plot, XY Chart, Correlation Chart, Dot Plot
|
|
78
88
|
* @documentation https://fusion.dev/helix/components/scatter-chart
|
|
79
89
|
*
|
|
90
|
+
* @slot empty - Content shown in place of the drawing when the table has no readings, such as a link to add the first. Falls back to `empty-text`.
|
|
91
|
+
* @slot loading - Content shown in place of the drawing while `loading` is set and there are no readings yet. Falls back to `loading-text`.
|
|
92
|
+
*
|
|
80
93
|
* @event {HelixReadingChangeEvent} helix-reading-change - The reading shown moved, or was put away: `index` is the row, or `-1` for none, with its `label`, the `series` the keys rest on, and the `pointerType` that moved it.
|
|
81
94
|
* @event {HelixReadingPressEvent} helix-reading-press - A reading was clicked, tapped, or pressed with Enter or Space, with the same detail as `helix-reading-change`.
|
|
82
95
|
*
|
|
96
|
+
* @csspart empty - The words in place of the drawing when the table has no readings: the `empty` slot, falling back to `empty-text`. Not drawn while the table cannot be read yet, on the server and during hydration.
|
|
83
97
|
* @csspart gridline - One line across the plot, drawn when `gridlines` is set.
|
|
84
98
|
* @csspart live - The readout: a polite live region that says the row the keys moved to. Hide it visually.
|
|
99
|
+
* @csspart loading - The words in place of the drawing while `loading` is set and there are no readings: the `loading` slot, falling back to `loading-text`.
|
|
85
100
|
* @csspart mark - The invisible point the tooltip is anchored to.
|
|
86
101
|
* @csspart plane - The grid that holds the plot, both axes, and their names. It runs left to right on every page.
|
|
87
102
|
* @csspart plot - The drawing, which takes focus to be read by key.
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { CHART_PLOT, customElement, drawTooltip, extent, formatNumbers, formatWords, HelixElement, HelixReadingChangeEvent, HelixReadingPressEvent, isRtl, linear, MixinChartSource, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, ticks, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { CHART_PLOT, customElement, drawTooltip, extent, formatNumbers, formatWords, HelixElement, HelixReadingChangeEvent, HelixReadingPressEvent, isRtl, linear, MixinChartSource, MixinChartStatus, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, ticks, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing, svg } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import '../../tooltip/index.js';
|
|
@@ -33,7 +33,7 @@ const nice = ([low, high]) => {
|
|
|
33
33
|
const round = (value) => Number(value.toPrecision(12));
|
|
34
34
|
return [round(Math.floor(low / step) * step), round(Math.ceil(high / step) * step)];
|
|
35
35
|
};
|
|
36
|
-
let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinChartSource(HelixElement) {
|
|
36
|
+
let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinChartStatus(MixinChartSource(HelixElement)) {
|
|
37
37
|
constructor() {
|
|
38
38
|
super(...arguments);
|
|
39
39
|
this.correlationLabel = 'correlation';
|
|
@@ -140,7 +140,7 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
|
|
|
140
140
|
this.drawn = field.points;
|
|
141
141
|
if (field.points.length === 0) {
|
|
142
142
|
return html `<div dir="ltr" part="plane">
|
|
143
|
-
<div id=${CHART_PLOT} part="plot"
|
|
143
|
+
<div id=${CHART_PLOT} part="plot">${this.renderStatus()}</div>
|
|
144
144
|
</div>`;
|
|
145
145
|
}
|
|
146
146
|
const format = formatNumbers(wordsLocale(this));
|
|
@@ -30,6 +30,21 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
30
30
|
* `collapsed-size` keeps a sliver of the pane visible, such as a tab strip.
|
|
31
31
|
* Collapsing overrides `min` and `max`.
|
|
32
32
|
*
|
|
33
|
+
* Set `primary` to the pane that keeps its size when the splitter's own size
|
|
34
|
+
* changes, such as a sidebar beside a window being resized. The other pane
|
|
35
|
+
* takes all of the change, as Web Awesome's split panel does. Without it,
|
|
36
|
+
* both panes keep their share.
|
|
37
|
+
*
|
|
38
|
+
* **Keyboard.** The divider is one tab stop.
|
|
39
|
+
*
|
|
40
|
+
* | Key | What it does |
|
|
41
|
+
* |---|---|
|
|
42
|
+
* | Left, Right | Moves a side-by-side divider by `step`. On a right-to-left page, Left moves it toward the end. |
|
|
43
|
+
* | Up, Down | Moves a stacked divider by `step`. |
|
|
44
|
+
* | Home, End | Moves the divider to its bounds. |
|
|
45
|
+
* | Enter | Collapses the start pane, and opens it again. |
|
|
46
|
+
* | F6 | Moves focus from one pane to the first control in the other, and from the divider to the start pane. |
|
|
47
|
+
*
|
|
33
48
|
* @since 0.1.0
|
|
34
49
|
* @status stable
|
|
35
50
|
* @category layout
|
|
@@ -191,6 +206,32 @@ export declare class HelixHeadlessSplitter extends HelixElement {
|
|
|
191
206
|
*/
|
|
192
207
|
get position(): number;
|
|
193
208
|
set position(next: number);
|
|
209
|
+
/**
|
|
210
|
+
* The pane that keeps its size when the splitter's own size changes. The
|
|
211
|
+
* other pane takes all of the change. Unset, both keep their share.
|
|
212
|
+
*/
|
|
213
|
+
primary?: 'end' | 'start';
|
|
214
|
+
/** Watches this element's length along its axis while `primary` is set. */
|
|
215
|
+
private lengths;
|
|
216
|
+
/** The length along the axis last seen, in pixels. */
|
|
217
|
+
private length;
|
|
218
|
+
/**
|
|
219
|
+
* Starts or stops watching the length, according to `primary`, and holds
|
|
220
|
+
* the primary pane's size in pixels as the length changes.
|
|
221
|
+
*/
|
|
222
|
+
private watchLength;
|
|
223
|
+
/**
|
|
224
|
+
* Moves the divider so the primary pane keeps the pixels it had, as the
|
|
225
|
+
* splitter's length goes from the one last seen to `length`. Not while a
|
|
226
|
+
* pane is shut or a drag holds the divider.
|
|
227
|
+
*/
|
|
228
|
+
private holdPrimary;
|
|
229
|
+
/**
|
|
230
|
+
* F6, which moves focus between the panes, as the window splitter pattern
|
|
231
|
+
* allows: from the start pane to the end pane, and from the end pane or the
|
|
232
|
+
* divider to the start pane. Only a pane that holds a control takes focus.
|
|
233
|
+
*/
|
|
234
|
+
private readonly handlePaneKeys;
|
|
194
235
|
/**
|
|
195
236
|
* Where a dragged divider snaps, comma separated, as Web Awesome's split
|
|
196
237
|
* panel does: a bare number is a percentage and a length such as `240px` is
|
|
@@ -11,6 +11,7 @@ import { property, state } from 'lit/decorators.js';
|
|
|
11
11
|
import { snapTo } from '../snap.js';
|
|
12
12
|
const STACK_BELOW = 512;
|
|
13
13
|
const channel = new WeakMap();
|
|
14
|
+
const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
14
15
|
let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
15
16
|
static { HelixHeadlessSplitter_1 = this; }
|
|
16
17
|
static { this.words = 'helix-splitter'; }
|
|
@@ -69,6 +70,41 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
69
70
|
this.reached = undefined;
|
|
70
71
|
this.requestUpdate('position', previous);
|
|
71
72
|
}
|
|
73
|
+
watchLength() {
|
|
74
|
+
if (!this.primary || typeof ResizeObserver === 'undefined') {
|
|
75
|
+
this.lengths?.disconnect();
|
|
76
|
+
this.lengths = undefined;
|
|
77
|
+
this.length = 0;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (this.lengths) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this.lengths = new ResizeObserver(([entry]) => {
|
|
84
|
+
const box = entry?.borderBoxSize?.[0];
|
|
85
|
+
const length = (this.vertical
|
|
86
|
+
? (box?.blockSize ?? entry?.contentRect.height)
|
|
87
|
+
: (box?.inlineSize ?? entry?.contentRect.width)) ?? 0;
|
|
88
|
+
this.holdPrimary(length);
|
|
89
|
+
});
|
|
90
|
+
this.lengths.observe(this);
|
|
91
|
+
}
|
|
92
|
+
holdPrimary(length) {
|
|
93
|
+
const before = this.length;
|
|
94
|
+
this.length = length;
|
|
95
|
+
if (!this.primary || before <= 0 || length <= 0 || length === before) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (this.collapsed || this.dragging) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const position = this.position;
|
|
102
|
+
const kept = ((this.primary === 'start' ? position : 100 - position) / 100) * before;
|
|
103
|
+
const share = (kept / length) * 100;
|
|
104
|
+
const previous = position;
|
|
105
|
+
this.positionState = this.clamp(this.primary === 'start' ? share : 100 - share);
|
|
106
|
+
this.requestUpdate('position', previous);
|
|
107
|
+
}
|
|
72
108
|
get showing() {
|
|
73
109
|
return this.reached === undefined ? this.position : this.clamp(this.reached);
|
|
74
110
|
}
|
|
@@ -225,6 +261,34 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
225
261
|
});
|
|
226
262
|
this.max = 100;
|
|
227
263
|
this.min = 0;
|
|
264
|
+
this.length = 0;
|
|
265
|
+
this.handlePaneKeys = (event) => {
|
|
266
|
+
if (event.key !== 'F6' ||
|
|
267
|
+
event.altKey ||
|
|
268
|
+
event.ctrlKey ||
|
|
269
|
+
event.metaKey ||
|
|
270
|
+
event.defaultPrevented) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const path = event.composedPath();
|
|
274
|
+
const pane = (name) => this.renderRoot
|
|
275
|
+
?.querySelector(`slot[name="${name}"]`)
|
|
276
|
+
?.assignedElements({ flatten: true }) ?? [];
|
|
277
|
+
const inner = path.find((node) => node instanceof HelixHeadlessSplitter_1);
|
|
278
|
+
if (inner !== this) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const fromStart = pane('start').some((one) => path.includes(one));
|
|
282
|
+
const to = pane(fromStart ? 'end' : 'start');
|
|
283
|
+
const first = to.flatMap((one) => [
|
|
284
|
+
...(one instanceof HTMLElement && one.matches(FOCUSABLE) ? [one] : []),
|
|
285
|
+
...one.querySelectorAll(FOCUSABLE),
|
|
286
|
+
])[0];
|
|
287
|
+
if (first) {
|
|
288
|
+
event.preventDefault();
|
|
289
|
+
first.focus();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
228
292
|
this.snap = '';
|
|
229
293
|
this.snapThreshold = 12;
|
|
230
294
|
this.step = 2;
|
|
@@ -435,13 +499,19 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
435
499
|
super.connectedCallback();
|
|
436
500
|
if (this.hasUpdated) {
|
|
437
501
|
this.watchWidth();
|
|
502
|
+
this.watchLength();
|
|
438
503
|
this.findOuter();
|
|
439
504
|
}
|
|
505
|
+
this.addEventListener('keydown', this.handlePaneKeys);
|
|
440
506
|
}
|
|
441
507
|
disconnectedCallback() {
|
|
442
508
|
super.disconnectedCallback();
|
|
509
|
+
this.removeEventListener('keydown', this.handlePaneKeys);
|
|
443
510
|
this.sizes?.disconnect();
|
|
444
511
|
this.sizes = undefined;
|
|
512
|
+
this.lengths?.disconnect();
|
|
513
|
+
this.lengths = undefined;
|
|
514
|
+
this.length = 0;
|
|
445
515
|
if (this.outer) {
|
|
446
516
|
channel.get(this.outer)?.uncross(this);
|
|
447
517
|
}
|
|
@@ -451,6 +521,7 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
451
521
|
firstUpdated(changed) {
|
|
452
522
|
super.firstUpdated(changed);
|
|
453
523
|
this.watchWidth();
|
|
524
|
+
this.watchLength();
|
|
454
525
|
this.findOuter();
|
|
455
526
|
this.finishHydration();
|
|
456
527
|
}
|
|
@@ -459,6 +530,9 @@ let HelixHeadlessSplitter = class HelixHeadlessSplitter extends HelixElement {
|
|
|
459
530
|
if (changed.has('orientation')) {
|
|
460
531
|
this.watchWidth();
|
|
461
532
|
}
|
|
533
|
+
if (changed.has('primary')) {
|
|
534
|
+
this.watchLength();
|
|
535
|
+
}
|
|
462
536
|
if (this.collapsed) {
|
|
463
537
|
this.dataset.collapsed = this.collapsed;
|
|
464
538
|
}
|
|
@@ -578,6 +652,9 @@ __decorate([
|
|
|
578
652
|
__decorate([
|
|
579
653
|
property({ type: Number })
|
|
580
654
|
], HelixHeadlessSplitter.prototype, "position", null);
|
|
655
|
+
__decorate([
|
|
656
|
+
property()
|
|
657
|
+
], HelixHeadlessSplitter.prototype, "primary", void 0);
|
|
581
658
|
__decorate([
|
|
582
659
|
property()
|
|
583
660
|
], HelixHeadlessSplitter.prototype, "snap", void 0);
|
|
@@ -36,6 +36,20 @@ declare const HelixHeadlessTabs_base: (new (...args: any[]) => import("@fusion.d
|
|
|
36
36
|
* Disabled tabs are passed over. Tab leaves the list for the selected panel,
|
|
37
37
|
* which is a tab stop only while it holds nothing focusable.
|
|
38
38
|
*
|
|
39
|
+
* **Keyboard.**
|
|
40
|
+
*
|
|
41
|
+
* | Key | What it does |
|
|
42
|
+
* |---|---|
|
|
43
|
+
* | Right, Down | Moves to the next tab, from the last round to the first. Selects it, unless `manual` is set or a tab is a link. |
|
|
44
|
+
* | Left, Up | Moves to the tab before, from the first round to the last. |
|
|
45
|
+
* | Home, End | Moves to the first or the last tab. |
|
|
46
|
+
* | Enter, Space | Selects the tab with focus, or follows its link. |
|
|
47
|
+
* | Tab | Leaves the list for the selected panel. |
|
|
48
|
+
*
|
|
49
|
+
* Removing the selected tab selects the first enabled tab that is left, and
|
|
50
|
+
* `active` says so, with `helix-activate`. Where the removed tab had focus,
|
|
51
|
+
* focus moves to the tab now selected.
|
|
52
|
+
*
|
|
39
53
|
* The `indicator` part moves to the selected tab and carries its place and
|
|
40
54
|
* size, so a mark of your own can be drawn there: style the part, or slot an
|
|
41
55
|
* element into `indicator`, which sits inside the part and reads the same
|
|
@@ -125,8 +139,19 @@ export declare class HelixHeadlessTabs extends HelixHeadlessTabs_base {
|
|
|
125
139
|
private get linked();
|
|
126
140
|
private get panels();
|
|
127
141
|
private readonly handleSelection;
|
|
142
|
+
/** The values of the tabs the last pairing read, to tell a removed tab from one not yet here. */
|
|
143
|
+
private held;
|
|
128
144
|
/** Pushes the current value down to whichever tab and panel own it. */
|
|
129
145
|
private sync;
|
|
146
|
+
/** The tab that last had focus, so its removal can hand focus on. */
|
|
147
|
+
private focused;
|
|
148
|
+
private readonly handleFocusIn;
|
|
149
|
+
/**
|
|
150
|
+
* Puts focus on the selected tab where the tab that had it was removed and
|
|
151
|
+
* focus fell to the page, which is where removing a focused element leaves
|
|
152
|
+
* it.
|
|
153
|
+
*/
|
|
154
|
+
private refocus;
|
|
130
155
|
connectedCallback(): void;
|
|
131
156
|
disconnectedCallback(): void;
|
|
132
157
|
protected firstUpdated(changed: PropertyValues): void;
|