@fusion.dev/helix-headless 0.103.2 → 0.103.3

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.
@@ -1,9 +1,13 @@
1
- import { HelixElement } from '@fusion.dev/helix-core';
1
+ import { HelixElement, type ViewportControlsPlacement } from '@fusion.dev/helix-core';
2
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
- /** Where the controls sit, in the corner words the rest of the library uses. */
6
- export type MapControlsPlacement = 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
3
+ /**
4
+ * What `ViewportControlsPlacement` is called for a map.
5
+ *
6
+ * The corners are the surface's and the name is the family's, so a consumer who
7
+ * imported this before the surface was shared still has it.
8
+ */
9
+ export type MapControlsPlacement = ViewportControlsPlacement;
10
+ declare const HelixHeadlessMap_base: (new (...args: any[]) => import("@fusion.dev/helix-core").ViewportElement) & typeof HelixElement;
7
11
  /**
8
12
  * @summary A frame around somebody else's map.
9
13
  *
@@ -63,57 +67,7 @@ export type MapControlsPlacement = 'top-start' | 'top-end' | 'bottom-start' | 'b
63
67
  * @cssstate fullscreen - While the frame fills the screen.
64
68
  * @cssstate idle - While the reader has been still long enough to hide the chrome.
65
69
  */
66
- export declare class HelixHeadlessMap extends HelixElement {
67
- /**
68
- * Which of the controls this frame draws, as a list of what to keep.
69
- *
70
- * **A list rather than a switch, because the overlap is not symmetric.**
71
- * Every map library draws zoom controls and none of them draws pan buttons:
72
- * MapLibre's navigation control is zoom and a compass, Leaflet's is zoom,
73
- * Google's is zoom. So a consumer slotting one of those has our zoom twice
74
- * over and our pan pad not at all, and a single switch would make them choose
75
- * between a duplicate and nothing.
76
- *
77
- * `none` draws none of them, which is the right answer where the slotted map
78
- * already has controls a reader can find: what this family argues for is that
79
- * *a* findable control exists, never that it is ours. The methods stay public
80
- * either way, so a consumer drawing their own chrome can still drive the
81
- * frame.
82
- *
83
- * Absent is all three, which is this library's rule that a resting value is an
84
- * attribute's absence, and it is what the stylesheet asks about: the layout
85
- * rules are written against `[controls]:not([controls~="zoom"])` rather than
86
- * the bare negation, or a frame that named no list at all would be laid out as
87
- * though it had dropped everything.
88
- */
89
- controls?: string;
90
- /**
91
- * Which corner the controls sit in.
92
- *
93
- * **Configurable because there is no safe default**, and this is a collision
94
- * rather than a taste: MapLibre puts its own controls at the top end, Leaflet
95
- * and OpenLayers at the top start, Google's zoom at the bottom end. Whichever
96
- * corner this took would land on somebody's, which is why every one of those
97
- * libraries made it a setting too.
98
- *
99
- * Logical corners rather than left and right, so a right-to-left page moves
100
- * them without a consumer writing a second rule.
101
- *
102
- * Absent is the top end, which is this library's rule that a resting value is
103
- * an attribute's absence: a default that reflected would write an attribute
104
- * onto every host that no server sent and nobody asked for.
105
- */
106
- controlsPlacement?: MapControlsPlacement;
107
- /** What the control that fills the screen is called. */
108
- fullscreenLabel: string;
109
- /**
110
- * How long the reader may be still before the chrome hides, in milliseconds.
111
- *
112
- * Zero keeps it up, which is the default: a map's controls are how a keyboard
113
- * reader operates it, and chrome that hides itself by default would be this
114
- * component undoing its own argument. Set it for a display map nobody drives.
115
- */
116
- idleDelay: number;
70
+ export declare class HelixHeadlessMap extends HelixHeadlessMap_base {
117
71
  /**
118
72
  * Accessible name for the map, which is the one thing a consumer must write.
119
73
  *
@@ -130,79 +84,14 @@ export declare class HelixHeadlessMap extends HelixElement {
130
84
  */
131
85
  label: string;
132
86
  /**
133
- * What the controls are called, for a page not in English.
134
- */
135
- panLabel: string;
136
- /**
137
- * How far one press moves the map, as a fraction of what is on screen.
138
- *
139
- * A fraction rather than a distance, because this frame does not know the
140
- * scale of what is inside it: a quarter of the viewport is the same gesture at
141
- * street level and at country level, and a consumer who wants metres can
142
- * multiply by their own.
143
- */
144
- panStep: number;
145
- /** What the zoom controls are called. */
146
- zoomInLabel: string;
147
- /** What the zoom control that goes the other way is called. */
148
- zoomOutLabel: string;
149
- /** Whether the reader has been still long enough to hide the chrome. */
150
- private resting;
151
- /** Whether the frame is filling the screen. */
152
- private filled;
153
- private readonly fullscreen;
154
- private readonly idle;
155
- /** Whether a named group of controls is one this frame draws. */
156
- private draws;
157
- /** Fills the screen with the frame, or comes back. */
158
- toggleFullscreen(): void;
159
- /** Asks for the map to move, which the consumer answers. */
160
- pan(to: Way): void;
161
- /** Asks for the map to zoom, `1` in and `-1` out. */
162
- zoom(by: number): void;
163
- /**
164
- * The arrow keys, which are the second way rather than the only one.
165
- *
166
- * `inlineStep` rather than a test against `ArrowLeft`, so the reader on a
167
- * right-to-left page gets `end` from the key that points the way their text
168
- * runs, which is what the pan buttons say too.
87
+ * What the group of controls is called.
169
88
  *
170
- * **Only from this frame's own chrome, and that is the difference between one
171
- * move and two.** MapLibre and Leaflet both answer the arrow keys themselves,
172
- * so a press inside a slotted map moves it once on its own and once more on
173
- * the event this would have sent: every press moving twice, and the consumer
174
- * with no way to tell which half to turn off. A press that started in the
175
- * shadow root is this frame's to relay; anything from the map belongs to the
176
- * map.
89
+ * The surface is shared and this is the half that is a map: a reader meeting
90
+ * the group hears what it drives rather than "view controls", which names the
91
+ * mechanism.
177
92
  */
178
- private readonly handleKeydown;
179
- /**
180
- * Starts the wait, so a map nobody has touched counts as still.
181
- *
182
- * Without this the timer only ever starts on a pointer or a focus, and a frame
183
- * rendered on a page the reader never reaches stays awake for as long as the
184
- * page is open: the chrome a consumer asked to hide would hide only after
185
- * being used once, which is the opposite of what an idle delay is for.
186
- */
187
- protected firstUpdated(changed: PropertyValues): void;
93
+ protected get controlsLabel(): string;
188
94
  protected willUpdate(changed: PropertyValues): void;
189
- /**
190
- * What one pan button is called, with the way it goes filled in.
191
- *
192
- * Left and right rather than start and end, because the label is read aloud
193
- * and a reader knows which way left is. The event keeps the reading-order
194
- * name, which is the one a right-to-left page needs.
195
- */
196
- private named;
197
- /**
198
- * The controls, or nothing where a consumer kept none of them.
199
- *
200
- * **Written out rather than generated**, because a part name behind an
201
- * interpolation is a part the manifest cannot see: it would publish `pan-`
202
- * and a consumer's `::part(pan-up)` would match nothing. The same cost
203
- * `drawIcon` names for `Trend`.
204
- */
205
- private renderControls;
206
95
  /**
207
96
  * The frame, with each region in a box of its own.
208
97
  *
@@ -4,73 +4,16 @@ 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, FullscreenController, HelixElement, HelixMapPanEvent, HelixMapZoomEvent, hostAria, IdleController, inlineStep, } from '@fusion.dev/helix-core';
8
- import { html, nothing } from 'lit';
9
- import { property, state } from 'lit/decorators.js';
10
- let HelixHeadlessMap = class HelixHeadlessMap extends HelixElement {
7
+ import { customElement, HelixElement, hostAria, MixinViewport, } from '@fusion.dev/helix-core';
8
+ import { html } from 'lit';
9
+ import { property } from 'lit/decorators.js';
10
+ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement) {
11
11
  constructor() {
12
12
  super(...arguments);
13
- this.fullscreenLabel = 'Fill the screen';
14
- this.idleDelay = 0;
15
13
  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
- if (!this.renderRoot.contains(event.target)) {
30
- return;
31
- }
32
- const along = inlineStep(this, event.key);
33
- if (along === 1) {
34
- event.preventDefault();
35
- this.pan('end');
36
- }
37
- else if (along === -1) {
38
- event.preventDefault();
39
- this.pan('start');
40
- }
41
- else if (event.key === 'ArrowUp') {
42
- event.preventDefault();
43
- this.pan('up');
44
- }
45
- else if (event.key === 'ArrowDown') {
46
- event.preventDefault();
47
- this.pan('down');
48
- }
49
- else if (event.key === '+' || event.key === '=') {
50
- event.preventDefault();
51
- this.zoom(1);
52
- }
53
- else if (event.key === '-' || event.key === '_') {
54
- event.preventDefault();
55
- this.zoom(-1);
56
- }
57
- };
58
14
  }
59
- draws(group) {
60
- return this.controls === undefined ? true : this.controls.split(/\s+/).includes(group);
61
- }
62
- toggleFullscreen() {
63
- void this.fullscreen.toggle();
64
- }
65
- pan(to) {
66
- this.dispatchEvent(new HelixMapPanEvent({ by: this.panStep, to }));
67
- }
68
- zoom(by) {
69
- this.dispatchEvent(new HelixMapZoomEvent({ by }));
70
- }
71
- firstUpdated(changed) {
72
- super.firstUpdated(changed);
73
- this.idle.stir();
15
+ get controlsLabel() {
16
+ return 'Map controls';
74
17
  }
75
18
  willUpdate(changed) {
76
19
  super.willUpdate(changed);
@@ -78,53 +21,6 @@ let HelixHeadlessMap = class HelixHeadlessMap extends HelixElement {
78
21
  this.toggleState('fullscreen', this.filled);
79
22
  this.toggleState('idle', this.resting);
80
23
  }
81
- named(way) {
82
- const spoken = way === 'start' ? 'left' : way === 'end' ? 'right' : way;
83
- return this.panLabel.replace('{way}', spoken);
84
- }
85
- renderControls() {
86
- const pan = this.draws('pan');
87
- const zoom = this.draws('zoom');
88
- const fullscreen = this.draws('fullscreen');
89
- if (!pan && !zoom && !fullscreen) {
90
- return nothing;
91
- }
92
- return html `<div part="controls" role="group" aria-label="Map controls">
93
- ${pan
94
- ? html `<button part="pan-up" aria-label=${this.named('up')} type="button" @click=${() => this.pan('up')}>
95
- ↑
96
- </button>
97
- <button part="pan-start" aria-label=${this.named('start')} type="button" @click=${() => this.pan('start')}>
98
- ←
99
- </button>
100
- <button part="pan-end" aria-label=${this.named('end')} type="button" @click=${() => this.pan('end')}>
101
- →
102
- </button>
103
- <button part="pan-down" aria-label=${this.named('down')} type="button" @click=${() => this.pan('down')}>
104
- ↓
105
- </button>`
106
- : nothing}
107
- ${zoom
108
- ? html `<button part="zoom-in" aria-label=${this.zoomInLabel} type="button" @click=${() => this.zoom(1)}>
109
- +
110
- </button>
111
- <button part="zoom-out" aria-label=${this.zoomOutLabel} type="button" @click=${() => this.zoom(-1)}>
112
- −
113
- </button>`
114
- : nothing}
115
- ${fullscreen
116
- ? html `<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
- : nothing}
126
- </div>`;
127
- }
128
24
  render() {
129
25
  return html `<div
130
26
  part="region"
@@ -153,39 +49,9 @@ let HelixHeadlessMap = class HelixHeadlessMap extends HelixElement {
153
49
  </div>`;
154
50
  }
155
51
  };
156
- __decorate([
157
- property({ reflect: true })
158
- ], HelixHeadlessMap.prototype, "controls", void 0);
159
- __decorate([
160
- property({ attribute: 'controls-placement', reflect: true })
161
- ], HelixHeadlessMap.prototype, "controlsPlacement", void 0);
162
- __decorate([
163
- property({ attribute: 'fullscreen-label' })
164
- ], HelixHeadlessMap.prototype, "fullscreenLabel", void 0);
165
- __decorate([
166
- property({ attribute: 'idle-delay', type: Number })
167
- ], HelixHeadlessMap.prototype, "idleDelay", void 0);
168
52
  __decorate([
169
53
  property()
170
54
  ], HelixHeadlessMap.prototype, "label", void 0);
171
- __decorate([
172
- property({ attribute: 'pan-label' })
173
- ], HelixHeadlessMap.prototype, "panLabel", void 0);
174
- __decorate([
175
- property({ attribute: 'pan-step', type: Number })
176
- ], HelixHeadlessMap.prototype, "panStep", void 0);
177
- __decorate([
178
- property({ attribute: 'zoom-in-label' })
179
- ], HelixHeadlessMap.prototype, "zoomInLabel", void 0);
180
- __decorate([
181
- property({ attribute: 'zoom-out-label' })
182
- ], HelixHeadlessMap.prototype, "zoomOutLabel", void 0);
183
- __decorate([
184
- state()
185
- ], HelixHeadlessMap.prototype, "resting", void 0);
186
- __decorate([
187
- state()
188
- ], HelixHeadlessMap.prototype, "filled", void 0);
189
55
  HelixHeadlessMap = __decorate([
190
56
  customElement('helix-headless-map')
191
57
  ], 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.103.2",
4
+ "version": "0.103.3",
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.103.2",
50
- "@fusion.dev/helix-icons": "0.103.2",
49
+ "@fusion.dev/helix-core": "0.103.3",
50
+ "@fusion.dev/helix-icons": "0.103.3",
51
51
  "uqr": "^0.1.3"
52
52
  },
53
53
  "devDependencies": {
54
- "@fusion.dev/helix-cem": "0.103.2",
55
- "@fusion.dev/helix-testing": "0.103.2",
54
+ "@fusion.dev/helix-cem": "0.103.3",
55
+ "@fusion.dev/helix-testing": "0.103.3",
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",