@fusion.dev/helix-headless 0.162.1 → 0.163.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 +233 -12
- package/dist/canvas/index.d.ts +1 -1
- package/dist/canvas/root/canvas-document.d.ts +33 -0
- package/dist/canvas/root/canvas-root.d.ts +50 -2
- package/dist/canvas/root/canvas-root.js +98 -3
- package/dist/chat/composer/chat-composer.js +1 -1
- package/dist/chat/root/chat-root.d.ts +15 -4
- package/dist/chat/root/chat-root.js +19 -8
- package/dist/map/root/map-root.d.ts +8 -1
- package/dist/map/root/map-root.js +12 -0
- package/dist/menu/item/menu-item.d.ts +38 -7
- package/dist/menu/item/menu-item.js +20 -9
- package/dist/menu/root/menu-root.d.ts +10 -1
- package/dist/menu/root/menu-root.js +8 -3
- package/dist/steps/root/steps-root.d.ts +7 -1
- package/dist/steps/root/steps-root.js +6 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +162 -24
- package/types/vue.d.ts +40 -7
|
@@ -733,6 +733,35 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
|
|
|
733
733
|
this.handleSlotChange = () => {
|
|
734
734
|
this.requestUpdate();
|
|
735
735
|
};
|
|
736
|
+
this.handleMinimapPointer = (event) => {
|
|
737
|
+
if (event.type === 'pointermove' && (event.buttons & 1) === 0) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const picture = event.currentTarget;
|
|
741
|
+
if (event.type === 'pointerdown') {
|
|
742
|
+
if (event.button !== 0) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
capturePointer(picture, event.pointerId);
|
|
746
|
+
}
|
|
747
|
+
event.preventDefault();
|
|
748
|
+
const area = this.minimapArea;
|
|
749
|
+
const shown = picture.getBoundingClientRect();
|
|
750
|
+
const scale = Math.min(shown.width / area.width, shown.height / area.height) || 1;
|
|
751
|
+
const point = {
|
|
752
|
+
x: area.x + (event.clientX - shown.left - (shown.width - area.width * scale) / 2) / scale,
|
|
753
|
+
y: area.y + (event.clientY - shown.top - (shown.height - area.height * scale) / 2) / scale,
|
|
754
|
+
};
|
|
755
|
+
const board = extent(this.nodes);
|
|
756
|
+
const { zoom } = this.at;
|
|
757
|
+
this.direct = event.type === 'pointermove';
|
|
758
|
+
this.touched = true;
|
|
759
|
+
this.at = {
|
|
760
|
+
...this.at,
|
|
761
|
+
x: this.box.width / 2 - (point.x - board.x + MARGIN) * zoom,
|
|
762
|
+
y: this.box.height / 2 - (point.y - board.y + MARGIN) * zoom,
|
|
763
|
+
};
|
|
764
|
+
};
|
|
736
765
|
}
|
|
737
766
|
static { this.words = 'helix-canvas'; }
|
|
738
767
|
static { this.shadowRootOptions = {
|
|
@@ -1120,9 +1149,25 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
|
|
|
1120
1149
|
if (!from || !to) {
|
|
1121
1150
|
return undefined;
|
|
1122
1151
|
}
|
|
1123
|
-
const drawn =
|
|
1152
|
+
const drawn = this.draw(edge, onPlane(from, origin), onPlane(to, origin), edge.path ?? this.path ?? 'curve', heads(edge));
|
|
1124
1153
|
return { ...drawn, from, to };
|
|
1125
1154
|
}
|
|
1155
|
+
draw(edge, from, to, path, drawnHeads) {
|
|
1156
|
+
if (this.edgeRoute) {
|
|
1157
|
+
const { end, fromSide, start, toSide } = ends(edge, from, to);
|
|
1158
|
+
let own;
|
|
1159
|
+
try {
|
|
1160
|
+
own = this.edgeRoute({ edge, end, fromSide, path, start, toSide });
|
|
1161
|
+
}
|
|
1162
|
+
catch (error) {
|
|
1163
|
+
globalThis.reportError?.(error);
|
|
1164
|
+
}
|
|
1165
|
+
if (own?.d) {
|
|
1166
|
+
return { d: own.d, mid: own.mid ?? { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 } };
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
return connector(edge, from, to, path, drawnHeads);
|
|
1170
|
+
}
|
|
1126
1171
|
cardOf(event) {
|
|
1127
1172
|
return event
|
|
1128
1173
|
.composedPath()
|
|
@@ -2134,7 +2179,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
|
|
|
2134
2179
|
if (target) {
|
|
2135
2180
|
const reversed = linking.moving === 'from';
|
|
2136
2181
|
const [from, to] = reversed ? [target, anchor] : [anchor, target];
|
|
2137
|
-
const drawn =
|
|
2182
|
+
const drawn = this.draw({
|
|
2138
2183
|
fromNode: from.id,
|
|
2139
2184
|
fromSide: reversed ? linking.toSide : linking.side,
|
|
2140
2185
|
id: '',
|
|
@@ -2157,6 +2202,53 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
|
|
|
2157
2202
|
}
|
|
2158
2203
|
return nothing;
|
|
2159
2204
|
}
|
|
2205
|
+
get onScreen() {
|
|
2206
|
+
const board = extent(this.nodes);
|
|
2207
|
+
const { height, width } = this.box;
|
|
2208
|
+
const { x, y, zoom } = this.at;
|
|
2209
|
+
return {
|
|
2210
|
+
height: height / zoom,
|
|
2211
|
+
width: width / zoom,
|
|
2212
|
+
x: board.x - MARGIN - x / zoom,
|
|
2213
|
+
y: board.y - MARGIN - y / zoom,
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
get minimapArea() {
|
|
2217
|
+
const board = extent(this.nodes);
|
|
2218
|
+
const view = this.onScreen;
|
|
2219
|
+
const x = Math.min(board.x, view.x);
|
|
2220
|
+
const y = Math.min(board.y, view.y);
|
|
2221
|
+
const width = Math.max(board.x + board.width, view.x + view.width) - x;
|
|
2222
|
+
const height = Math.max(board.y + board.height, view.y + view.height) - y;
|
|
2223
|
+
const room = Math.max(width, height) * 0.05;
|
|
2224
|
+
return {
|
|
2225
|
+
height: height + room * 2,
|
|
2226
|
+
width: width + room * 2,
|
|
2227
|
+
x: x - room,
|
|
2228
|
+
y: y - room,
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
renderMinimap() {
|
|
2232
|
+
const nodes = this.nodes;
|
|
2233
|
+
if (!this.drawsControls('minimap') || nodes.length === 0) {
|
|
2234
|
+
return nothing;
|
|
2235
|
+
}
|
|
2236
|
+
const area = this.minimapArea;
|
|
2237
|
+
const view = this.onScreen;
|
|
2238
|
+
return html `<svg
|
|
2239
|
+
part="minimap"
|
|
2240
|
+
aria-hidden="true"
|
|
2241
|
+
height="120"
|
|
2242
|
+
preserveAspectRatio="xMidYMid meet"
|
|
2243
|
+
viewBox=${`${area.x} ${area.y} ${area.width} ${area.height}`}
|
|
2244
|
+
width="160"
|
|
2245
|
+
@pointerdown=${this.handleMinimapPointer}
|
|
2246
|
+
@pointermove=${this.handleMinimapPointer}
|
|
2247
|
+
>
|
|
2248
|
+
${nodes.map((node) => svg `<rect part="minimap-node" data-group=${node.type === 'group' ? '' : nothing} x=${node.x} y=${node.y} width=${node.width} height=${node.height} />`)}
|
|
2249
|
+
${svg `<rect part="minimap-view" x=${view.x} y=${view.y} width=${view.width} height=${view.height} />`}
|
|
2250
|
+
</svg>`;
|
|
2251
|
+
}
|
|
2160
2252
|
render() {
|
|
2161
2253
|
const nodes = this.nodes;
|
|
2162
2254
|
const drawn = extent(nodes);
|
|
@@ -2219,7 +2311,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
|
|
|
2219
2311
|
<slot name="empty"></slot>
|
|
2220
2312
|
${this.renderGestures()}
|
|
2221
2313
|
</div>
|
|
2222
|
-
${this.renderControls()}
|
|
2314
|
+
${this.renderControls()} ${this.renderMinimap()}
|
|
2223
2315
|
<span part="live" role="status">${this.said}</span>
|
|
2224
2316
|
<div part="footer">
|
|
2225
2317
|
<slot name="footer"></slot>
|
|
@@ -2255,6 +2347,9 @@ __decorate([
|
|
|
2255
2347
|
words,
|
|
2256
2348
|
property({ attribute: 'copied-label' })
|
|
2257
2349
|
], HelixHeadlessCanvas.prototype, "copiedLabel", void 0);
|
|
2350
|
+
__decorate([
|
|
2351
|
+
property({ attribute: false })
|
|
2352
|
+
], HelixHeadlessCanvas.prototype, "edgeRoute", void 0);
|
|
2258
2353
|
__decorate([
|
|
2259
2354
|
words,
|
|
2260
2355
|
property({ attribute: 'fallback-label' })
|
|
@@ -113,7 +113,7 @@ let HelixHeadlessChatComposer = class HelixHeadlessChatComposer extends HelixFor
|
|
|
113
113
|
}
|
|
114
114
|
const invalid = this.touched && !this.internals.validity.valid;
|
|
115
115
|
this.toggleState('invalid', invalid);
|
|
116
|
-
this.internals.ariaInvalid = invalid ? 'true' :
|
|
116
|
+
this.internals.ariaInvalid = invalid ? 'true' : 'false';
|
|
117
117
|
if (invalid !== this.shownInvalid) {
|
|
118
118
|
this.shownInvalid = invalid;
|
|
119
119
|
}
|
|
@@ -517,12 +517,23 @@ export declare class HelixHeadlessChat extends HelixHeadlessChat_base {
|
|
|
517
517
|
* Moves focus to turn `at`, drawing it first where the transcript is
|
|
518
518
|
* windowed and it is not drawn.
|
|
519
519
|
*
|
|
520
|
-
* **A turn takes focus only once a reader moves to it**, so the
|
|
521
|
-
*
|
|
522
|
-
*
|
|
523
|
-
* to.
|
|
520
|
+
* **A turn takes focus only once a reader moves to it**, so the `tabindex`
|
|
521
|
+
* it needs is written then, long after any server's markup has hydrated,
|
|
522
|
+
* and never on a turn nobody moved to.
|
|
524
523
|
*/
|
|
525
524
|
private focusTurn;
|
|
525
|
+
/** The turn that is a tab stop because a reader moved to it. */
|
|
526
|
+
private stop;
|
|
527
|
+
/**
|
|
528
|
+
* Focuses a turn, making it the transcript's one stop a reader moved to.
|
|
529
|
+
*
|
|
530
|
+
* **`0`, and never `-1`.** Chromium leaves the whole of a shadow host at a
|
|
531
|
+
* negative `tabindex` out of the Tab order, its own controls and what is
|
|
532
|
+
* slotted into it alike, so a turn at `-1` lost its links and its Try again
|
|
533
|
+
* once a reader had paged through it. The stop moves with the reader, and a
|
|
534
|
+
* `tabindex` you wrote on a turn is left as it is.
|
|
535
|
+
*/
|
|
536
|
+
private visit;
|
|
526
537
|
/** The first control in the regions `names` holds, in their order. */
|
|
527
538
|
private firstControlIn;
|
|
528
539
|
/**
|
|
@@ -524,7 +524,9 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
524
524
|
}
|
|
525
525
|
async focusTurn(at) {
|
|
526
526
|
const count = this.fed ? this.turns.length : this.messages.length;
|
|
527
|
-
|
|
527
|
+
const log = this.log;
|
|
528
|
+
const scrolls = log !== null && log !== undefined && log.scrollHeight > log.clientHeight;
|
|
529
|
+
if (at < count - 1 && this.pinned && scrolls) {
|
|
528
530
|
this.requestPinned(false);
|
|
529
531
|
}
|
|
530
532
|
if (this.fed) {
|
|
@@ -537,17 +539,27 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
537
539
|
this.requestUpdate();
|
|
538
540
|
await this.updateComplete;
|
|
539
541
|
}
|
|
540
|
-
drawn()
|
|
542
|
+
const turn = drawn();
|
|
543
|
+
if (turn) {
|
|
544
|
+
this.visit(turn);
|
|
545
|
+
}
|
|
541
546
|
return;
|
|
542
547
|
}
|
|
543
548
|
const message = this.messages[at];
|
|
544
|
-
if (
|
|
545
|
-
|
|
549
|
+
if (message) {
|
|
550
|
+
this.visit(message);
|
|
546
551
|
}
|
|
547
|
-
|
|
548
|
-
|
|
552
|
+
}
|
|
553
|
+
visit(turn) {
|
|
554
|
+
if (this.stop !== turn) {
|
|
555
|
+
this.stop?.removeAttribute('tabindex');
|
|
556
|
+
this.stop = undefined;
|
|
557
|
+
if (!turn.hasAttribute('tabindex')) {
|
|
558
|
+
turn.setAttribute('tabindex', '0');
|
|
559
|
+
this.stop = turn;
|
|
560
|
+
}
|
|
549
561
|
}
|
|
550
|
-
|
|
562
|
+
turn.focus();
|
|
551
563
|
}
|
|
552
564
|
firstControlIn(names) {
|
|
553
565
|
for (const name of names) {
|
|
@@ -582,7 +594,6 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
|
|
|
582
594
|
.turnWords=${this.words}
|
|
583
595
|
.turnPosition=${first + offset + 1}
|
|
584
596
|
.turnCount=${this.turns.length}
|
|
585
|
-
tabindex="-1"
|
|
586
597
|
.failedLabel=${this.failedLabel}
|
|
587
598
|
state=${turn.state ?? nothing}
|
|
588
599
|
>${turn.text}</${message}>
|
|
@@ -46,7 +46,8 @@ declare const HelixHeadlessMap_base: (new (...args: any[]) => import("@fusion.de
|
|
|
46
46
|
* same `group`, such as a route and its dotted legs, share one tile. A tile
|
|
47
47
|
* sends `helix-before-toggle` from the layer first, so you can store the
|
|
48
48
|
* choice or refuse it, and `helix-toggle` from it once `hidden` holds the
|
|
49
|
-
* choice.
|
|
49
|
+
* choice. Escape closes the menu and returns focus to its button, and the menu
|
|
50
|
+
* closes when focus moves on from it or a press lands outside the map.
|
|
50
51
|
*
|
|
51
52
|
* Imagery, a `helix-map-raster` with `ground`, is a map type and not a tile.
|
|
52
53
|
* The menu opens with a card for each type, Standard, Hybrid, and Satellite,
|
|
@@ -315,6 +316,12 @@ export declare class HelixHeadlessMap extends HelixHeadlessMap_base {
|
|
|
315
316
|
private hiding?;
|
|
316
317
|
/** Closes the layers menu on a press anywhere outside the frame. */
|
|
317
318
|
private readonly handleOutside;
|
|
319
|
+
/**
|
|
320
|
+
* Closes the layers menu when focus moves on from its button and the menu,
|
|
321
|
+
* as a Tab past the last tile does. Focus leaving the page, with nothing
|
|
322
|
+
* focused in its place, leaves the menu as it is.
|
|
323
|
+
*/
|
|
324
|
+
private readonly handleMenuFocusOut;
|
|
318
325
|
/**
|
|
319
326
|
* Keys pressed in the layers menu. Escape closes it and returns to its
|
|
320
327
|
* button, and no key reaches the region, where the arrows would pan the map.
|
|
@@ -82,6 +82,16 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
82
82
|
this.layersOpen = false;
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
|
+
this.handleMenuFocusOut = (event) => {
|
|
86
|
+
const to = event.relatedTarget;
|
|
87
|
+
if (!(this.layersOpen && to)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const owners = this.renderRoot.querySelectorAll('[part="layers-toggle"], [part="layers-menu"]');
|
|
91
|
+
if (![...owners].some((owner) => owner.contains(to))) {
|
|
92
|
+
this.layersOpen = false;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
85
95
|
this.handleMenuKeydown = (event) => {
|
|
86
96
|
event.stopPropagation();
|
|
87
97
|
if (event.key === 'Escape' && this.layersOpen) {
|
|
@@ -402,6 +412,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
402
412
|
this.dispatchEvent(new HelixMapPartChangeEvent({ kind: 'menu' }));
|
|
403
413
|
}
|
|
404
414
|
}}
|
|
415
|
+
@focusout=${this.handleMenuFocusOut}
|
|
405
416
|
@keydown=${this.handleMenuKeydown}
|
|
406
417
|
>
|
|
407
418
|
<slot name="layers-icon">
|
|
@@ -416,6 +427,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
|
|
|
416
427
|
role="group"
|
|
417
428
|
aria-label=${this.layersLabel}
|
|
418
429
|
?hidden=${!this.layersOpen}
|
|
430
|
+
@focusout=${this.handleMenuFocusOut}
|
|
419
431
|
@keydown=${this.handleMenuKeydown}
|
|
420
432
|
>
|
|
421
433
|
${this.imagery.length > 0 ? this.renderMapTypes() : nothing}
|
|
@@ -10,16 +10,22 @@ import '../../kbd/index.js';
|
|
|
10
10
|
* - `checked` makes the row a choice.
|
|
11
11
|
* - With neither, the row is a command.
|
|
12
12
|
*
|
|
13
|
-
* The role follows the kind. A row with `
|
|
14
|
-
* `aria-checked`, and `exclusive`
|
|
15
|
-
* only one can be on. A row that opens a page
|
|
16
|
-
* `aria-haspopup`.
|
|
13
|
+
* The role follows the kind. A row with `checkable` or `checked` is
|
|
14
|
+
* `menuitemcheckbox` with `aria-checked`, and an `exclusive` row is
|
|
15
|
+
* `menuitemradio`, for a set where only one can be on. A row that opens a page
|
|
16
|
+
* keeps `menuitem` and sets `aria-haspopup`.
|
|
17
17
|
*
|
|
18
18
|
* Choosing a choice row checks or unchecks it before `helix-select` is sent,
|
|
19
19
|
* so a listener reads the new `checked`. An `exclusive` row turns on and turns
|
|
20
20
|
* off the `exclusive` rows next to it. Cancel `helix-before-toggle` to keep a
|
|
21
21
|
* row as it is and set `checked` yourself.
|
|
22
22
|
*
|
|
23
|
+
* Space on a choice row changes it and leaves the menu open, so a reader can
|
|
24
|
+
* set several in a row, as the APG's menu does. Enter and a press change it and
|
|
25
|
+
* close the menu. `close-on-select="false"` on the row keeps the menu open after
|
|
26
|
+
* any choice of it, and `close-on-select` on the row closes it after a Space as
|
|
27
|
+
* well.
|
|
28
|
+
*
|
|
23
29
|
* The menu sets whether the row's page is showing as a property, never as an
|
|
24
30
|
* attribute. An attribute written onto your element after it upgrades causes a
|
|
25
31
|
* hydration mismatch.
|
|
@@ -82,7 +88,8 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
82
88
|
* as a checkbox tells a reader its siblings are commands, which is the one
|
|
83
89
|
* thing they are not.
|
|
84
90
|
*
|
|
85
|
-
* `checked`
|
|
91
|
+
* `checked` and `exclusive` imply it, so a row that is on, or one of a set,
|
|
92
|
+
* never has to say both.
|
|
86
93
|
*
|
|
87
94
|
* Choosing the row checks or unchecks it, after a `helix-before-toggle` that
|
|
88
95
|
* you can cancel.
|
|
@@ -95,6 +102,14 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
95
102
|
* state you start the row in, and `:state(checked)` carries the current one.
|
|
96
103
|
*/
|
|
97
104
|
checked: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Whether choosing the row closes the menu.
|
|
107
|
+
*
|
|
108
|
+
* Unset takes the menu's `close-on-select`, which is on, and keeps the menu
|
|
109
|
+
* open after a Space on a choice row. Write `close-on-select="false"` for a
|
|
110
|
+
* row that changes something the reader is watching, such as a zoom step.
|
|
111
|
+
*/
|
|
112
|
+
closeOnSelect?: boolean;
|
|
98
113
|
/**
|
|
99
114
|
* Whether the row is disabled, so it cannot be chosen.
|
|
100
115
|
*/
|
|
@@ -116,8 +131,7 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
116
131
|
* it checked. `menuitemradio` tells a reader which kind of set they are in,
|
|
117
132
|
* where `menuitemcheckbox` says every row is independent.
|
|
118
133
|
*
|
|
119
|
-
*
|
|
120
|
-
* one of a set of anything.
|
|
134
|
+
* It makes the row a choice, so it needs no `checkable` beside it.
|
|
121
135
|
*/
|
|
122
136
|
exclusive: boolean;
|
|
123
137
|
/**
|
|
@@ -192,6 +206,17 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
192
206
|
* protected. Not a consumer's to read or set.
|
|
193
207
|
*/
|
|
194
208
|
readonly isMenuItem = true;
|
|
209
|
+
/**
|
|
210
|
+
* @internal Whether the last choice of this row asked the menu to stay open:
|
|
211
|
+
* a Space on a choice row, or `close-on-select="false"`. Read by the menu
|
|
212
|
+
* while it hears the row's `helix-select`.
|
|
213
|
+
*/
|
|
214
|
+
keepsOpen: boolean;
|
|
215
|
+
/**
|
|
216
|
+
* @internal Whether the row is a choice: it can be ticked, or is one of a
|
|
217
|
+
* set. Read by the menu to reserve the trailing column.
|
|
218
|
+
*/
|
|
219
|
+
get choice(): boolean;
|
|
195
220
|
/**
|
|
196
221
|
* Whether anything was slotted into `submenu`.
|
|
197
222
|
*
|
|
@@ -342,8 +367,14 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
342
367
|
private tick;
|
|
343
368
|
/** The other `exclusive` rows in this row's run of siblings. */
|
|
344
369
|
private exclusivePeers;
|
|
370
|
+
/**
|
|
371
|
+
* Chooses the row. `space` is whether a Space chose it, which keeps the menu
|
|
372
|
+
* open on a choice row unless the row says otherwise.
|
|
373
|
+
*/
|
|
345
374
|
private choose;
|
|
346
375
|
private readonly handleClick;
|
|
376
|
+
/** Whether the click on its way is the one a Space on a linked row sent. */
|
|
377
|
+
private spacePressed;
|
|
347
378
|
/**
|
|
348
379
|
* Enter and Space choose the row, which a `<div>` would not do on its own.
|
|
349
380
|
*
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { ariaChord, auditName, customElement, deepActiveElement, HelixBeforeToggleEvent, HelixElement, HelixSelectEvent, isRtl, onApple, PopoverController, requestChange, slotFilled, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { ariaChord, auditName, customElement, deepActiveElement, HelixBeforeToggleEvent, HelixElement, HelixSelectEvent, isRtl, onApple, onUnlessFalse, PopoverController, requestChange, slotFilled, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { caretRight, check } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -29,6 +29,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
29
29
|
this.target = '';
|
|
30
30
|
this.value = '';
|
|
31
31
|
this.isMenuItem = true;
|
|
32
|
+
this.keepsOpen = false;
|
|
32
33
|
this.submenu = false;
|
|
33
34
|
this.start = false;
|
|
34
35
|
this.end = false;
|
|
@@ -58,8 +59,10 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
58
59
|
surface: () => (this.flyout ? this.partNamed('surface') : null),
|
|
59
60
|
});
|
|
60
61
|
this.handleClick = () => {
|
|
61
|
-
this.choose();
|
|
62
|
+
this.choose(this.spacePressed);
|
|
63
|
+
this.spacePressed = false;
|
|
62
64
|
};
|
|
65
|
+
this.spacePressed = false;
|
|
63
66
|
this.handleKeydown = (event) => {
|
|
64
67
|
if (event.key !== 'Enter' && event.key !== ' ') {
|
|
65
68
|
return;
|
|
@@ -70,12 +73,17 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
70
73
|
}
|
|
71
74
|
event.preventDefault();
|
|
72
75
|
if (link) {
|
|
76
|
+
this.spacePressed = true;
|
|
73
77
|
link.click();
|
|
78
|
+
this.spacePressed = false;
|
|
74
79
|
return;
|
|
75
80
|
}
|
|
76
|
-
this.
|
|
81
|
+
this.choose(event.key === ' ');
|
|
77
82
|
};
|
|
78
83
|
}
|
|
84
|
+
get choice() {
|
|
85
|
+
return this.checkable || this.checked || this.exclusive;
|
|
86
|
+
}
|
|
79
87
|
get hasSubmenu() {
|
|
80
88
|
return this.submenu;
|
|
81
89
|
}
|
|
@@ -93,9 +101,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
93
101
|
const rows = this.knownChildren.filter((child) => child.slot === 'submenu' && child.isMenuItem);
|
|
94
102
|
this.toggleState('submenu-start', this.flyout && rows.some((row) => row.querySelector(':scope > [slot="start"]') !== null));
|
|
95
103
|
this.toggleState('submenu-mark', this.flyout &&
|
|
96
|
-
rows.some((row) => row.
|
|
97
|
-
row.checked ||
|
|
98
|
-
[...row.children].some((child) => child.slot === 'submenu')));
|
|
104
|
+
rows.some((row) => row.choice || [...row.children].some((child) => child.slot === 'submenu')));
|
|
99
105
|
}
|
|
100
106
|
firstUpdated(changed) {
|
|
101
107
|
super.firstUpdated(changed);
|
|
@@ -152,7 +158,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
152
158
|
return this.active;
|
|
153
159
|
}
|
|
154
160
|
tick() {
|
|
155
|
-
if (!
|
|
161
|
+
if (!this.choice || this.hasSubmenu) {
|
|
156
162
|
return;
|
|
157
163
|
}
|
|
158
164
|
const next = this.exclusive ? true : !this.checked;
|
|
@@ -188,10 +194,12 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
188
194
|
}
|
|
189
195
|
return peers;
|
|
190
196
|
}
|
|
191
|
-
choose() {
|
|
197
|
+
choose(space = false) {
|
|
192
198
|
if (this.disabled) {
|
|
193
199
|
return;
|
|
194
200
|
}
|
|
201
|
+
const spaceKeeps = space && this.choice && !this.hasSubmenu;
|
|
202
|
+
this.keepsOpen = this.closeOnSelect === undefined ? spaceKeeps : !this.closeOnSelect;
|
|
195
203
|
this.tick();
|
|
196
204
|
this.dispatchEvent(new HelixSelectEvent({ value: this.value }));
|
|
197
205
|
}
|
|
@@ -221,7 +229,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
221
229
|
}
|
|
222
230
|
const submenu = this.hasSubmenu;
|
|
223
231
|
const linked = this.href !== '' && !this.disabled;
|
|
224
|
-
const ticks = this.
|
|
232
|
+
const ticks = this.choice;
|
|
225
233
|
const role = ticks ? (this.exclusive ? 'menuitemradio' : 'menuitemcheckbox') : 'menuitem';
|
|
226
234
|
const checkedState = ticks ? (this.checked ? 'true' : 'false') : nothing;
|
|
227
235
|
const keys = this.shortcut === '' ? nothing : ariaChord(this.shortcut, !this.hydrating && onApple());
|
|
@@ -281,6 +289,9 @@ __decorate([
|
|
|
281
289
|
__decorate([
|
|
282
290
|
property({ type: Boolean })
|
|
283
291
|
], HelixHeadlessMenuItem.prototype, "checked", void 0);
|
|
292
|
+
__decorate([
|
|
293
|
+
property({ attribute: 'close-on-select', converter: onUnlessFalse })
|
|
294
|
+
], HelixHeadlessMenuItem.prototype, "closeOnSelect", void 0);
|
|
284
295
|
__decorate([
|
|
285
296
|
property({ reflect: true, type: Boolean })
|
|
286
297
|
], HelixHeadlessMenuItem.prototype, "disabled", void 0);
|
|
@@ -40,7 +40,8 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
|
|
|
40
40
|
* Keys: Enter, Space or Down Arrow on the trigger opens the menu on its first
|
|
41
41
|
* row, and Up Arrow on its last. In the menu, Up and Down Arrow move between
|
|
42
42
|
* rows, Home and End go to the first and the last, a typed letter moves to
|
|
43
|
-
* the next row whose name starts with it, Enter and Space choose
|
|
43
|
+
* the next row whose name starts with it, Enter and Space choose (Space on a
|
|
44
|
+
* choice row changes it and leaves the menu open), Right Arrow
|
|
44
45
|
* opens a row's submenu and Left Arrow goes back, the two swapping on a
|
|
45
46
|
* right-to-left page. Left Arrow on the top page does nothing. Escape closes
|
|
46
47
|
* the menu and returns focus to the trigger. Tab closes it and moves on.
|
|
@@ -109,6 +110,14 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
109
110
|
* The text of the row that returns from a submenu.
|
|
110
111
|
*/
|
|
111
112
|
backLabel: string;
|
|
113
|
+
/**
|
|
114
|
+
* Whether choosing a row closes the menu.
|
|
115
|
+
*
|
|
116
|
+
* On by default, and a Space on a choice row leaves the menu open either way.
|
|
117
|
+
* Write `close-on-select="false"` to keep the menu open after every choice, as
|
|
118
|
+
* Ark UI's `closeOnSelect` does. A row's own `close-on-select` wins over this.
|
|
119
|
+
*/
|
|
120
|
+
closeOnSelect: boolean;
|
|
112
121
|
/**
|
|
113
122
|
* Whether the menu is the trigger's context menu: it opens where the reader
|
|
114
123
|
* asks for one, with a right-click, a long press, Shift+F10 or the menu key.
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, anchorElement, customElement, DrilldownController, deepActiveElement, deepContains, focusTarget, HelixElement, inlineStep, KeyboardNavigationController, linkDescription, MixinCollection, MixinControllerOpen, PopoverController, scrollItemIntoView, sideOf, slottedWords, tellDisclosure, typeaheadBy, unlinkDescription, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, anchorElement, customElement, DrilldownController, deepActiveElement, deepContains, focusTarget, HelixElement, inlineStep, KeyboardNavigationController, linkDescription, MixinCollection, MixinControllerOpen, onUnlessFalse, PopoverController, scrollItemIntoView, sideOf, slottedWords, tellDisclosure, typeaheadBy, unlinkDescription, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { caretLeft } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property } from 'lit/decorators.js';
|
|
@@ -31,6 +31,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
31
31
|
constructor() {
|
|
32
32
|
super(...arguments);
|
|
33
33
|
this.backLabel = 'Back';
|
|
34
|
+
this.closeOnSelect = true;
|
|
34
35
|
this.context = false;
|
|
35
36
|
this.label = '';
|
|
36
37
|
this.longPressLabel = 'Long press or press Alt+Down to show the menu';
|
|
@@ -119,8 +120,9 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
119
120
|
this.enter(chosen);
|
|
120
121
|
return;
|
|
121
122
|
}
|
|
123
|
+
const keep = chosen.keepsOpen || (chosen.closeOnSelect === undefined && !this.closeOnSelect);
|
|
122
124
|
queueMicrotask(() => {
|
|
123
|
-
if (!event.defaultPrevented) {
|
|
125
|
+
if (!(event.defaultPrevented || keep)) {
|
|
124
126
|
this.hide();
|
|
125
127
|
}
|
|
126
128
|
});
|
|
@@ -538,7 +540,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
538
540
|
markColumns() {
|
|
539
541
|
const rows = this.flies ? this.topRows : this.drilldown.rows;
|
|
540
542
|
this.toggleState('start', rows.some((row) => row.querySelector(':scope > [slot="start"]') !== null));
|
|
541
|
-
this.toggleState('mark', rows.some((row) => row.
|
|
543
|
+
this.toggleState('mark', rows.some((row) => row.choice || this.drilldown.hasPage(row)));
|
|
542
544
|
}
|
|
543
545
|
mark(item, onPage) {
|
|
544
546
|
const page = this.drilldown.page;
|
|
@@ -608,6 +610,9 @@ __decorate([
|
|
|
608
610
|
words,
|
|
609
611
|
property({ attribute: 'back-label' })
|
|
610
612
|
], HelixHeadlessMenu.prototype, "backLabel", void 0);
|
|
613
|
+
__decorate([
|
|
614
|
+
property({ attribute: 'close-on-select', converter: onUnlessFalse })
|
|
615
|
+
], HelixHeadlessMenu.prototype, "closeOnSelect", void 0);
|
|
611
616
|
__decorate([
|
|
612
617
|
property({ reflect: true, type: Boolean })
|
|
613
618
|
], HelixHeadlessMenu.prototype, "context", void 0);
|
|
@@ -150,7 +150,13 @@ export declare class HelixHeadlessSteps extends HelixHeadlessSteps_base {
|
|
|
150
150
|
protected readonly keys: KeyboardNavigationController;
|
|
151
151
|
/** The steps, in the order the consumer wrote them. */
|
|
152
152
|
private get steps();
|
|
153
|
-
/**
|
|
153
|
+
/**
|
|
154
|
+
* The index of the stage the reader is on, or `-1`.
|
|
155
|
+
*
|
|
156
|
+
* Read from `active` once anything has set it, because a move writes
|
|
157
|
+
* `active` at once and each step's `current` only on the next update: two
|
|
158
|
+
* calls to `next()` in one task then move two stages.
|
|
159
|
+
*/
|
|
154
160
|
private get at();
|
|
155
161
|
/**
|
|
156
162
|
* Moves to the next stage that can be gone to, as Ark UI's `goToNextStep`
|
|
@@ -58,7 +58,12 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
|
|
|
58
58
|
return [...this.querySelectorAll(':scope > *')].filter((one) => 'current' in one);
|
|
59
59
|
}
|
|
60
60
|
get at() {
|
|
61
|
-
|
|
61
|
+
const steps = this.steps;
|
|
62
|
+
if (this.active === '') {
|
|
63
|
+
return steps.findIndex((step) => step.current);
|
|
64
|
+
}
|
|
65
|
+
const item = this.resolveActive(steps);
|
|
66
|
+
return item ? steps.indexOf(item) : -1;
|
|
62
67
|
}
|
|
63
68
|
next() {
|
|
64
69
|
return this.step(1);
|