@fusion.dev/helix-headless 0.100.0 → 0.102.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 +4478 -2166
- package/dist/dialog/root/dialog-root.d.ts +1 -1
- package/dist/element-events.generated.js +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lightbox/index.d.ts +2 -0
- package/dist/lightbox/index.js +2 -0
- package/dist/lightbox/item/lightbox-item.d.ts +60 -0
- package/dist/lightbox/item/lightbox-item.js +45 -0
- package/dist/lightbox/root/lightbox-root.d.ts +176 -0
- package/dist/lightbox/root/lightbox-root.js +212 -0
- package/dist/map/index.d.ts +1 -0
- package/dist/map/index.js +1 -0
- package/dist/map/root/map-root.d.ts +142 -0
- package/dist/map/root/map-root.js +161 -0
- package/package.json +5 -5
- package/types/jsx.d.ts +1049 -0
- package/types/svelte.d.ts +3 -0
- package/types/vue.d.ts +208 -2
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
+
/** Which way a press or a key asked the map to go. */
|
|
4
|
+
type Way = 'up' | 'down' | 'start' | 'end';
|
|
5
|
+
/**
|
|
6
|
+
* @summary A frame around somebody else's map.
|
|
7
|
+
*
|
|
8
|
+
* @description
|
|
9
|
+
* **Helix draws no map.** There are no tiles here, no projection and no
|
|
10
|
+
* network, and there should not be: a tile renderer is a library, and what a
|
|
11
|
+
* design system can add to MapLibre or Leaflet is the frame. The consumer slots
|
|
12
|
+
* their map and this owns the box, the name, the controls and the way out,
|
|
13
|
+
* which is the shape `VideoPlaylist` uses for a player it never renders.
|
|
14
|
+
*
|
|
15
|
+
* **The accessibility argument is the reason this exists**, and it was measured
|
|
16
|
+
* rather than assumed: a map embedded the ordinary way is a canvas with no role
|
|
17
|
+
* and no name, and it has *no node at all* in the accessibility tree. A reader
|
|
18
|
+
* using a screen reader does not walk past it so much as never meet it. A named
|
|
19
|
+
* region with controls in it is the whole of the difference.
|
|
20
|
+
*
|
|
21
|
+
* **The controls are buttons, and that is a deliberate choice against arrow
|
|
22
|
+
* keys.** Arrows work here too, but what a screen reader does with them is not
|
|
23
|
+
* ours to decide: macOS passes them through, Windows readers hold them for
|
|
24
|
+
* their own reading mode unless a page takes `role="application"`, which costs
|
|
25
|
+
* the reader their normal way of reading everything else in the frame. A button
|
|
26
|
+
* is findable and operable in every screen reader on every platform without
|
|
27
|
+
* that bargain, so panning and zooming are buttons first and keys second.
|
|
28
|
+
*
|
|
29
|
+
* **Nothing here moves the map.** Both controls ask, through `helix-map-pan`
|
|
30
|
+
* and `helix-map-zoom`, and the consumer answers against whatever they slotted.
|
|
31
|
+
* A frame that moved the map would have to know whose map it was.
|
|
32
|
+
*
|
|
33
|
+
* @since 0.100.0
|
|
34
|
+
* @status stable
|
|
35
|
+
* @category media
|
|
36
|
+
* @aliases Map frame, Map container, Geo
|
|
37
|
+
* @documentation https://fusion.dev/helix/components/map
|
|
38
|
+
*
|
|
39
|
+
* @slot - The map, which is the consumer's own.
|
|
40
|
+
* @slot attribution - Whoever the tiles belong to, which most licenses require you to say.
|
|
41
|
+
* @slot footer - Chrome below the map, such as a legend or a scale.
|
|
42
|
+
* @slot header - Chrome above the map, such as a title or a search field.
|
|
43
|
+
*
|
|
44
|
+
* @event {HelixMapPanEvent} helix-map-pan - The reader asked the map to move. Answer it against your own map.
|
|
45
|
+
* @event {HelixMapZoomEvent} helix-map-zoom - The reader asked the map to zoom, by a direction rather than to a level.
|
|
46
|
+
*
|
|
47
|
+
* @csspart controls - The group the pan and zoom buttons sit in.
|
|
48
|
+
* @csspart fullscreen - The button that fills the screen.
|
|
49
|
+
* @csspart pan-down - The button that moves the map down.
|
|
50
|
+
* @csspart pan-end - The button that moves the map the way the text ends, which is right on an English page.
|
|
51
|
+
* @csspart pan-start - The button that moves the map the way the text starts, which is left on an English page.
|
|
52
|
+
* @csspart pan-up - The button that moves the map up.
|
|
53
|
+
* @csspart region - The named region the map is slotted into.
|
|
54
|
+
* @csspart viewport - The box the map is drawn in.
|
|
55
|
+
* @csspart zoom-in - The button that zooms in.
|
|
56
|
+
* @csspart zoom-out - The button that zooms out.
|
|
57
|
+
*
|
|
58
|
+
* @cssstate fullscreen - While the frame fills the screen.
|
|
59
|
+
* @cssstate idle - While the reader has been still long enough to hide the chrome.
|
|
60
|
+
*/
|
|
61
|
+
export declare class HelixHeadlessMap extends HelixElement {
|
|
62
|
+
/** What the control that fills the screen is called. */
|
|
63
|
+
fullscreenLabel: string;
|
|
64
|
+
/**
|
|
65
|
+
* How long the reader may be still before the chrome hides, in milliseconds.
|
|
66
|
+
*
|
|
67
|
+
* Zero keeps it up, which is the default: a map's controls are how a keyboard
|
|
68
|
+
* reader operates it, and chrome that hides itself by default would be this
|
|
69
|
+
* component undoing its own argument. Set it for a display map nobody drives.
|
|
70
|
+
*/
|
|
71
|
+
idleDelay: number;
|
|
72
|
+
/**
|
|
73
|
+
* Accessible name for the map, which is the one thing a consumer must write.
|
|
74
|
+
*
|
|
75
|
+
* There is no sensible default. "Map" names the widget rather than the place,
|
|
76
|
+
* and a reader meeting three of them on a page learns nothing from hearing the
|
|
77
|
+
* same word three times.
|
|
78
|
+
*/
|
|
79
|
+
label: string;
|
|
80
|
+
/**
|
|
81
|
+
* What the controls are called, for a page not in English.
|
|
82
|
+
*/
|
|
83
|
+
panLabel: string;
|
|
84
|
+
/**
|
|
85
|
+
* How far one press moves the map, as a fraction of what is on screen.
|
|
86
|
+
*
|
|
87
|
+
* A fraction rather than a distance, because this frame does not know the
|
|
88
|
+
* scale of what is inside it: a quarter of the viewport is the same gesture at
|
|
89
|
+
* street level and at country level, and a consumer who wants metres can
|
|
90
|
+
* multiply by their own.
|
|
91
|
+
*/
|
|
92
|
+
panStep: number;
|
|
93
|
+
/** What the zoom controls are called. */
|
|
94
|
+
zoomInLabel: string;
|
|
95
|
+
/** What the zoom control that goes the other way is called. */
|
|
96
|
+
zoomOutLabel: string;
|
|
97
|
+
/** Whether the reader has been still long enough to hide the chrome. */
|
|
98
|
+
private resting;
|
|
99
|
+
/** Whether the frame is filling the screen. */
|
|
100
|
+
private filled;
|
|
101
|
+
private readonly fullscreen;
|
|
102
|
+
private readonly idle;
|
|
103
|
+
/** Fills the screen with the frame, or comes back. */
|
|
104
|
+
toggleFullscreen(): void;
|
|
105
|
+
/** Asks for the map to move, which the consumer answers. */
|
|
106
|
+
pan(to: Way): void;
|
|
107
|
+
/** Asks for the map to zoom, `1` in and `-1` out. */
|
|
108
|
+
zoom(by: number): void;
|
|
109
|
+
/**
|
|
110
|
+
* The arrow keys, which are the second way rather than the only one.
|
|
111
|
+
*
|
|
112
|
+
* `inlineStep` rather than a test against `ArrowLeft`, so the reader on a
|
|
113
|
+
* right-to-left page gets `end` from the key that points the way their text
|
|
114
|
+
* runs, which is what the pan buttons say too.
|
|
115
|
+
*/
|
|
116
|
+
private readonly handleKeydown;
|
|
117
|
+
/**
|
|
118
|
+
* Starts the wait, so a map nobody has touched counts as still.
|
|
119
|
+
*
|
|
120
|
+
* Without this the timer only ever starts on a pointer or a focus, and a frame
|
|
121
|
+
* rendered on a page the reader never reaches stays awake for as long as the
|
|
122
|
+
* page is open: the chrome a consumer asked to hide would hide only after
|
|
123
|
+
* being used once, which is the opposite of what an idle delay is for.
|
|
124
|
+
*/
|
|
125
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
126
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
127
|
+
/**
|
|
128
|
+
* What one pan button is called, with the way it goes filled in.
|
|
129
|
+
*
|
|
130
|
+
* Left and right rather than start and end, because the label is read aloud
|
|
131
|
+
* and a reader knows which way left is. The event keeps the reading-order
|
|
132
|
+
* name, which is the one a right-to-left page needs.
|
|
133
|
+
*/
|
|
134
|
+
private named;
|
|
135
|
+
protected render(): TemplateResult;
|
|
136
|
+
}
|
|
137
|
+
declare global {
|
|
138
|
+
interface HTMLElementTagNameMap {
|
|
139
|
+
'helix-headless-map': HelixHeadlessMap;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export {};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
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
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { customElement, FullscreenController, HelixElement, HelixMapPanEvent, HelixMapZoomEvent, hostAria, IdleController, inlineStep, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { html } from 'lit';
|
|
9
|
+
import { property, state } from 'lit/decorators.js';
|
|
10
|
+
let HelixHeadlessMap = class HelixHeadlessMap extends HelixElement {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this.fullscreenLabel = 'Fill the screen';
|
|
14
|
+
this.idleDelay = 0;
|
|
15
|
+
this.label = '';
|
|
16
|
+
this.panLabel = 'Pan {way}';
|
|
17
|
+
this.panStep = 0.25;
|
|
18
|
+
this.zoomInLabel = 'Zoom in';
|
|
19
|
+
this.zoomOutLabel = 'Zoom out';
|
|
20
|
+
this.resting = false;
|
|
21
|
+
this.filled = false;
|
|
22
|
+
this.fullscreen = new FullscreenController(this, (full) => {
|
|
23
|
+
this.filled = full;
|
|
24
|
+
});
|
|
25
|
+
this.idle = new IdleController(this, () => this.idleDelay, (resting) => {
|
|
26
|
+
this.resting = resting;
|
|
27
|
+
});
|
|
28
|
+
this.handleKeydown = (event) => {
|
|
29
|
+
const along = inlineStep(this, event.key);
|
|
30
|
+
if (along === 1) {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
this.pan('end');
|
|
33
|
+
}
|
|
34
|
+
else if (along === -1) {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
this.pan('start');
|
|
37
|
+
}
|
|
38
|
+
else if (event.key === 'ArrowUp') {
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
this.pan('up');
|
|
41
|
+
}
|
|
42
|
+
else if (event.key === 'ArrowDown') {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
this.pan('down');
|
|
45
|
+
}
|
|
46
|
+
else if (event.key === '+' || event.key === '=') {
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
this.zoom(1);
|
|
49
|
+
}
|
|
50
|
+
else if (event.key === '-' || event.key === '_') {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
this.zoom(-1);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
toggleFullscreen() {
|
|
57
|
+
void this.fullscreen.toggle();
|
|
58
|
+
}
|
|
59
|
+
pan(to) {
|
|
60
|
+
this.dispatchEvent(new HelixMapPanEvent({ by: this.panStep, to }));
|
|
61
|
+
}
|
|
62
|
+
zoom(by) {
|
|
63
|
+
this.dispatchEvent(new HelixMapZoomEvent({ by }));
|
|
64
|
+
}
|
|
65
|
+
firstUpdated(changed) {
|
|
66
|
+
super.firstUpdated(changed);
|
|
67
|
+
this.idle.stir();
|
|
68
|
+
}
|
|
69
|
+
willUpdate(changed) {
|
|
70
|
+
super.willUpdate(changed);
|
|
71
|
+
hostAria(this.internals, { label: this.label });
|
|
72
|
+
this.toggleState('fullscreen', this.filled);
|
|
73
|
+
this.toggleState('idle', this.resting);
|
|
74
|
+
}
|
|
75
|
+
named(way) {
|
|
76
|
+
const spoken = way === 'start' ? 'left' : way === 'end' ? 'right' : way;
|
|
77
|
+
return this.panLabel.replace('{way}', spoken);
|
|
78
|
+
}
|
|
79
|
+
render() {
|
|
80
|
+
return html `<div
|
|
81
|
+
part="region"
|
|
82
|
+
role="group"
|
|
83
|
+
aria-label=${this.label || 'Map'}
|
|
84
|
+
@focusin=${this.idle.stir}
|
|
85
|
+
@keydown=${this.handleKeydown}
|
|
86
|
+
@pointerleave=${this.idle.leave}
|
|
87
|
+
@pointermove=${this.idle.stir}
|
|
88
|
+
>
|
|
89
|
+
<slot name="header"></slot>
|
|
90
|
+
<div part="viewport">
|
|
91
|
+
<slot></slot>
|
|
92
|
+
</div>
|
|
93
|
+
<div part="controls" role="group" aria-label="Map controls">
|
|
94
|
+
<!-- **Written out rather than generated in a loop**, because a part name
|
|
95
|
+
behind an interpolation is a part the manifest cannot see: it would
|
|
96
|
+
publish \`pan-\` and a consumer's \`::part(pan-up)\` would match
|
|
97
|
+
nothing. The same cost \`drawIcon\` names for \`Trend\`. -->
|
|
98
|
+
<button part="pan-up" aria-label=${this.named('up')} type="button" @click=${() => this.pan('up')}>
|
|
99
|
+
↑
|
|
100
|
+
</button>
|
|
101
|
+
<button part="pan-start" aria-label=${this.named('start')} type="button" @click=${() => this.pan('start')}>
|
|
102
|
+
←
|
|
103
|
+
</button>
|
|
104
|
+
<button part="pan-end" aria-label=${this.named('end')} type="button" @click=${() => this.pan('end')}>
|
|
105
|
+
→
|
|
106
|
+
</button>
|
|
107
|
+
<button part="pan-down" aria-label=${this.named('down')} type="button" @click=${() => this.pan('down')}>
|
|
108
|
+
↓
|
|
109
|
+
</button>
|
|
110
|
+
<button part="zoom-in" aria-label=${this.zoomInLabel} type="button" @click=${() => this.zoom(1)}>
|
|
111
|
+
+
|
|
112
|
+
</button>
|
|
113
|
+
<button part="zoom-out" aria-label=${this.zoomOutLabel} type="button" @click=${() => this.zoom(-1)}>
|
|
114
|
+
−
|
|
115
|
+
</button>
|
|
116
|
+
<button
|
|
117
|
+
part="fullscreen"
|
|
118
|
+
aria-label=${this.fullscreenLabel}
|
|
119
|
+
aria-pressed=${this.filled ? 'true' : 'false'}
|
|
120
|
+
type="button"
|
|
121
|
+
@click=${this.toggleFullscreen}
|
|
122
|
+
>
|
|
123
|
+
⤢
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
<slot name="footer"></slot>
|
|
127
|
+
<slot name="attribution"></slot>
|
|
128
|
+
</div>`;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
__decorate([
|
|
132
|
+
property({ attribute: 'fullscreen-label' })
|
|
133
|
+
], HelixHeadlessMap.prototype, "fullscreenLabel", void 0);
|
|
134
|
+
__decorate([
|
|
135
|
+
property({ attribute: 'idle-delay', type: Number })
|
|
136
|
+
], HelixHeadlessMap.prototype, "idleDelay", void 0);
|
|
137
|
+
__decorate([
|
|
138
|
+
property()
|
|
139
|
+
], HelixHeadlessMap.prototype, "label", void 0);
|
|
140
|
+
__decorate([
|
|
141
|
+
property({ attribute: 'pan-label' })
|
|
142
|
+
], HelixHeadlessMap.prototype, "panLabel", void 0);
|
|
143
|
+
__decorate([
|
|
144
|
+
property({ attribute: 'pan-step', type: Number })
|
|
145
|
+
], HelixHeadlessMap.prototype, "panStep", void 0);
|
|
146
|
+
__decorate([
|
|
147
|
+
property({ attribute: 'zoom-in-label' })
|
|
148
|
+
], HelixHeadlessMap.prototype, "zoomInLabel", void 0);
|
|
149
|
+
__decorate([
|
|
150
|
+
property({ attribute: 'zoom-out-label' })
|
|
151
|
+
], HelixHeadlessMap.prototype, "zoomOutLabel", void 0);
|
|
152
|
+
__decorate([
|
|
153
|
+
state()
|
|
154
|
+
], HelixHeadlessMap.prototype, "resting", void 0);
|
|
155
|
+
__decorate([
|
|
156
|
+
state()
|
|
157
|
+
], HelixHeadlessMap.prototype, "filled", void 0);
|
|
158
|
+
HelixHeadlessMap = __decorate([
|
|
159
|
+
customElement('helix-headless-map')
|
|
160
|
+
], HelixHeadlessMap);
|
|
161
|
+
export { HelixHeadlessMap };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion.dev/helix-headless",
|
|
3
3
|
"description": "Headless component tier. Structure, accessibility, state, and form participation. No styling.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.102.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -46,13 +46,13 @@
|
|
|
46
46
|
},
|
|
47
47
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@fusion.dev/helix-core": "0.
|
|
50
|
-
"@fusion.dev/helix-icons": "0.
|
|
49
|
+
"@fusion.dev/helix-core": "0.102.0",
|
|
50
|
+
"@fusion.dev/helix-icons": "0.102.0",
|
|
51
51
|
"uqr": "^0.1.3"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@fusion.dev/helix-cem": "0.
|
|
55
|
-
"@fusion.dev/helix-testing": "0.
|
|
54
|
+
"@fusion.dev/helix-cem": "0.102.0",
|
|
55
|
+
"@fusion.dev/helix-testing": "0.102.0",
|
|
56
56
|
"@fusion.dev/scripts": "0.0.0-dev",
|
|
57
57
|
"@happy-dom/global-registrator": "^20.0.0",
|
|
58
58
|
"@lit-labs/ssr": "^4.1.0",
|