@fusion.dev/helix-headless 0.104.0 → 0.105.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.
@@ -700,6 +700,18 @@ export declare class HelixHeadlessCanvas extends HelixHeadlessCanvas_base {
700
700
  * lurch under every press.
701
701
  */
702
702
  private readonly handleFocusIn;
703
+ /**
704
+ * A connection being made from the keyboard ends when the reader leaves
705
+ * the board, as Escape ends it.
706
+ *
707
+ * Otherwise the board went on showing every card as somewhere to land, and
708
+ * a reader who came back and pressed Enter on a card connected something
709
+ * they had long since walked away from. Only for somewhere else on the
710
+ * page: a window losing focus names nothing, and the reader switching away
711
+ * and back is still in the middle of it. A pointer's connection is its
712
+ * own, ended by its release.
713
+ */
714
+ private readonly handleFocusOut;
703
715
  /** Brings a card into view, where it is not already whole in it. */
704
716
  private reveal;
705
717
  /**
@@ -291,7 +291,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
291
291
  if (this.linking.pressed) {
292
292
  const clicked = this.linking.edge;
293
293
  this.linking = undefined;
294
- if (clicked !== undefined && pointing) {
294
+ if (clicked !== undefined && pointing && event.type !== 'pointercancel') {
295
295
  this.clicked(clicked, event);
296
296
  }
297
297
  return;
@@ -316,7 +316,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
316
316
  return;
317
317
  }
318
318
  this.carrying = undefined;
319
- if (!carry.started && pointing) {
319
+ if (!carry.started && pointing && event.type !== 'pointercancel') {
320
320
  this.clicked(carry.pressed, event);
321
321
  }
322
322
  if (carry.moved) {
@@ -326,7 +326,8 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
326
326
  }
327
327
  if (this.dragging?.pointer === event.pointerId) {
328
328
  const drag = this.dragging;
329
- if (Math.hypot(event.clientX - drag.from.x, event.clientY - drag.from.y) < SLOP) {
329
+ if (event.type !== 'pointercancel' &&
330
+ Math.hypot(event.clientX - drag.from.x, event.clientY - drag.from.y) < SLOP) {
330
331
  if (drag.pressed !== undefined && pointing) {
331
332
  this.clicked(drag.pressed, event);
332
333
  }
@@ -508,6 +509,20 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
508
509
  this.reveal(card.name);
509
510
  }
510
511
  };
512
+ this.handleFocusOut = (event) => {
513
+ const next = event.relatedTarget;
514
+ const linking = this.linking;
515
+ if (!linking ||
516
+ linking.pointer !== undefined ||
517
+ linking.held ||
518
+ !(next instanceof Node) ||
519
+ this.contains(next) ||
520
+ this.renderRoot.contains(next)) {
521
+ return;
522
+ }
523
+ this.linking = undefined;
524
+ this.said = this.stoppedLabel;
525
+ };
511
526
  this.handleDoubleClick = (event) => {
512
527
  const path = event.composedPath();
513
528
  if (path.some((one) => one instanceof Element && one.matches(INTERACTIVE))) {
@@ -1462,6 +1477,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1462
1477
  this.watchWords();
1463
1478
  this.addEventListener('dblclick', this.handleDoubleClick);
1464
1479
  this.addEventListener('focusin', this.handleFocusIn);
1480
+ this.addEventListener('focusout', this.handleFocusOut);
1465
1481
  this.addEventListener('keydown', this.handleStopKeydown);
1466
1482
  this.addEventListener('pointercancel', this.handlePointerUp);
1467
1483
  this.addEventListener('pointerdown', this.handlePointerDown);
@@ -1478,6 +1494,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1478
1494
  this.words?.disconnect();
1479
1495
  this.removeEventListener('dblclick', this.handleDoubleClick);
1480
1496
  this.removeEventListener('focusin', this.handleFocusIn);
1497
+ this.removeEventListener('focusout', this.handleFocusOut);
1481
1498
  this.removeEventListener('keydown', this.handleStopKeydown);
1482
1499
  this.removeEventListener('pointercancel', this.handlePointerUp);
1483
1500
  this.removeEventListener('pointerdown', this.handlePointerDown);
@@ -184,6 +184,16 @@ export declare class HelixHeadlessColorInput extends HelixHeadlessColorInput_bas
184
184
  private get painted();
185
185
  /** Relayed under this element's name, so a consumer hears one control. */
186
186
  private readonly handleInput;
187
+ /**
188
+ * The picker's request, taken as this element's own.
189
+ *
190
+ * **Only the picker's.** A request from anything inside it arrives here
191
+ * retargeted to the picker, and a slider's carried its number: a hue of 120
192
+ * became this field's value and every page listening heard it. The picker
193
+ * keeps its sliders' requests now, and this checks where the event started
194
+ * as well, so a picker a tier above renders cannot say for it what it did
195
+ * not mean.
196
+ */
187
197
  private readonly handlePickerChange;
188
198
  /**
189
199
  * The picker is about to take the pointer over, so the panel gets out of the
@@ -29,6 +29,9 @@ let HelixHeadlessColorInput = class HelixHeadlessColorInput extends MixinPickerF
29
29
  };
30
30
  this.handlePickerChange = (event) => {
31
31
  event.stopPropagation();
32
+ if (event.composedPath()[0] !== event.currentTarget) {
33
+ return;
34
+ }
32
35
  this.requestValue(event.detail.value);
33
36
  this.restore();
34
37
  };
@@ -382,6 +382,18 @@ export declare class HelixHeadlessColorPicker extends HelixHeadlessColorPicker_b
382
382
  * renders its own ramp through.
383
383
  */
384
384
  protected renderChannel(channel: 'alpha' | 'hue', label: string, max: number, value: number): TemplateResult;
385
+ /**
386
+ * A slider's own request, kept inside.
387
+ *
388
+ * **The slider is a Helix control, so it asks before it moves**, with
389
+ * `helix-before-change` carrying its number, and that event is composed. Let
390
+ * out, it reached a consumer listening on this element as though the picker
391
+ * had asked for a hue of 120 or an alpha of 0 as its color, which Color
392
+ * Input took for its value and the theme builder then failed to read as a
393
+ * brand. What this element asks for is a color, and it asks through `move`.
394
+ * Not refused, only kept: the slider applies, and `takeChannel` reads it.
395
+ */
396
+ private readonly keepChannelRequest;
385
397
  /**
386
398
  * One axis of the area, taken the way `takeChannel` takes a rail.
387
399
  *
@@ -116,6 +116,9 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinContr
116
116
  const down = Math.min(1, Math.max(0, (event.clientY - box.top) / box.height));
117
117
  this.move({ brightness: Math.round((1 - down) * 100), saturation: Math.round(across * 100) });
118
118
  };
119
+ this.keepChannelRequest = (event) => {
120
+ event.stopPropagation();
121
+ };
119
122
  }
120
123
  get color() {
121
124
  return this.moved ?? parseColor(this.value) ?? parseColor(this.settled) ?? START;
@@ -179,6 +182,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinContr
179
182
  ?disabled=${this.isDisabled}
180
183
  @input=${(event) => this.takeChannel(channel, event)}
181
184
  @change=${(event) => this.takeChannel(channel, event)}
185
+ @helix-before-change=${this.keepChannelRequest}
182
186
  ></${this.tag('slider')}>`;
183
187
  }
184
188
  takeArea(axis, event) {
@@ -116,6 +116,11 @@ export declare class HelixHeadlessCommand extends HelixHeadlessCommand_base {
116
116
  * holds a value and the wrong one for a palette: nothing here changed, a
117
117
  * command ran. It is stopped and re-announced rather than relayed, so a
118
118
  * consumer hears one event naming the element they put on the page.
119
+ *
120
+ * **And refused, so the list keeps no answer.** A listbox holding what was
121
+ * chosen hears the same row chosen again as no change and says nothing, so
122
+ * the command a reader ran last, which on a site is the page they are on,
123
+ * could not be run again: Enter did nothing and the palette stayed open.
119
124
  */
120
125
  private readonly handleChange;
121
126
  /**
@@ -43,6 +43,7 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
43
43
  };
44
44
  this.handleChange = (event) => {
45
45
  event.stopPropagation();
46
+ event.preventDefault();
46
47
  const { value } = event.detail;
47
48
  if (!this.dispatchEvent(new HelixSelectEvent({ value }))) {
48
49
  return;
@@ -100,7 +100,18 @@ registerElementEvents({
100
100
  'toggle',
101
101
  ],
102
102
  'helix-headless-listbox': ['change', 'helix-before-change', 'helix-filter', 'input'],
103
- 'helix-headless-map': ['helix-map-pan', 'helix-map-zoom'],
103
+ 'helix-headless-map': [
104
+ 'helix-activate',
105
+ 'helix-map-cluster',
106
+ 'helix-map-pan',
107
+ 'helix-map-part-change',
108
+ 'helix-map-zoom',
109
+ ],
110
+ 'helix-headless-map-area': ['helix-map-part-change'],
111
+ 'helix-headless-map-marker': ['helix-map-cluster', 'helix-map-part-change'],
112
+ 'helix-headless-map-points': ['helix-activate', 'helix-map-cluster', 'helix-map-part-change'],
113
+ 'helix-headless-map-position': ['helix-map-part-change'],
114
+ 'helix-headless-map-route': ['helix-map-part-change'],
104
115
  'helix-headless-megamenu': ['helix-before-close', 'helix-before-open', 'toggle'],
105
116
  'helix-headless-menu': ['helix-before-close', 'helix-before-open', 'helix-select', 'toggle'],
106
117
  'helix-headless-menu-item': ['helix-select'],
@@ -0,0 +1,42 @@
1
+ import { HelixElement } from '@fusion.dev/helix-core';
2
+ import { nothing, type PropertyValues } from 'lit';
3
+ import { type MapPoint } from '../route/map-route.js';
4
+ /**
5
+ * @summary A shape on a map, such as a delivery zone.
6
+ *
7
+ * @description
8
+ * `connectMap` fills the shape as a layer of the map, under its place names.
9
+ * The frame lists each area by its `label` and `description` for screen
10
+ * readers.
11
+ *
12
+ * @since 0.105.0
13
+ * @status experimental
14
+ * @category media
15
+ * @aliases Map region, Polygon, Zone
16
+ * @documentation https://fusion.dev/helix/components/map
17
+ *
18
+ * @event {HelixMapPartChangeEvent} helix-map-part-change - The area's points, name, or color changed. Used by `connectMap`; pages do not need to listen for it.
19
+ */
20
+ export declare class HelixHeadlessMapArea extends HelixElement {
21
+ /** More about the area. Read after its label in the frame's list. */
22
+ description?: string;
23
+ /** Marks this as a layer for the frame and the connector, whatever its tag. */
24
+ readonly isMapLayer = true;
25
+ /** @internal Which kind of layer, which is how the connector draws it. */
26
+ readonly layerKind: 'area' | 'route';
27
+ /** The area's name, used in the frame's list. */
28
+ label?: string;
29
+ /**
30
+ * The corners of the shape, in order. The last point connects back to the
31
+ * first.
32
+ */
33
+ points?: MapPoint[];
34
+ /** Tells the frame and the connector the shape moved, was renamed or was retoned. */
35
+ protected updated(changed: PropertyValues): void;
36
+ protected render(): typeof nothing;
37
+ }
38
+ declare global {
39
+ interface HTMLElementTagNameMap {
40
+ 'helix-headless-map-area': HelixHeadlessMapArea;
41
+ }
42
+ }
@@ -0,0 +1,37 @@
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, HelixElement, HelixMapPartChangeEvent } from '@fusion.dev/helix-core';
8
+ import { nothing } from 'lit';
9
+ import { property } from 'lit/decorators.js';
10
+ import { pointsConverter } from '../route/map-route.js';
11
+ let HelixHeadlessMapArea = class HelixHeadlessMapArea extends HelixElement {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.isMapLayer = true;
15
+ this.layerKind = 'area';
16
+ }
17
+ updated(changed) {
18
+ super.updated(changed);
19
+ this.dispatchEvent(new HelixMapPartChangeEvent({ kind: 'layer' }));
20
+ }
21
+ render() {
22
+ return nothing;
23
+ }
24
+ };
25
+ __decorate([
26
+ property()
27
+ ], HelixHeadlessMapArea.prototype, "description", void 0);
28
+ __decorate([
29
+ property()
30
+ ], HelixHeadlessMapArea.prototype, "label", void 0);
31
+ __decorate([
32
+ property({ converter: pointsConverter })
33
+ ], HelixHeadlessMapArea.prototype, "points", void 0);
34
+ HelixHeadlessMapArea = __decorate([
35
+ customElement('helix-headless-map-area')
36
+ ], HelixHeadlessMapArea);
37
+ export { HelixHeadlessMapArea };
@@ -1 +1,8 @@
1
+ export { HelixHeadlessMapArea } from './area/map-area.js';
2
+ export { HelixHeadlessMapMarker } from './marker/map-marker.js';
3
+ export type { MapPointsCollection, MapPointsInView, MapPointsPlace, } from './points/map-points.js';
4
+ export { HelixHeadlessMapPoints } from './points/map-points.js';
5
+ export { HelixHeadlessMapPosition } from './position/map-position.js';
1
6
  export { HelixHeadlessMap } from './root/map-root.js';
7
+ export type { MapPoint } from './route/map-route.js';
8
+ export { HelixHeadlessMapRoute } from './route/map-route.js';
package/dist/map/index.js CHANGED
@@ -1 +1,6 @@
1
+ export { HelixHeadlessMapArea } from './area/map-area.js';
2
+ export { HelixHeadlessMapMarker } from './marker/map-marker.js';
3
+ export { HelixHeadlessMapPoints } from './points/map-points.js';
4
+ export { HelixHeadlessMapPosition } from './position/map-position.js';
1
5
  export { HelixHeadlessMap } from './root/map-root.js';
6
+ export { HelixHeadlessMapRoute } from './route/map-route.js';
@@ -0,0 +1,113 @@
1
+ import { HelixElement } from '@fusion.dev/helix-core';
2
+ import { type PropertyValues, type TemplateResult } from 'lit';
3
+ /**
4
+ * @summary A named place on a map.
5
+ *
6
+ * @description
7
+ * A button placed at a latitude and longitude. `connectMap` positions it on
8
+ * the map and moves it as the map moves. Its name shows beside it on hover
9
+ * and focus.
10
+ *
11
+ * Pressing a marker sends `helix-activate` from the frame with the marker's
12
+ * `name`, or its `label` if it has no name.
13
+ *
14
+ * All the markers in a map share one tab stop. The arrow keys move between
15
+ * them in the order they are written.
16
+ *
17
+ * @since 0.105.0
18
+ * @status experimental
19
+ * @category media
20
+ * @aliases Map pin, Place, Point of interest
21
+ * @documentation https://fusion.dev/helix/components/map
22
+ *
23
+ * @slot icon - A letter, number, or icon shown in the point, such as a stop's order. Screen readers read the marker's label only.
24
+ *
25
+ * @event {HelixMapClusterEvent} helix-map-cluster - This marker shows a cluster and was pressed. `connectMap` zooms in to its places.
26
+ * @event {HelixMapPartChangeEvent} helix-map-part-change - The marker moved or was renamed. Used by `connectMap`; pages do not need to listen for it.
27
+ *
28
+ * @csspart label - The place's name, next to the point.
29
+ * @csspart marker - The button, holding the point and the name.
30
+ * @csspart point - The dot on the place, holding the icon if there is one.
31
+ *
32
+ * @cssstate cluster - Shows several markers as one count.
33
+ * @cssstate clustered - Hidden inside another marker's cluster.
34
+ * @cssstate current - Holds the map's tab stop.
35
+ * @cssstate fanned - Spread out from a location it shares with other markers, after their cluster was pressed.
36
+ * @cssstate icon - Has content in the icon slot.
37
+ * @cssstate passed - Marked as passed.
38
+ * @cssstate start - Shows its name before the point, because there was no room after it.
39
+ */
40
+ export declare class HelixHeadlessMapMarker extends HelixElement {
41
+ /**
42
+ * @internal Whether the map's one tab stop is on this marker, written by the
43
+ * frame. Not a consumer's to set: the frame keeps exactly one.
44
+ */
45
+ current: boolean;
46
+ /**
47
+ * @internal The names of the places this stands for, its own first, written
48
+ * by the connector while it gathers markers that would draw over each other.
49
+ * Empty while it stands only for itself.
50
+ */
51
+ gathered: string[];
52
+ /**
53
+ * @internal Whether this stands fanned out from a spot it shares with
54
+ * other places, written by the connector when their cluster is pressed and
55
+ * no zoom would draw them apart.
56
+ */
57
+ fanned: boolean;
58
+ /**
59
+ * @internal The marker standing for this one while they are gathered,
60
+ * written by the connector. This one is hidden meanwhile, and the frame's
61
+ * walk and tab stop go to the one standing for it.
62
+ */
63
+ gatheredBy: HelixHeadlessMapMarker | undefined;
64
+ /**
65
+ * Marks this as a map marker for the frame and the connector, which find
66
+ * their markers among the frame's children by this rather than by tag, so a
67
+ * styled marker and a headless one are the same thing to them.
68
+ */
69
+ readonly isMapMarker = true;
70
+ /** The place's name. Shown next to the marker and read by screen readers. */
71
+ label?: string;
72
+ /** Latitude, in degrees. A marker without both `lat` and `lng` is not shown. */
73
+ lat?: number;
74
+ /** Longitude, in degrees. */
75
+ lng?: number;
76
+ /**
77
+ * @internal Which side of the point the name sits on, in reading order,
78
+ * written by the connector from the room either side of the place. After
79
+ * the point unless the view's edge would cut the name off there.
80
+ */
81
+ side: 'end' | 'start';
82
+ /** The value `helix-activate` reports. Defaults to the label. */
83
+ name: string;
84
+ /**
85
+ * Marks the place as passed, such as a stop already visited. The marker is
86
+ * drawn muted, and its name is followed by `passed-label`.
87
+ */
88
+ passed: boolean;
89
+ /** The word added after a passed place's name. Defaults to "passed". */
90
+ passedLabel: string;
91
+ protected willUpdate(changed: PropertyValues): void;
92
+ /**
93
+ * Tells the connector the place moved or was renamed, which moves the point
94
+ * or changes how far the name reaches, and the frame that it was gathered
95
+ * or let go, which moves its tab stop. Not for the rest of the frame's own
96
+ * bookkeeping, `current` and `side`, which the frame and the connector wrote.
97
+ */
98
+ protected updated(changed: PropertyValues): void;
99
+ /**
100
+ * How far the marker reaches from its point along the line, name included,
101
+ * in CSS pixels: the button's width, which is laid out even while its name is
102
+ * faded, so the connector can ask before the name is ever shown.
103
+ */
104
+ extent(): number;
105
+ /** Focuses the button rather than the host, which draws no box of its own. */
106
+ focus(options?: FocusOptions): void;
107
+ protected render(): TemplateResult;
108
+ }
109
+ declare global {
110
+ interface HTMLElementTagNameMap {
111
+ 'helix-headless-map-marker': HelixHeadlessMapMarker;
112
+ }
113
+ }
@@ -0,0 +1,97 @@
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, HelixElement, HelixMapPartChangeEvent } from '@fusion.dev/helix-core';
8
+ import { html, nothing } from 'lit';
9
+ import { property, state } from 'lit/decorators.js';
10
+ const BOOKKEEPING = new Set(['current', 'fanned', 'gathered', 'gatheredBy', 'side']);
11
+ let HelixHeadlessMapMarker = class HelixHeadlessMapMarker extends HelixElement {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.current = false;
15
+ this.gathered = [];
16
+ this.fanned = false;
17
+ this.gatheredBy = undefined;
18
+ this.isMapMarker = true;
19
+ this.side = 'end';
20
+ this.name = '';
21
+ this.passed = false;
22
+ this.passedLabel = 'passed';
23
+ }
24
+ willUpdate(changed) {
25
+ super.willUpdate(changed);
26
+ this.toggleState('cluster', this.gathered.length > 1);
27
+ this.toggleState('clustered', this.gatheredBy !== undefined);
28
+ this.toggleState('current', this.current);
29
+ this.toggleState('fanned', this.fanned);
30
+ this.toggleState('passed', this.passed);
31
+ this.toggleState('start', this.side === 'start');
32
+ if (this.childrenKnown) {
33
+ this.toggleState('icon', this.knownChildren.some((child) => child.slot === 'icon'));
34
+ }
35
+ }
36
+ updated(changed) {
37
+ super.updated(changed);
38
+ const keys = [...changed.keys()];
39
+ if (keys.some((key) => key === 'gathered' || key === 'gatheredBy')) {
40
+ this.dispatchEvent(new HelixMapPartChangeEvent({ kind: 'cluster' }));
41
+ }
42
+ if (keys.some((key) => !BOOKKEEPING.has(key))) {
43
+ this.dispatchEvent(new HelixMapPartChangeEvent({ kind: 'place' }));
44
+ }
45
+ }
46
+ extent() {
47
+ return this.partNamed('marker')?.getBoundingClientRect().width ?? 0;
48
+ }
49
+ focus(options) {
50
+ this.partNamed('marker')?.focus(options);
51
+ }
52
+ render() {
53
+ const cluster = this.gathered.length > 1;
54
+ const lang = cluster ? this.closest('[lang]')?.getAttribute('lang') || undefined : undefined;
55
+ return html `<span part="marker" role="button" tabindex=${this.current ? '0' : '-1'} ?hidden=${this.gatheredBy !== undefined}>
56
+ <span part="point" aria-hidden="true">${cluster ? new Intl.NumberFormat(lang).format(this.gathered.length) : nothing}<slot name="icon" ?hidden=${cluster} @slotchange=${() => this.requestUpdate()}></slot></span>
57
+ <span part="label">${cluster ? new Intl.ListFormat(lang, { type: 'conjunction' }).format(this.gathered) : this.passed ? `${this.label ?? ''}, ${this.passedLabel}` : (this.label ?? '')}</span>
58
+ </span>`;
59
+ }
60
+ };
61
+ __decorate([
62
+ state()
63
+ ], HelixHeadlessMapMarker.prototype, "current", void 0);
64
+ __decorate([
65
+ state()
66
+ ], HelixHeadlessMapMarker.prototype, "gathered", void 0);
67
+ __decorate([
68
+ state()
69
+ ], HelixHeadlessMapMarker.prototype, "fanned", void 0);
70
+ __decorate([
71
+ state()
72
+ ], HelixHeadlessMapMarker.prototype, "gatheredBy", void 0);
73
+ __decorate([
74
+ property()
75
+ ], HelixHeadlessMapMarker.prototype, "label", void 0);
76
+ __decorate([
77
+ property({ type: Number })
78
+ ], HelixHeadlessMapMarker.prototype, "lat", void 0);
79
+ __decorate([
80
+ property({ type: Number })
81
+ ], HelixHeadlessMapMarker.prototype, "lng", void 0);
82
+ __decorate([
83
+ state()
84
+ ], HelixHeadlessMapMarker.prototype, "side", void 0);
85
+ __decorate([
86
+ property()
87
+ ], HelixHeadlessMapMarker.prototype, "name", void 0);
88
+ __decorate([
89
+ property({ type: Boolean })
90
+ ], HelixHeadlessMapMarker.prototype, "passed", void 0);
91
+ __decorate([
92
+ property({ attribute: 'passed-label' })
93
+ ], HelixHeadlessMapMarker.prototype, "passedLabel", void 0);
94
+ HelixHeadlessMapMarker = __decorate([
95
+ customElement('helix-headless-map-marker')
96
+ ], HelixHeadlessMapMarker);
97
+ export { HelixHeadlessMapMarker };
@@ -0,0 +1,133 @@
1
+ import { HelixElement } from '@fusion.dev/helix-core';
2
+ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
+ /** A place in a points layer, where it is written as a list rather than GeoJSON. */
4
+ export interface MapPointsPlace {
5
+ /** What pressing it reports, where it is not its label. */
6
+ id?: string | number | undefined;
7
+ /** Its name. */
8
+ label?: string | undefined;
9
+ lat: number;
10
+ lng: number;
11
+ }
12
+ /**
13
+ * A point or a count the renderer drew in view, as the connector found it and
14
+ * stood it on the screen, in pixels from the map's top left.
15
+ */
16
+ export interface MapPointsInView {
17
+ /** How many places it stands for, where it is a count. */
18
+ count?: number | undefined;
19
+ /** Which it is, as the connector keys it: the same point or count keeps it while it stays in view. */
20
+ key: string;
21
+ /** Its name, where it is a point that has one. */
22
+ label?: string | undefined;
23
+ /** What pressing it reports, where it is a point. */
24
+ value?: string | undefined;
25
+ x: number;
26
+ y: number;
27
+ }
28
+ /** A GeoJSON collection of points, longitude first as GeoJSON writes it. */
29
+ export interface MapPointsCollection {
30
+ features: {
31
+ geometry: {
32
+ coordinates: number[];
33
+ type: string;
34
+ } | null;
35
+ id?: string | number | undefined;
36
+ properties?: Record<string, unknown> | null;
37
+ type: 'Feature';
38
+ }[];
39
+ type: 'FeatureCollection';
40
+ }
41
+ /**
42
+ * @summary Thousands of places on a map, loaded from data.
43
+ *
44
+ * @description
45
+ * Use a points layer for more places than markers can handle, such as every
46
+ * café in a city. `connectMap` draws the points as one layer of the map. Set
47
+ * `src` to a GeoJSON file, or set `data` to a GeoJSON collection or a list of
48
+ * places. With `cluster`, nearby points combine into counts.
49
+ *
50
+ * Pressing a point sends `helix-activate` from this element with the point's
51
+ * id, or its name if it has no id. Each point is named by its `name`
52
+ * property, or by the property set in `name-key`.
53
+ *
54
+ * The points and counts in view share one tab stop. The arrow keys, Home, and
55
+ * End move between them from top to bottom and along each line. Enter on a
56
+ * count zooms in to it.
57
+ *
58
+ * @since 0.105.0
59
+ * @status experimental
60
+ * @category media
61
+ * @aliases Data layer, GeoJSON layer, Point cloud, Point layer
62
+ * @documentation https://fusion.dev/helix/components/map
63
+ *
64
+ * @event {HelixActivateEvent} helix-activate - A point in view was pressed. The value is its id, or its name if it has no id.
65
+ * @event {HelixMapClusterEvent} helix-map-cluster - A count in view was pressed from the keyboard. `connectMap` zooms in to it.
66
+ * @event {HelixMapPartChangeEvent} helix-map-part-change - The points, name, or color changed. Used by `connectMap`; pages do not need to listen for it.
67
+ *
68
+ * @csspart count - The button on a count, which is also a `place`.
69
+ * @csspart label - The name of the focused point or count.
70
+ * @csspart place - The button on a point or count in view.
71
+ * @csspart places - The group of buttons, named by the layer's label.
72
+ */
73
+ export declare class HelixHeadlessMapPoints extends HelixElement {
74
+ /** Combines nearby points into counts. Pressing a count zooms in to it. */
75
+ cluster: boolean;
76
+ /**
77
+ * The name read for a count. `{count}` is replaced by the number of places.
78
+ * Defaults to "{count} places".
79
+ */
80
+ countLabel: string;
81
+ /**
82
+ * The points, as a GeoJSON collection or a list of `{ lat, lng, label, id }`
83
+ * places.
84
+ */
85
+ data?: MapPointsCollection | readonly MapPointsPlace[];
86
+ /**
87
+ * More about the points, such as how many there are. Read after the label in
88
+ * the frame's list.
89
+ */
90
+ description?: string;
91
+ /** Marks this as a layer for the frame and the connector, whatever its tag. */
92
+ readonly isMapLayer = true;
93
+ /** The layer's name, used in the frame's list. */
94
+ label?: string;
95
+ /** @internal Which kind of layer, which is how the connector draws it. */
96
+ readonly layerKind: 'points';
97
+ /** The GeoJSON property each point is named by. Defaults to `name`. */
98
+ nameKey: string;
99
+ /** A GeoJSON file of points to load. Ignored when `data` is set. */
100
+ src?: string;
101
+ /**
102
+ * @internal The points and counts in view, in reading order and stood on
103
+ * the screen, written by the connector as the view moves and settles.
104
+ */
105
+ walk: MapPointsInView[];
106
+ /** The one the layer's tab stop is on, by its key: the first until a reader moves it. */
107
+ private stop;
108
+ /** Where focus was when the walk changed, so it can go to what stands nearest there. */
109
+ private held;
110
+ /** What a point or a count in view is called. */
111
+ private nameOf;
112
+ /** The button standing on one, where it is drawn. */
113
+ private buttonOf;
114
+ /** Moves the tab stop and the focus to one. */
115
+ private reach;
116
+ /** Presses one: a point is the page's, a count the connector's to zoom in to. */
117
+ private press;
118
+ private readonly handleKeydown;
119
+ private readonly handleKeyup;
120
+ private readonly handleFocusin;
121
+ protected willUpdate(changed: PropertyValues): void;
122
+ /**
123
+ * Tells the frame and the connector the points changed, were renamed or were
124
+ * retoned; not the walk, which the connector wrote, nor where it stands.
125
+ */
126
+ protected updated(changed: PropertyValues): void;
127
+ protected render(): TemplateResult | typeof nothing;
128
+ }
129
+ declare global {
130
+ interface HTMLElementTagNameMap {
131
+ 'helix-headless-map-points': HelixHeadlessMapPoints;
132
+ }
133
+ }