@fusion.dev/helix-headless 0.2.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
@@ -8,251 +8,27 @@ var HelixHeadlessSplitter_1;
8
8
  import { capturePointer, closestInstance, customElement, HelixBeforeCollapseEvent, HelixBeforeResizeEvent, HelixElement, requestChange, } from '@fusion.dev/helix-core';
9
9
  import { html } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
- /**
12
- * The inline size under which `orientation="auto"` stacks, in pixels.
13
- *
14
- * 32rem at the default root size. Two panes side by side under this have about
15
- * 240px each before the divider, which is narrower than the content anybody
16
- * puts in one: the sandbox at 390 gave its source pane 237px and cut every line
17
- * mid-token, which is the measurement this number comes from.
18
- *
19
- * A constant and not a property, until something wants a different one. The
20
- * axis is the decision worth exposing; where it turns is a detail of this
21
- * component doing what it was asked.
22
- */
23
11
  const STACK_BELOW = 512;
24
- /**
25
- * @summary Two panes and a divider the reader can move.
26
- *
27
- * @description
28
- * **One axis, and nesting is how you get two.** The sandbox this was taken from
29
- * moves a cross: a divider down the middle, another across the bottom, and a
30
- * corner that moves both. That is a layout rather than a component, and every
31
- * part of it that is worth sharing is one divider between two panes. A splitter
32
- * inside a splitter gives the cross back, and gives the three-pane and the
33
- * four-pane arrangements it never had.
34
- *
35
- * **Where two of them cross there is a corner, and it moves both.** A splitter
36
- * slotted into a pane of another, at right angles to it, has one end of its
37
- * divider sitting exactly on the other's. That end grows a handle: `move` under
38
- * the pointer, both arrow axes on the keyboard, and a drag that carries the two
39
- * positions at once.
40
- *
41
- * **The outer splitter draws it and the nested one announces itself upward.**
42
- * The nested one drawing its own is the tempting version, since it already
43
- * knows where its divider begins and needs no measurement of the other, and it
44
- * is clipped: the crossing lies on the boundary between a pane and the divider
45
- * beside it, a handle centered there reaches back over the boundary, and a pane
46
- * is `overflow: auto`. Exactly half the corner is cut off. The outer separator
47
- * is the one element that already straddles the seam, so the corner belongs in
48
- * it.
49
- *
50
- * That makes the channel between the two a single call in a single direction:
51
- * `cross(child, at)` says where the child crosses, `moveTo` drives either one,
52
- * and neither reads the other's state. The child is what calls it, because a
53
- * parent cannot poll for a child that has not upgraded yet.
54
- *
55
- * The one number it carries is a percentage rather than a measurement, which is
56
- * what keeps either of them from measuring the other, and it assumes the nested
57
- * splitter fills its pane. That is what slotting one into a pane does. Give the
58
- * nested splitter its own inset, or wrap it in something smaller than the pane,
59
- * and the corner is drawn where the crossing would be rather than where it is.
60
- *
61
- * **It is a client-side enhancement and says so.** Finding the parent means
62
- * inspecting the tree, which a server cannot do, so the corner appears at
63
- * `firstUpdated` and never in the served markup. That is allowed for exactly
64
- * this: it is an affordance rather than content, and a splitter with no corner
65
- * yet is a splitter with two working dividers.
66
- *
67
- * Where two dividers are parallel there is no corner, because there is nothing
68
- * to take hold of. Where they cross without a corner, the inner divider takes
69
- * the pointer: every separator sits on the same layer, so a tie goes to document
70
- * order and a deeper splitter is always later.
71
- *
72
- * **A divider that is moving says so, whoever is moving it.** `:hover` cannot
73
- * carry that: a drag holds the pointer through capture and a fast one leaves the
74
- * target behind, and a corner moves the nested divider while the reader is on
75
- * the crossing, nowhere near it. So a drag in progress is a state, and both
76
- * dividers a corner moves are lit for as long as it moves them.
77
- *
78
- * **The divider is a real `role="separator"` with `aria-valuenow`, moved by the
79
- * arrow keys as well as by pointer.** A divider only a mouse can move is
80
- * unusable without one, which is the reason the sandbox's carries the same, and
81
- * `Home` and `End` take it to its bounds.
82
- *
83
- * **It goes where it is put, all the way to either end.** `min` and `max` are
84
- * nothing and everything unless a consumer says otherwise, because a divider
85
- * that stops dead short of the edge gives a reader no way to put a pane away.
86
- * Where a pane should keep a sliver of itself, only whoever put the tabs on it
87
- * knows how much, so they say so.
88
- *
89
- * **A pane can be shut, and that is not the same as a divider at the edge.**
90
- * The geometry is identical and the knowledge is not: only a shut pane knows
91
- * where to open back to. So `collapsed` is a second state rather than a reading
92
- * of the position, and the position it opens to is simply the one collapsing
93
- * never overwrote. `collapsed-size` keeps a sliver showing where a pane has a
94
- * tab strip worth leaving, and collapse overrides `min` and `max`, which is
95
- * what those are for: they keep a pane from vanishing by accident, and this is
96
- * the deliberate version.
97
- *
98
- * The two are kept in step in one direction only: a drag that settles on the
99
- * collapsed size latches shut, and any move opens it again. Otherwise a reader
100
- * could drag a pane closed and the consumer's own button would still be
101
- * offering to close it.
102
- *
103
- * A pane shut to nothing is told apart from one shut to a sliver, because the
104
- * two want different dividers: a sliver still has a boundary worth drawing, and
105
- * nothing leaves the divider sitting on the container's own edge.
106
- *
107
- * **The box is measured once, when a drag starts.** Measuring per move is a
108
- * forced layout on every pointer event, in the middle of a gesture whose whole
109
- * job is to stay ahead of the pointer, and nothing about the box moves while the
110
- * divider does.
111
- *
112
- * **`helix-before-resize` fires when it settles, not while it moves.** A drag is a
113
- * stream of positions and only the one it ends at is a preference; announcing
114
- * each would ask a consumer to write to storage a hundred times to record one
115
- * decision. Nothing is stored here either: where a preference lives, and whether
116
- * it should be kept at all, is the consumer's to decide, and a component writing
117
- * to storage on its own would be deciding for all of them under a key it chose.
118
- *
119
- * The properties below are written by this component and read by whoever draws
120
- * it. A consumer sets none of them: they carry a number this element computed
121
- * and CSS cannot, which is what lets a stylesheet on this tier draw the thing
122
- * without measuring anything itself.
123
- *
124
- * @since 0.1.0
125
- * @status experimental
126
- * @category layout
127
- * @aliases SplitPane, Resizable, SplitView
128
- * @documentation https://helix.fusion.dev/components/splitter
129
- *
130
- * @slot end - The pane after it.
131
- * @slot start - The pane before the divider.
132
- *
133
- * @event {HelixBeforeCollapseEvent} helix-before-collapse - Fired before a pane shuts or reopens, and cancelable.
134
- * @event {HelixBeforeResizeEvent} helix-before-resize - Fired before the divider settles, and cancelable.
135
- *
136
- * @csspart corner - The handle where two dividers cross, added on the client.
137
- * @csspart end - The box holding the second.
138
- * @csspart separator - The divider itself.
139
- * @csspart start - The box holding the first pane.
140
- *
141
- * @cssproperty --splitter-corner-at - Where a nested splitter crosses this one, as a percentage.
142
- * @cssproperty --splitter-position - Where the divider sits, as a percentage of the box.
143
- */
144
12
  let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitter extends HelixElement {
145
13
  constructor() {
146
14
  super(...arguments);
147
- /**
148
- * How much of a shut pane is still shown, as a percentage.
149
- *
150
- * Zero by default, which is a pane fully gone. A tab strip or a title bar
151
- * that should survive its own collapse says how much of itself to keep, and
152
- * only whoever put it there knows that, which is the same reason `min` is a
153
- * consumer's to set.
154
- */
155
15
  this.collapsedSize = 0;
156
- /**
157
- * Accessible name for the handle where two dividers cross.
158
- */
159
16
  this.cornerLabel = 'Resize both';
160
- /**
161
- * Where the divider starts, where nothing declared it.
162
- */
163
17
  this.defaultPosition = 50;
164
- /**
165
- * Accessible name for the divider.
166
- */
167
18
  this.label = 'Resize panes';
168
- /**
169
- * Nearest it may come to the end, on the same terms as `min`.
170
- */
171
19
  this.max = 100;
172
- /**
173
- * Nearest the divider may come to the start.
174
- *
175
- * A bare number is a percentage, which is what this always took. A CSS length
176
- * is resolved against the splitter's own box along its axis, so `min="15rem"`
177
- * means fifteen rem of pane whatever the container is doing.
178
- *
179
- * **The length is the one that holds.** A percentage minimum does not: twenty
180
- * per cent is 280px in a 1400px splitter and 100px in a 500px one, and the
181
- * number a consumer picked was almost certainly about how much text fits
182
- * rather than about a ratio. The pane that a percentage protects at desk
183
- * width is squeezed past reading at half of it.
184
- *
185
- * **It does not fight `collapsed`.** Collapse overrides `min` and `max`, as
186
- * the class docstring says, so a pane can still be shut and reopened. What a
187
- * minimum forbids is the space between shut and usable: a pane wide enough to
188
- * be there and too narrow to read is the state nobody wants and every drag
189
- * passes through.
190
- */
191
20
  this.min = 0;
192
- /**
193
- * How far one arrow press moves it, as a percentage.
194
- */
195
21
  this.step = 2;
196
- /** The position, whoever set it, and `undefined` until somebody has. */
197
22
  this.positionState = undefined;
198
- /** The collapse, whoever set it, and `undefined` until somebody has. */
199
23
  this.collapsedState = undefined;
200
- /**
201
- * Where the divider was before the gesture that may be about to shut it.
202
- *
203
- * A drag writes a position on every pointer move, so by the time it reaches
204
- * the edge the number it started from is gone. Taken once, on the press, and
205
- * written back if the gesture ends shut, so the pane opens to where it was
206
- * rather than to the edge it was dragged against.
207
- */
208
24
  this.opened = undefined;
209
- /**
210
- * Where the gesture has moved to, whether or not it was applied. See `move`.
211
- *
212
- * `@state`, because the divider is drawn from it while a drag is under way.
213
- */
214
25
  this.reached = undefined;
215
- /**
216
- * Whether this divider is being moved right now, by anything.
217
- *
218
- * `:hover` is not enough on its own. A drag holds the pointer through capture
219
- * and a fast one leaves the eight pixel target behind, and a corner moves a
220
- * divider the pointer is nowhere near: the nested one, while the reader is on
221
- * the crossing. Both are dividers that are moving and should say so.
222
- *
223
- * A `data-` attribute on an element in this shadow root, never on the host.
224
- */
225
26
  this.dragging = false;
226
- /**
227
- * Nested splitters that cross this one, and how far along they cross it.
228
- *
229
- * Written by the child, not found by the parent: a parent cannot poll for a
230
- * child that has not upgraded, so the child announces itself once it has. The
231
- * number is the child's own `position`, which is also its offset along this
232
- * divider, because the child fills the pane and the pane spans this splitter
233
- * end to end on the axis the divider runs.
234
- *
235
- * Not `@state`, because the array is mutated in place. `cross` and `uncross`
236
- * request the update themselves.
237
- */
238
27
  this.crossings = [];
239
- /** The splitter this one is nested in, once it has found it. */
240
28
  this.outer = null;
241
- /** The last position announced upward, so an unchanged one costs nothing. */
242
29
  this.crossedAt = undefined;
243
- /** The two boxes a corner drag is measured against, taken once. */
244
30
  this.cornerOrigin = null;
245
- /**
246
- * The host's box, measured once when a drag starts.
247
- *
248
- * Measuring per move is a forced layout on every pointer event, in the middle
249
- * of a gesture whose whole job is to stay ahead of the pointer. Nothing about
250
- * it moves while the divider does.
251
- */
252
31
  this.origin = null;
253
- /**
254
- * Whether `auto` has resolved to stacked, which only the observer sets.
255
- */
256
32
  this.stacked = false;
257
33
  this.handlePointerDown = (event) => {
258
34
  const separator = event.currentTarget;
@@ -260,14 +36,9 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
260
36
  this.origin = this.vertical
261
37
  ? { length: box.height, start: box.top }
262
38
  : { length: box.width, start: box.left };
263
- /* Captured, so the divider keeps the pointer once it has it: a drag that
264
- outruns a thin target otherwise ends the moment the pointer leaves it,
265
- which on a fast gesture is immediately. */
266
39
  capturePointer(separator, event.pointerId);
267
40
  this.opened = this.collapsed ? undefined : this.position;
268
- // Cleared, so a number from the last gesture cannot settle this one.
269
41
  this.reached = undefined;
270
- // On the press rather than the first move, so holding it still says so too.
271
42
  this.dragging = true;
272
43
  event.preventDefault();
273
44
  };
@@ -277,9 +48,6 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
277
48
  return;
278
49
  }
279
50
  const along = this.vertical ? event.clientY : event.clientX;
280
- /* Signed against the writing direction, because a pointer is physical and
281
- the panes are not: in a right-to-left document the start pane is on the
282
- right, so dragging right makes it *larger*. */
283
51
  const raw = ((along - origin.start) / origin.length) * 100;
284
52
  const rtl = !this.vertical && getComputedStyle(this).direction === 'rtl';
285
53
  this.move(rtl ? 100 - raw : raw);
@@ -294,32 +62,11 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
294
62
  }
295
63
  this.origin = null;
296
64
  this.dragging = false;
297
- /* Nothing moved, so nothing is announced. Settling on every press would
298
- dispatch a change carrying the position it already had, so a plain click
299
- rewrites a consumer's stored value from a gesture that moved nothing:
300
- harmless where the value round-trips exactly, and not where it does not,
301
- which is any consumer storing pixels and dividing by a box. */
302
65
  if (this.reached === undefined) {
303
66
  return;
304
67
  }
305
- /* `reached` is deliberately left standing. It is what the divider is drawn
306
- from until the consumer answers, and the setter is what clears it. */
307
68
  this.settle(this.reached);
308
69
  };
309
- /**
310
- * The keys a separator answers.
311
- *
312
- * One axis, the one the divider actually travels along, plus `Home` and `End`
313
- * for its bounds. The other two arrows are left alone and the page scrolls
314
- * under them as it would anywhere else.
315
- *
316
- * **Not all four, which is the tempting version.** Taking every arrow would
317
- * mean `ArrowLeft` moving a horizontal divider up and down: not a reader
318
- * being helped past a wrong guess, an arrow that does not go the way it
319
- * points. A reader who guesses wrong is answered better by the corner, where
320
- * all four are live and each moves the divider it points along, which is the
321
- * only place in a splitter where a second axis means anything.
322
- */
323
70
  this.handleKeydown = (event) => {
324
71
  const back = this.vertical ? 'ArrowUp' : 'ArrowLeft';
325
72
  const forward = this.vertical ? 'ArrowDown' : 'ArrowRight';
@@ -341,19 +88,11 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
341
88
  }
342
89
  this.move(next);
343
90
  this.settle(next);
344
- // Or the arrows scroll the page under the divider being moved.
345
91
  event.preventDefault();
346
92
  };
347
93
  this.handleDoubleClick = () => {
348
94
  this.reset();
349
95
  };
350
- /**
351
- * Both of them, because the corner moves both.
352
- *
353
- * Its own handler, because the corner is a sibling of the separator rather
354
- * than a child of it, for the stacking reason in the stylesheet, so nothing
355
- * reaches the separator's own double click by bubbling.
356
- */
357
96
  this.handleCornerDoubleClick = (event) => {
358
97
  const crossing = this.crossings[Number(event.currentTarget.dataset.cross)];
359
98
  if (!crossing) {
@@ -385,14 +124,10 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
385
124
  crossing.by.opened = crossing.by.collapsed ? undefined : crossing.by.position;
386
125
  this.reached = undefined;
387
126
  crossing.by.reached = undefined;
388
- /* Both of them, on the press. The nested one is reachable here because it is
389
- the same class, and this is the whole of what the corner needs beyond
390
- `moveTo`: a divider being moved looks like one. */
391
127
  this.dragging = true;
392
128
  crossing.by.dragging = true;
393
129
  this.leaveSeparator(event);
394
130
  };
395
- /** Both axes at once, each measured against the splitter that owns it. */
396
131
  this.handleCornerMove = (event) => {
397
132
  const origin = this.cornerOrigin;
398
133
  if (!origin) {
@@ -430,11 +165,6 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
430
165
  origin.by.moveTo(origin.by.reached ?? origin.by.position, true);
431
166
  this.leaveSeparator(event);
432
167
  };
433
- /**
434
- * The corner takes every arrow, each moving the splitter whose axis it points
435
- * along, which is the only reading a keyboard can give a handle that moves in
436
- * two directions.
437
- */
438
168
  this.handleCornerKeydown = (event) => {
439
169
  const crossing = this.crossings[Number(event.currentTarget.dataset.cross)];
440
170
  if (!crossing) {
@@ -457,13 +187,6 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
457
187
  this.leaveSeparator(event);
458
188
  };
459
189
  }
460
- /**
461
- * Where the divider sits, as a percentage of the whole.
462
- *
463
- * Reading gives where it is; writing declares it the consumer's, and
464
- * `default-position` seeds one this element owns. The same accessor pair
465
- * `Pagination.page` and the calendar's `month` carry.
466
- */
467
190
  get position() {
468
191
  const shut = this.collapsed;
469
192
  if (shut) {
@@ -474,27 +197,9 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
474
197
  set position(next) {
475
198
  const previous = this.position;
476
199
  this.positionState = next;
477
- /* The consumer has spoken, so the drag stops standing in for them. Without
478
- this the divider flashes back: `showing` falls to the position set before
479
- the gesture and is drawn there for the frame it takes them to answer the
480
- announcement, snapping to where the drag started and then jumping to
481
- where it was dropped. */
482
200
  this.reached = undefined;
483
201
  this.requestUpdate('position', previous);
484
202
  }
485
- /**
486
- * Which pane is shut, if either.
487
- *
488
- * The same accessor pair as `position`, and deliberately a second state
489
- * rather than a reading of the first. A pane shut by dragging the divider to
490
- * the edge and a pane shut on purpose look identical in geometry and are not
491
- * the same thing: only the second knows where to go back to. Kept apart, the
492
- * open position is simply the one collapsing never overwrote, so restoring
493
- * needs no memory of its own.
494
- *
495
- * It overrides `min` and `max`, which is the point of it. Those keep a sliver
496
- * of a pane during a drag; this is the thing that says the sliver can go.
497
- */
498
203
  get collapsed() {
499
204
  const held = this.collapsedState;
500
205
  if (held === undefined) {
@@ -504,21 +209,9 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
504
209
  }
505
210
  set collapsed(next) {
506
211
  const previous = this.collapsed;
507
- /* `none` rather than `undefined`, because the two mean different things
508
- here and conflating them cost a spec: `undefined` is nobody having
509
- spoken, which is the seed's cue to answer, so clearing a collapse handed
510
- it straight back to `default-collapsed` and the pane would not open. */
511
212
  this.collapsedState = next ?? 'none';
512
213
  this.requestUpdate('collapsed', previous);
513
214
  }
514
- /**
515
- * Either kind of bound as a percentage of the box.
516
- *
517
- * Measured rather than cached: a length is only meaningful against the size
518
- * the splitter is right now, and the drag that asks is already reading layout.
519
- * Falls back where there is nothing to measure, which is the server and an
520
- * element that is not displayed, so `aria-valuemin` is a number either way.
521
- */
522
215
  toPercent(value, fallback) {
523
216
  if (typeof value === 'number') {
524
217
  return Number.isFinite(value) ? value : fallback;
@@ -543,41 +236,16 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
543
236
  const extent = this.vertical ? box.height : box.width;
544
237
  return extent > 0 ? (pixels / extent) * 100 : fallback;
545
238
  }
546
- /** `min` as a percentage, whichever way it was written. */
547
239
  get minPercent() {
548
240
  return this.toPercent(this.min, 0);
549
241
  }
550
- /** `max` as a percentage, whichever way it was written. */
551
242
  get maxPercent() {
552
243
  return this.toPercent(this.max, 100);
553
244
  }
554
- /**
555
- * The position to draw, which is not always the position to report.
556
- *
557
- * A controlled splitter applies nothing, so `position` holds still for the
558
- * whole gesture: drawn from it, the divider stays where it was until the
559
- * pointer comes up and the consumer answers, jumping to the end rather than
560
- * following the hand.
561
- *
562
- * So a drag draws itself. The number is still not announced until it settles,
563
- * and the consumer's answer takes over the moment they give one.
564
- */
565
245
  get showing() {
566
246
  return this.reached === undefined ? this.position : this.clamp(this.reached);
567
247
  }
568
- /**
569
- * Moves the divider, and optionally says so.
570
- *
571
- * Public because a corner on a nested splitter has to drive the one it is
572
- * nested in, and that is the whole of what it needs: the pair are not
573
- * otherwise coupled and neither reads the other's state. `settled` is false
574
- * for each step of a drag and true once, at the end, for the reason a drag
575
- * announces once: only the position it ends at is a preference.
576
- */
577
248
  moveTo(next, settled = false) {
578
- /* An unsettled move is a drag in progress, which is what `settled` already
579
- means, so the nested splitter a corner drives lights up without either of
580
- them being told about the other's presentation. */
581
249
  this.dragging = !settled;
582
250
  if (settled) {
583
251
  this.settle(next);
@@ -586,34 +254,12 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
586
254
  this.move(next);
587
255
  }
588
256
  }
589
- /** The collapsed size, held inside the range a percentage can be. */
590
257
  get sliver() {
591
258
  return Math.min(100, Math.max(0, this.collapsedSize));
592
259
  }
593
- /**
594
- * Whether the divider is against an edge with nothing beside it.
595
- *
596
- * Geometry, not state. Asking whether the pane is `collapsed` is the wrong
597
- * question: a divider driven to the edge by a consumer setting `position` is
598
- * just as flush and would still get a line drawn. What the stylesheet needs
599
- * to know is whether there is a pane on both sides, which is a fact about
600
- * where the divider is however it got there.
601
- *
602
- * Still not the same question as `collapsed`: a pane shut to a sliver has a
603
- * boundary worth drawing, and one shut to nothing has the container's own edge
604
- * there instead.
605
- */
606
260
  get flush() {
607
261
  return this.sliver === 0 && this.shutAt(this.position) !== undefined;
608
262
  }
609
- /**
610
- * The collapse a settled position amounts to, if any, so a pane dragged shut
611
- * and a pane shut by a consumer's button are the same state.
612
- *
613
- * No separate threshold. Landing on the collapsed size *is* the gesture, and
614
- * `clamp` puts every overshoot exactly on the bound, so the default of zero
615
- * latches on any drag that runs off the end.
616
- */
617
263
  shutAt(position) {
618
264
  const size = this.sliver;
619
265
  if (position <= size) {
@@ -621,20 +267,11 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
621
267
  }
622
268
  return position >= 100 - size ? 'end' : undefined;
623
269
  }
624
- /** Pulled between the bounds, which are the ends unless a consumer moved them. */
625
270
  clamp(next) {
626
271
  const min = Math.max(0, Math.min(100, this.minPercent));
627
272
  const max = Math.max(min, Math.min(100, this.maxPercent));
628
273
  return Math.min(max, Math.max(min, Number.isFinite(next) ? next : min));
629
274
  }
630
- /**
631
- * Starts or stops watching, according to what the axis was asked to be.
632
- *
633
- * Called from `firstUpdated` and from `updated`, so a consumer switching to
634
- * `auto` after mount is observed and one switching away stops being: an
635
- * observer left running would keep writing `stacked` that nothing reads, and
636
- * a splitter told `horizontal` has been given its answer already.
637
- */
638
275
  watchWidth() {
639
276
  if (this.orientation !== 'auto') {
640
277
  this.sizes?.disconnect();
@@ -646,58 +283,26 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
646
283
  }
647
284
  this.sizes = new ResizeObserver(([entry]) => {
648
285
  const width = entry?.borderBoxSize?.[0]?.inlineSize ?? entry?.contentRect.width ?? 0;
649
- /* Zero while the element is `display: none` or not yet laid out, and a
650
- zero is not a narrow splitter: taking it would stack every instance
651
- that starts hidden and never turn it back. */
652
286
  if (width > 0) {
653
287
  this.stacked = width < STACK_BELOW;
654
288
  }
655
289
  });
656
290
  this.sizes.observe(this);
657
291
  }
658
- /**
659
- * Public because a nested splitter compares axes with the one it is in.
660
- *
661
- * The one place the axis is decided, which is why `auto` costs a single line
662
- * here rather than a pass over seventeen readers: the drag, the arrow keys and
663
- * `aria-orientation` all ask this and all turn together.
664
- */
665
292
  get vertical() {
666
293
  return this.orientation === 'vertical' || (this.orientation === 'auto' && this.stacked);
667
294
  }
668
- /**
669
- * Asks to move, and applies it where the position is this element's to move.
670
- *
671
- * Not announced per step: `helix-before-resize` is the settling, below. A drag that
672
- * announced every position would be asking a consumer to record a hundred
673
- * decisions to keep one.
674
- */
675
295
  move(next) {
676
296
  const wanted = this.clamp(next);
677
- /* Where the gesture has reached, recorded before anything decides whether to
678
- apply it. A controlled splitter applies nothing, so its `position` is
679
- whatever the consumer last said, and settling at *that* would announce
680
- the number it already had: the drag ends where it began and the divider
681
- never moves. Only the pointer is exposed to it, the keyboard computing
682
- its next position rather than reading it back. */
683
297
  this.reached = wanted;
684
298
  if (wanted === this.position) {
685
299
  return;
686
300
  }
687
301
  const previous = this.position;
688
302
  this.positionState = wanted;
689
- /* A divider that is moving is a pane that is opening, and a shut one is
690
- still draggable precisely so it can be. Silent, like the move itself:
691
- the settling below is what announces either. */
692
303
  this.collapsedState = 'none';
693
304
  this.requestUpdate('position', previous);
694
305
  }
695
- /**
696
- * Says the arrangement has settled, so a consumer can keep it.
697
- *
698
- * Through the audited path, so a consumer who owns `position` is told what was
699
- * asked for and decides, and one who does not has already had it applied.
700
- */
701
306
  settle(next) {
702
307
  const wanted = this.clamp(next);
703
308
  const shut = this.shutAt(wanted);
@@ -712,14 +317,9 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
712
317
  next: wanted,
713
318
  });
714
319
  if (shut !== this.collapsed) {
715
- /* Two values from one gesture, so two announcements. `ChangeDetail`
716
- carries a name for exactly this, and a consumer listening for one of
717
- them is not made to care about the other. */
718
320
  requestChange({
719
321
  apply: (value) => {
720
322
  const previous = this.collapsed;
721
- // Back to where the gesture started, so opening it again is a return
722
- // rather than a jump to the edge it was dragged against.
723
323
  if (value !== '' && this.opened !== undefined) {
724
324
  this.positionState = this.opened;
725
325
  }
@@ -732,30 +332,11 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
732
332
  });
733
333
  }
734
334
  }
735
- /** Puts the divider back where the markup put it. */
736
335
  reset() {
737
- /* `default-position`, never the position captured at first render. Those
738
- are the same only when nobody owns the position: a consumer who does owns
739
- what it was on that first frame too, and theirs can be a number they
740
- cannot compute yet, as the sandbox's is, sizing its panel in pixels with
741
- no box to divide by until it has rendered once. Home would then be
742
- "panel closed" and a double click would shut the panel rather than
743
- restore it.
744
-
745
- Declared rather than remembered, so it is a value a consumer can state
746
- and change, and it needs no capture at all. */
747
336
  const home = this.clamp(this.defaultPosition);
748
337
  this.move(home);
749
338
  this.settle(home);
750
339
  }
751
- /**
752
- * Records that a nested splitter crosses this one at `at` percent along it.
753
- *
754
- * Public because it is the whole of the channel between the two, and it runs
755
- * one way: the child tells the parent where it is, and the parent draws the
756
- * corner and drives them both. Nothing is read back, so neither has to know
757
- * anything about the other's state.
758
- */
759
340
  cross(by, at) {
760
341
  const found = this.crossings.find((crossing) => crossing.by === by);
761
342
  if (found) {
@@ -769,7 +350,6 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
769
350
  }
770
351
  this.requestUpdate();
771
352
  }
772
- /** Forgets a nested splitter that has left the tree. See `cross`. */
773
353
  uncross(by) {
774
354
  const at = this.crossings.findIndex((crossing) => crossing.by === by);
775
355
  if (at === -1) {
@@ -778,63 +358,22 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
778
358
  this.crossings.splice(at, 1);
779
359
  this.requestUpdate();
780
360
  }
781
- /**
782
- * Announces this splitter to the one it is nested in, if they cross.
783
- *
784
- * Client only. The server has no tree to walk, and a parent that has not
785
- * upgraded has no `orientation` to compare against, so this waits for
786
- * `firstUpdated`. That is what the corner is allowed to be: an affordance
787
- * rather than content, absent from the served markup and appearing at
788
- * hydration, with two working dividers in the meantime.
789
- *
790
- * `parentElement` first, so a splitter does not find itself.
791
- *
792
- * **By identity rather than by tag, which is what `closestInstance` exists
793
- * for.** A selector like `'helix-splitter, helix-headless-splitter'` names a
794
- * styled tag from a headless package and misses any third tag, which is
795
- * exactly what subclassing a headless element produces: the lookup returns
796
- * null, the nested splitter gets no corner, and nothing reports an error.
797
- *
798
- * It also crosses shadow boundaries, which `closest` does not: a splitter
799
- * slotted into another component's shadow root has a `parentNode` chain
800
- * ending at a `ShadowRoot`, and nesting through a slot is the ordinary case
801
- * for anything composable.
802
- */
803
361
  findOuter() {
804
362
  const outer = closestInstance(this.parentElement, HelixHeadlessSplitter_1);
805
363
  if (!outer || outer === this || outer.vertical === this.vertical) {
806
- // Parallel dividers never meet, so there is no corner to take hold of.
807
364
  return;
808
365
  }
809
366
  this.outer = outer;
810
367
  this.crossedAt = this.position;
811
368
  outer.cross(this, this.position);
812
369
  }
813
- /**
814
- * Keeps a gesture on the corner to itself.
815
- *
816
- * The stop is not redundant with the corner being a sibling of the separator.
817
- * Should a press on the corner ever reach the separator, the separator calls
818
- * `setPointerCapture`, which retargets every later pointer event to *it*: the
819
- * corner's `pointerup` never arrives, its origin is never cleared, and it
820
- * goes on resizing with no button held. Worse than moving twice.
821
- *
822
- * `preventDefault` is needed either way: a drag across a pane would otherwise
823
- * select the text under it.
824
- */
825
370
  leaveSeparator(event) {
826
371
  event.stopPropagation();
827
372
  event.preventDefault();
828
373
  }
829
374
  connectedCallback() {
830
375
  super.connectedCallback();
831
- // Only on a re-connect. The first pass waits for `firstUpdated`, which is
832
- // where a parent is guaranteed to have upgraded.
833
376
  if (this.hasUpdated) {
834
- /* The width watch too, and it belongs here for a plainer reason than the
835
- one above: `disconnectedCallback` drops the observer on every move, so
836
- a splitter reordered in the DOM would stop watching its own width and
837
- `auto` would freeze on whatever it last saw. */
838
377
  this.watchWidth();
839
378
  this.findOuter();
840
379
  }
@@ -852,41 +391,23 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
852
391
  this.watchWidth();
853
392
  this.findOuter();
854
393
  }
855
- /** Keeps the corner in step while this divider moves under it. */
856
394
  updated(changed) {
857
395
  super.updated(changed);
858
396
  if (changed.has('orientation')) {
859
397
  this.watchWidth();
860
398
  }
861
- /* The resolved collapse, mirrored for a consumer's own rules: a shut pane
862
- usually wants its contents to stop being read as well as stop being seen.
863
- `data-` and not `collapsed`, so a serialized host cannot be re-parsed with
864
- it declared, which is the trap `value` records. It carries which pane
865
- rather than a bare boolean, and that is still a state and not a payload:
866
- two names for the two sides, nothing of the consumer's in it. */
867
399
  if (this.collapsed) {
868
400
  this.dataset.collapsed = this.collapsed;
869
401
  }
870
402
  else {
871
403
  delete this.dataset.collapsed;
872
404
  }
873
- /* The resolved axis, mirrored for the same reason and on the same terms: a
874
- stylesheet selecting `[orientation="vertical"]` cannot see an `auto` that
875
- has turned, so the layout would stay side by side while the drag, the keys
876
- and `aria-orientation` had all gone the other way. That is the lie the
877
- policy warns a CSS-only version tells, arrived at from the other side.
878
-
879
- Only where `auto` produced it. An explicit `vertical` already carries an
880
- attribute saying so, and writing a second would put two sources of one
881
- truth on the host and a flash of the wrong axis before hydration. */
882
405
  if (this.orientation === 'auto' && this.stacked) {
883
406
  this.dataset.axis = 'vertical';
884
407
  }
885
408
  else {
886
409
  delete this.dataset.axis;
887
410
  }
888
- /* `showing` rather than `position`, so a corner drawn on the splitter above
889
- follows this divider through a drag instead of catching up at the end. */
890
411
  if (this.outer && this.showing !== this.crossedAt) {
891
412
  this.crossedAt = this.showing;
892
413
  this.outer.cross(this, this.showing);
@@ -897,16 +418,6 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
897
418
  }
898
419
  render() {
899
420
  const position = this.showing;
900
- /* The panes are marked while the divider moves, so the styled tier can take
901
- them out of the pointer's way.
902
- A pane holds whatever a consumer put in it, and an `<iframe>` is the case
903
- that breaks: a cross-origin frame is its own hit-testing world, and a drag
904
- whose path crosses one can be lost to it. The sandbox is exactly this, a
905
- sandboxed preview sitting against the panel divider, so the drag that
906
- makes the panel bigger is the drag that goes straight over it.
907
- On the panes rather than on the host, which the `dragging` state above
908
- records as a rule: a `data-` attribute here is one this element owns, and
909
- one on the host is one a React consumer's server markup never had. */
910
421
  return html `<div
911
422
  part="start"
912
423
  ?data-dragging=${this.dragging}