@fusion.dev/helix-headless 0.102.0 → 0.103.1

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.
@@ -55446,14 +55446,26 @@
55446
55446
  "description": "**Helix draws no map.** There are no tiles here, no projection and no\nnetwork, and there should not be: a tile renderer is a library, and what a\ndesign system can add to MapLibre or Leaflet is the frame. The consumer slots\ntheir map and this owns the box, the name, the controls and the way out,\nwhich is the shape `VideoPlaylist` uses for a player it never renders.\n\n**The accessibility argument is the reason this exists**, and it was measured\nrather than assumed: a map embedded the ordinary way is a canvas with no role\nand no name, and it has *no node at all* in the accessibility tree. A reader\nusing a screen reader does not walk past it so much as never meet it. A named\nregion with controls in it is the whole of the difference.\n\n**The controls are buttons, and that is a deliberate choice against arrow\nkeys.** Arrows work here too, but what a screen reader does with them is not\nours to decide: macOS passes them through, Windows readers hold them for\ntheir own reading mode unless a page takes `role=\"application\"`, which costs\nthe reader their normal way of reading everything else in the frame. A button\nis findable and operable in every screen reader on every platform without\nthat bargain, so panning and zooming are buttons first and keys second.\n\n**Nothing here moves the map.** Both controls ask, through `helix-map-pan`\nand `helix-map-zoom`, and the consumer answers against whatever they slotted.\nA frame that moved the map would have to know whose map it was.",
55447
55447
  "name": "HelixHeadlessMap",
55448
55448
  "cssParts": [
55449
+ {
55450
+ "description": "The strip the credit sits in, under the map.",
55451
+ "name": "attribution"
55452
+ },
55449
55453
  {
55450
55454
  "description": "The group the pan and zoom buttons sit in.",
55451
55455
  "name": "controls"
55452
55456
  },
55457
+ {
55458
+ "description": "The strip below the map, for what a consumer slots there.",
55459
+ "name": "footer"
55460
+ },
55453
55461
  {
55454
55462
  "description": "The button that fills the screen.",
55455
55463
  "name": "fullscreen"
55456
55464
  },
55465
+ {
55466
+ "description": "The strip above the map, for what a consumer slots there.",
55467
+ "name": "header"
55468
+ },
55457
55469
  {
55458
55470
  "description": "The button that moves the map down.",
55459
55471
  "name": "pan-down"
@@ -55506,6 +55518,26 @@
55506
55518
  }
55507
55519
  ],
55508
55520
  "members": [
55521
+ {
55522
+ "kind": "field",
55523
+ "name": "controls",
55524
+ "type": {
55525
+ "text": "string | undefined"
55526
+ },
55527
+ "description": "Which of the controls this frame draws, as a list of what to keep.\n\n**A list rather than a switch, because the overlap is not symmetric.**\nEvery map library draws zoom controls and none of them draws pan buttons:\nMapLibre's navigation control is zoom and a compass, Leaflet's is zoom,\nGoogle's is zoom. So a consumer slotting one of those has our zoom twice\nover and our pan pad not at all, and a single switch would make them choose\nbetween a duplicate and nothing.\n\n`none` draws none of them, which is the right answer where the slotted map\nalready has controls a reader can find: what this family argues for is that\n*a* findable control exists, never that it is ours. The methods stay public\neither way, so a consumer drawing their own chrome can still drive the\nframe.\n\nAbsent is all three, which is this library's rule that a resting value is an\nattribute's absence, and it is what the stylesheet asks about: the layout\nrules are written against `[controls]:not([controls~=\"zoom\"])` rather than\nthe bare negation, or a frame that named no list at all would be laid out as\nthough it had dropped everything.",
55528
+ "attribute": "controls",
55529
+ "reflects": true
55530
+ },
55531
+ {
55532
+ "kind": "field",
55533
+ "name": "controlsPlacement",
55534
+ "type": {
55535
+ "text": "'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | undefined"
55536
+ },
55537
+ "description": "Which corner the controls sit in.\n\n**Configurable because there is no safe default**, and this is a collision\nrather than a taste: MapLibre puts its own controls at the top end, Leaflet\nand OpenLayers at the top start, Google's zoom at the bottom end. Whichever\ncorner this took would land on somebody's, which is why every one of those\nlibraries made it a setting too.\n\nLogical corners rather than left and right, so a right-to-left page moves\nthem without a consumer writing a second rule.\n\nAbsent is the top end, which is this library's rule that a resting value is\nan attribute's absence: a default that reflected would write an attribute\nonto every host that no server sent and nobody asked for.",
55538
+ "attribute": "controls-placement",
55539
+ "reflects": true
55540
+ },
55509
55541
  {
55510
55542
  "kind": "field",
55511
55543
  "name": "fullscreenLabel",
@@ -55610,6 +55642,25 @@
55610
55642
  "readonly": true,
55611
55643
  "default": "new IdleController( this, () => this.idleDelay, (resting) => { this.resting = resting }, )"
55612
55644
  },
55645
+ {
55646
+ "kind": "method",
55647
+ "name": "draws",
55648
+ "privacy": "private",
55649
+ "return": {
55650
+ "type": {
55651
+ "text": "boolean"
55652
+ }
55653
+ },
55654
+ "parameters": [
55655
+ {
55656
+ "name": "group",
55657
+ "type": {
55658
+ "text": "'pan' | 'zoom' | 'fullscreen'"
55659
+ }
55660
+ }
55661
+ ],
55662
+ "description": "Whether a named group of controls is one this frame draws."
55663
+ },
55613
55664
  {
55614
55665
  "kind": "method",
55615
55666
  "name": "toggleFullscreen",
@@ -55691,6 +55742,17 @@
55691
55742
  ],
55692
55743
  "description": "What one pan button is called, with the way it goes filled in.\n\nLeft and right rather than start and end, because the label is read aloud\nand a reader knows which way left is. The event keeps the reading-order\nname, which is the one a right-to-left page needs."
55693
55744
  },
55745
+ {
55746
+ "kind": "method",
55747
+ "name": "renderControls",
55748
+ "privacy": "private",
55749
+ "return": {
55750
+ "type": {
55751
+ "text": "TemplateResult | typeof nothing"
55752
+ }
55753
+ },
55754
+ "description": "The controls, or nothing where a consumer kept none of them.\n\n**Written out rather than generated**, because a part name behind an\ninterpolation is a part the manifest cannot see: it would publish `pan-`\nand a consumer's `::part(pan-up)` would match nothing. The same cost\n`drawIcon` names for `Trend`."
55755
+ },
55694
55756
  {
55695
55757
  "kind": "field",
55696
55758
  "name": "tier",
@@ -56035,6 +56097,22 @@
56035
56097
  }
56036
56098
  ],
56037
56099
  "attributes": [
56100
+ {
56101
+ "name": "controls",
56102
+ "type": {
56103
+ "text": "string | undefined"
56104
+ },
56105
+ "description": "Which of the controls this frame draws, as a list of what to keep.\n\n**A list rather than a switch, because the overlap is not symmetric.**\nEvery map library draws zoom controls and none of them draws pan buttons:\nMapLibre's navigation control is zoom and a compass, Leaflet's is zoom,\nGoogle's is zoom. So a consumer slotting one of those has our zoom twice\nover and our pan pad not at all, and a single switch would make them choose\nbetween a duplicate and nothing.\n\n`none` draws none of them, which is the right answer where the slotted map\nalready has controls a reader can find: what this family argues for is that\n*a* findable control exists, never that it is ours. The methods stay public\neither way, so a consumer drawing their own chrome can still drive the\nframe.\n\nAbsent is all three, which is this library's rule that a resting value is an\nattribute's absence, and it is what the stylesheet asks about: the layout\nrules are written against `[controls]:not([controls~=\"zoom\"])` rather than\nthe bare negation, or a frame that named no list at all would be laid out as\nthough it had dropped everything.",
56106
+ "fieldName": "controls"
56107
+ },
56108
+ {
56109
+ "name": "controls-placement",
56110
+ "type": {
56111
+ "text": "'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | undefined"
56112
+ },
56113
+ "description": "Which corner the controls sit in.\n\n**Configurable because there is no safe default**, and this is a collision\nrather than a taste: MapLibre puts its own controls at the top end, Leaflet\nand OpenLayers at the top start, Google's zoom at the bottom end. Whichever\ncorner this took would land on somebody's, which is why every one of those\nlibraries made it a setting too.\n\nLogical corners rather than left and right, so a right-to-left page moves\nthem without a consumer writing a second rule.\n\nAbsent is the top end, which is this library's rule that a resting value is\nan attribute's absence: a default that reflected would write an attribute\nonto every host that no server sent and nobody asked for.",
56114
+ "fieldName": "controlsPlacement"
56115
+ },
56038
56116
  {
56039
56117
  "name": "fullscreen-label",
56040
56118
  "type": {
@@ -2,6 +2,8 @@ import { HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  /** Which way a press or a key asked the map to go. */
4
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';
5
7
  /**
6
8
  * @summary A frame around somebody else's map.
7
9
  *
@@ -44,8 +46,11 @@ type Way = 'up' | 'down' | 'start' | 'end';
44
46
  * @event {HelixMapPanEvent} helix-map-pan - The reader asked the map to move. Answer it against your own map.
45
47
  * @event {HelixMapZoomEvent} helix-map-zoom - The reader asked the map to zoom, by a direction rather than to a level.
46
48
  *
49
+ * @csspart attribution - The strip the credit sits in, under the map.
47
50
  * @csspart controls - The group the pan and zoom buttons sit in.
51
+ * @csspart footer - The strip below the map, for what a consumer slots there.
48
52
  * @csspart fullscreen - The button that fills the screen.
53
+ * @csspart header - The strip above the map, for what a consumer slots there.
49
54
  * @csspart pan-down - The button that moves the map down.
50
55
  * @csspart pan-end - The button that moves the map the way the text ends, which is right on an English page.
51
56
  * @csspart pan-start - The button that moves the map the way the text starts, which is left on an English page.
@@ -59,6 +64,46 @@ type Way = 'up' | 'down' | 'start' | 'end';
59
64
  * @cssstate idle - While the reader has been still long enough to hide the chrome.
60
65
  */
61
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;
62
107
  /** What the control that fills the screen is called. */
63
108
  fullscreenLabel: string;
64
109
  /**
@@ -100,6 +145,8 @@ export declare class HelixHeadlessMap extends HelixElement {
100
145
  private filled;
101
146
  private readonly fullscreen;
102
147
  private readonly idle;
148
+ /** Whether a named group of controls is one this frame draws. */
149
+ private draws;
103
150
  /** Fills the screen with the frame, or comes back. */
104
151
  toggleFullscreen(): void;
105
152
  /** Asks for the map to move, which the consumer answers. */
@@ -132,6 +179,27 @@ export declare class HelixHeadlessMap extends HelixElement {
132
179
  * name, which is the one a right-to-left page needs.
133
180
  */
134
181
  private named;
182
+ /**
183
+ * The controls, or nothing where a consumer kept none of them.
184
+ *
185
+ * **Written out rather than generated**, because a part name behind an
186
+ * interpolation is a part the manifest cannot see: it would publish `pan-`
187
+ * and a consumer's `::part(pan-up)` would match nothing. The same cost
188
+ * `drawIcon` names for `Trend`.
189
+ */
190
+ private renderControls;
191
+ /**
192
+ * The frame, with each region in a box of its own.
193
+ *
194
+ * **The boxes are load-bearing rather than tidiness.** A `<slot>` is
195
+ * `display: contents`, so what lands in the grid is whatever a consumer
196
+ * assigned rather than the slot itself: with no header slotted, the viewport
197
+ * took the first row and the attribution took the `1fr`, and the map collapsed
198
+ * to the height of one line of text. Which slots a consumer fills is not
199
+ * something the layout may depend on, so each region is a box that holds its
200
+ * row whether or not anything is in it, and an empty one is a row of no
201
+ * height.
202
+ */
135
203
  protected render(): TemplateResult;
136
204
  }
137
205
  declare global {
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { customElement, FullscreenController, HelixElement, HelixMapPanEvent, HelixMapZoomEvent, hostAria, IdleController, inlineStep, } from '@fusion.dev/helix-core';
8
- import { html } from 'lit';
8
+ import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  let HelixHeadlessMap = class HelixHeadlessMap extends HelixElement {
11
11
  constructor() {
@@ -53,6 +53,9 @@ let HelixHeadlessMap = class HelixHeadlessMap extends HelixElement {
53
53
  }
54
54
  };
55
55
  }
56
+ draws(group) {
57
+ return this.controls === undefined ? true : this.controls.split(/\s+/).includes(group);
58
+ }
56
59
  toggleFullscreen() {
57
60
  void this.fullscreen.toggle();
58
61
  }
@@ -76,6 +79,49 @@ let HelixHeadlessMap = class HelixHeadlessMap extends HelixElement {
76
79
  const spoken = way === 'start' ? 'left' : way === 'end' ? 'right' : way;
77
80
  return this.panLabel.replace('{way}', spoken);
78
81
  }
82
+ renderControls() {
83
+ const pan = this.draws('pan');
84
+ const zoom = this.draws('zoom');
85
+ const fullscreen = this.draws('fullscreen');
86
+ if (!pan && !zoom && !fullscreen) {
87
+ return nothing;
88
+ }
89
+ return html `<div part="controls" role="group" aria-label="Map controls">
90
+ ${pan
91
+ ? html `<button part="pan-up" aria-label=${this.named('up')} type="button" @click=${() => this.pan('up')}>
92
+ ↑
93
+ </button>
94
+ <button part="pan-start" aria-label=${this.named('start')} type="button" @click=${() => this.pan('start')}>
95
+ ←
96
+ </button>
97
+ <button part="pan-end" aria-label=${this.named('end')} type="button" @click=${() => this.pan('end')}>
98
+ →
99
+ </button>
100
+ <button part="pan-down" aria-label=${this.named('down')} type="button" @click=${() => this.pan('down')}>
101
+ ↓
102
+ </button>`
103
+ : nothing}
104
+ ${zoom
105
+ ? html `<button part="zoom-in" aria-label=${this.zoomInLabel} type="button" @click=${() => this.zoom(1)}>
106
+ +
107
+ </button>
108
+ <button part="zoom-out" aria-label=${this.zoomOutLabel} type="button" @click=${() => this.zoom(-1)}>
109
+ −
110
+ </button>`
111
+ : nothing}
112
+ ${fullscreen
113
+ ? html `<button
114
+ part="fullscreen"
115
+ aria-label=${this.fullscreenLabel}
116
+ aria-pressed=${this.filled ? 'true' : 'false'}
117
+ type="button"
118
+ @click=${this.toggleFullscreen}
119
+ >
120
+ ⤢
121
+ </button>`
122
+ : nothing}
123
+ </div>`;
124
+ }
79
125
  render() {
80
126
  return html `<div
81
127
  part="region"
@@ -86,48 +132,28 @@ let HelixHeadlessMap = class HelixHeadlessMap extends HelixElement {
86
132
  @pointerleave=${this.idle.leave}
87
133
  @pointermove=${this.idle.stir}
88
134
  >
89
- <slot name="header"></slot>
135
+ <div part="header">
136
+ <slot name="header"></slot>
137
+ </div>
90
138
  <div part="viewport">
91
139
  <slot></slot>
92
140
  </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>
141
+ ${this.renderControls()}
142
+ <div part="footer">
143
+ <slot name="footer"></slot>
144
+ </div>
145
+ <div part="attribution">
146
+ <slot name="attribution"></slot>
125
147
  </div>
126
- <slot name="footer"></slot>
127
- <slot name="attribution"></slot>
128
148
  </div>`;
129
149
  }
130
150
  };
151
+ __decorate([
152
+ property({ reflect: true })
153
+ ], HelixHeadlessMap.prototype, "controls", void 0);
154
+ __decorate([
155
+ property({ attribute: 'controls-placement', reflect: true })
156
+ ], HelixHeadlessMap.prototype, "controlsPlacement", void 0);
131
157
  __decorate([
132
158
  property({ attribute: 'fullscreen-label' })
133
159
  ], HelixHeadlessMap.prototype, "fullscreenLabel", void 0);
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.102.0",
4
+ "version": "0.103.1",
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.102.0",
50
- "@fusion.dev/helix-icons": "0.102.0",
49
+ "@fusion.dev/helix-core": "0.103.1",
50
+ "@fusion.dev/helix-icons": "0.103.1",
51
51
  "uqr": "^0.1.3"
52
52
  },
53
53
  "devDependencies": {
54
- "@fusion.dev/helix-cem": "0.102.0",
55
- "@fusion.dev/helix-testing": "0.102.0",
54
+ "@fusion.dev/helix-cem": "0.103.1",
55
+ "@fusion.dev/helix-testing": "0.103.1",
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",
package/types/jsx.d.ts CHANGED
@@ -9396,6 +9396,57 @@ export type HelixHeadlessMapHelixMapZoomElementEvent =
9396
9396
  HelixHeadlessMapElementEvent<HelixMapZoomEvent>;
9397
9397
 
9398
9398
  export type HelixHeadlessMapProps = {
9399
+ /** Which of the controls this frame draws, as a list of what to keep.
9400
+
9401
+ **A list rather than a switch, because the overlap is not symmetric.**
9402
+ Every map library draws zoom controls and none of them draws pan buttons:
9403
+ MapLibre's navigation control is zoom and a compass, Leaflet's is zoom,
9404
+ Google's is zoom. So a consumer slotting one of those has our zoom twice
9405
+ over and our pan pad not at all, and a single switch would make them choose
9406
+ between a duplicate and nothing.
9407
+
9408
+ `none` draws none of them, which is the right answer where the slotted map
9409
+ already has controls a reader can find: what this family argues for is that
9410
+ *a* findable control exists, never that it is ours. The methods stay public
9411
+ either way, so a consumer drawing their own chrome can still drive the
9412
+ frame.
9413
+
9414
+ Absent is all three, which is this library's rule that a resting value is an
9415
+ attribute's absence, and it is what the stylesheet asks about: the layout
9416
+ rules are written against `[controls]:not([controls~="zoom"])` rather than
9417
+ the bare negation, or a frame that named no list at all would be laid out as
9418
+ though it had dropped everything. */
9419
+ controls?: (HelixHeadlessMap["controls"]) | undefined;
9420
+ /** Which corner the controls sit in.
9421
+
9422
+ **Configurable because there is no safe default**, and this is a collision
9423
+ rather than a taste: MapLibre puts its own controls at the top end, Leaflet
9424
+ and OpenLayers at the top start, Google's zoom at the bottom end. Whichever
9425
+ corner this took would land on somebody's, which is why every one of those
9426
+ libraries made it a setting too.
9427
+
9428
+ Logical corners rather than left and right, so a right-to-left page moves
9429
+ them without a consumer writing a second rule.
9430
+
9431
+ Absent is the top end, which is this library's rule that a resting value is
9432
+ an attribute's absence: a default that reflected would write an attribute
9433
+ onto every host that no server sent and nobody asked for. */
9434
+ "controls-placement"?: (HelixHeadlessMap["controlsPlacement"]) | undefined;
9435
+ /** Which corner the controls sit in.
9436
+
9437
+ **Configurable because there is no safe default**, and this is a collision
9438
+ rather than a taste: MapLibre puts its own controls at the top end, Leaflet
9439
+ and OpenLayers at the top start, Google's zoom at the bottom end. Whichever
9440
+ corner this took would land on somebody's, which is why every one of those
9441
+ libraries made it a setting too.
9442
+
9443
+ Logical corners rather than left and right, so a right-to-left page moves
9444
+ them without a consumer writing a second rule.
9445
+
9446
+ Absent is the top end, which is this library's rule that a resting value is
9447
+ an attribute's absence: a default that reflected would write an attribute
9448
+ onto every host that no server sent and nobody asked for. */
9449
+ controlsPlacement?: (HelixHeadlessMap["controlsPlacement"]) | undefined;
9399
9450
  /** What the control that fills the screen is called. */
9400
9451
  "fullscreen-label"?: (HelixHeadlessMap["fullscreenLabel"]) | undefined;
9401
9452
  /** What the control that fills the screen is called. */
@@ -9452,6 +9503,57 @@ multiply by their own. */
9452
9503
  };
9453
9504
 
9454
9505
  export type HelixHeadlessMapSolidJsProps = {
9506
+ /** Which of the controls this frame draws, as a list of what to keep.
9507
+
9508
+ **A list rather than a switch, because the overlap is not symmetric.**
9509
+ Every map library draws zoom controls and none of them draws pan buttons:
9510
+ MapLibre's navigation control is zoom and a compass, Leaflet's is zoom,
9511
+ Google's is zoom. So a consumer slotting one of those has our zoom twice
9512
+ over and our pan pad not at all, and a single switch would make them choose
9513
+ between a duplicate and nothing.
9514
+
9515
+ `none` draws none of them, which is the right answer where the slotted map
9516
+ already has controls a reader can find: what this family argues for is that
9517
+ *a* findable control exists, never that it is ours. The methods stay public
9518
+ either way, so a consumer drawing their own chrome can still drive the
9519
+ frame.
9520
+
9521
+ Absent is all three, which is this library's rule that a resting value is an
9522
+ attribute's absence, and it is what the stylesheet asks about: the layout
9523
+ rules are written against `[controls]:not([controls~="zoom"])` rather than
9524
+ the bare negation, or a frame that named no list at all would be laid out as
9525
+ though it had dropped everything. */
9526
+ "prop:controls"?: (HelixHeadlessMap["controls"]) | undefined;
9527
+ /** Which corner the controls sit in.
9528
+
9529
+ **Configurable because there is no safe default**, and this is a collision
9530
+ rather than a taste: MapLibre puts its own controls at the top end, Leaflet
9531
+ and OpenLayers at the top start, Google's zoom at the bottom end. Whichever
9532
+ corner this took would land on somebody's, which is why every one of those
9533
+ libraries made it a setting too.
9534
+
9535
+ Logical corners rather than left and right, so a right-to-left page moves
9536
+ them without a consumer writing a second rule.
9537
+
9538
+ Absent is the top end, which is this library's rule that a resting value is
9539
+ an attribute's absence: a default that reflected would write an attribute
9540
+ onto every host that no server sent and nobody asked for. */
9541
+ "attr:controls-placement"?: (HelixHeadlessMap["controlsPlacement"]) | undefined;
9542
+ /** Which corner the controls sit in.
9543
+
9544
+ **Configurable because there is no safe default**, and this is a collision
9545
+ rather than a taste: MapLibre puts its own controls at the top end, Leaflet
9546
+ and OpenLayers at the top start, Google's zoom at the bottom end. Whichever
9547
+ corner this took would land on somebody's, which is why every one of those
9548
+ libraries made it a setting too.
9549
+
9550
+ Logical corners rather than left and right, so a right-to-left page moves
9551
+ them without a consumer writing a second rule.
9552
+
9553
+ Absent is the top end, which is this library's rule that a resting value is
9554
+ an attribute's absence: a default that reflected would write an attribute
9555
+ onto every host that no server sent and nobody asked for. */
9556
+ "prop:controlsPlacement"?: (HelixHeadlessMap["controlsPlacement"]) | undefined;
9455
9557
  /** What the control that fills the screen is called. */
9456
9558
  "attr:fullscreen-label"?: (HelixHeadlessMap["fullscreenLabel"]) | undefined;
9457
9559
  /** What the control that fills the screen is called. */
@@ -27514,6 +27616,40 @@ export type CustomElements = {
27514
27616
  *
27515
27617
  * Component attributes and properties that can be applied to the element or by using JavaScript.
27516
27618
  *
27619
+ * - `controls`: Which of the controls this frame draws, as a list of what to keep.
27620
+ *
27621
+ * **A list rather than a switch, because the overlap is not symmetric.**
27622
+ * Every map library draws zoom controls and none of them draws pan buttons:
27623
+ * MapLibre's navigation control is zoom and a compass, Leaflet's is zoom,
27624
+ * Google's is zoom. So a consumer slotting one of those has our zoom twice
27625
+ * over and our pan pad not at all, and a single switch would make them choose
27626
+ * between a duplicate and nothing.
27627
+ *
27628
+ * `none` draws none of them, which is the right answer where the slotted map
27629
+ * already has controls a reader can find: what this family argues for is that
27630
+ * *a* findable control exists, never that it is ours. The methods stay public
27631
+ * either way, so a consumer drawing their own chrome can still drive the
27632
+ * frame.
27633
+ *
27634
+ * Absent is all three, which is this library's rule that a resting value is an
27635
+ * attribute's absence, and it is what the stylesheet asks about: the layout
27636
+ * rules are written against `[controls]:not([controls~="zoom"])` rather than
27637
+ * the bare negation, or a frame that named no list at all would be laid out as
27638
+ * though it had dropped everything.
27639
+ * - `controls-placement`/`controlsPlacement`: Which corner the controls sit in.
27640
+ *
27641
+ * **Configurable because there is no safe default**, and this is a collision
27642
+ * rather than a taste: MapLibre puts its own controls at the top end, Leaflet
27643
+ * and OpenLayers at the top start, Google's zoom at the bottom end. Whichever
27644
+ * corner this took would land on somebody's, which is why every one of those
27645
+ * libraries made it a setting too.
27646
+ *
27647
+ * Logical corners rather than left and right, so a right-to-left page moves
27648
+ * them without a consumer writing a second rule.
27649
+ *
27650
+ * Absent is the top end, which is this library's rule that a resting value is
27651
+ * an attribute's absence: a default that reflected would write an attribute
27652
+ * onto every host that no server sent and nobody asked for.
27517
27653
  * - `fullscreen-label`/`fullscreenLabel`: What the control that fills the screen is called.
27518
27654
  * - `idle-delay`/`idleDelay`: How long the reader may be still before the chrome hides, in milliseconds.
27519
27655
  *
@@ -27583,8 +27719,11 @@ export type CustomElements = {
27583
27719
  *
27584
27720
  * Custom selectors for styling elements within the component.
27585
27721
  *
27722
+ * - `attribution`: The strip the credit sits in, under the map.
27586
27723
  * - `controls`: The group the pan and zoom buttons sit in.
27724
+ * - `footer`: The strip below the map, for what a consumer slots there.
27587
27725
  * - `fullscreen`: The button that fills the screen.
27726
+ * - `header`: The strip above the map, for what a consumer slots there.
27588
27727
  * - `pan-down`: The button that moves the map down.
27589
27728
  * - `pan-end`: The button that moves the map the way the text ends, which is right on an English page.
27590
27729
  * - `pan-start`: The button that moves the map the way the text starts, which is left on an English page.
@@ -45982,6 +46121,40 @@ export type CustomElementsSolidJs = {
45982
46121
  *
45983
46122
  * Component attributes and properties that can be applied to the element or by using JavaScript.
45984
46123
  *
46124
+ * - `controls`: Which of the controls this frame draws, as a list of what to keep.
46125
+ *
46126
+ * **A list rather than a switch, because the overlap is not symmetric.**
46127
+ * Every map library draws zoom controls and none of them draws pan buttons:
46128
+ * MapLibre's navigation control is zoom and a compass, Leaflet's is zoom,
46129
+ * Google's is zoom. So a consumer slotting one of those has our zoom twice
46130
+ * over and our pan pad not at all, and a single switch would make them choose
46131
+ * between a duplicate and nothing.
46132
+ *
46133
+ * `none` draws none of them, which is the right answer where the slotted map
46134
+ * already has controls a reader can find: what this family argues for is that
46135
+ * *a* findable control exists, never that it is ours. The methods stay public
46136
+ * either way, so a consumer drawing their own chrome can still drive the
46137
+ * frame.
46138
+ *
46139
+ * Absent is all three, which is this library's rule that a resting value is an
46140
+ * attribute's absence, and it is what the stylesheet asks about: the layout
46141
+ * rules are written against `[controls]:not([controls~="zoom"])` rather than
46142
+ * the bare negation, or a frame that named no list at all would be laid out as
46143
+ * though it had dropped everything.
46144
+ * - `controls-placement`/`controlsPlacement`: Which corner the controls sit in.
46145
+ *
46146
+ * **Configurable because there is no safe default**, and this is a collision
46147
+ * rather than a taste: MapLibre puts its own controls at the top end, Leaflet
46148
+ * and OpenLayers at the top start, Google's zoom at the bottom end. Whichever
46149
+ * corner this took would land on somebody's, which is why every one of those
46150
+ * libraries made it a setting too.
46151
+ *
46152
+ * Logical corners rather than left and right, so a right-to-left page moves
46153
+ * them without a consumer writing a second rule.
46154
+ *
46155
+ * Absent is the top end, which is this library's rule that a resting value is
46156
+ * an attribute's absence: a default that reflected would write an attribute
46157
+ * onto every host that no server sent and nobody asked for.
45985
46158
  * - `fullscreen-label`/`fullscreenLabel`: What the control that fills the screen is called.
45986
46159
  * - `idle-delay`/`idleDelay`: How long the reader may be still before the chrome hides, in milliseconds.
45987
46160
  *
@@ -46051,8 +46224,11 @@ export type CustomElementsSolidJs = {
46051
46224
  *
46052
46225
  * Custom selectors for styling elements within the component.
46053
46226
  *
46227
+ * - `attribution`: The strip the credit sits in, under the map.
46054
46228
  * - `controls`: The group the pan and zoom buttons sit in.
46229
+ * - `footer`: The strip below the map, for what a consumer slots there.
46055
46230
  * - `fullscreen`: The button that fills the screen.
46231
+ * - `header`: The strip above the map, for what a consumer slots there.
46056
46232
  * - `pan-down`: The button that moves the map down.
46057
46233
  * - `pan-end`: The button that moves the map the way the text ends, which is right on an English page.
46058
46234
  * - `pan-start`: The button that moves the map the way the text starts, which is left on an English page.
package/types/vue.d.ts CHANGED
@@ -4808,6 +4808,57 @@ export type HelixHeadlessMapHelixMapZoomElementEvent =
4808
4808
  HelixHeadlessMapElementEvent<HelixMapZoomEvent>;
4809
4809
 
4810
4810
  export type HelixHeadlessMapVueProps = {
4811
+ /** Which of the controls this frame draws, as a list of what to keep.
4812
+
4813
+ **A list rather than a switch, because the overlap is not symmetric.**
4814
+ Every map library draws zoom controls and none of them draws pan buttons:
4815
+ MapLibre's navigation control is zoom and a compass, Leaflet's is zoom,
4816
+ Google's is zoom. So a consumer slotting one of those has our zoom twice
4817
+ over and our pan pad not at all, and a single switch would make them choose
4818
+ between a duplicate and nothing.
4819
+
4820
+ `none` draws none of them, which is the right answer where the slotted map
4821
+ already has controls a reader can find: what this family argues for is that
4822
+ *a* findable control exists, never that it is ours. The methods stay public
4823
+ either way, so a consumer drawing their own chrome can still drive the
4824
+ frame.
4825
+
4826
+ Absent is all three, which is this library's rule that a resting value is an
4827
+ attribute's absence, and it is what the stylesheet asks about: the layout
4828
+ rules are written against `[controls]:not([controls~="zoom"])` rather than
4829
+ the bare negation, or a frame that named no list at all would be laid out as
4830
+ though it had dropped everything. */
4831
+ controls?: (HelixHeadlessMap["controls"]) | undefined;
4832
+ /** Which corner the controls sit in.
4833
+
4834
+ **Configurable because there is no safe default**, and this is a collision
4835
+ rather than a taste: MapLibre puts its own controls at the top end, Leaflet
4836
+ and OpenLayers at the top start, Google's zoom at the bottom end. Whichever
4837
+ corner this took would land on somebody's, which is why every one of those
4838
+ libraries made it a setting too.
4839
+
4840
+ Logical corners rather than left and right, so a right-to-left page moves
4841
+ them without a consumer writing a second rule.
4842
+
4843
+ Absent is the top end, which is this library's rule that a resting value is
4844
+ an attribute's absence: a default that reflected would write an attribute
4845
+ onto every host that no server sent and nobody asked for. */
4846
+ "controls-placement"?: (HelixHeadlessMap["controlsPlacement"]) | undefined;
4847
+ /** Which corner the controls sit in.
4848
+
4849
+ **Configurable because there is no safe default**, and this is a collision
4850
+ rather than a taste: MapLibre puts its own controls at the top end, Leaflet
4851
+ and OpenLayers at the top start, Google's zoom at the bottom end. Whichever
4852
+ corner this took would land on somebody's, which is why every one of those
4853
+ libraries made it a setting too.
4854
+
4855
+ Logical corners rather than left and right, so a right-to-left page moves
4856
+ them without a consumer writing a second rule.
4857
+
4858
+ Absent is the top end, which is this library's rule that a resting value is
4859
+ an attribute's absence: a default that reflected would write an attribute
4860
+ onto every host that no server sent and nobody asked for. */
4861
+ controlsPlacement?: (HelixHeadlessMap["controlsPlacement"]) | undefined;
4811
4862
  /** What the control that fills the screen is called. */
4812
4863
  "fullscreen-label"?: (HelixHeadlessMap["fullscreenLabel"]) | undefined;
4813
4864
  /** What the control that fills the screen is called. */