@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
|
@@ -14,15 +14,29 @@ const isControl = (element) => {
|
|
|
14
14
|
root?.delegatesFocus === true ||
|
|
15
15
|
root?.querySelector(CONTROL) != null);
|
|
16
16
|
};
|
|
17
|
-
export const
|
|
17
|
+
export const wordsAmong = (elements, skip = null) => {
|
|
18
18
|
const words = [];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const read = (list) => {
|
|
20
|
+
for (const one of list) {
|
|
21
|
+
if (one === skip) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (skip && one.contains(skip)) {
|
|
25
|
+
if (!read([...one.children])) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (isControl(one) || [...one.querySelectorAll('*')].some(isControl)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if ((one.textContent ?? '').trim() !== '') {
|
|
34
|
+
words.push(one);
|
|
35
|
+
}
|
|
22
36
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
37
|
+
return true;
|
|
38
|
+
};
|
|
39
|
+
read(elements);
|
|
27
40
|
return words;
|
|
28
41
|
};
|
|
42
|
+
export const wordsIn = (slot) => wordsAmong(slot?.assignedElements({ flatten: true }) ?? []);
|
|
@@ -39,6 +39,16 @@ declare const HelixHeadlessDialog_base: (new (...args: any[]) => import("@fusion
|
|
|
39
39
|
* announces with its description as it opens. Set `dismissible` to draw a
|
|
40
40
|
* close control, labeled by `dismiss-label`.
|
|
41
41
|
*
|
|
42
|
+
* **Keyboard.** Opening moves focus to the first control in the dialog.
|
|
43
|
+
*
|
|
44
|
+
* | Key | What it does |
|
|
45
|
+
* |---|---|
|
|
46
|
+
* | Tab, Shift+Tab | Moves through the controls in the dialog, from the last round to the first and from the first round to the last. |
|
|
47
|
+
* | Escape | Asks to close the dialog. A `helix-before-close` listener can refuse, and `persistent` or `closedby="none"` keeps it open. |
|
|
48
|
+
* | Enter, Space | On the close control, closes the dialog. |
|
|
49
|
+
*
|
|
50
|
+
* Closing puts focus back on the element that had it before the dialog opened.
|
|
51
|
+
*
|
|
42
52
|
* @since 0.1.0
|
|
43
53
|
* @status stable
|
|
44
54
|
* @category overlays
|
|
@@ -94,8 +104,14 @@ export declare class HelixHeadlessDialog extends HelixHeadlessDialog_base {
|
|
|
94
104
|
* `closedby="any"` gains light dismiss without giving up the refusal, which is
|
|
95
105
|
* the property this component exists to keep.
|
|
96
106
|
*
|
|
107
|
+
* The press on the backdrop that this element handles itself follows it too:
|
|
108
|
+
* `closerequest` and `none` leave the dialog open on that press, as the
|
|
109
|
+
* platform does, and `none` also keeps it open on Escape in a browser without
|
|
110
|
+
* `closedby`.
|
|
111
|
+
*
|
|
97
112
|
* Absent by default, which is the platform's `auto` and this library's rule
|
|
98
|
-
* that a resting value is an attribute's absence.
|
|
113
|
+
* that a resting value is an attribute's absence. Absent, a press on the
|
|
114
|
+
* backdrop closes the dialog.
|
|
99
115
|
*/
|
|
100
116
|
closedBy?: 'any' | 'closerequest' | 'none';
|
|
101
117
|
/**
|
|
@@ -36,7 +36,7 @@ let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(
|
|
|
36
36
|
this.returned = '';
|
|
37
37
|
this.handleCancel = (event) => {
|
|
38
38
|
event.preventDefault();
|
|
39
|
-
if (this.persistent) {
|
|
39
|
+
if (this.persistent || this.closedBy === 'none') {
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
this.requestOpen(false);
|
|
@@ -84,7 +84,7 @@ let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(
|
|
|
84
84
|
event.clientY <= box.bottom) {
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
|
-
if (this.persistent) {
|
|
87
|
+
if (this.persistent || this.closedBy === 'none' || this.closedBy === 'closerequest') {
|
|
88
88
|
this.refuse();
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
@@ -20,6 +20,11 @@ declare const HelixHeadlessDrawer_base: (new (...args: any[]) => import("@fusion
|
|
|
20
20
|
* A drawer with `modal="false"` leaves the page usable and still closes on
|
|
21
21
|
* Escape, in a browser without `closedby` as well.
|
|
22
22
|
*
|
|
23
|
+
* Set `persistent` for a drawer that has to be answered: Escape, a press
|
|
24
|
+
* outside, and a swipe leave it open, and the close control and `close()`
|
|
25
|
+
* still close it. `closedby="none"` keeps it open on all three as well, and
|
|
26
|
+
* `closedby="closerequest"` keeps it open on a press outside.
|
|
27
|
+
*
|
|
23
28
|
* **A touch or a pen swipes it away**, toward the edge it came from, resolved
|
|
24
29
|
* against the writing direction. A swipe past halfway, or a quick one, closes
|
|
25
30
|
* it through the same request as every other way out. `snap-points` lists the
|
|
@@ -40,7 +45,20 @@ declare const HelixHeadlessDrawer_base: (new (...args: any[]) => import("@fusion
|
|
|
40
45
|
*
|
|
41
46
|
* The panel's name is `label` when you set it. Otherwise it is the heading in
|
|
42
47
|
* the `header` slot, or the elements an `aria-labelledby` on the host names,
|
|
43
|
-
* and `fallback-label` where nothing names it.
|
|
48
|
+
* and `fallback-label` where nothing names it. A development build warns
|
|
49
|
+
* when a drawer opens with nothing but `fallback-label` to name it.
|
|
50
|
+
*
|
|
51
|
+
* The panel is described by the words at the start of its body, as `Dialog`
|
|
52
|
+
* is: the elements in the default slot before the first one that is or holds
|
|
53
|
+
* a control. An `aria-describedby` on the host replaces it.
|
|
54
|
+
*
|
|
55
|
+
* **Keyboard.** Opening moves focus to the first control in the panel.
|
|
56
|
+
*
|
|
57
|
+
* | Key | What it does |
|
|
58
|
+
* |---|---|
|
|
59
|
+
* | Tab, Shift+Tab | In a modal drawer, moves through the controls in the panel, from the last round to the first and from the first round to the last. In a drawer that is not modal, moves on to the page. |
|
|
60
|
+
* | Escape | Asks to close the drawer. A `helix-before-close` listener can refuse, and `persistent` or `closedby="none"` keeps it open. |
|
|
61
|
+
* | Enter, Space | On the close control, closes the drawer. |
|
|
44
62
|
*
|
|
45
63
|
* Content stays mounted when the drawer is closed, so it is in the server
|
|
46
64
|
* markup and can animate. A closed `<dialog>` is already out of the tab order
|
|
@@ -114,6 +132,8 @@ export declare class HelixHeadlessDrawer extends HelixHeadlessDrawer_base {
|
|
|
114
132
|
* way out.
|
|
115
133
|
*/
|
|
116
134
|
dismissible: boolean;
|
|
135
|
+
/** Whether a request to dismiss the drawer, by Escape or a swipe, is refused. */
|
|
136
|
+
private get refusesDismissal();
|
|
117
137
|
/**
|
|
118
138
|
* The close control's accessible name. Translate it with the rest of your
|
|
119
139
|
* interface.
|
|
@@ -129,7 +149,6 @@ export declare class HelixHeadlessDrawer extends HelixHeadlessDrawer_base {
|
|
|
129
149
|
* `header` slot names the panel.
|
|
130
150
|
*/
|
|
131
151
|
label: string;
|
|
132
|
-
protected readonly aria: AriaForwardController;
|
|
133
152
|
/**
|
|
134
153
|
* Whether the drawer blocks the page behind it.
|
|
135
154
|
*
|
|
@@ -144,6 +163,15 @@ export declare class HelixHeadlessDrawer extends HelixHeadlessDrawer_base {
|
|
|
144
163
|
* not, rather than shown or hidden by CSS.
|
|
145
164
|
*/
|
|
146
165
|
modal: boolean;
|
|
166
|
+
protected readonly aria: AriaForwardController;
|
|
167
|
+
/**
|
|
168
|
+
* Stops Escape, a press outside, and a swipe from closing the drawer, for a
|
|
169
|
+
* drawer that has to be answered.
|
|
170
|
+
*
|
|
171
|
+
* The close control and `close()` still close it, as on `Dialog`: a panel
|
|
172
|
+
* with no way out at all is a trap.
|
|
173
|
+
*/
|
|
174
|
+
persistent: boolean;
|
|
147
175
|
/**
|
|
148
176
|
* Which edge the drawer comes from.
|
|
149
177
|
*
|
|
@@ -281,6 +309,14 @@ export declare class HelixHeadlessDrawer extends HelixHeadlessDrawer_base {
|
|
|
281
309
|
* reflection keeps the fallback name.
|
|
282
310
|
*/
|
|
283
311
|
private nameByHeader;
|
|
312
|
+
/** Whether the body's words are what currently describe the panel. */
|
|
313
|
+
private describedByBody;
|
|
314
|
+
/**
|
|
315
|
+
* Describes the panel by the words at the start of its body, where nothing
|
|
316
|
+
* else describes it, as `Dialog` does. An `aria-describedby` on the host
|
|
317
|
+
* comes first.
|
|
318
|
+
*/
|
|
319
|
+
private describeByBody;
|
|
284
320
|
connectedCallback(): void;
|
|
285
321
|
disconnectedCallback(): void;
|
|
286
322
|
protected updated(changed: PropertyValues): void;
|
|
@@ -4,9 +4,10 @@ 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, CommandController, customElement, drawDismiss, drawDismissGlyph, edgeOffset, FocusScopeController, HelixElement, headingsIn, isRtl, lockScroll, MixinControllerOpen, nearest, OpenPhaseController, onUnlessFalse, parseSnapPoints, reflectElements, SwipeController, settleSwipe, snapTravel, swipeAxis, swipeEdge, towardEdge, unlockScroll, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, auditName, CommandController, customElement, drawDismiss, drawDismissGlyph, edgeOffset, FocusScopeController, HelixElement, headingsIn, isRtl, lockScroll, MixinControllerOpen, nearest, OpenPhaseController, onUnlessFalse, parseSnapPoints, reflectElements, SwipeController, settleSwipe, snapTravel, swipeAxis, swipeEdge, towardEdge, unlockScroll, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
+
import { wordsIn } from '../../dialog/description.js';
|
|
10
11
|
import { beyondWindow } from '../reveal.js';
|
|
11
12
|
import { startsSwipe } from '../swipe-start.js';
|
|
12
13
|
let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(HelixElement) {
|
|
@@ -23,8 +24,9 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
|
|
|
23
24
|
this.dismissLabel = 'Close';
|
|
24
25
|
this.fallbackLabel = 'Drawer';
|
|
25
26
|
this.label = '';
|
|
26
|
-
this.aria = new AriaForwardController(this, { target: () => this.panel });
|
|
27
27
|
this.modal = true;
|
|
28
|
+
this.aria = new AriaForwardController(this, { target: () => this.panel });
|
|
29
|
+
this.persistent = false;
|
|
28
30
|
this.snapPoints = '';
|
|
29
31
|
this.swipe = true;
|
|
30
32
|
this.returned = '';
|
|
@@ -63,7 +65,9 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
|
|
|
63
65
|
return;
|
|
64
66
|
}
|
|
65
67
|
event.preventDefault();
|
|
66
|
-
this.
|
|
68
|
+
if (!this.refusesDismissal) {
|
|
69
|
+
this.requestOpen(false);
|
|
70
|
+
}
|
|
67
71
|
};
|
|
68
72
|
this.scope = new FocusScopeController(this, {
|
|
69
73
|
contain: () => this.open && this.modal,
|
|
@@ -77,7 +81,9 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
|
|
|
77
81
|
});
|
|
78
82
|
this.handleCancel = (event) => {
|
|
79
83
|
event.preventDefault();
|
|
80
|
-
this.
|
|
84
|
+
if (!this.refusesDismissal) {
|
|
85
|
+
this.requestOpen(false);
|
|
86
|
+
}
|
|
81
87
|
};
|
|
82
88
|
this.handleClose = (event) => {
|
|
83
89
|
if (this.open) {
|
|
@@ -97,11 +103,18 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
|
|
|
97
103
|
event.clientY <= box.bottom) {
|
|
98
104
|
return;
|
|
99
105
|
}
|
|
106
|
+
if (this.refusesDismissal || this.closedBy === 'closerequest') {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
100
109
|
this.requestOpen(false);
|
|
101
110
|
};
|
|
102
111
|
this.namedByHeader = false;
|
|
112
|
+
this.describedByBody = false;
|
|
103
113
|
}
|
|
104
114
|
static { this.words = 'helix-drawer'; }
|
|
115
|
+
get refusesDismissal() {
|
|
116
|
+
return this.persistent || this.closedBy === 'none';
|
|
117
|
+
}
|
|
105
118
|
get returnValue() {
|
|
106
119
|
return this.panel?.returnValue ?? this.returned;
|
|
107
120
|
}
|
|
@@ -138,7 +151,7 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
|
|
|
138
151
|
travel: Math.max(0, this.rest + towardEdge(edge, dx, dy)),
|
|
139
152
|
velocity: towardEdge(edge, vx, vy),
|
|
140
153
|
});
|
|
141
|
-
if (shown === 0 && this.requestOpen(false)) {
|
|
154
|
+
if (shown === 0 && !this.refusesDismissal && this.requestOpen(false)) {
|
|
142
155
|
return;
|
|
143
156
|
}
|
|
144
157
|
if (shown !== 0) {
|
|
@@ -192,6 +205,11 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
|
|
|
192
205
|
: headingsIn(slot?.assignedElements({ flatten: true }) ?? []);
|
|
193
206
|
this.namedByHeader = reflectElements(this.panel, 'ariaLabelledByElements', heading, this.namedByHeader);
|
|
194
207
|
}
|
|
208
|
+
describeByBody() {
|
|
209
|
+
const slot = this.renderRoot.querySelector('slot:not([name])');
|
|
210
|
+
const said = this.hasAttribute('aria-describedby') ? [] : wordsIn(slot);
|
|
211
|
+
this.describedByBody = reflectElements(this.panel, 'ariaDescribedByElements', said, this.describedByBody);
|
|
212
|
+
}
|
|
195
213
|
connectedCallback() {
|
|
196
214
|
super.connectedCallback();
|
|
197
215
|
this.addEventListener('focusin', this.handleFocusIn);
|
|
@@ -204,6 +222,7 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
|
|
|
204
222
|
updated(changed) {
|
|
205
223
|
super.updated(changed);
|
|
206
224
|
this.nameByHeader();
|
|
225
|
+
this.describeByBody();
|
|
207
226
|
if (changed.has('open') && !this.open) {
|
|
208
227
|
this.rest = 0;
|
|
209
228
|
this.shown = 1;
|
|
@@ -221,6 +240,11 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
|
|
|
221
240
|
return;
|
|
222
241
|
}
|
|
223
242
|
if (this.open && !panel.open) {
|
|
243
|
+
auditName(this, () => null, 'Give it a `label`, or a heading in the `header` slot.', () => this.label !== '' ||
|
|
244
|
+
this.namedByHeader ||
|
|
245
|
+
headingsIn(this.renderRoot
|
|
246
|
+
.querySelector('slot[name="header"]')
|
|
247
|
+
?.assignedElements({ flatten: true }) ?? []).length > 0);
|
|
224
248
|
if (this.modal) {
|
|
225
249
|
panel.showModal();
|
|
226
250
|
}
|
|
@@ -245,7 +269,7 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
|
|
|
245
269
|
@keydown=${this.handleKeydown}
|
|
246
270
|
>
|
|
247
271
|
<div part="header"><slot name="header" @slotchange=${this.nameByHeader}></slot></div>
|
|
248
|
-
<div part="content"><slot></slot></div>
|
|
272
|
+
<div part="content"><slot @slotchange=${this.describeByBody}></slot></div>
|
|
249
273
|
<div part="footer"><slot name="footer"></slot></div>
|
|
250
274
|
${this.dismissible
|
|
251
275
|
? drawDismiss({
|
|
@@ -282,6 +306,9 @@ __decorate([
|
|
|
282
306
|
__decorate([
|
|
283
307
|
property({ converter: onUnlessFalse })
|
|
284
308
|
], HelixHeadlessDrawer.prototype, "modal", void 0);
|
|
309
|
+
__decorate([
|
|
310
|
+
property({ reflect: true, type: Boolean })
|
|
311
|
+
], HelixHeadlessDrawer.prototype, "persistent", void 0);
|
|
285
312
|
__decorate([
|
|
286
313
|
property({ reflect: true })
|
|
287
314
|
], HelixHeadlessDrawer.prototype, "placement", void 0);
|
|
@@ -328,6 +328,9 @@ export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
|
|
|
328
328
|
* in a browser is still known, and still empty: a `for` naming no table
|
|
329
329
|
* should say so rather than sit blank, which is why this asks
|
|
330
330
|
* `searchableById` and not `elementById`.
|
|
331
|
+
*
|
|
332
|
+
* `readingsKnown` asks it for every chart, and for `data` too, which a
|
|
333
|
+
* server does not draw.
|
|
331
334
|
*/
|
|
332
335
|
private get sourceKnown();
|
|
333
336
|
/**
|
|
@@ -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, byRow, CHART_PLOT, commaList, customElement, drawTooltip, formatWords, HelixElement, HelixResizeEvent, MixinChartSource, markRegion, pressedValue, rangeAdd, requestValue, StatusController,
|
|
7
|
+
import { AriaForwardController, byRow, CHART_PLOT, commaList, customElement, drawTooltip, formatWords, HelixElement, HelixResizeEvent, MixinChartSource, markRegion, pressedValue, rangeAdd, requestValue, StatusController, words, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property, state } from 'lit/decorators.js';
|
|
10
10
|
import '../../tooltip/index.js';
|
|
@@ -110,13 +110,7 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
|
|
|
110
110
|
return byRow(series, this.sourceRows.length).map((point) => point ?? { value: undefined });
|
|
111
111
|
}
|
|
112
112
|
get sourceKnown() {
|
|
113
|
-
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
if (this.hydrating) {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
return searchableById(this);
|
|
113
|
+
return this.readingsKnown;
|
|
120
114
|
}
|
|
121
115
|
get rowCount() {
|
|
122
116
|
return Math.max(1, Math.floor(this.rows) || 1);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CommandController, HelixElement, SwipeController } from '@fusion.dev/helix-core';
|
|
1
|
+
import { CommandController, FocusScopeController, HelixElement, SwipeController } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerActiveInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
|
|
4
4
|
/**
|
|
@@ -41,6 +41,25 @@ declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusi
|
|
|
41
41
|
* swiped across the picture shows the next or the previous item, the way the
|
|
42
42
|
* line of text runs.
|
|
43
43
|
*
|
|
44
|
+
* A press on the empty area around the item closes the viewer, as a press on
|
|
45
|
+
* the backdrop does. Under `max-zoom`, a press beside the item zooms on a
|
|
46
|
+
* double press and closes nothing, and only the space outside the viewport
|
|
47
|
+
* closes the viewer. A press that dragged closes nothing.
|
|
48
|
+
*
|
|
49
|
+
* **Keyboard.** Opening moves focus to the viewer itself, which a screen
|
|
50
|
+
* reader names.
|
|
51
|
+
*
|
|
52
|
+
* | Key | What it does |
|
|
53
|
+
* |---|---|
|
|
54
|
+
* | Right, Down | Shows the next item. On a right-to-left page, Left does. While the item is zoomed, the arrows move about it instead. |
|
|
55
|
+
* | Left, Up | Shows the previous item. |
|
|
56
|
+
* | Home, End | Shows the first or the last item. |
|
|
57
|
+
* | `+`, `-`, `0` | Zooms in, zooms out, and puts the item back at its own size, where `max-zoom` allows zooming. |
|
|
58
|
+
* | Tab, Shift+Tab | Moves through the controls in the viewer, from the last round to the first and from the first round to the last. |
|
|
59
|
+
* | Escape | Closes the viewer. A `helix-before-close` listener can refuse. |
|
|
60
|
+
*
|
|
61
|
+
* A development build warns when a viewer opens with no `label`.
|
|
62
|
+
*
|
|
44
63
|
* The chrome hides when there is no input, through `IdleController`, and comes
|
|
45
64
|
* back on a pointer move, a key press, or a focus. It stays while focus is on a
|
|
46
65
|
* control in the header or the footer, so a keyboard reader is never left on
|
|
@@ -205,6 +224,7 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
|
|
|
205
224
|
/** Whether the zoom is being put back because the item changed, which says nothing. */
|
|
206
225
|
private quietZoom;
|
|
207
226
|
protected readonly swipes: SwipeController;
|
|
227
|
+
protected readonly scope: FocusScopeController;
|
|
208
228
|
private readonly zoomer;
|
|
209
229
|
/** Writes the zoom on the viewport, and nothing at the item's own size. */
|
|
210
230
|
private drawZoom;
|
|
@@ -287,6 +307,11 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
|
|
|
287
307
|
private readonly handleKeydown;
|
|
288
308
|
/** `+` and `-` zoom in and out by half again, and `0` puts the item back. Returns whether it was one. */
|
|
289
309
|
private zoomKey;
|
|
310
|
+
/**
|
|
311
|
+
* The arrow keys on a zoomed item, which move about it rather than to
|
|
312
|
+
* another item. Returns whether it was one.
|
|
313
|
+
*/
|
|
314
|
+
private panKey;
|
|
290
315
|
/** Zooms in by half again, as `+` and the zoom-in control do. */
|
|
291
316
|
private zoomIn;
|
|
292
317
|
/** Zooms out by as much, as `-` and the zoom-out control do. */
|
|
@@ -296,14 +321,27 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
|
|
|
296
321
|
/** The item on screen: the one `active` names, or the first where it names none. */
|
|
297
322
|
private get shown();
|
|
298
323
|
/**
|
|
299
|
-
* A press on the backdrop
|
|
324
|
+
* A press on the backdrop or on the empty area around the item.
|
|
300
325
|
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
326
|
+
* A modal `<dialog>` attributes backdrop presses to itself, and the styled
|
|
327
|
+
* tier draws the viewer over the whole screen, so a press beside the item
|
|
328
|
+
* lands on the dialog, the viewport, or the item's own box. Any of those is
|
|
329
|
+
* a reader done with the viewer, as a press on a dark room around a picture
|
|
330
|
+
* is. Not a press that dragged, which was a swipe or a pan.
|
|
305
331
|
*/
|
|
306
332
|
private readonly handleBackdrop;
|
|
333
|
+
/** Where the last press began, so a press that dragged closes nothing. */
|
|
334
|
+
private pressedAt;
|
|
335
|
+
private readonly handlePointerDown;
|
|
336
|
+
/** Whether the press moved further than a click does, as a swipe or a pan does. */
|
|
337
|
+
private dragged;
|
|
338
|
+
/**
|
|
339
|
+
* Whether a press landed on the empty area around the item: the viewer's
|
|
340
|
+
* own surface, the viewport, or an item's box beside what it holds. The
|
|
341
|
+
* last two not where `max-zoom` lets a reader zoom, since a double press
|
|
342
|
+
* there zooms.
|
|
343
|
+
*/
|
|
344
|
+
private onEmptyArea;
|
|
307
345
|
protected willUpdate(changed: PropertyValues): void;
|
|
308
346
|
disconnectedCallback(): void;
|
|
309
347
|
protected updated(changed: PropertyValues): void;
|
|
@@ -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 { CommandController, customElement, DelayController, deepActiveElement, deepContains, drawDismiss, drawDismissGlyph, FullscreenController, formatWords, HelixElement, hostAria, IdleController, inlineStep, isRtl, keyClaimed, lockScroll, MixinControllerActive, MixinControllerOpen, resolveActiveItem, SwipeController, swipeStep, unlockScroll, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { auditName, CommandController, customElement, DelayController, deepActiveElement, deepContains, drawDismiss, drawDismissGlyph, FocusScopeController, FullscreenController, formatWords, HelixElement, hostAria, IdleController, inlineStep, isRtl, keyClaimed, lockScroll, MixinControllerActive, MixinControllerOpen, resolveActiveItem, SwipeController, swipeStep, unlockScroll, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { caretLeft, caretRight, minus, plus } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -53,6 +53,16 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
53
53
|
start: (_target, event) => event.pointerType !== 'mouse',
|
|
54
54
|
surface: () => this.dialog,
|
|
55
55
|
});
|
|
56
|
+
this.scope = new FocusScopeController(this, {
|
|
57
|
+
contain: () => this.open,
|
|
58
|
+
contents: () => [
|
|
59
|
+
...['header', '', 'footer'].flatMap((name) => this.renderRoot
|
|
60
|
+
?.querySelector(name ? `slot[name="${name}"]` : 'slot:not([name])')
|
|
61
|
+
?.assignedElements({ flatten: true }) ?? []),
|
|
62
|
+
...[...(this.dialog?.children ?? [])].filter((one) => one.localName === 'button'),
|
|
63
|
+
],
|
|
64
|
+
surface: () => this.dialog,
|
|
65
|
+
});
|
|
56
66
|
this.zoomer = new ZoomController(this, {
|
|
57
67
|
max: () => (this.open ? this.maxZoom : 1),
|
|
58
68
|
onChange: (scale, pan) => this.drawZoom(scale, pan),
|
|
@@ -89,7 +99,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
89
99
|
if (keyClaimed(event) || event.altKey || event.ctrlKey || event.metaKey) {
|
|
90
100
|
return;
|
|
91
101
|
}
|
|
92
|
-
if (this.zoomKey(event.key)) {
|
|
102
|
+
if (this.zoomKey(event.key) || this.panKey(event.key)) {
|
|
93
103
|
event.preventDefault();
|
|
94
104
|
return;
|
|
95
105
|
}
|
|
@@ -112,19 +122,14 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
112
122
|
}
|
|
113
123
|
};
|
|
114
124
|
this.handleBackdrop = (event) => {
|
|
115
|
-
|
|
116
|
-
if (!panel || event.target !== panel) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
const box = panel.getBoundingClientRect();
|
|
120
|
-
if (event.clientX >= box.left &&
|
|
121
|
-
event.clientX <= box.right &&
|
|
122
|
-
event.clientY >= box.top &&
|
|
123
|
-
event.clientY <= box.bottom) {
|
|
125
|
+
if (this.dragged(event) || !this.onEmptyArea(event)) {
|
|
124
126
|
return;
|
|
125
127
|
}
|
|
126
128
|
this.requestOpen(false);
|
|
127
129
|
};
|
|
130
|
+
this.handlePointerDown = (event) => {
|
|
131
|
+
this.pressedAt = { x: event.clientX, y: event.clientY };
|
|
132
|
+
};
|
|
128
133
|
}
|
|
129
134
|
static { this.words = 'helix-lightbox'; }
|
|
130
135
|
drawZoom(scale, pan) {
|
|
@@ -217,6 +222,25 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
217
222
|
}
|
|
218
223
|
return true;
|
|
219
224
|
}
|
|
225
|
+
panKey(key) {
|
|
226
|
+
if (this.zoomer.scale <= 1) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
const box = this.partNamed('viewport')?.getBoundingClientRect();
|
|
230
|
+
const across = Math.max(40, (box?.width ?? 0) / 10);
|
|
231
|
+
const down = Math.max(40, (box?.height ?? 0) / 10);
|
|
232
|
+
const by = {
|
|
233
|
+
ArrowDown: { x: 0, y: -down },
|
|
234
|
+
ArrowLeft: { x: across, y: 0 },
|
|
235
|
+
ArrowRight: { x: -across, y: 0 },
|
|
236
|
+
ArrowUp: { x: 0, y: down },
|
|
237
|
+
}[key];
|
|
238
|
+
if (!by) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
this.zoomer.panBy(by);
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
220
244
|
zoomIn() {
|
|
221
245
|
this.zoomer.zoomTo(this.zoomer.scale * 1.5);
|
|
222
246
|
}
|
|
@@ -232,6 +256,28 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
232
256
|
get shown() {
|
|
233
257
|
return resolveActiveItem(this.active ?? '', this.items);
|
|
234
258
|
}
|
|
259
|
+
dragged(event) {
|
|
260
|
+
const from = this.pressedAt;
|
|
261
|
+
this.pressedAt = undefined;
|
|
262
|
+
return from !== undefined && Math.hypot(event.clientX - from.x, event.clientY - from.y) > 8;
|
|
263
|
+
}
|
|
264
|
+
onEmptyArea(event) {
|
|
265
|
+
const pressed = event.composedPath()[0];
|
|
266
|
+
if (!(pressed instanceof Element)) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
if (pressed === this.dialog) {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
if (this.maxZoom > 1) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
const root = pressed.getRootNode();
|
|
276
|
+
const inItem = root instanceof ShadowRoot &&
|
|
277
|
+
root.host instanceof HelixHeadlessLightboxItem &&
|
|
278
|
+
pressed.getAttribute('part') === 'item';
|
|
279
|
+
return inItem || pressed === this.partNamed('viewport');
|
|
280
|
+
}
|
|
235
281
|
willUpdate(changed) {
|
|
236
282
|
super.willUpdate(changed);
|
|
237
283
|
hostAria(this.internals, { label: this.label });
|
|
@@ -263,6 +309,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
263
309
|
return;
|
|
264
310
|
}
|
|
265
311
|
if (this.open && !dialog.open) {
|
|
312
|
+
auditName(this, () => null, 'Give it a `label`, such as "Project photos".', () => this.label !== '');
|
|
266
313
|
dialog.showModal();
|
|
267
314
|
dialog.focus({ preventScroll: true });
|
|
268
315
|
this.idle.stir();
|
|
@@ -334,6 +381,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
334
381
|
@close=${this.handleClose}
|
|
335
382
|
@focusin=${this.idle.stir}
|
|
336
383
|
@keydown=${this.handleKeydown}
|
|
384
|
+
@pointerdown=${this.handlePointerDown}
|
|
337
385
|
@pointerleave=${this.idle.leave}
|
|
338
386
|
@pointermove=${this.idle.stir}
|
|
339
387
|
>
|
|
@@ -41,6 +41,11 @@ export declare class ZoomController implements ReactiveController {
|
|
|
41
41
|
hostDisconnected(): void;
|
|
42
42
|
/** Zooms to `scale` about the middle of the box, held to the range. */
|
|
43
43
|
zoomTo(scale: number, at?: Offset): void;
|
|
44
|
+
/**
|
|
45
|
+
* Moves a zoomed item by `by`, in pixels, never so far that an edge comes
|
|
46
|
+
* inside the box. Returns whether it moved.
|
|
47
|
+
*/
|
|
48
|
+
panBy(by: Offset): boolean;
|
|
44
49
|
/** Back to the item at its own size, in the middle. */
|
|
45
50
|
reset(): void;
|
|
46
51
|
private set;
|
|
@@ -79,6 +79,14 @@ export class ZoomController {
|
|
|
79
79
|
const next = clampScale(scale, this.options.max());
|
|
80
80
|
this.set(next, zoomAt(at, this.scale, next, this.pan));
|
|
81
81
|
}
|
|
82
|
+
panBy(by) {
|
|
83
|
+
if (this.scale <= 1) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
const before = this.pan;
|
|
87
|
+
this.set(this.scale, { x: before.x + by.x, y: before.y + by.y });
|
|
88
|
+
return this.pan.x !== before.x || this.pan.y !== before.y;
|
|
89
|
+
}
|
|
82
90
|
reset() {
|
|
83
91
|
if (this.scale !== 1 || this.pan.x !== 0 || this.pan.y !== 0) {
|
|
84
92
|
this.set(1, { x: 0, y: 0 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Curve, HelixElement, type Series } from '@fusion.dev/helix-core';
|
|
2
2
|
import { nothing, type TemplateResult } from 'lit';
|
|
3
3
|
import '../../tooltip/index.js';
|
|
4
|
-
declare const HelixHeadlessLineChart_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface);
|
|
4
|
+
declare const HelixHeadlessLineChart_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartStatusInterface);
|
|
5
5
|
/**
|
|
6
6
|
* @summary A chart that draws series over time as lines, read from a table.
|
|
7
7
|
*
|
|
@@ -45,12 +45,25 @@ declare const HelixHeadlessLineChart_base: typeof HelixElement & (new (...args:
|
|
|
45
45
|
* tap or key, and `helix-reading-press` says a reading was clicked, tapped, or
|
|
46
46
|
* pressed with Enter or Space, for a page that opens what a reading stands for.
|
|
47
47
|
*
|
|
48
|
+
* **`data` takes the readings as data** where the page has no table to point
|
|
49
|
+
* `for` at, in a table's shape: the column headings, then one row per reading
|
|
50
|
+
* with its label first. A table is still the better source where the page can
|
|
51
|
+
* show one, since it reads before any script runs.
|
|
52
|
+
*
|
|
53
|
+
* **A chart with nothing to draw says so.** A table with no readings shows
|
|
54
|
+
* `empty-text`, "No readings", in the plot, and `loading` shows `loading-text`
|
|
55
|
+
* there until readings arrive. The `empty` and `loading` slots take markup in
|
|
56
|
+
* their place, such as a link to add the first reading.
|
|
57
|
+
*
|
|
48
58
|
* @since 0.1.0
|
|
49
59
|
* @status stable
|
|
50
60
|
* @category charts
|
|
51
61
|
* @aliases Time Series, Trend Chart, Multi-line Chart
|
|
52
62
|
* @documentation https://fusion.dev/helix/components/line-chart
|
|
53
63
|
*
|
|
64
|
+
* @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`.
|
|
65
|
+
* @slot loading - Content shown in place of the drawing while `loading` is set and there are no readings yet. Falls back to `loading-text`.
|
|
66
|
+
*
|
|
54
67
|
* @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.
|
|
55
68
|
* @event {HelixReadingPressEvent} helix-reading-press - A reading was clicked, tapped, or pressed with Enter or Space, with the same detail as `helix-reading-change`.
|
|
56
69
|
*
|
|
@@ -58,6 +71,7 @@ declare const HelixHeadlessLineChart_base: typeof HelixElement & (new (...args:
|
|
|
58
71
|
* @csspart axis - The numbers of the scale beside the plot, under `value-axis`.
|
|
59
72
|
* @csspart band - The band around the reading under the pointer.
|
|
60
73
|
* @csspart baseline - The rule at zero, where zero is inside the range.
|
|
74
|
+
* @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.
|
|
61
75
|
* @csspart gridline - One rule across the plot, under `gridlines`.
|
|
62
76
|
* @csspart guide - A rule down the reading a pointer is resting on.
|
|
63
77
|
* @csspart label - One reading's name.
|
|
@@ -65,6 +79,7 @@ declare const HelixHeadlessLineChart_base: typeof HelixElement & (new (...args:
|
|
|
65
79
|
* @csspart line - The line for one series.
|
|
66
80
|
* @csspart line-dashed - A `line` whose series `dashed` names. Set alongside `line`.
|
|
67
81
|
* @csspart live - The readout: a polite live region that says the reading the keys moved to. Hide it visually.
|
|
82
|
+
* @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`.
|
|
68
83
|
* @csspart mark - The invisible point the tooltip is anchored to.
|
|
69
84
|
* @csspart marker - The mark on one reading, under `markers`, and on a reading with a blank on either side. Placed with `inset-inline-start` and `inset-block-start`, and carries its series color as `--chart-series`.
|
|
70
85
|
* @csspart marker-lone - A `marker` on a reading with a blank on either side, which no line reaches. Set alongside `marker`.
|
|
@@ -4,11 +4,11 @@ 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 { byRow, CHART_PLOT, commaList, customElement, drawTooltip, drawValueAxis, extent, formatNumbers, HelixElement, HelixReadingChangeEvent, HelixReadingPressEvent, linear, linePath, MixinChartSource, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, seriesKey, ticks, unbroken, wordsLocale, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { byRow, CHART_PLOT, commaList, customElement, drawTooltip, drawValueAxis, extent, formatNumbers, HelixElement, HelixReadingChangeEvent, HelixReadingPressEvent, linear, linePath, MixinChartSource, MixinChartStatus, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, seriesKey, ticks, unbroken, 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';
|
|
11
|
-
let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartSource(HelixElement) {
|
|
11
|
+
let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartStatus(MixinChartSource(HelixElement)) {
|
|
12
12
|
constructor() {
|
|
13
13
|
super(...arguments);
|
|
14
14
|
this.area = false;
|
|
@@ -59,7 +59,7 @@ let HelixHeadlessLineChart = class HelixHeadlessLineChart extends MixinChartSour
|
|
|
59
59
|
const series = this.series;
|
|
60
60
|
const readings = series.length === 0 ? [] : this.sourceRows.map((row) => row.label);
|
|
61
61
|
if (readings.length === 0) {
|
|
62
|
-
return html `<div id=${CHART_PLOT} part="plot"
|
|
62
|
+
return html `<div id=${CHART_PLOT} part="plot">${this.renderStatus()}</div>`;
|
|
63
63
|
}
|
|
64
64
|
const domain = this.range(series);
|
|
65
65
|
const y = linear(domain, [PLOT_HEIGHT, 0]);
|