@fusion.dev/helix-headless 0.1.0 → 0.3.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.
Files changed (127) hide show
  1. package/custom-elements.json +234 -234
  2. package/dist/accordion/item/accordion-item.js +0 -108
  3. package/dist/accordion/root/accordion-root.js +0 -33
  4. package/dist/announcement/root/announcement-root.js +1 -76
  5. package/dist/app-header/root/app-header-root.js +0 -84
  6. package/dist/avatar/group/avatar-group.js +0 -77
  7. package/dist/avatar/root/avatar-root.js +0 -72
  8. package/dist/badge/root/badge-root.js +0 -67
  9. package/dist/bar-chart/root/bar-chart-root.js +3 -263
  10. package/dist/blockquote/root/blockquote-root.js +0 -46
  11. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +0 -72
  12. package/dist/breadcrumbs/root/breadcrumbs-root.js +0 -52
  13. package/dist/button/root/button-root.js +0 -215
  14. package/dist/button-group/root/button-group-root.js +0 -30
  15. package/dist/callout/root/callout-root.js +0 -52
  16. package/dist/card/root/card-root.js +0 -41
  17. package/dist/carousel/root/carousel-root.d.ts +2 -2
  18. package/dist/carousel/root/carousel-root.js +2 -362
  19. package/dist/chat/composer/chat-composer.js +0 -131
  20. package/dist/chat/message/chat-message.js +0 -137
  21. package/dist/chat/root/chat-root.js +0 -193
  22. package/dist/checkbox/root/checkbox-root.js +0 -113
  23. package/dist/checkbox-group/root/checkbox-group-root.js +1 -157
  24. package/dist/code/root/code-root.js +0 -26
  25. package/dist/code-block/root/code-block-root.js +0 -100
  26. package/dist/color-input/root/color-input-root.js +0 -217
  27. package/dist/color-picker/color.js +0 -69
  28. package/dist/color-picker/root/color-picker-root.js +0 -272
  29. package/dist/combobox/root/combobox-root.js +0 -428
  30. package/dist/command/root/command-root.js +0 -177
  31. package/dist/copy-button/root/copy-button-root.js +0 -134
  32. package/dist/data-table/root/data-table-root.js +0 -336
  33. package/dist/data-table/root/order.js +0 -100
  34. package/dist/date-input/root/date-input-root.js +1 -398
  35. package/dist/date-picker/root/date-picker-root.js +0 -358
  36. package/dist/dialog/root/dialog-root.js +0 -189
  37. package/dist/divider/root/divider-root.js +0 -71
  38. package/dist/donut-chart/root/donut-chart-root.js +0 -214
  39. package/dist/drawer/root/drawer-root.js +0 -116
  40. package/dist/drilldown/item/drilldown-item.js +0 -82
  41. package/dist/drilldown/root/drilldown-root.js +0 -91
  42. package/dist/element-events.generated.js +0 -5
  43. package/dist/empty/root/empty-root.js +0 -35
  44. package/dist/file-input/root/file-input-root.js +0 -232
  45. package/dist/format-date/root/format-date-root.js +0 -95
  46. package/dist/heatmap/root/buckets.js +0 -57
  47. package/dist/heatmap/root/heatmap-root.js +0 -313
  48. package/dist/icon/root/icon-root.js +0 -48
  49. package/dist/icon-button/root/icon-button-root.js +0 -30
  50. package/dist/image/root/image-root.js +0 -174
  51. package/dist/index.js +0 -9
  52. package/dist/input/root/input-root.js +0 -228
  53. package/dist/kbd/root/kbd-root.js +0 -117
  54. package/dist/legend/root/legend-root.js +0 -73
  55. package/dist/line-chart/root/line-chart-root.js +0 -172
  56. package/dist/list/item/list-item.js +0 -92
  57. package/dist/list/root/list-root.js +0 -26
  58. package/dist/listbox/root/listbox-root.js +2 -429
  59. package/dist/loader/root/loader-root.js +0 -40
  60. package/dist/megamenu/root/megamenu-root.js +0 -284
  61. package/dist/menu/item/menu-item.js +1 -221
  62. package/dist/menu/root/menu-root.js +1 -171
  63. package/dist/meter/root/meter-root.js +1 -152
  64. package/dist/navigation/root/navigation-root.js +0 -56
  65. package/dist/notifications/notification/notification.js +0 -159
  66. package/dist/notifications/root/notifications-root.js +0 -148
  67. package/dist/number-input/root/number-input-root.js +1 -263
  68. package/dist/option/group/option-group.js +0 -26
  69. package/dist/option/root/option-root.js +0 -157
  70. package/dist/outline/root/outline-root.js +0 -292
  71. package/dist/pagination/root/pagination-root.js +0 -361
  72. package/dist/pagination/size/pagination-size.js +0 -104
  73. package/dist/pagination/summary/pagination-summary.js +0 -90
  74. package/dist/pin-input/root/pin-input-root.js +0 -276
  75. package/dist/popover/root/popover-root.js +0 -103
  76. package/dist/progress/root/progress-root.js +1 -133
  77. package/dist/qr-code/root/qr-code-root.js +0 -96
  78. package/dist/quote/root/quote-root.js +0 -25
  79. package/dist/radio/group/radio-group.js +0 -81
  80. package/dist/radio/root/radio-root.js +0 -85
  81. package/dist/rating/root/rating-root.js +0 -220
  82. package/dist/scroll-area/root/scroll-area-root.js +0 -100
  83. package/dist/select/root/select-root.js +2 -218
  84. package/dist/skeleton/root/skeleton-root.js +0 -20
  85. package/dist/slider/root/slider-root.js +1 -267
  86. package/dist/sparkline/root/sparkline-root.js +3 -164
  87. package/dist/speed-dial/item/speed-dial-item.js +0 -52
  88. package/dist/speed-dial/root/speed-dial-root.js +1 -116
  89. package/dist/splitter/root/splitter-root.js +0 -489
  90. package/dist/stat/group/stat-group.js +0 -18
  91. package/dist/stat/root/stat-root.js +0 -23
  92. package/dist/steps/root/steps-root.js +0 -62
  93. package/dist/steps/step/step.js +0 -131
  94. package/dist/switch/root/switch-root.js +0 -80
  95. package/dist/table/cell/table-cell.js +0 -23
  96. package/dist/table/column/table-column.js +0 -158
  97. package/dist/table/root/table-root.js +0 -82
  98. package/dist/table/row/table-row.js +0 -21
  99. package/dist/tabs/panel/tab-panel.js +0 -49
  100. package/dist/tabs/root/tabs-root.js +0 -98
  101. package/dist/tabs/tab/tab.js +0 -59
  102. package/dist/tag/root/tag-root.js +0 -31
  103. package/dist/textarea/root/textarea-root.js +0 -162
  104. package/dist/time-input/root/time-input-root.js +0 -283
  105. package/dist/time-picker/root/time-picker-root.js +0 -326
  106. package/dist/timeline/item/timeline-item.js +0 -66
  107. package/dist/timeline/root/timeline-root.js +0 -41
  108. package/dist/toast/item/toast-item.js +0 -165
  109. package/dist/toast/root/toast-root.js +0 -51
  110. package/dist/toggle/group/toggle-group.js +0 -150
  111. package/dist/toggle/root/toggle-root.js +0 -78
  112. package/dist/toolbar/root/toolbar-root.js +0 -116
  113. package/dist/tooltip/root/tooltip-root.js +0 -427
  114. package/dist/tour/root/tour-root.js +0 -130
  115. package/dist/tour/stop/tour-stop.js +1 -127
  116. package/dist/tree/item/tree-item.js +0 -198
  117. package/dist/tree/root/tree-root.js +0 -97
  118. package/dist/trend/root/trend-root.js +0 -53
  119. package/dist/video/root/video-root.d.ts +8 -12
  120. package/dist/video/root/video-root.js +5 -756
  121. package/dist/video-playlist/item/video-playlist-item.js +1 -90
  122. package/dist/video-playlist/menu/video-playlist-menu.js +1 -132
  123. package/dist/video-playlist/root/video-playlist-root.js +0 -113
  124. package/dist/virtual-list/root/virtual-list-root.js +0 -105
  125. package/jsx.d.ts +1408 -2576
  126. package/package.json +6 -6
  127. package/vue.d.ts +1 -1
@@ -7,99 +7,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixBeforeActivateEvent, HelixBeforeCloseEvent, HelixCompleteEvent, HelixElement, hostAria, requestChange, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property, queryAssignedElements, state } from 'lit/decorators.js';
10
- /**
11
- * @summary A guided run through an interface, one stop at a time.
12
- *
13
- * @description
14
- * The root owns the order and nothing else. Which stop is current, what Next
15
- * and Back mean, when the run is finished; the stops own where they point, and
16
- * the consumer owns every word.
17
- *
18
- * **The spotlight is a shadow, not a hole.** The scrim is a box the size of the
19
- * current target with an enormous spread on its `box-shadow`, so the darkness
20
- * is what the shadow paints and the target is simply where the box is. A
21
- * `clip-path` on a full-screen scrim is the other way to do it and is worse
22
- * here: it needs the viewport in the same coordinate space as the cut, which
23
- * stops being true the moment anything between them is transformed.
24
- *
25
- * **It measures, and this is a case where measuring is the only option.** The
26
- * target is an element somewhere else on the page whose position nothing can
27
- * know statically. That is different from a trail folding by width, where the
28
- * design rule was expressible and the measurement was answering a question
29
- * nobody had asked. Here the question is "where is that button", and there is
30
- * no other way to ask it.
31
- *
32
- * **Nothing is measured on the server and nothing needs to be.** A tour starts
33
- * closed and a closed tour draws nothing, so the server renders the stops as
34
- * inert markup and the first frame is correct. This is also what makes it
35
- * acceptable for the root to write `current` onto stops it does not own: there
36
- * is no server state for the write to disagree with.
37
- *
38
- * **It cannot point inside a modal, and nothing can fix that here.**
39
- * `showModal()` makes everything outside the dialog's own subtree inert, so a
40
- * panel anywhere else is unpressable however the top layer is ordered: this is
41
- * what modal means rather than a stacking accident. Measured with a modal
42
- * `Drawer` open, `elementsFromPoint` over the tour's own Done button returned
43
- * the drawer, the press light-dismissed the drawer, and the tour heard nothing
44
- * at all. Anything a tour points into has to stay reachable, which for `Drawer`
45
- * and `Dialog` means not modal while the tour is running.
46
- *
47
- * **Focus is not trapped.** A reader has to be able to look at, and reach, the
48
- * thing being pointed at. `Escape` leaves, which is the one binding a tour
49
- * needs and the one every reader already tries.
50
- *
51
- * @since 0.1.0
52
- * @status experimental
53
- * @category overlays
54
- * @aliases Coachmark, Product tour, Walkthrough, Onboarding
55
- * @documentation https://helix.fusion.dev/components/tour
56
- *
57
- * @slot - The stops, in the order they should be visited.
58
- *
59
- * @event {HelixBeforeActivateEvent} helix-before-activate - Fired before the current stop moves, carrying the new index. Cancelable: refusing it takes the decision and nothing moves.
60
- * @event {HelixBeforeCloseEvent} helix-before-close - Fired before the reader leaves early. Cancelable: refusing it takes the decision and nothing moves.
61
- * @event {HelixCompleteEvent} helix-complete - Fired when the last stop is
62
- * finished.
63
- * @event {Event} toggle - Says the state moved, carrying `newState` and `oldState`.
64
- *
65
- * @csspart spotlight - The box over the current target, which paints the scrim.
66
- */
67
10
  let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
68
11
  constructor() {
69
12
  super(...arguments);
70
- /**
71
- * Label for the control that goes back, handed to every stop.
72
- */
73
13
  this.backLabel = '';
74
- /**
75
- * Stop to start on, where nothing declared one.
76
- */
77
14
  this.defaultIndex = 0;
78
- /** The stop being shown, whoever set it, and `undefined` until somebody has. */
79
15
  this.indexState = undefined;
80
- /**
81
- * Label for the control that goes on from the last stop.
82
- */
83
16
  this.doneLabel = '';
84
- /**
85
- * Accessible name for the run, such as "Getting started".
86
- */
87
17
  this.label = '';
88
- /**
89
- * Label for the control that goes on.
90
- */
91
18
  this.nextLabel = '';
92
- /**
93
- * Whether the tour is running.
94
- */
95
19
  this.open = false;
96
- /**
97
- * Label for the control that leaves the tour.
98
- */
99
20
  this.skipLabel = '';
100
- /**
101
- * Space left around the target when it is lit, in pixels.
102
- */
103
21
  this.spotlightPadding = 8;
104
22
  this.handleAsk = (event) => {
105
23
  const what = event.detail?.what;
@@ -119,24 +37,10 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
119
37
  this.skip();
120
38
  }
121
39
  };
122
- /* On the window, because the scrim is fixed and a scroll or a resize moves
123
- the target out from under it. Passive: neither handler cancels anything. */
124
40
  this.handleViewport = () => {
125
41
  this.light();
126
42
  };
127
43
  }
128
- /**
129
- * Which stop the reader is on.
130
- *
131
- * **Moved through `requestChange`, never by assignment from `move`.** That
132
- * announces first and applies only if nobody refused, and refusing is how a
133
- * consumer takes the decision; writing the property and then announcing puts
134
- * `preventDefault` after the move it is refusing.
135
- *
136
- * The shape is `Carousel`'s, which is the nearest thing in the library: a
137
- * `default-index` for markup that only seeds and a held state that is
138
- * `undefined` until somebody sets one.
139
- */
140
44
  get index() {
141
45
  return this.indexState ?? this.defaultIndex;
142
46
  }
@@ -145,15 +49,12 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
145
49
  this.indexState = next;
146
50
  this.requestUpdate('index', previous);
147
51
  }
148
- /** The stops, in the order they were written. */
149
52
  get stops() {
150
53
  return (this.assigned ?? []).filter((one) => one.current !== undefined);
151
54
  }
152
- /** How many stops there are. */
153
55
  get length() {
154
56
  return this.stops.length;
155
57
  }
156
- /** Goes on, and finishes the run on the last stop. */
157
58
  next() {
158
59
  if (this.index >= this.length - 1) {
159
60
  this.finish();
@@ -161,20 +62,12 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
161
62
  }
162
63
  this.move(this.index + 1);
163
64
  }
164
- /** Goes back, and does nothing on the first stop. */
165
65
  back() {
166
66
  if (this.index <= 0) {
167
67
  return;
168
68
  }
169
69
  this.move(this.index - 1);
170
70
  }
171
- /**
172
- * Leaves the run early, which is a close rather than a completion.
173
- *
174
- * Announced before it is applied, and cancelable because of it: writing
175
- * `open` first and dispatching after leaves a consumer calling
176
- * `preventDefault` refusing something that has already happened.
177
- */
178
71
  skip() {
179
72
  const event = new HelixBeforeCloseEvent();
180
73
  this.dispatchEvent(event);
@@ -183,17 +76,10 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
183
76
  }
184
77
  this.open = false;
185
78
  }
186
- /* Announced before it closes, and not cancelable, which is the difference
187
- from `skip`. `HelixCompleteEvent` is `cancelable: false` by construction:
188
- leaving early is a request a consumer may refuse, and having reached the
189
- end is something that has already happened. What ordering still buys is a
190
- listener that reads `open` and sees the tour as it was when it finished. */
191
79
  finish() {
192
80
  this.dispatchEvent(new HelixCompleteEvent({ value: String(this.index) }));
193
81
  this.open = false;
194
82
  }
195
- /* The one path every stateful change in the library goes through: it
196
- announces, then applies where nothing refused. */
197
83
  move(to) {
198
84
  requestChange({
199
85
  apply: (value) => {
@@ -206,13 +92,6 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
206
92
  }
207
93
  connectedCallback() {
208
94
  super.connectedCallback();
209
- /* On the host, not on the slot the stops are assigned to.
210
- The event is composed and bubbles, so in a browser it reaches either. It
211
- does not reach the slot under `bun test`: happy-dom does not propagate
212
- events through slot assignment, so a listener bound there hears nothing
213
- and every spec about pressing Next passes for the wrong reason or fails
214
- for a reason that is not the component's. `Megamenu` moved its own
215
- listener to the host for exactly this. */
216
95
  this.addEventListener('helix-tour-ask', this.handleAsk);
217
96
  this.ownerDocument.addEventListener('keydown', this.handleKeydown);
218
97
  globalThis.addEventListener('resize', this.handleViewport, { passive: true });
@@ -234,14 +113,6 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
234
113
  this.settle();
235
114
  this.light();
236
115
  }
237
- /**
238
- * Tells each stop where it is in the run.
239
- *
240
- * Written onto the stops, which is the one outward write in this family and
241
- * is argued for on the class above. The labels go with it, and only where the
242
- * root was given one, so a stop keeps its own default unless somebody has
243
- * localised the tour.
244
- */
245
116
  settle() {
246
117
  const stops = this.stops;
247
118
  const at = Math.max(0, Math.min(this.index, stops.length - 1));
@@ -263,7 +134,6 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
263
134
  }
264
135
  });
265
136
  }
266
- /** Reads the current target's box, which is where the light falls. */
267
137
  light() {
268
138
  const stops = this.stops;
269
139
  const target = this.open
@@ -8,139 +8,36 @@ import { customElement, HelixElement, HelixTourAskEvent, hostAria, PopoverContro
8
8
  import { property } from 'lit/decorators.js';
9
9
  import { html } from 'lit/static-html.js';
10
10
  import '../../button/index.js';
11
- /**
12
- * @summary One stop on a tour: what it points at, and a panel to say it in.
13
- *
14
- * @description
15
- * A stop names a target with `for` and holds whatever the consumer puts in it.
16
- * It owns no content of its own, which is the whole shape of this family: the
17
- * tour abstracts the three things that are genuinely hard, and the words are
18
- * the consumer's.
19
- *
20
- * What it does own:
21
- *
22
- * - **Where the panel goes.** Anchored to the target through the same floating
23
- * pass every other overlay here uses.
24
- * - **That it is on top.** A popover, so it is in the top layer. A tour points
25
- * at things inside dialogs, inside scrollers, inside anything with a stacking
26
- * context, and a panel in the ordinary flow loses to all three.
27
- * - **When it shows.** Which is `current`, and which the root decides.
28
- *
29
- * What it does not own is the heading, the paragraph, the picture and the
30
- * layout. Slot a `helix-card`, or a heading and a line of prose, or anything
31
- * else. Fixed `title` and `body` parts would be a component deciding what a
32
- * tour is allowed to say.
33
- *
34
- * **The buttons are slot fallback, so the common case costs nothing and the
35
- * uncommon one is possible.** Write nothing and get Back, Next and Skip already
36
- * wired. Slot `actions` and they are yours, driven by `next()`, `back()` and
37
- * `skip()` on the root, which are public for exactly that.
38
- *
39
- * **`for` is an id, the same as `Tooltip`'s.** The target is elsewhere on the
40
- * page, and an id is the one reference a consumer can write in markup without
41
- * holding an element. Resolved against the root node, so a stop inside a shadow
42
- * tree points at a target in that tree.
43
- *
44
- * @since 0.1.0
45
- * @status experimental
46
- * @category overlays
47
- * @documentation https://helix.fusion.dev/components/tour
48
- *
49
- * @slot - What this stop says. A card, a heading and a line, anything.
50
- * @slot actions - Replaces the controls, which are Back, Next and Skip.
51
- *
52
- * @event {HelixTourAskEvent} helix-tour-ask - A stop asking the tour to move, from its own controls.
53
- *
54
- * @csspart actions - The row the controls sit in.
55
- * @csspart arrow - The point, which the floating pass places against the target.
56
- * @csspart back - The control that goes to the previous stop.
57
- * @csspart back-button - The inner control.
58
- * @csspart bubble - The panel that points at the target.
59
- * @csspart content - The region holding the slotted content.
60
- * @csspart next - The control that goes on, and finishes on the last stop.
61
- * @csspart next-button - The inner control.
62
- * @csspart skip - The control that leaves the tour.
63
- * @csspart skip-button - The inner control.
64
- */
65
11
  let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
66
12
  constructor() {
67
13
  super(...arguments);
68
- /**
69
- * Label for the control that goes back.
70
- */
71
14
  this.backLabel = 'Back';
72
- /**
73
- * Whether this is the stop the reader is on.
74
- *
75
- * Written by the root rather than by a consumer, which is the one place this
76
- * family writes onto markup it does not own. It is acceptable here for a
77
- * reason that did not hold for the trail or the avatar group: a tour starts
78
- * closed, so there is no server state to get wrong. The server renders a
79
- * closed tour, a closed tour shows nothing, and nothing can disagree.
80
- */
81
15
  this.current = false;
82
- /**
83
- * Label for the control that goes on from the last stop.
84
- */
85
16
  this.doneLabel = 'Done';
86
- /**
87
- * Whether this is the first stop, so there is nowhere back to.
88
- */
89
17
  this.first = false;
90
- /**
91
- * The id of the element this stop is about.
92
- */
93
18
  this.for = '';
94
- /**
95
- * Whether this is the last stop, so the control says Done.
96
- */
97
19
  this.last = false;
98
- /**
99
- * Label for the control that goes on.
100
- */
101
20
  this.nextLabel = 'Next';
102
- /**
103
- * Which side of the target the panel prefers.
104
- */
105
21
  this.placement = 'bottom';
106
- /**
107
- * Label for the control that leaves the tour.
108
- */
109
22
  this.skipLabel = 'Skip';
110
23
  this.floating = new PopoverController(this, {
111
24
  anchor: () => this.target,
112
25
  arrow: () => this.renderRoot?.querySelector('[part="arrow"]'),
113
- /* A press outside does not end a tour, and this is the decision rather than
114
- a stub. Everywhere else a surface closes on an outside press because
115
- pressing elsewhere means the reader is finished with it. Here elsewhere
116
- is the thing being pointed at: the scrim lets the pointer through on
117
- purpose, so the first press a reader makes on the button the tour is
118
- describing would be the press that ends the tour. Leaving is the skip
119
- control and `Escape`, both of which say so. */
120
26
  onDismiss: () => { },
121
27
  placement: () => this.placement,
122
28
  surface: () => this.renderRoot?.querySelector('[part="bubble"]'),
123
29
  });
124
30
  }
125
- /** The element this stop is about, or null where the id resolves to nothing. */
126
31
  get target() {
127
32
  const scope = this.getRootNode();
128
33
  return this.for === '' ? null : (scope.getElementById?.(this.for) ?? null);
129
34
  }
130
35
  willUpdate(changed) {
131
36
  super.willUpdate(changed);
132
- /* `group`, not `dialog`, and this is a decision rather than a shortcut. A
133
- tour must not trap focus: the reader has to be able to look at the thing
134
- being pointed at, and an overlay that will not let them has hidden its
135
- own subject. */
136
37
  hostAria(this.internals, { role: 'group' });
137
38
  }
138
39
  updated(changed) {
139
40
  super.updated(changed);
140
- /* Shown and hidden from here rather than by a controller reading a
141
- property, because `current` is written onto this element by the root and
142
- is not a property this element decides. The controller does the top layer
143
- and the placement; when is ours. */
144
41
  if (this.current) {
145
42
  this.floating.show();
146
43
  }
@@ -148,15 +45,6 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
148
45
  this.floating.hide();
149
46
  }
150
47
  }
151
- /**
152
- * Skip, back and next, behind a seam the styled tier can move.
153
- *
154
- * They are slot fallback rather than markup a consumer has to write, so a
155
- * tour with nothing slotted still has the three controls a tour needs; and
156
- * they are a method as well, so a tier above can render its own three without
157
- * reimplementing the panel around them. Both are needed: the slot is for the
158
- * consumer and the method is for the tier.
159
- */
160
48
  renderActions() {
161
49
  return html `<slot name="actions">
162
50
  <${this.tag('button')}
@@ -187,14 +75,7 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
187
75
  }
188
76
  render() {
189
77
  return html `<div part="bubble" popover="manual" ?hidden=${!this.current}>
190
- ${
191
- /* The one surface in this library that draws an arrow, and every other
192
- one deliberately does not. A menu or a select hangs off its trigger
193
- and adjacency says what it belongs to; a tour panel is anchored to
194
- something it did not open, on a page full of other things, and
195
- collision may flip it to the far side of its target. Leaving arrows
196
- off elsewhere is house style rather than a fact about the system. */
197
- ''}
78
+ ${''}
198
79
  <div part="arrow" aria-hidden="true"></div>
199
80
  <div part="content"><slot></slot></div>
200
81
  <div part="actions">
@@ -202,13 +83,6 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
202
83
  </div>
203
84
  </div>`;
204
85
  }
205
- /**
206
- * Says what the reader pressed and lets the root decide.
207
- *
208
- * A stop cannot move a tour: it does not know what is after it, and a stop
209
- * that reached up to its parent to find out would be a stop that only works
210
- * inside one. Composed, so the root hears it through its own slot.
211
- */
212
86
  ask(what) {
213
87
  this.dispatchEvent(new HelixTourAskEvent({ what }));
214
88
  }
@@ -9,141 +9,23 @@ import { caretDown } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
- /**
13
- * @summary One node in a tree.
14
- *
15
- * @description
16
- * **`open`, on `MixinControllerOpen`, rather than a bare boolean of its own.** A
17
- * branch showing its children is the same state a disclosure holds, and
18
- * `AccordionItem` is on that mixin too. A hand-written toggle announces nothing,
19
- * so a consumer can neither know a branch opened nor own the state.
20
- * `aria-expanded` is still what the row renders, because that is the attribute a
21
- * tree uses; the property is what differs, not the semantics.
22
- *
23
- * An element rather than a slot because it owns state: whether it is open,
24
- * whether it is selected, and whether it is the one holding focus.
25
- *
26
- * A branch is any item with something slotted into `children`, detected in
27
- * `firstUpdated` rather than declared by the consumer. That is a client-side
28
- * enhancement by necessity, since the server cannot inspect children, so the
29
- * server renders the node as a leaf and the twisty appears on hydration. It
30
- * appears rather than disappearing, which is the harmless direction.
31
- *
32
- * **`href` makes the row itself the link.** A tree used as navigation is a list
33
- * of links, and the row is what a reader presses: with the anchor slotted into
34
- * the name instead, everything outside the words is dead, Enter does nothing,
35
- * and there is no middle-click, no status bar and no open-in-new-tab. Given an
36
- * `href` the row *is* an `<a>`, so the platform provides all of that and the
37
- * keyboard needs no handler at all.
38
- *
39
- * It changes the shape of the ARIA, which is why it is a property and not the
40
- * default. An anchor cannot contain the group, since the group holds more
41
- * anchors and nesting them is invalid, so the row carries `treeitem` and the
42
- * group becomes its sibling under a presentational wrapper. That is exactly the
43
- * navigation flavor of the tree pattern, and the arrangement without an `href`
44
- * stays as it was: `treeitem` containing `group`, which is what an `<li>`
45
- * holding a `<ul>` is.
46
- *
47
- * A consumer whose framework owns routing slots its own link instead and leaves
48
- * `href` unset. The row follows that one too, so the whole row works either
49
- * way; what the anchor adds is what only a real anchor can give.
50
- *
51
- * The properties below are written by this component and read by whoever draws
52
- * it. A consumer sets none of them: they carry a number this element computed
53
- * and CSS cannot, which is what lets a stylesheet on this tier draw the thing
54
- * without measuring anything itself.
55
- *
56
- * @since 0.1.0
57
- * @status experimental
58
- * @aliases TreeNode, Branch, Leaf
59
- * @documentation https://helix.fusion.dev/components/tree
60
- *
61
- * @slot - The item's label.
62
- * @slot children - Nested items, which make this a branch.
63
- * @slot end - After the label, at the row's far side. A count, or a state.
64
- * @slot start - Before the label, after the chevron. A mark about the item.
65
- *
66
- * @event {HelixBeforeCloseEvent} helix-before-close - Fired before a branch closes, and cancelable.
67
- * @event {HelixBeforeOpenEvent} helix-before-open - Fired before a branch opens, and cancelable.
68
- * @event {HelixSelectEvent} helix-select - Fired when the item is chosen. Cancelable: refusing it takes the decision and nothing moves.
69
- * @event {Event} toggle - Says the state moved, carrying `newState` and `oldState`.
70
- *
71
- * @csspart group - The container for nested items.
72
- * @csspart item - The node, and the focus target unless `href` moves it to the row.
73
- * @csspart label - The label text.
74
- * @csspart row - The clickable label row, which is an `<a>` where `href` is set.
75
- * @csspart toggle - The expand and collapse control.
76
- *
77
- * @cssproperty --tree-level - How deep this row sits, counting from zero, for the indent.
78
- */
79
12
  let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerOpen(HelixElement) {
80
13
  constructor() {
81
14
  super(...arguments);
82
- /**
83
- * Where this node goes, which makes its row a real link.
84
- *
85
- * Unset by default, and unreflected: a tree of choosable nodes is not a tree
86
- * of links, and an empty `href` written onto every row would be a link to the
87
- * current page on every item a consumer never asked to be one.
88
- */
89
15
  this.href = '';
90
- /**
91
- * What the link declares about where it goes.
92
- *
93
- * Set by the consumer rather than derived from `target`: `noreferrer` is a
94
- * decision about their own analytics and a component should not make it for
95
- * them. `noopener` is not needed as a default either, since every browser
96
- * this library supports implies it for `target="_blank"` already.
97
- */
98
16
  this.rel = '';
99
- /**
100
- * Whether this item is the current one.
101
- *
102
- * Not reflected, for the reason `level` below gives in full: the tree assigns
103
- * this after it upgrades, so the attribute would be a change to markup the
104
- * consumer rendered and hydration would report it. Mirroring to
105
- * `data-selected` on the host instead is the same fault under another name.
106
- *
107
- * The stylesheet reads it from the row inside this shadow root, because the
108
- * row is not part of the tree React hydrates.
109
- */
110
17
  this.selected = false;
111
- /**
112
- * Where the link opens, for the rare row that leaves the site.
113
- */
114
18
  this.target = '';
115
- /**
116
- * Identifies the item to the consumer, carried in `helix-select`.
117
- */
118
19
  this.value = '';
119
- /**
120
- * Whether this item holds the tree's single tab stop.
121
- */
122
20
  this.focusable = false;
123
- /**
124
- * Whether anything is slotted into `children`.
125
- *
126
- * Read after first render.
127
- */
128
21
  this.branch = false;
129
22
  this.handleToggleClick = (event) => {
130
- /* Only where the row is a link, and then it has to stop: the row would
131
- navigate away from the branch the reader was trying to open. Without an
132
- `href` the whole row toggles, which is what a tree of folders wants, so
133
- there is nothing here to intercept. */
134
23
  event.preventDefault();
135
24
  event.stopPropagation();
136
25
  this.toggleOpen();
137
26
  };
138
27
  this.handleRowClick = (event) => {
139
- /* A press that landed on a link is that link's, whichever branch of this
140
- runs, and the test has to be made on both sides. A row can hold a link in
141
- its `end` slot, and asked on one side only, pressing that link collapses
142
- the branch on the way out: one press, `open` false and the route
143
- changed. */
144
28
  const onLink = event.composedPath().some((node) => node instanceof HTMLAnchorElement);
145
- /* A branch that is also a link goes where it points, and its chevron is
146
- what opens it. A branch that is not toggles from anywhere in the row. */
147
29
  if (this.branch && this.href === '') {
148
30
  if (!onLink) {
149
31
  this.toggleOpen();
@@ -151,8 +33,6 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
151
33
  return;
152
34
  }
153
35
  this.select();
154
- /* Following it here as well would follow it twice and, where a consumer's
155
- router is listening, route twice. */
156
36
  if (!onLink) {
157
37
  this.follow();
158
38
  }
@@ -166,74 +46,22 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
166
46
  this.branch = slot.assignedElements().length > 0;
167
47
  });
168
48
  }
169
- /**
170
- * @internal Read by the tree to identify a branch across the light DOM,
171
- * which is cheaper than `instanceof` and is why this is public rather than
172
- * protected. A consumer asks `branch`, which is the property they set.
173
- */
174
49
  get isBranch() {
175
50
  return this.branch;
176
51
  }
177
- /**
178
- * Moves focus to this node.
179
- *
180
- * Which element that is moves with `href`: the thing carrying `treeitem` is
181
- * the thing the tree's roving tab stop lives on, and with a link that is the
182
- * row rather than the wrapper around it.
183
- */
184
52
  focusItem() {
185
- /* `preventScroll`, because the browser's own answer to a focus is to walk
186
- every scrollable ancestor and reveal the element, which is
187
- `scrollIntoView` by another route and takes the page with it. The root
188
- calls `scrollItemIntoView` on the next line precisely to avoid that, and
189
- without this the careful call arrives after the thing it exists to
190
- prevent has already happened. */
191
53
  this.renderRoot
192
54
  .querySelector(this.href === '' ? '[part="item"]' : '[part="row"]')
193
55
  ?.focus({ preventScroll: true });
194
56
  }
195
- /** Selects this node and announces it. */
196
57
  select() {
197
58
  this.dispatchEvent(new HelixSelectEvent({ value: this.value }));
198
59
  }
199
- /**
200
- * Chooses this node the way a reader does, from a press or from Enter.
201
- *
202
- * Selecting and following are two things and this is where they meet, so the
203
- * pointer and the keyboard take one path to the same end. The tree calls this
204
- * rather than `select` for the keyboard, or a nav tree whose rows are links
205
- * could be walked with the arrows and never entered: choosing announces a
206
- * value and nothing listens for it.
207
- */
208
60
  activate() {
209
61
  this.select();
210
62
  this.follow();
211
63
  }
212
- /**
213
- * Follows a link slotted into the name, if there is one.
214
- *
215
- * A row is wider than the words in it: the indent, the padding, and with the
216
- * chevron at the far end most of the row's width are all outside the anchor,
217
- * so a reader who pressed the row anywhere but on the text got nothing. The
218
- * words are the consumer's own content and this does not rewrite them, so
219
- * what it does instead is press the link they put there.
220
- *
221
- * The consumer's anchor rather than an `href` of this element's own,
222
- * deliberately: a framework's link component is what carries prefetching and
223
- * client-side routing, and a plain `<a href>` rendered in here would be a
224
- * full page load on a site that had neither. A synthetic click is what those
225
- * components listen for, so both keep working.
226
- *
227
- * `querySelector` on the light DOM, not the render root: the link is the
228
- * consumer's, and it is scoped to this item's own name rather than to the
229
- * whole subtree, or a branch would follow the first link belonging to one of
230
- * its children.
231
- */
232
64
  follow() {
233
- /* Its own row first, where `href` made one. Pressed with a pointer the
234
- anchor has already navigated and this never runs; from the keyboard it
235
- has not, because the tree calls `preventDefault` on every key it handles
236
- and that is what would otherwise swallow Enter on a link. */
237
65
  const own = this.renderRoot?.querySelector('a[part="row"]');
238
66
  if (own) {
239
67
  own.click();
@@ -250,7 +78,6 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
250
78
  }
251
79
  }
252
80
  }
253
- /** Everything inside the row, which is the same whichever element it is. */
254
81
  renderRow() {
255
82
  return html `${this.branch
256
83
  ? html `<svg
@@ -267,36 +94,11 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
267
94
  <span part="label"><slot></slot></span>
268
95
  <slot name="end"></slot>`;
269
96
  }
270
- /**
271
- * Mirrors the chosen state where a stylesheet can select on it.
272
- *
273
- * **A custom state, never a reflected `selected` attribute.** The parent
274
- * writes this: a listbox choosing a row, a tab strip moving the tab, a radio
275
- * group answering for its set. Reflected, that puts the element's own
276
- * bookkeeping onto markup a consumer wrote and a consumer's server never sent,
277
- * which React reconciles and reports as a mismatch, and which a serialized
278
- * element pasted back declares as a choice nobody made. `ElementInternals`
279
- * holds it instead, where it is not in the DOM at all.
280
- *
281
- * Reading `selected` is unaffected and so is writing it: the attribute still
282
- * seeds the property, which is what `<helix-tree-item selected>` in a consumer's
283
- * markup means. What stops is the write back.
284
- */
285
97
  willUpdate(changed) {
286
98
  super.willUpdate(changed);
287
99
  this.toggleState('selected', this.selected);
288
100
  }
289
101
  render() {
290
- /* Where the semantics sit, which `href` decides.
291
- *
292
- * A link row carries them itself and the wrapper drops to `none`, because
293
- * an anchor cannot contain the group: the group holds more anchors and
294
- * nesting them is invalid markup that no assistive technology reads the way
295
- * it looks. So the row becomes the `treeitem` and the group becomes its
296
- * sibling, which is the navigation flavor of the pattern.
297
- *
298
- * Without one, nothing changes: the wrapper is the `treeitem` and holds the
299
- * group, which is what an `<li>` holding a `<ul>` is. */
300
102
  const linked = this.href !== '';
301
103
  const level = this.level ?? 1;
302
104
  const expanded = this.branch ? (this.open ? 'true' : 'false') : nothing;