@fusion.dev/helix-headless 0.4.2 → 0.4.4

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 (44) hide show
  1. package/custom-elements.json +39 -39
  2. package/dist/app-header/root/app-header-root.d.ts +8 -28
  3. package/dist/avatar/group/avatar-group.d.ts +5 -30
  4. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +9 -28
  5. package/dist/chat/root/chat-root.d.ts +9 -28
  6. package/dist/color-picker/root/color-picker-root.d.ts +10 -28
  7. package/dist/combobox/root/combobox-root.d.ts +18 -60
  8. package/dist/copy-button/root/copy-button-root.d.ts +6 -21
  9. package/dist/data-table/root/data-table-root.d.ts +9 -32
  10. package/dist/date-input/root/date-input-root.d.ts +5 -21
  11. package/dist/date-picker/root/date-picker-root.d.ts +10 -35
  12. package/dist/dialog/root/dialog-root.d.ts +7 -29
  13. package/dist/donut-chart/root/donut-chart-root.d.ts +5 -25
  14. package/dist/file-input/root/file-input-root.d.ts +5 -22
  15. package/dist/heatmap/root/heatmap-root.d.ts +13 -36
  16. package/dist/image/root/image-root.d.ts +5 -22
  17. package/dist/input/root/input-root.d.ts +11 -29
  18. package/dist/kbd/root/kbd-root.d.ts +8 -26
  19. package/dist/legend/root/legend-root.d.ts +6 -24
  20. package/dist/listbox/root/listbox-root.d.ts +4 -26
  21. package/dist/megamenu/root/megamenu-root.d.ts +7 -39
  22. package/dist/meter/root/meter-root.d.ts +6 -24
  23. package/dist/number-input/root/number-input-root.d.ts +11 -20
  24. package/dist/outline/root/outline-root.d.ts +8 -27
  25. package/dist/pagination/root/pagination-root.d.ts +21 -54
  26. package/dist/pagination/summary/pagination-summary.d.ts +10 -31
  27. package/dist/progress/root/progress-root.d.ts +4 -21
  28. package/dist/rating/root/rating-root.d.ts +8 -31
  29. package/dist/slider/root/slider-root.d.ts +9 -32
  30. package/dist/splitter/root/splitter-root.d.ts +17 -85
  31. package/dist/steps/root/steps-root.d.ts +8 -26
  32. package/dist/steps/step/step.d.ts +11 -41
  33. package/dist/toast/item/toast-item.d.ts +6 -30
  34. package/dist/toggle/group/toggle-group.d.ts +7 -25
  35. package/dist/tooltip/root/tooltip-root.d.ts +14 -36
  36. package/dist/tour/root/tour-root.d.ts +6 -29
  37. package/dist/tree/item/tree-item.d.ts +13 -35
  38. package/dist/video/root/video-root.d.ts +12 -40
  39. package/dist/video-playlist/menu/video-playlist-menu.d.ts +3 -25
  40. package/dist/virtual-list/root/virtual-list-root.d.ts +5 -44
  41. package/package.json +17 -18
  42. package/{jsx.d.ts → types/jsx.d.ts} +724 -2537
  43. package/{vue.d.ts → types/vue.d.ts} +39 -39
  44. /package/{svelte.d.ts → types/svelte.d.ts} +0 -0
@@ -7991,13 +7991,13 @@ export interface WcToolkitVueGlobalComponents {
7991
7991
  HelixHeadlessAnnouncementVueProps,
7992
7992
  HelixHeadlessAnnouncementVueEvents
7993
7993
  >;
7994
- /** Tracks whether the page has scrolled and carries it as the custom state 'scrolled', so the styled tier can lift the bar off the page without a scroll handler of its own. Scroll position is the one piece of state the server genuinely cannot know, so it is read on connect rather than derived: a fresh load starts at the top, which is what the server rendered, and a reload part-way down corrects on hydration. **A custom state and never 'data-scrolled'.** An attribute the element writes onto its own host is one the server did not send, so a hydrating framework reports the tree as mismatched, intermittently: only when the reader reloads far enough down the page to pass the threshold. A consumer can answer that only by suppressing hydration warnings on an element they did not write. 'ElementInternals.states' is deliberately not in the DOM, so there is nothing for a hydration pass to disagree with, and ':state(scrolled)' selects it. The base class made the same move for 'data-settled' and its note explains the rest. The listener is passive and reads 'scrollY' only, so it never blocks scrolling. Three regions, each in its own wrapper, because the row is a grid and a grid needs three items to put the middle one in the middle. A bare slot is 'display: contents', so its assigned elements become grid items themselves: two actions would take two tracks and push the navigation off center. These wrappers earn their boxes, which is the test; they carry no inset or fill of their own, so an empty one still paints nothing. The host is the banner and spans the viewport; the row inside it is what gets centered. A banner that is itself narrowed leaves its background stopping short of the window edges, which reads as a floating bar rather than as the top of the page. That needs one element and one row, not a wrapper as well: the host carries the material and 'content' carries the measure. */
7994
+ /** Tracks whether the page has scrolled and carries it as the custom state 'scrolled', so the styled tier can lift the bar without a scroll handler of its own. It is read on connect rather than derived: a fresh load starts at the top, which is what the server rendered, and a reload part-way down corrects on hydration. **A custom state and never 'data-scrolled'.** An attribute the element writes onto its own host is one the server did not send, so a hydrating framework reports a mismatch, intermittently: only when the reader reloads far enough down the page. 'ElementInternals.states' is not in the DOM, so there is nothing for a hydration pass to disagree with, and ':state(scrolled)' selects it. */
7995
7995
  "helix-headless-app-header": DefineCustomElement<
7996
7996
  HelixHeadlessAppHeader,
7997
7997
  HelixHeadlessAppHeaderVueProps,
7998
7998
  HelixHeadlessAppHeaderVueEvents
7999
7999
  >;
8000
- /** A part of 'Avatar' rather than a family of its own: it arranges avatars and is about nothing else, and its member is a complete 'Avatar' the group did not have to explain. **The overflow count is the group's, not a slot.** Which avatars are hidden is a fact the group works out from 'max' and how many it was given, so a consumer writing the "+3" themselves would be writing a number that has to agree with a list they do not control. **Hidden, not removed.** Past 'max', the extra avatars stay in the DOM and stop being shown, so nothing re-renders when 'max' changes and the accessible name can still be built from all of them. The count is what a sighted reader gets; the group's own name is what everyone else gets, and "Seven people" is more useful than seven names read in a row. **And hidden by the stylesheet, not by this class.** Hiding from 'updated' is one update too late and one element too far out: Lit renders on a microtask, so an avatar appended past the limit is laid out and painted before anything runs to hide it, and marking each one means writing attributes onto markup a consumer owns. The styled tier's ':host([max="N"]) ::slotted(:nth-child(n + N+1))' is right at the first style resolution and writes nothing. **The fold is finite, and the count agrees with it.** 'An+B' takes a literal, so there is one selector per value of 'max' and the range stops at twelve. Past that the group shows everyone, counts nothing and says so, because showing everyone is a defensible answer and showing everyone beside a "+5" is not. 'max' above twelve is a list the consumer should be slicing themselves. **It is a list.** A row of faces is a set of separate people, and a group with no role announces as one run of text. 'role="list"' needs its children to be 'listitem', which they cannot be told to become without writing an attribute onto a consumer's element, so the group carries 'role="group"' and its name instead: one announcement, correct, and nothing written to anyone else's markup. */
8000
+ /** A part of 'Avatar' rather than a family of its own: it arranges avatars and is about nothing else. **The overflow count is the group's, not a slot.** Which avatars are hidden is a fact the group works out from 'max', so a consumer writing the "+3" would be writing a number that has to agree with a list they do not control. **Hidden, not removed.** Past 'max' the extra avatars stay in the DOM and stop being shown, so nothing re-renders when 'max' changes and the accessible name is still built from all of them. */
8001
8001
  "helix-headless-avatar-group": DefineCustomElement<
8002
8002
  HelixHeadlessAvatarGroup,
8003
8003
  HelixHeadlessAvatarGroupVueProps,
@@ -8033,7 +8033,7 @@ export interface WcToolkitVueGlobalComponents {
8033
8033
  HelixHeadlessBreadcrumbVueProps,
8034
8034
  HelixHeadlessBreadcrumbVueEvents
8035
8035
  >;
8036
- /** The host is the landmark, so 'aria-label' on it names the trail directly and there is no property mirroring one. A page may hold more than one navigation, and landmarks are how a screen-reader user chooses between them. 'role="list"' is set explicitly on the '<ol>' even though it is already a list: removing the markers with 'list-style: none' strips list semantics in Safari, and a breadcrumb trail always removes its markers. Items are slotted rather than wrapped, so they are '<ol>' children in the flat tree and each carries 'role="listitem"' itself. **The trail does not measure itself, and the overflow is markup.** Measuring how many crumbs fit can never reach the server: there is no width without a DOM, so the server ships every crumb and the client removes some after hydrating, and a trail that collapses paints wrong on every single load. Nor does anybody specify a breadcrumb in units of fit. Designs say "keep the root and the current page, fold the middle", which is a fact about depth, or "fold below this width", which is a fact CSS knows. So the trail lays out crumbs and separators, and the consumer writes what folds: a 'helix-menu' of 'helix-menu-item's in a crumb, where the gap belongs. Server rendered, no observer, no stylesheet, and correct before the first script runs. A trail that should fold only at some widths is a container query in the consumer's own sheet, which is theirs to write and theirs to leave out. **And the crumbs carry no marker for any of it.** CSS can already see it: the crumb with the menu in it is ':has(helix-menu)', and the middle is ':not(:first-child, [current])'. A marker would be the design rule written twice, once in the HTML and once in the sheet, with nothing keeping the two honest. */
8036
+ /** The host is the landmark, so 'aria-label' on it names the trail directly. A page may hold more than one navigation, and landmarks are how a screen-reader user chooses between them. 'role="list"' is set explicitly on the '<ol>' even though it is one: 'list-style: none' strips list semantics in Safari, and a breadcrumb trail always removes its markers. Items are slotted rather than wrapped, so they are '<ol>' children in the flat tree and each carries 'role="listitem"' itself. **The trail does not measure itself, and the overflow is markup.** */
8037
8037
  "helix-headless-breadcrumbs": DefineCustomElement<
8038
8038
  HelixHeadlessBreadcrumbs,
8039
8039
  HelixHeadlessBreadcrumbsVueProps,
@@ -8081,7 +8081,7 @@ export interface WcToolkitVueGlobalComponents {
8081
8081
  HelixHeadlessChatMessageVueProps,
8082
8082
  HelixHeadlessChatMessageVueEvents
8083
8083
  >;
8084
- /** **It has no opinion about where it sits.** A chat is asked for as a floating button, a page, a palette, a sidebar, a modal and an inline block, and none of those is a variant of a conversation: they are containers a conversation is put in, and the containers already exist. 'Popover' and 'SpeedDial' make the floating one, 'Drawer' the sidebar, 'Dialog' the modal, 'Command' the palette, and a page is a page. Nothing here is positioned, nothing here has a backdrop, and nothing here owns a trigger, because a trigger opens a container and containers own their own. If a form factor needs a mode on this element, the split is wrong. **Scroll ownership is the behavior everything else depends on, and it is the most-got-wrong thing in the category.** A transcript that grows wants to stay at the bottom, except when the reader has scrolled up to read, where pinning is hostile. The rule: pin while the reader is within 'threshold' of the bottom, release the moment they leave it, and offer a way back. **Never scroll a reader who is reading.** The element owns it, because it is the only party that knows a message arrived and where the viewport was when it did. 'pinned' announces before it moves, so a consumer who wants to own it refuses the request and assigns their own. **The live region announces on completion, not on arrival.** The transcript is 'role="log"' with 'aria-live="polite"', and a streaming message carries 'aria-busy' while it arrives: a screen reader reading a stream token by token is unusable, and holding the announcement until the flag clears is the only arrangement that works. Somebody typing is a separate status region, so it never interleaves with what was said. **Not a client.** No transport, no protocol, no model. It renders a conversation and reports what the reader did. */
8084
+ /** **It has no opinion about where it sits.** A chat is asked for as a floating button, a page, a palette, a sidebar, a modal and an inline block, and none of those is a variant of a conversation: they are containers, and the containers already exist. Nothing here is positioned, nothing has a backdrop, and nothing owns a trigger. If a form factor needs a mode on this element, the split is wrong. **Scroll ownership is the behavior everything else depends on.** A transcript that grows wants to stay at the bottom, except when the reader has scrolled up to read, where pinning is hostile. The rule: pin while the reader is within 'threshold' of the bottom, and let go the moment they leave it. */
8085
8085
  "helix-headless-chat": DefineCustomElement<
8086
8086
  HelixHeadlessChat,
8087
8087
  HelixHeadlessChatVueProps,
@@ -8117,13 +8117,13 @@ export interface WcToolkitVueGlobalComponents {
8117
8117
  HelixHeadlessColorInputVueProps,
8118
8118
  HelixHeadlessColorInputVueEvents
8119
8119
  >;
8120
- /** **The picker itself, with no field around it.** Color Input is this element in a panel behind a text field, the same way Date Input is 'helix-date-picker' in a panel: a form wants a field, and a settings pane or a toolbar wants the picker sitting flat on the page with nothing to open. Neither should be the other one with parts hidden. **The square is two real range inputs.** Saturation and brightness are each an '<input type="range">', laid over the square and made invisible, and the handle is drawn from their values. The pointer gets the square; the keyboard gets the arrow keys, Home, End and the page keys; a screen reader gets two sliders that announce a name and a number. All of it is the platform's, and nothing here implements a key handler. There is no 'role="application"', which is the usual way this control is built and amounts to asking a screen reader to stop helping. **The working model is HSB, and the value is derived from it.** The reverse loses colors: black is 'brightness: 0', whose hue and saturation are both undefined, so a reader who drags to the bottom of the square and back would get gray where they had red. Holding the model means a color survives a trip through one that has no hue. A value arriving from outside is read into the model once, which is the only moment the conversion runs in that direction. **Every syntax is read; the one 'format' names is written.** '#f00', '#ff0000', 'rgb(255 0 0)', 'rgba(255,0,0,.5)' and 'hsl(0 100% 50%)' are all colors. Named colors are the exception, and deliberately: resolving 'rebeccapurple' means shipping a table of a hundred and forty or asking the browser, and there is no browser during a server render. The properties below are written by this component and read by whoever draws it. A consumer sets none of them: they carry a number this element computed and CSS cannot, which is what lets a stylesheet on this tier draw the thing without measuring anything itself. */
8120
+ /** **The picker itself, with no field around it.** Color Input is this element in a panel behind a text field; a settings pane or a toolbar wants the picker flat on the page with nothing to open. **The square is two real range inputs.** Saturation and brightness are each an '<input type="range">', laid over the square and made invisible, and the handle is drawn from their values. The keyboard, the announcements and the touch targets are the platform's, and there is no 'role="application"'. **The working model is HSB, and the value is derived from it.** The reverse loses colors: black is 'brightness: 0', whose hue and saturation are both undefined, so dragging to the bottom of the square and back would give gray where there had been red. A value arriving from outside is read into the model once. */
8121
8121
  "helix-headless-color-picker": DefineCustomElement<
8122
8122
  HelixHeadlessColorPicker,
8123
8123
  HelixHeadlessColorPickerVueProps,
8124
8124
  HelixHeadlessColorPickerVueEvents
8125
8125
  >;
8126
- /** **The list is 'helix-listbox', and the filtering is its own.** It already owns narrowing, the empty state, the highlight, 'aria-activedescendant' and scrolling an option into view. Rewriting them here would be a second copy to keep in step; what is left for this element is the field, the panel and the traffic between them. **The text and the value are two things.** What a reader types is a filter, not an answer: it narrows the list and is thrown away. The value is whichever option they then choose, and the field is rewritten to that option's words once they have. A combobox that treated the typing as the value would submit "jav" to a form, which is the mistake a plain input makes and the reason this component exists. That is also why the field is emptied of its filter on close rather than left holding it: a control reopened still narrowed by the last visit's search is a control whose second use starts from the first one's leftovers. Select records the same. **Focus stays in the field, which is the whole of what a combobox is.** The arrows move the list without leaving the text, which is why the listbox has a public 'navigate': the moving, the wrapping, the skipping of disabled options and the seeding from the current value all stay in the one place that already does them. 'role="combobox"' on the input rather than on a wrapper, because the thing the pattern describes is the text field, and 'aria-controls' can name the listbox because both are in this shadow root. The active option cannot be named that way, being the consumer's light DOM, so it is linked by element reference for the reason Listbox records at length. **With 'multiple', the chosen options are tags in the field.** A field that says "Rust, Ruby, Go" is a sentence the reader has to parse to find out what is in it, and offers nothing to press to take one out: Select does that today and it is the weakest part of it. A tag apiece is one object per answer, each with its own remove control, which is what the reader already believes they are looking at. The panel stays open while more than one can be chosen, because a control that shut after each pick is one the reader has to reopen to keep using. The filter clears on each choice instead, so the next pick starts from the whole list rather than from the last search. **'creatable' announces, and does not write.** The options are the consumer's light DOM and nothing here puts an element into it: a component that grew the consumer's markup would be a component their framework re-renders over. What this does is offer a row saying what would be added, take the value when the reader presses it, and say so with 'helix-create'. A consumer who has to validate first refuses the event and adds nothing. Which leaves a value naming no option, and that is what the words fall back to: for anything chosen the field shows the option's text if there is one and the value itself otherwise. No copy is stored, so a consumer who then adds a real option with that value has its words take over on the next render. **'suggest' finishes the word, and only ever forwards.** The field completes to the first option the typing begins, with the part the reader did not type selected, so the next character replaces it and Delete removes it. Applied on insertion alone: completing after a deletion would put back what the reader just took out, and a field that cannot be shortened is a field that cannot be corrected. Measured as the difference between 'insertText' and the several 'delete*' input types, which is what the platform already says about which happened. The filter stays what was typed rather than what was completed. Filtering on the completion would narrow the list to the one option it came from, so a reader typing "ru" would be shown "Rust" alone and never learn Ruby was there. */
8126
+ /** **The list is 'helix-listbox', and the filtering is its own.** It owns narrowing, the empty state, the highlight, 'aria-activedescendant' and scrolling an option into view. What is left for this element is the field, the panel and the traffic between them. **The text and the value are two things.** What a reader types is a filter: it narrows the list and is thrown away. The value is whichever option they then choose, and the field is rewritten to that option's words once they have. The filter is cleared on close, so a reopened control does not start from the last visit's leftovers. **Focus stays in the field, which is the whole of what a combobox is.** The arrows move the list without leaving the text, through the listbox's public 'navigate'. 'role="combobox"' is on the input rather than on a wrapper, because the thing the pattern describes is the text field. **With 'multiple', the chosen options are tags in the field**, one object per answer, each with its own remove control. The panel stays open while more than one can be chosen and the filter clears on each choice, so the next pick starts from the whole list. **'creatable' announces, and does not write.** The options are the consumer's light DOM and nothing here puts an element into it: a component that grew the consumer's markup would be one their framework re-renders over. It offers the row and fires the event; adding the option is the consumer's. */
8127
8127
  "helix-headless-combobox": DefineCustomElement<
8128
8128
  HelixHeadlessCombobox,
8129
8129
  HelixHeadlessComboboxVueProps,
@@ -8135,31 +8135,31 @@ export interface WcToolkitVueGlobalComponents {
8135
8135
  HelixHeadlessCommandVueProps,
8136
8136
  HelixHeadlessCommandVueEvents
8137
8137
  >;
8138
- /** **The confirmation is the component.** Writing to the clipboard is three lines; what makes this worth an element is that the platform gives a reader no feedback at all. A press that copies and looks identical to a press that failed is a control nobody trusts, so they press it again, and the second press is indistinguishable from the first. **Announced after the write, never before.** A clipboard write is a permission the browser may refuse, and it is refused for real reasons: an insecure origin, a document that is not focused, a user agent that requires a gesture this press did not qualify as. So the confirmation and the event both wait for the promise, and a refusal leaves the button exactly as it was. 'HelixCopyEvent' is not cancelable for the same reason: by the time there is anything to say, it has already happened. **An icon button, extending IconButton.** Both of this button's faces are icons and it never carries a word, so it is the square control rather than a text button that happens to hold a glyph: it inherits the square box, and with it the rule that an icon-only control must be named. 'label' is not optional here for the same reason it is not optional there, and the two faces make it sharper: the accessible name has to describe the press, not whichever picture is showing when a reader arrives. 'data-copied', not a reflected 'copied'. State never reflects here: a serialized 'copied' attribute would come back from a paste as a button claiming to have copied something it never held. */
8138
+ /** **The confirmation is the component.** Writing to the clipboard is three lines; what makes this worth an element is that the platform gives a reader no feedback at all, so a press that copies looks identical to one that failed. **Announced after the write, never before.** A clipboard write is a permission the browser may refuse, for real reasons: an insecure origin, a document that is not focused, a gesture that did not qualify. The confirmation and the event both wait for the promise, and a refusal leaves the button exactly as it was. */
8139
8139
  "helix-headless-copy-button": DefineCustomElement<
8140
8140
  HelixHeadlessCopyButton,
8141
8141
  HelixHeadlessCopyButtonVueProps,
8142
8142
  HelixHeadlessCopyButtonVueEvents
8143
8143
  >;
8144
- /** **The schema is markup and the data is a property.** Columns are declared as 'helix-table-column' children, because a schema is small, declarative, and something a reader should be able to see in the HTML; rows are handed over as an array, because data is large and a table that draws ten thousand elements cannot window them. '''html <helix-data-table .rows=${rows} sort="name:asc" page-size="20"> <helix-table-column field="name" sortable>Name</helix-table-column> <helix-table-column field="size" align="end" sortable .cell=${bytes}>Size</helix-table-column> </helix-data-table> ''' 'helix-table' takes rows as markup and is the right component for a table of twelve things a person wrote out. **Sort and filter apply to the whole set, never the page.** Sorting the drawn window is correct on the first screen and wrong on every other one, which is how it survives being looked at. The arithmetic lives in 'order.ts' where nothing can see a window, and its spec asserts the composition rather than the three steps, because each step alone passes either way. **What it does not do, stated where a consumer will read it: the cell values do not server-render.** The headings do, because a column renders its own, and the row elements do, because their count comes from 'rows'. Reading a cell needs the columns, and the columns are markup children that Lit SSR does not hand this element: 'children' is undefined there. So a crawler and a reader on a slow connection see a table of the right shape with nothing in it, which is strictly less than 'helix-table' serves for a table whose rows are markup. That is the cost of "the schema is markup and the data is a property". Use 'helix-table' where the content has to be in the served HTML and the result is small enough to write out; use this where the result is large, and accept that the first paint is a shell. The obvious workaround, a 'columns' property beside the markup columns, is deliberately not offered: it splits one source of truth for the columns into two that nothing checks against each other, and a table rendering one way on the server and another in the browser is that failure with a hydration mismatch on top. **Every axis is controlled or uncontrolled, and that is what makes a server mode fall out rather than be bolted on.** 'sort', 'filter', 'page' and 'page-size' each defer to the consumer when set and seed themselves from a 'default-*' when not. A consumer who answers all four holds the data flow: they hear the reader's request, fetch, and hand back a new 'rows' with a 'total'. Retrofitting that inverts the flow and is a rewrite, so it is here from the start. */
8144
+ /** **The schema is markup and the data is a property.** Columns are declared as 'helix-table-column' children, because a schema is small and something a reader should see in the HTML; rows are handed over as an array, because a table that draws ten thousand elements cannot window them. '''html <helix-data-table .rows=${rows} sort="name:asc" page-size="20"> <helix-table-column field="name" sortable>Name</helix-table-column> <helix-table-column field="size" align="end" sortable .cell=${bytes}>Size</helix-table-column> </helix-data-table> ''' 'helix-table' takes rows as markup and is the right component for a table of twelve things a person wrote out. **Sort and filter apply to the whole set, never the page.** Sorting the drawn window is correct on the first screen and wrong on every other one. **The cell values do not server-render.** The headings do, and the row elements do, but reading a cell needs the columns, and the columns are markup children Lit SSR does not hand this element. So a crawler sees a table of the right shape with nothing in it. That is the cost of the first paragraph: use 'helix-table' where the served HTML has to carry the values. */
8145
8145
  "helix-headless-data-table": DefineCustomElement<
8146
8146
  HelixHeadlessDataTable,
8147
8147
  HelixHeadlessDataTableVueProps,
8148
8148
  HelixHeadlessDataTableVueEvents
8149
8149
  >;
8150
- /** **The calendar is 'helix-date-picker', not a second one written here.** It already knows about months, ranges, disabled days, the first day of the week and every keyboard rule a grid owes a reader, and a picker that reimplemented a tenth of that would be a worse grid that also had to be maintained. This element is the field, the overlay, and the translation between them. **Typed as well as picked, and the typing is the reason it is not '<input type="date">'.** The native control gives a free picker and a free locale and takes almost all styling with it, which is a fair trade until a product needs the calendar to match everything around it. What is kept from the native one is the part worth keeping: a real text field, so a reader who knows the date types it and never opens anything. **The value is 'YYYY-MM-DD', always.** That is what a form submits, what 'min' and 'max' compare against, and what the calendar speaks. What the field *shows* is the same string: a locale-formatted display would have to be parsed back on every keystroke, and half-typed input does not survive a round trip through a formatter. The same reasoning the sandbox records for its two syntaxes, which is that a document being edited must not be a rendering of something else. **Opening does not commit.** The calendar announces a date, this element takes it, and the overlay shuts, because a single date has nothing more to say. A consumer who owns 'value' answers the announcement the way they answer any other control here. */
8150
+ /** **The calendar is 'helix-date-picker', not a second one written here.** It already knows about months, ranges, disabled days, the first day of the week and every keyboard rule a grid owes a reader. This element is the field, the overlay, and the translation between them. **Typed as well as picked, and the typing is why it is not '<input type="date">'.** The native control gives a free picker and a free locale and takes almost all styling with it. What is kept from it is the part worth keeping: a real text field. */
8151
8151
  "helix-headless-date-input": DefineCustomElement<
8152
8152
  HelixHeadlessDateInput,
8153
8153
  HelixHeadlessDateInputVueProps,
8154
8154
  HelixHeadlessDateInputVueEvents
8155
8155
  >;
8156
- /** A grid of days, per the APG date grid: the whole grid is one tab stop, arrows move by day and week, 'PageUp' and 'PageDown' move by month, and 'Home' and 'End' go to the ends of the week. Only the focused cell is in the tab sequence. Roving 'tabindex' is written on the cells, which is free here because they are rendered into this shadow root rather than being markup a consumer wrote. Dates are 'YYYY-MM-DD' strings and every calculation is in UTC. A local-time 'Date' shifts across a timezone or a daylight-saving boundary, so the string is what the component holds and the arithmetic never leaves UTC. 'month' is what the grid shows, separately from 'value', so paging through months does not select anything and a consumer can drive the view. Left unset, it follows the first selected date, and then the current month. 'mode' decides how many dates are held. Absent takes one. 'range' takes two and marks everything between them, starting over on the third click rather than replacing an end at random. 'multiple' toggles, so clicking a chosen date takes it out. All three read and write the same comma-separated 'value'. 'locale' is worth setting where the output is compared across environments. Left unset, month and day names come from the runtime's own locale, and a server and a browser need not agree on what that is. A control whose value changes dispatches 'helix-before-change': 'Input', 'Select', 'Listbox', 'Radio.Group' and 'Toggle.Group' all do, and a calendar submitting a date is the same kind of thing. 'helix-select' stays what it is elsewhere, which is a row or an item being chosen in something that reports no value at all, such as 'Menu' or 'Tree'. Paging is 'helix-navigate' and not either of those, because it chooses nothing: the view moves and the answer does not. The properties below are written by this component and read by whoever draws it. A consumer sets none of them: they carry a number this element computed and CSS cannot, which is what lets a stylesheet on this tier draw the thing without measuring anything itself. */
8156
+ /** A grid of days, per the APG date grid: the whole grid is one tab stop, arrows move by day and week, 'PageUp' and 'PageDown' by month, 'Home' and 'End' to the ends of the week. Only the focused cell is in the tab sequence. **Dates are 'YYYY-MM-DD' strings and every calculation is in UTC.** A local-time 'Date' shifts across a timezone or a daylight-saving boundary. 'month' is what the grid shows, separately from 'value', so paging does not select anything and a consumer can drive the view. Left unset it follows the first selected date, then the current month. 'mode' decides how many dates are held: absent takes one, 'range' takes two and marks everything between them, 'multiple' toggles. All three read and write the same comma-separated 'value'. */
8157
8157
  "helix-headless-date-picker": DefineCustomElement<
8158
8158
  HelixHeadlessDatePicker,
8159
8159
  HelixHeadlessDatePickerVueProps,
8160
8160
  HelixHeadlessDatePickerVueEvents
8161
8161
  >;
8162
- /** A real '<dialog>' opened with 'showModal()', not a div with a role and a hand-built trap. The platform gives four things that are each a day's work to reimplement and easy to get subtly wrong: the top layer, so the dialog is above everything regardless of what 'z-index' anyone else wrote; a real backdrop; Escape; and inert-behind, so the page underneath is not reachable by pointer, keyboard, or a screen reader's virtual cursor. **'open' drives 'showModal()', and every way it can close comes back through 'requestOpen'.** A native dialog can close from three directions: the property, Escape, and a '<form method="dialog">' submit inside it. Left alone, two of those would shut the dialog while the consumer's state still said open, which is the bug that makes a modal impossible to reopen. Escape is intercepted and routed through the request, so 'preventDefault()' on the 'helix-before-close' refuses it; a close that happens anyway is reflected back. **Nothing here traps focus.** 'showModal()' does it, and better: 'inert' on the rest of the document is something no JavaScript trap can achieve. 'FocusTrapController' stays for the surfaces that are not modal. The regions are bare slots rather than wrappers, for the reason the card's are: an unassigned '<slot>' is 'display: contents' and generates no box, so a dialog with no footer has no empty band where one would be. Opened declaratively with the platform's invoker attributes: '''html <helix-button commandfor="confirm" command="--show">Delete…</helix-button> <helix-dialog id="confirm">…</helix-dialog> ''' '--show', '--close' and '--toggle'. Custom commands rather than the built-in 'show-modal', because those are defined against native elements and do nothing when pointed at a custom one. */
8162
+ /** A real '<dialog>' opened with 'showModal()', not a div with a role and a hand-built trap. The platform gives four things that are each a day's work to reimplement: the top layer, so the dialog is above everything whatever 'z-index' anyone wrote; a real backdrop; Escape; and inert-behind, so the page underneath is unreachable by pointer, keyboard or virtual cursor. **'open' drives 'showModal()', and every way it can close comes back through 'requestOpen'.** A native dialog closes from three directions: the property, Escape, and a '<form method="dialog">' submit. Left alone, two of them would shut the dialog while the consumer's state still said open, which is the bug that makes a modal impossible to reopen. */
8163
8163
  "helix-headless-dialog": DefineCustomElement<
8164
8164
  HelixHeadlessDialog,
8165
8165
  HelixHeadlessDialogVueProps,
@@ -8171,7 +8171,7 @@ export interface WcToolkitVueGlobalComponents {
8171
8171
  HelixHeadlessDividerVueProps,
8172
8172
  HelixHeadlessDividerVueEvents
8173
8173
  >;
8174
- /** The chart with the narrowest honest use, so the component is narrow too. **It reads one column and treats the rows as the parts.** Every other chart here takes a column per series; a ring has one whole to divide, and a second ring inside the first is two charts sharing a picture in the most literal way. So the first column of numbers is the series and each row is a slice. **It refuses negative values rather than drawing them.** A share of a whole cannot be less than nothing, and an arc for a negative number either runs backwards over its neighbors or is silently dropped. Those rows are skipped and the total is of what is left, which is the only reading that stays true. **The shape is drawn with a dashed stroke, not with arc commands.** One '<circle>' per slice, its dash set to the slice's share of the circumference and its offset to where the slice begins. Arc paths need the sweep flag and a large-arc flag, both of which flip at values a reader never sees coming, and a slice of exactly one whole becomes a path with no length at all. **And it keeps its aspect ratio**, alone among the charts here. The others set 'preserveAspectRatio="none"' so one drawing fills any box and rescue their strokes with 'vector-effect'; that rescues strokes and not geometry, so a ring drawn the same way is an ellipse in anything but a square. The table is the accessible representation and this drawing is 'aria-hidden'. The properties below are written by this component and read by whoever draws it. A consumer sets none of them: they carry a number this element computed and CSS cannot, which is what lets a stylesheet on this tier draw the thing without measuring anything itself. */
8174
+ /** The chart with the narrowest honest use, so the component is narrow too. **It reads one column and treats the rows as the parts.** Every other chart here takes a column per series; a ring has one whole to divide, so the first column of numbers is the series and each row is a slice. **It refuses negative values rather than drawing them.** An arc for a negative number either runs backwards over its neighbors or is silently dropped. Those rows are skipped and the total is of what is left. */
8175
8175
  "helix-headless-donut-chart": DefineCustomElement<
8176
8176
  HelixHeadlessDonutChart,
8177
8177
  HelixHeadlessDonutChartVueProps,
@@ -8201,7 +8201,7 @@ export interface WcToolkitVueGlobalComponents {
8201
8201
  HelixHeadlessEmptyVueProps,
8202
8202
  HelixHeadlessEmptyVueEvents
8203
8203
  >;
8204
- /** **A real '<input type="file">', kept and made invisible rather than replaced.** It is the only way to open a file picker: the dialog is a privileged action a script cannot start on its own, and it opens for a click on the input or on a '<label>' pointing at it. A component that draws its own button ends up calling '.click()' on a hidden input anyway, and then has two things a screen reader can find where there is one control. So the input stays, holds the label, the name, the validity and the focus, and the visible trigger is a '<label for>' rather than a button. A label is not focusable and does not need to be: the input is, and Enter and Space on it open the picker because that is what the platform does with a file input. **Dropping is why this is a component rather than an attribute.** A bare file input accepts a drop only on the input itself, which is invisible here, so the field is the drop target and it says when something is over it. **Files are not a value a consumer can hand back.** Every other control here takes the controlled and uncontrolled pair, and this one does not: a 'FileList' cannot be constructed from markup, and browsers refuse a consumer writing 'value' on a file input outright, because a page that could name a file could read it. So this element owns what was chosen, announces it, and offers 'clear()' for the one change a consumer can legitimately ask for. **The list under it is the control's own, not decoration.** A file input with files chosen reports "3 files" and nothing else, which is unusable for checking that the right ones were picked. The names are rendered, and each carries the way to take it back out. */
8204
+ /** **A real '<input type="file">', kept and made invisible rather than replaced.** It is the only way to open a file picker: the dialog is a privileged action a script cannot start on its own, and it opens for a click on the input or on a '<label>' pointing at it. A component that draws its own button calls '.click()' on a hidden input anyway, and then has two things a screen reader can find where there is one control. So the input stays, holding the label, the name, the validity and the focus, and the visible trigger is a '<label for>'. A label is not focusable and does not need to be: the input is, and Enter and Space on it open the picker. */
8205
8205
  "helix-headless-file-input": DefineCustomElement<
8206
8206
  HelixHeadlessFileInput,
8207
8207
  HelixHeadlessFileInputVueProps,
@@ -8213,7 +8213,7 @@ export interface WcToolkitVueGlobalComponents {
8213
8213
  HelixHeadlessFormatDateVueProps,
8214
8214
  HelixHeadlessFormatDateVueEvents
8215
8215
  >;
8216
- /** **One family covers both readings, and the split people expect does not exist.** The argument for two is that a contribution graph needs a continuous ramp where uptime needs a category. GitHub's contribution graph is five discrete levels rather than a gradient, so a cell that takes a tone covers both: 'rows="1"' is a status strip, 'rows="7"' is a contribution graph, and heatmap is the name of that. **What it cannot be is a bar chart with flat values**, and the data model is what closes that rather than taste: chart color is per *series* everywhere else here, '--chart-series-1' through '-15', so ninety differently colored cells would be ninety series. The data is a real '<table>' named by 'for', the same convention every chart in this library uses, which also answers the accessibility question without inventing anything: **the table is the accessible representation**, it is already in the markup, and the drawing is a picture of it. Hide it with 'data-hidden-visually' and the reading is still there for anyone who needs it. A cell takes its tone from its own 'data-tone', or from 'thresholds' and 'tones' bucketing its value. The first is what a status page wants and the second is what a contribution graph wants; the cell's own wins, so a page that already knows a day was degraded does not have to encode that as a number for this component to decode again. **A tone is a name this tier never resolves.** 'tone="success"' writes '--heatmap-tone: var(--heatmap-tone-success)' onto the cell and stops there, so what a name is worth is the answering tier's to say: 'helix/styled' points the semantic names at the theme's status colors and the numbered ones at a ramp, and a consumer on 'headless' declares the same names as whatever they like. Reaching for 'var(--helix-color-${tone})' here is the dependency the tiers exist to prevent, and it fails loudly rather than quietly: off-tier that property is not undefined, it is *invalid*, so 'background-color: var(--heatmap-tone, …)' declines its own fallback and every cell comes out transparent, leaving a blank page under a row of footer text. 'shared/chart/series.css' records the same fault in Bar Chart. **On a narrow container it draws fewer cells rather than thinner ones.** Ninety cells in 320 pixels is three and a half pixels each, which is a smear and not a strip. The cells it drops are the oldest, and it reflects 'visible' and fires 'helix-resize' when the number changes, because **a summary saying "99.2% over 90 days" above a strip drawing 45 of them is telling the reader something false about the picture in front of them** and nothing about the picture says so. */
8216
+ /** **One family covers both readings.** 'rows="1"' is a status strip, 'rows="7"' is a contribution graph, and a cell that takes a tone covers both: GitHub's contribution graph is five discrete levels rather than a gradient. **What it cannot be is a bar chart with flat values**, and the data model is what closes that: chart color is per *series* everywhere else here, so ninety differently colored cells would be ninety series. **The data is a real '<table>' named by 'for'**, the convention every chart in this library uses, and it is the accessible representation. Hide it with 'data-hidden-visually' and the reading is still there. A cell takes its tone from its own 'data-tone', or from 'thresholds' and 'tones' bucketing its value. The cell's own wins, so a page that already knows a day was degraded does not encode that as a number to be decoded. **A tone is a name this tier never resolves.** 'tone="success"' writes '--heatmap-tone: var(--heatmap-tone-success)' and stops, so what a name is worth is the answering tier's to say. Reaching for 'var(--helix-color-*)' here is the dependency the tiers exist to prevent. */
8217
8217
  "helix-headless-heatmap": DefineCustomElement<
8218
8218
  HelixHeadlessHeatmap,
8219
8219
  HelixHeadlessHeatmapVueProps,
@@ -8231,25 +8231,25 @@ export interface WcToolkitVueGlobalComponents {
8231
8231
  HelixHeadlessIconVueProps,
8232
8232
  HelixHeadlessIconVueEvents
8233
8233
  >;
8234
- /** **An element rather than a recipe, because of the part CSS cannot do.** Reserving the box is 'aspect-ratio' and needs nothing from anybody; 'Link' and the megamenu row are recipes in 'preview.css' for exactly that reason. What cannot be written as a rule is *when the picture arrived*: a stylesheet has no way to know, and no way to hear that it never will. So this owns three states and the two events that move between them, and leaves the box to the stylesheet. **The placeholder is a slot with nothing in it by default.** A default of 'Skeleton' would be right and is not written here: this tier renders no other component, and reaching for one would make the headless package depend on the styled one. The styled tier draws the resting placeholder instead, and a consumer who wants a 'Skeleton', a blurred thumbnail or a solid color slots it. **No source is a loading state, not a fourth one.** A picture whose 'src' has not arrived yet is the common case for anything drawn from data, and the useful answer there is the same as for one still on the wire: stand something in its place and keep the ground reserved. So an element with no 'src' shows its placeholder and waits, rather than resolving to an empty box that has to be laid out twice. **'alt' is a property and not an afterthought.** An empty string is a real answer, meaning the picture is decoration and a screen reader should skip it, which is why the attribute is always written even when it is empty: omitting it entirely makes a reader announce the file name instead. */
8234
+ /** **An element rather than a recipe, because of the part CSS cannot do.** Reserving the box is 'aspect-ratio' and needs nothing from anybody. What cannot be written as a rule is *when the picture arrived*: a stylesheet has no way to know, and no way to hear that it never will. So this owns three states and the two events that move between them, and leaves the box to the stylesheet. **The placeholder is a slot with nothing in it by default.** A default of 'Skeleton' would make this tier depend on a component it does not ship. */
8235
8235
  "helix-headless-image": DefineCustomElement<
8236
8236
  HelixHeadlessImage,
8237
8237
  HelixHeadlessImageVueProps,
8238
8238
  HelixHeadlessImageVueEvents
8239
8239
  >;
8240
- /** Renders a real '<input>', so the caret, selection, autofill, spellcheck, the password manager, and the on-screen keyboard a phone chooses all come from the platform. 'delegatesFocus' puts focus on it, and it serializes into the server's markup, so the field is usable before any script loads. **The input inside a shadow root submits nothing**, because the form is on the other side of the boundary. The host is form-associated instead and reports its own value through 'ElementInternals', which is also what carries validity and what answers a form reset. 'value' and 'defaultValue' are separate for the reason 'checked' and 'defaultChecked' are: 'value' is what is in the field now and 'defaultValue' is what the markup asked for, which is where a reset returns to and is gone the moment anyone types. **Validity is the platform's.** The 'type' and 'required' are set on the real input and its 'validity' is copied onto the host, so an email field rejects what the browser says is not an email rather than what a regular expression here guesses. What this adds is the anchor, so 'reportValidity' points the bubble at the input rather than at the host. 'pattern', 'minlength' and 'maxlength' are forwarded with them. **'clearable' and 'reveal' are the field's, not the consumer's.** Neither is a slot's worth of variation: one empties the value, the other flips the type, and both want the same position, the same size and the same disabled behavior. The 'end' slot stays for what actually varies, a unit or a prefix. Both opt in rather than following from 'type', and are booleans that default to false. A Lit boolean property that defaults to *true* cannot be turned off from HTML at all, only from script, which is the wrong way round for revealing a password: a consumer should have to ask for it. Drawer's 'modal' defaults true for the opposite reason, that losing a focus trap by forgetting a word is worse than having to say so. */
8240
+ /** Renders a real '<input>', so the caret, selection, autofill, spellcheck, the password manager and the on-screen keyboard a phone chooses are all the platform's. It serializes into the server's markup, so the field is usable before any script loads. **The input inside a shadow root submits nothing**, the form being on the other side of the boundary. The host is form-associated instead and reports its own value through 'ElementInternals', which also carries validity and answers a form reset. 'value' and 'defaultValue' are separate the way 'checked' and 'defaultChecked' are: 'value' is what is in the field now, 'defaultValue' is what the markup asked for and where a reset returns to. **Validity is the platform's.** 'type' and 'required' are set on the real input and its 'validity' is copied onto the host. */
8241
8241
  "helix-headless-input": DefineCustomElement<
8242
8242
  HelixHeadlessInput,
8243
8243
  HelixHeadlessInputVueProps,
8244
8244
  HelixHeadlessInputVueEvents
8245
8245
  >;
8246
- /** Renders a real '<kbd>', so the passage still means "press this" to anything reading the markup rather than only to a reader looking at it. **One key per element, with 'keys' as the stated exception.** A *sequence* is several of these with the separator written between them as ordinary text, because that separator is prose: it is "then" in a sequence and "plus" in a simultaneous press, and a component that inserted one would have to guess which. 'keys' cannot be guessing, since a chord written 'mod+k' has said which it is. **'keys' exists because the glyphs were being typed by hand and were wrong.** A literal '⌘K' in a template tells a reader on Windows to press a key their keyboard does not have, and nothing catches it, a string being a string. Given 'mod' this renders '⌘K' on Apple and 'Ctrl+K' everywhere else. **'within' is what binds it, and it is absent by default.** Without it this draws a key and listens for nothing, which is what every existing use of this component wants. The two values are the two honest lifetimes: - 'control' listens within the element's own parent, which is the case this is mostly for: a hint sitting inside the control it belongs to, where the shortcut should work while that control has focus and stop existing when the control does. - 'document' listens on the document, for a chord that works from anywhere. **A hidden 'kbd' is a dead shortcut, and that is the point rather than a trap.** Binding here ties the key to its own advertisement, so a hint that is not rendered is a shortcut that is not offered. A chord that must outlive its label is a chord whose owner is not a label: give it to whatever owns the action and let this element only describe it. Nothing binds until this upgrades, so a shortcut is not available in the moments before hydration. */
8246
+ /** Renders a real '<kbd>', so the passage still means "press this" to anything reading the markup. **One key per element, with 'keys' as the stated exception.** A sequence is several of these with the separator written between them as ordinary text, because that separator is prose: "then" in a sequence, "plus" in a chord, and a component inserting one would have to guess which. **'keys' exists because the glyphs were being typed by hand and were wrong.** A literal '⌘K' in a template tells a reader on Windows to press a key their keyboard does not have, and nothing catches it. Given 'mod' this renders '⌘K' on Apple and 'Ctrl+K' everywhere else. **'within' is what binds it, and it is absent by default**, so this draws a key and listens for nothing unless asked. */
8247
8247
  "helix-headless-kbd": DefineCustomElement<
8248
8248
  HelixHeadlessKbd,
8249
8249
  HelixHeadlessKbdVueProps,
8250
8250
  HelixHeadlessKbdVueEvents
8251
8251
  >;
8252
- /** A legend is a list, so it is a list: not part of a chart, and shared by whichever of them needs one. It takes its entries from the same table the chart reads, through 'for', so the two cannot disagree about how many series there are or what they are called. 'entries' picks which way to read that table, and the two ways are the two shapes a chart comes in. 'series' is one entry per data column, named by the header row, which is a line or a bar chart. 'categories' is one entry per body row, named by its row header, which is a donut: its slices are the rows, and it has no axis to write their names along. **The table is resolved on the client and there is no server pass.** 'for' is an id and resolving it wants a document, which the server render does not have, so a legend ships empty and fills in on hydration. The table itself is ordinary markup and is served whole, so nothing is missing from the page before then, only from this list. The table is watched, because a legend that goes stale when its data changes is a legend that has to be told, and nothing else in the page would know to tell it. **Not hidden from a screen reader, though the mapping it draws is a color one.** The argument for hiding it is real: the colors mean nothing to a reader who cannot see them, and the names are already in the table. It stays because the cost of being wrong runs one way. A duplicated list of three words is noise; a consumer who took the table out of the accessibility tree and then found the legend hidden too has a chart nobody can read, and neither decision announces itself. */
8252
+ /** A legend is a list, so it is a list: not part of a chart, and shared by whichever of them needs one. It takes its entries from the same table the chart reads, through 'for', so the two cannot disagree. 'entries' picks which way to read that table. 'series' is one entry per data column, named by the header row, which is a line or a bar chart. 'categories' is one entry per body row, which is a donut: its slices are the rows and it has no axis to write their names along. **The table is resolved on the client and there is no server pass.** */
8253
8253
  "helix-headless-legend": DefineCustomElement<
8254
8254
  HelixHeadlessLegend,
8255
8255
  HelixHeadlessLegendVueProps,
@@ -8273,7 +8273,7 @@ export interface WcToolkitVueGlobalComponents {
8273
8273
  HelixHeadlessListVueProps,
8274
8274
  HelixHeadlessListVueEvents
8275
8275
  >;
8276
- /** **The listbox is the control and the options are its vocabulary.** A Select is this with a trigger in front of it, a Combobox is this with a text input, and a command palette is this again: they differ in what opens the list and what filters it, not in what the list is. Building it as its own family is what stops each of those reimplementing the keyboard and the filtering. **'value' is comma separated when 'multiple' is set**, the way 'Checkbox.Group''s is. It stays a string because that is what a form submits and what 'MixinControllerValue' holds; an array property would be a second shape for the same fact, and the two would disagree the first time one was set without the other. **Chosen and active are two different things.** Chosen is the answer; active is where the keyboard is. In a single-select list they move together, which is the radio group pattern and is what a reader expects from arrow keys. With 'multiple' they come apart: the arrows move without choosing and Space chooses, because a reader passing over four options on the way to the fifth did not mean to pick all five. **This does not use 'KeyboardNavigationController', and the reason is where focus lives rather than whether moving chooses.** The controller already separates those, taking 'origin: 'focus' | 'selection''. What it cannot do is leave focus alone: 'NavigableItem.focusItem()' moves DOM focus to the item, its model being a roving tab stop. A filterable list cannot have that, focus having to stay in the field or typing stops, so what moves is a virtual cursor and 'aria-activedescendant' is what says where it is. No flag reconciles the two. **Filtering hides rather than removes.** An option the filter has hidden is still the consumer's element in the consumer's order, so a filter that clears puts the list back exactly as it was with nothing re-rendered. What counts as a match is a plain case-insensitive substring, and a consumer wanting more hides options itself: 'filter' is a convenience over the same 'hidden' the component uses. */
8276
+ /** **The listbox is the control and the options are its vocabulary.** A Select is this with a trigger in front of it, a Combobox is this with a text input, and a command palette is this again: they differ in what opens the list and what filters it, not in what the list is. **'value' is comma separated when 'multiple' is set**, the way 'Checkbox.Group''s is. It stays a string because that is what a form submits and what 'MixinControllerValue' holds. **Chosen and active are two different things.** Chosen is the answer; active is where the keyboard is. Only the second moves with the arrows, and 'aria-activedescendant' names it while focus stays on the list. */
8277
8277
  "helix-headless-listbox": DefineCustomElement<
8278
8278
  HelixHeadlessListbox,
8279
8279
  HelixHeadlessListboxVueProps,
@@ -8285,7 +8285,7 @@ export interface WcToolkitVueGlobalComponents {
8285
8285
  HelixHeadlessLoaderVueProps,
8286
8286
  HelixHeadlessLoaderVueEvents
8287
8287
  >;
8288
- /** **A disclosure, not a menu, and that is the whole design.** 'role="menu"' and 'menuitem' are for an application's commands: they take over the arrow keys, announce "menu" to a reader who asked for navigation, and put every link inside a role that is not a link. What this is, is a button that shows and hides a region of the consumer's own anchors, which is the pattern the APG gives for site navigation and the one that leaves a middle click, a modified click and the status bar working. 'Menu' remains right for a row of commands. **It renders no links**, for the reason 'Navigation' gives: a router renders its own anchor and cannot render one inside this shadow root. The panel is a slot, and what goes in it is lists and headings the consumer wrote. **It opens on hover, and a press is always the fallback.** 'open-on' says which, and hover is the resting value because a nav bar is the one place a reader expects to sweep along a row and see each section: making them click, read, click again to compare two is the interaction hover exists to save. Hover is not the only way in, and the code is mostly about that. There is no hover on a touch screen, so a tap opens the panel there whatever 'open-on' says, which is why the pointer type is checked rather than assumed. Focus still opens nothing, for the same reason a tab through a nav bar should not unfold it, but Enter on the trigger does, and a keyboard press arrives as a click with no pointer behind it. A mouse click on a trigger the pointer is already sitting on is the case worth naming: toggling there would close the panel and the pointer, still on the trigger, would open it again, so the trigger would flicker and never close. In hover mode the mouse's own clicks are left alone and hover governs on its own. Escape and a click away still close it. The delays are intent, not decoration. Opening waits, so a pointer crossing the bar on its way somewhere else opens nothing; closing waits longer, so the diagonal from the trigger down into the panel does not pass through a gap and shut it. Moving along a bar where a panel is already down opens the next one at once, because at that point the reader is reading the bar, not crossing it. **Focus is not trapped and not moved.** A disclosure leaves the reader on the trigger and the panel follows in the tab order, which is what the markup already says, so Tab walks into the links and out the far side without anything managing it. Escape closes and puts focus back, because Escape is the one key that has to return the reader to where they were. The custom property below is written by this component and read by whoever draws it. A consumer never sets it: it carries a number this element computed and CSS cannot, which is what lets a stylesheet on this tier draw the thing without measuring anything itself. */
8288
+ /** **A disclosure, not a menu, and that is the whole design.** 'role="menu"' and 'menuitem' are for an application's commands: they take over the arrow keys, announce "menu" to a reader who asked for navigation, and put every link inside a role that is not a link. This is a button that shows and hides a region of the consumer's own anchors, which leaves a middle click, a modified click and the status bar working. 'Menu' remains right for a row of commands. **It renders no links.** A router renders its own anchor and cannot render one inside this shadow root, so the panel is a slot and what goes in it is the lists and headings the consumer wrote. **It opens on hover, and a press is always the fallback.** 'open-on' says which, and hover is the resting value because a nav bar is where a reader expects to sweep along a row and see each section. */
8289
8289
  "helix-headless-megamenu": DefineCustomElement<
8290
8290
  HelixHeadlessMegamenu,
8291
8291
  HelixHeadlessMegamenuVueProps,
@@ -8303,7 +8303,7 @@ export interface WcToolkitVueGlobalComponents {
8303
8303
  HelixHeadlessMenuVueProps,
8304
8304
  HelixHeadlessMenuVueEvents
8305
8305
  >;
8306
- /** **Not 'Progress', and the difference is the whole component.** A progress bar reports how far along a task is: it starts empty, ends full, and full is always the good end. A meter reports a level in a range that is not going anywhere, and which end is good is a fact about the thing being measured. Disk usage, a battery, a score, a rate limit. So there is no 'indeterminate' here: a measurement whose value is unknown is not a measurement. **'low', 'high' and 'optimum' are the reason it exists**, and they follow the HTML specification's own rule. The three thresholds cut the range into a low band, a middle and a high band, and 'optimum' says which of the three is the one to be in. The band the value falls in is then the good one, the one next to it is fair, and the one two away is poor. **Without thresholds there are no bands, and this is where it parts company with '<meter>'.** The platform defaults 'optimum' to the midpoint and both thresholds to the ends, so a bare '<meter value="20" max="100">' computes as optimum and every browser paints it green. A gauge with nothing said about it is not good news; it is a reading. Declared thresholds turn the color on. **And the bands are drawn as well as colored.** A tick sits at 'low' and at 'high', so the reading can be placed against them without seeing color at all, which is the same call the carousel's current dot makes by lengthening rather than only changing color. The properties below are written by this component and read by whoever draws it. A consumer sets none of them: they carry a number this element computed and CSS cannot, which is what lets a stylesheet on this tier draw the thing without measuring anything itself. */
8306
+ /** **Not 'Progress', and the difference is the whole component.** A progress bar reports how far along a task is: it starts empty, ends full, and full is always the good end. A meter reports a level in a range that is not going anywhere, and which end is good is a fact about the thing measured. So there is no 'indeterminate': a measurement whose value is unknown is not one. **'low', 'high' and 'optimum' are the reason it exists**, following the HTML specification's own rule. The three cut the range into a low band, a middle and a high band, and 'optimum' says which is the one to be in; the band next to it is fair and the one two away is poor. */
8307
8307
  "helix-headless-meter": DefineCustomElement<
8308
8308
  HelixHeadlessMeter,
8309
8309
  HelixHeadlessMeterVueProps,
@@ -8327,7 +8327,7 @@ export interface WcToolkitVueGlobalComponents {
8327
8327
  HelixHeadlessNotificationsVueProps,
8328
8328
  HelixHeadlessNotificationsVueEvents
8329
8329
  >;
8330
- /** **A family rather than 'Input''s 'type="number"', and rather than a part of it.** 'Input' already takes that type and will keep it: a bare numeric field in a form is a text field with a different keyboard, and that is all it needs to be. This is a different control. It renders two buttons, binds four keys that 'Input' does not, and owns clamping and stepping, so it is an alternative and not a variant. Namespacing it under 'Input' would say it is contained by one, which is what 'IconButton' is top-level for. **The field is 'type="text"' with 'inputmode="decimal"', and that is a measurement rather than a preference.** Asked of a real browser, 'type=number' fails three ways this control cannot live with: ''' typed "12e" into type=number → .value === "" focused, wheeled over it → 5 became 4 selectionStart on type=number → null same keystrokes into type=text → .value === "12e" ''' The first is fatal on its own: the element cannot see what the reader typed, so a controlled value is destroyed by a keystroke and there is nothing left to correct or to report. The second changes a value because a page scrolled past. The third means the caret cannot be managed at all, which any correcting or formatting field needs. 'inputmode' is what keeps the phone keypad, which is the one thing 'type=number' was giving. Validity is still the platform's, through 'min', 'max' and 'step' mirrored onto the host's own 'setValidity'. What is not mirrored is the field's 'type', because a text field has no opinion about numbers. **Stepping is separate from typing.** A reader mid-way through "1" of "12" has not asked for anything to be clamped, so the value is only pulled into range when they step, blur, or the form asks. Clamping on every keystroke is what makes a field that cannot be typed backwards into. */
8330
+ /** **A family rather than 'Input''s 'type="number"'.** It renders two buttons, binds four keys 'Input' does not, and owns clamping and stepping, so it is an alternative and not a variant. **The field is 'type="text"' with 'inputmode="decimal"', and that is a measurement.** Asked of a real browser, 'type=number' fails three ways this control cannot live with: ''' typed "12e" into type=number → .value === "" focused, wheeled over it → 5 became 4 selectionStart on type=number → null same keystrokes into type=text → .value === "12e" ''' The first is fatal alone: the element cannot see what the reader typed. The second changes a value because a page scrolled past. The third means the caret cannot be managed at all. 'inputmode' keeps the phone keypad, which is the one thing 'type=number' was giving. Validity is still the platform's, through 'min', 'max' and 'step' mirrored onto the host's own 'setValidity'. **Stepping is separate from typing**, so a reader mid-way through a number is not corrected under their hands. */
8331
8331
  "helix-headless-number-input": DefineCustomElement<
8332
8332
  HelixHeadlessNumberInput,
8333
8333
  HelixHeadlessNumberInputVueProps,
@@ -8345,13 +8345,13 @@ export interface WcToolkitVueGlobalComponents {
8345
8345
  HelixHeadlessOptionVueProps,
8346
8346
  HelixHeadlessOptionVueEvents
8347
8347
  >;
8348
- /** A table of contents, named for the job. It sits beside 'Navigation', which is the same job at the other scale: one navigates a site, the other navigates a document. 'Contents' and 'TableOfContents' are both taken by something else, 'display: contents' and the 'content' part in one case and the 'Table' family in the other. **The links are the consumer's own anchors**, the same as 'Navigation'. A router renders its own '<a>' and cannot render one inside this shadow root, and the hrefs are fragments the page already knows. **What is current is decided by position, not by intersection.** The obvious build is an 'IntersectionObserver' with a thin band near the top of the window, and it fails in both directions: with headings far apart nothing is in the band and the marker goes out entirely, and with headings close together several are and it flickers between them. So the observer is only the *trigger*, and the answer is computed from where the headings actually are: the current section is the last one whose top has passed the reading line, or the first heading before any of them has. That rule is what a reader means. Two headings on screen at once are not ambiguous to a person: they are reading under the upper one. **'aria-current="location"', and it is written onto the consumer's anchor.** This library does not normally write to light-DOM children, because an attribute added after upgrade is one a server's markup never had. It is right here for the reason 'Carousel' names its slides: what is current is derived from scroll position, so no server could have rendered it and there is nothing for hydration to disagree with. 'location' rather than 'page', which is what 'Navigation' uses: 'page' means this is the page, and every one of these is the same page. */
8348
+ /** A table of contents, named for the job. It sits beside 'Navigation', which is the same job at the other scale: one navigates a site, the other a document. **The links are the consumer's own anchors.** A router renders its own '<a>' and cannot render one inside this shadow root, and the hrefs are fragments the page already knows. **What is current is decided by position, not by intersection.** An 'IntersectionObserver' with a band near the top fails in both directions: with headings far apart nothing is in the band and the marker goes out, and with headings close together several are in it at once. */
8349
8349
  "helix-headless-outline": DefineCustomElement<
8350
8350
  HelixHeadlessOutline,
8351
8351
  HelixHeadlessOutlineVueProps,
8352
8352
  HelixHeadlessOutlineVueEvents
8353
8353
  >;
8354
- /** The component is the truncation. Given a page and a total it works out which numbers to show and where the runs break, which is the whole of what a consumer would otherwise write by hand and get subtly wrong; everything else here is markup around that. So this is data-driven rather than composed: '<helix-pagination page="4" pages="12">' rather than twelve slotted children a consumer has to keep in step with their own query. **The width does not change as you page.** The run around the current page is 'siblings' either side normally, and grows to absorb a gap slot when it meets a boundary, so the control holds the same number of slots from the first page to the last. Every arrangement that lets the count vary makes the buttons move under the pointer between one click and the next. **A gap is only drawn where it hides more than one page.** An ellipsis standing in for a single number costs the same space as the number and tells the reader less. **And a gap is a control.** It goes to the middle of the run it stands over, which is the binary-search step: any page in a set of any size is a handful of presses, where a jump of a fixed size would need forty of them on a set of a hundred and twenty-eight. A menu listing the hidden pages was the other idea and the arithmetic refuses it, for the reasons on 'renderGap'. **'href' is what makes these links.** Set it and every page renders an '<a>' with '{page}' filled in, so the server's output is crawlable, a middle click opens a tab, and the address bar is the state. Left unset they are buttons, which is right for a table paging in place. Either way the click announces 'helix-navigate' first, and 'preventDefault()' on it stops the navigation as well as the change: that is how a router takes over without the page reloading underneath it. **'helix-navigate' and not 'helix-change'**, for the reason the calendar pages with it too. Moving to page four chooses nothing; it moves the view over a result that has not changed. A consumer driving the view has to be able to hear that without being told an answer changed. **The count and the page-size control are their own elements.** A slot can only offer one place: at one end of one row, in one order, on one axis. 'Pagination.Summary' and 'Pagination.Size' sit beside this one instead, so the direction and the order of a footer are the consumer's, which is what they were always going to want. 'total' and 'page-size' stay here for the arithmetic below and nothing else. **A single page renders nothing.** A control offering one page offers no navigation, and the row it sits in should close over it rather than hold a space for a thing that cannot be used. Reported through 'ElementInternals.states' rather than an attribute on the host, because an attribute written at upgrade is markup a server's output never had and a framework reports the difference; a custom state carries the same fact to a stylesheet and none of that to the DOM. **And a page change is announced.** Moving the page moves 'aria-current' onto a different element, which a screen reader does not announce, so a reader who pressed 5 hears the word "5" from the button they were on and nothing at all about having arrived. A polite live region says where they now are. It ships empty and is filled only on a change, because a region populated as it enters the document announces itself on arrival. 'page' is what the element holds and 'default-page' seeds it. A consumer paging through a router refuses 'helix-navigate' and assigns their own, which is how they take the decision. */
8354
+ /** **The component is the truncation.** Given a page and a total it works out which numbers to show and where the runs break, so this is data-driven rather than composed: '<helix-pagination page="4" pages="12">' rather than twelve slotted children a consumer keeps in step with their own query. **The width does not change as you page.** The run around the current page is 'siblings' either side, and grows to absorb a gap slot at a boundary, so the control holds the same number of slots from the first page to the last. Anything that lets the count vary moves the buttons under the pointer between one click and the next. **A gap is only drawn where it hides more than one page, and a gap is a control.** It goes to the middle of the run it stands over, which is the binary-search step: any page in a set of any size is a handful of presses. **'href' is what makes these links.** Set it and every page renders an '<a>' with '{page}' filled in, so the output is crawlable, a middle click opens a tab, and the address bar is the state. Left unset they are buttons, which is right for a table paging in place. Either way the click announces 'helix-navigate' first, and 'preventDefault()' stops the navigation as well as the change, which is how a router takes over. **'helix-navigate' and not 'helix-change'.** Moving to page four chooses nothing; it moves the view over a result that has not changed. **The count and the page-size control are their own elements.** 'Pagination.Summary' and 'Pagination.Size' sit beside this one, because a slot can only offer one place: at one end of one row, in one order. */
8355
8355
  "helix-headless-pagination": DefineCustomElement<
8356
8356
  HelixHeadlessPagination,
8357
8357
  HelixHeadlessPaginationVueProps,
@@ -8363,7 +8363,7 @@ export interface WcToolkitVueGlobalComponents {
8363
8363
  HelixHeadlessPaginationSizeVueProps,
8364
8364
  HelixHeadlessPaginationSizeVueEvents
8365
8365
  >;
8366
- /** Its own element rather than a part of 'Pagination', so a consumer decides where it goes. Inside the run it could only ever be at one end of one row; beside it, it goes above, below, in a table's caption, or on the other side of the page, and the direction and order of the whole footer are the consumer's. **It repeats 'page', 'page-size' and 'total', and the cost of that is skew rather than verbosity.** Those three come from the consumer's own query, so writing them on two elements is one variable used twice; what it risks is the two disagreeing, and a count reading '76-100 of 248' beside a run showing page 7 is the same failure this element's own clamping exists to prevent, arriving from the outside instead. **Nothing removes it at this level, and that was measured rather than assumed.** A coordinating root can only reach children the consumer laid out by writing attributes onto them, which a framework reports as a hydration error, or through context, which is dead on a server: '@lit/context' subscribes from 'connectedCallback', and Lit SSR does not call it, so every value is 'undefined' in exactly the render this element exists to be part of. Confirmed against Lit SSR directly. The fork is real: children in the light DOM mean the consumer owns the layout and no root can feed them; children in a shadow root mean a root feeds them by ordinary binding and owns the layout. **It resolves one level up, at a composite, and the state is why.** 'page', 'page-size' and 'total' are facts about a *result*, not about a pagination, so the element that should hold them once is the one that holds the result: a data table composing a pagination inside itself, rather than a pagination root invented to hold three pagination elements together. That root would have been plumbing; a table's paging state is the thing itself. And because a composite renders its parts into its own shadow root, it lands on the side of the fork where binding works and the server renders, at no cost to anyone. Renders nothing without a 'total' and a 'page-size'. A wrong count is worse than none: it is the one number in a footer a reader cannot check against anything else on screen. */
8366
+ /** Its own element rather than a part of 'Pagination', so a consumer decides where it goes: above, below, in a table's caption, or on the other side of the page. Inside the run it could only ever be at one end of one row. **It repeats 'page', 'page-size' and 'total', and the cost of that is skew.** Those three come from the consumer's own query, so writing them on two elements is one variable used twice; what it risks is the two disagreeing. **Nothing removes that at this level, and it was measured.** A coordinating root can only reach light-DOM children by writing attributes onto them, which a framework reports as a hydration error, or through context, which is dead on a server: '@lit/context' subscribes from 'connectedCallback', and Lit SSR does not call it. */
8367
8367
  "helix-headless-pagination-summary": DefineCustomElement<
8368
8368
  HelixHeadlessPaginationSummary,
8369
8369
  HelixHeadlessPaginationSummaryVueProps,
@@ -8381,7 +8381,7 @@ export interface WcToolkitVueGlobalComponents {
8381
8381
  HelixHeadlessPopoverVueProps,
8382
8382
  HelixHeadlessPopoverVueEvents
8383
8383
  >;
8384
- /** Determinate by default, and 'indeterminate' for a task whose total is not known yet. 'Loader' is still the right answer where the wait is short or has no place of its own on the page; a bar earns its width where the work is the subject of the region it sits in. A bar that fills at a made-up rate would be a bar that lies, so the indeterminate one does not fill: it runs a segment across the track, which says work is happening and claims no proportion at all. 'aria-valuenow' is omitted with it, which is the platform's own signal for the same thing. **The fill is a fraction of the track, written as a custom property.** '--progress-fraction' is derived from 'value' and 'max' rather than measured, so it renders on a server with no layout and needs nothing after upgrade. That is what keeps it out of the class of geometry this library writes only on the client. A consumer never sets it: it carries a number this element computed and CSS cannot, which is what lets a stylesheet draw the thing without measuring anything itself. **'value' is clamped, and out-of-range input is not an error.** A count that runs past its own total is ordinary in real applications, and a bar that overflows its track or reports 'aria-valuenow' above 'aria-valuemax' is worse than one that reads as finished. Semantics are on the host through 'ElementInternals', the same as everywhere else: a 'role' in the shadow root announces correctly and leaves the host anonymous to anything walking the light DOM, and a 'role' written onto the host as an attribute is one a server's markup never had. */
8384
+ /** Determinate by default, and 'indeterminate' for a task whose total is not known yet. 'Loader' is still right where the wait is short or has no place of its own on the page. A bar that fills at a made-up rate would be a bar that lies, so the indeterminate one does not fill: it runs a segment across the track, which says work is happening and claims no proportion. 'aria-valuenow' is omitted with it, which is the platform's own signal for the same thing. **The fill is a fraction of the track, written as a custom property.** */
8385
8385
  "helix-headless-progress": DefineCustomElement<
8386
8386
  HelixHeadlessProgress,
8387
8387
  HelixHeadlessProgressVueProps,
@@ -8411,7 +8411,7 @@ export interface WcToolkitVueGlobalComponents {
8411
8411
  HelixHeadlessRadioVueProps,
8412
8412
  HelixHeadlessRadioVueEvents
8413
8413
  >;
8414
- /** **Half marks decide the shape.** Supporting them means the value is not an integer and the targets are not the marks: five stars at a half step is ten places to press, and a mark that is filled to the middle. The pointer handling and 'step' are both built around that rather than added over it. **A partial mark is the whole mark, clipped.** Not a second glyph filled to the middle: that needs a matching pair drawn for every mark anybody uses, and the moment a consumer wants a heart or a circle they need to draw one too. The fill is the same marks over the same marks, cut off partway, so any mark works and any fraction does. **Read-only is not this control grayed out, it is a different thing.** The commoner use is showing a score nobody is being asked for, a product's average, and that is not a control at all: it takes no focus, answers no keys, has no options to choose between, and reads as one thing rather than as five. So 'readonly' is 'role="img"' with the score in its name, where the control is a radio group. A disabled control is still a control and still says so; this is not one. **Moving is choosing here**, which is the radio group's reading and the opposite of 'Menu''s: arriving at four stars *is* rating it four, because nothing else follows and there is nothing to confirm. That is what 'KeyboardNavigationController' takes 'selects' for, and why this can use it where a menu cannot. **The mark is a stylesheet's, not a slot's.** A slot projects its content once, and there are two layers of marks here: slotting a star would put it in the track or the fill and leave the other empty. So the mark is a shape the styled tier masks with, which is also what lets a partial one be a clip of the whole rather than a second drawing of it. The properties below are written by this component and read by whoever draws it. A consumer sets none of them: they carry a number this element computed and CSS cannot, which is what lets a stylesheet on this tier draw the thing without measuring anything itself. */
8414
+ /** **Half marks decide the shape.** Supporting them means the value is not an integer and the targets are not the marks: five stars at a half step is ten places to press. The pointer handling and 'step' are built around that rather than added over it. **A partial mark is the whole mark, clipped**, not a second glyph filled to the middle: the fill is the same marks over the same marks, cut off partway, so any mark works and any fraction does. **Read-only is not this control grayed out.** The commoner use is showing a score nobody is being asked for, which is a reading rather than a disabled input, and it is announced as one. */
8415
8415
  "helix-headless-rating": DefineCustomElement<
8416
8416
  HelixHeadlessRating,
8417
8417
  HelixHeadlessRatingVueProps,
@@ -8435,7 +8435,7 @@ export interface WcToolkitVueGlobalComponents {
8435
8435
  HelixHeadlessSkeletonVueProps,
8436
8436
  HelixHeadlessSkeletonVueEvents
8437
8437
  >;
8438
- /** A real '<input type="range">', so the drag, the arrow keys, Home and End, the page keys and the touch target are all the platform's. Every one of those is a thing a hand-built slider gets wrong, and the last is the one nobody notices: a native range has a hit area larger than its thumb, sized by the platform for a finger. **The value is a string, like every other form control's.** A range submits text and 'MixinControllerValue' holds text, so the number is a reading of the value rather than the value itself. 'valueAsNumber' is there for anything doing arithmetic on it. **Marks are drawn rather than left to '<datalist>'.** A datalist's ticks cannot be styled at all, and Firefox does not render them for a range at all, so the platform's version is a mark that looks like someone else's design where it appears and no mark where it does not. Drawing them costs the semantics nothing, because a mark is decoration: what a reader can actually land on is 'step', which the input enforces whether or not anything is drawn. 'markers' is a count rather than a flag tied to 'step'. A flag reads well until a slider from zero to a hundred stepping by one asks for a hundred and one ticks, and then it is either noise or a silent refusal to draw. A number is what the reader sees, so it cannot surprise anyone. **A range is two of them, not a hand-built one.** There is no native two-thumb range, so two are stacked on one track and each thumb keeps its own drag, its own arrow keys, its own Home and End and its own finger-sized hit area. Rebuilding would throw away every argument this component is made of, to gain a second handle. Crossing is stopped in the handler rather than by the bounds, and that is not the obvious way round. Giving the lower input 'max' of the upper reads well and breaks the drawing: an input's thumb sits at a fraction of *its own* range, so a lower thumb bounded at the upper one paints against a shorter track and the two stop lining up. Both inputs span the whole range and the values are clamped against each other on the way in. The properties below are written by this component and read by whoever draws it. A consumer sets none of them: they carry a number this element computed and CSS cannot, which is what lets a stylesheet on this tier draw the thing without measuring anything itself. */
8438
+ /** A real '<input type="range">', so the drag, the arrow keys, Home and End, the page keys and the touch target are the platform's. The last is the one nobody notices: a native range has a hit area larger than its thumb, sized by the platform for a finger. **The value is a string, like every other form control's.** A range submits text and 'MixinControllerValue' holds text, so the number is a reading of the value. 'valueAsNumber' is there for arithmetic. **Marks are drawn rather than left to '<datalist>'**, whose ticks cannot be styled and which Firefox does not render for a range at all. A mark is decoration; what a reader can land on is 'step', which the input enforces whether or not anything is drawn. 'markers' is a count rather than a flag, so a slider from zero to a hundred cannot silently ask for a hundred and one. **A range is two of them.** There is no native two-thumb range, so two are stacked on one track and each thumb keeps its own. */
8439
8439
  "helix-headless-slider": DefineCustomElement<
8440
8440
  HelixHeadlessSlider,
8441
8441
  HelixHeadlessSliderVueProps,
@@ -8459,7 +8459,7 @@ export interface WcToolkitVueGlobalComponents {
8459
8459
  HelixHeadlessSpeedDialVueProps,
8460
8460
  HelixHeadlessSpeedDialVueEvents
8461
8461
  >;
8462
- /** **One axis, and nesting is how you get two.** The sandbox this was taken from moves a cross: a divider down the middle, another across the bottom, and a corner that moves both. That is a layout rather than a component, and every part of it that is worth sharing is one divider between two panes. A splitter inside a splitter gives the cross back, and gives the three-pane and the four-pane arrangements it never had. **Where two of them cross there is a corner, and it moves both.** A splitter slotted into a pane of another, at right angles to it, has one end of its divider sitting exactly on the other's. That end grows a handle: 'move' under the pointer, both arrow axes on the keyboard, and a drag that carries the two positions at once. **The outer splitter draws it and the nested one announces itself upward.** The nested one drawing its own is the tempting version, since it already knows where its divider begins and needs no measurement of the other, and it is clipped: the crossing lies on the boundary between a pane and the divider beside it, a handle centered there reaches back over the boundary, and a pane is 'overflow: auto'. Exactly half the corner is cut off. The outer separator is the one element that already straddles the seam, so the corner belongs in it. That makes the channel between the two a single call in a single direction: 'cross(child, at)' says where the child crosses, 'moveTo' drives either one, and neither reads the other's state. The child is what calls it, because a parent cannot poll for a child that has not upgraded yet. The one number it carries is a percentage rather than a measurement, which is what keeps either of them from measuring the other, and it assumes the nested splitter fills its pane. That is what slotting one into a pane does. Give the nested splitter its own inset, or wrap it in something smaller than the pane, and the corner is drawn where the crossing would be rather than where it is. **It is a client-side enhancement and says so.** Finding the parent means inspecting the tree, which a server cannot do, so the corner appears at 'firstUpdated' and never in the served markup. That is allowed for exactly this: it is an affordance rather than content, and a splitter with no corner yet is a splitter with two working dividers. Where two dividers are parallel there is no corner, because there is nothing to take hold of. Where they cross without a corner, the inner divider takes the pointer: every separator sits on the same layer, so a tie goes to document order and a deeper splitter is always later. **A divider that is moving says so, whoever is moving it.** ':hover' cannot carry that: a drag holds the pointer through capture and a fast one leaves the target behind, and a corner moves the nested divider while the reader is on the crossing, nowhere near it. So a drag in progress is a state, and both dividers a corner moves are lit for as long as it moves them. **The divider is a real 'role="separator"' with 'aria-valuenow', moved by the arrow keys as well as by pointer.** A divider only a mouse can move is unusable without one, which is the reason the sandbox's carries the same, and 'Home' and 'End' take it to its bounds. **It goes where it is put, all the way to either end.** 'min' and 'max' are nothing and everything unless a consumer says otherwise, because a divider that stops dead short of the edge gives a reader no way to put a pane away. Where a pane should keep a sliver of itself, only whoever put the tabs on it knows how much, so they say so. **A pane can be shut, and that is not the same as a divider at the edge.** The geometry is identical and the knowledge is not: only a shut pane knows where to open back to. So 'collapsed' is a second state rather than a reading of the position, and the position it opens to is simply the one collapsing never overwrote. 'collapsed-size' keeps a sliver showing where a pane has a tab strip worth leaving, and collapse overrides 'min' and 'max', which is what those are for: they keep a pane from vanishing by accident, and this is the deliberate version. The two are kept in step in one direction only: a drag that settles on the collapsed size latches shut, and any move opens it again. Otherwise a reader could drag a pane closed and the consumer's own button would still be offering to close it. A pane shut to nothing is told apart from one shut to a sliver, because the two want different dividers: a sliver still has a boundary worth drawing, and nothing leaves the divider sitting on the container's own edge. **The box is measured once, when a drag starts.** Measuring per move is a forced layout on every pointer event, in the middle of a gesture whose whole job is to stay ahead of the pointer, and nothing about the box moves while the divider does. **'helix-before-resize' fires when it settles, not while it moves.** A drag is a stream of positions and only the one it ends at is a preference; announcing each would ask a consumer to write to storage a hundred times to record one decision. Nothing is stored here either: where a preference lives, and whether it should be kept at all, is the consumer's to decide, and a component writing to storage on its own would be deciding for all of them under a key it chose. The properties below are written by this component and read by whoever draws it. A consumer sets none of them: they carry a number this element computed and CSS cannot, which is what lets a stylesheet on this tier draw the thing without measuring anything itself. */
8462
+ /** **One axis, and nesting is how you get two.** A splitter inside a splitter gives back the cross, and gives the three-pane and four-pane arrangements a single divider never had. **Where two of them cross there is a corner, and it moves both.** A splitter slotted into a pane of another, at right angles to it, grows a handle at the crossing: 'move' under the pointer, both arrow axes on the keyboard, and a drag that carries the two positions at once. It appears at 'firstUpdated' rather than in the served markup, being an affordance rather than content, and a splitter with no corner yet is a splitter with two working dividers. **The divider is a real 'role="separator"' with 'aria-valuenow'**, moved by the arrow keys as well as by pointer, with 'Home' and 'End' taking it to its bounds. A drag in progress is a state rather than ':hover', so both dividers a corner moves are lit for as long as it moves them. **It goes where it is put, all the way to either end.** 'min' and 'max' are nothing and everything unless a consumer says otherwise, because a divider that stops short of the edge gives a reader no way to put a pane away. **A pane can be shut, and that is not the same as a divider at the edge.** The geometry is identical and the knowledge is not: only a shut pane knows where to open back to. So 'collapsed' is a second state rather than a reading of the position, and the position it opens to is the one collapsing never overwrote. 'collapsed-size' keeps a sliver showing where a pane has a tab strip worth leaving, and collapse overrides 'min' and 'max', which is what those are for. */
8463
8463
  "helix-headless-splitter": DefineCustomElement<
8464
8464
  HelixHeadlessSplitter,
8465
8465
  HelixHeadlessSplitterVueProps,
@@ -8477,13 +8477,13 @@ export interface WcToolkitVueGlobalComponents {
8477
8477
  HelixHeadlessStatVueProps,
8478
8478
  HelixHeadlessStatVueEvents
8479
8479
  >;
8480
- /** **'active' names where the reader is. 'complete' stays on each step.** They are two different facts and only one of them is positional. Deriving both from a position is the obvious design and it is wrong the first time anybody steps back: return to stage one of three and stages one and two are still finished, while "everything before the active one" says only the first is. So this element drives 'current' and never touches 'complete'. **'active' is optional, and a sequence without it still renders.** Left unset, each step's own 'current' stands and nothing here overwrites it, which is what lets the whole sequence be right in the server's markup: this element would have to count its slotted children to know which one is third, and the server has no children to count. Set it, and it wins, because it is a statement about the sequence where the attribute on a step is a statement about one step. That is also why the numbering is not JavaScript. It is a CSS counter in the tier above, which the browser resolves at layout with no script at all, so the third step reads "3" in the server's own markup. **The connector belongs to the step, not to the list.** Each one draws the line that arrives at it and hides it when it is first, the same arrangement Breadcrumbs uses for its separator and for the same reason: a line drawn between items needs either an index or a parent that counts, and drawing it on the item needs neither. 'role="list"' on the host rather than an '<ol>' in the shadow root. The steps are the consumer's elements in the light DOM, so an '<ol>' here would be a list whose items are somewhere else. */
8480
+ /** **'active' names where the reader is. 'complete' stays on each step.** Only one of them is positional: return to stage one of three and stages one and two are still finished, where "everything before the active one" says only the first is. So this element drives 'current' and never touches 'complete'. **'active' is optional, and a sequence without it still renders.** Left unset, each step's own 'current' stands, which is what lets the whole sequence be right in the server's markup: this element would have to count its slotted children to know which is third, and the server has none to count. */
8481
8481
  "helix-headless-steps": DefineCustomElement<
8482
8482
  HelixHeadlessSteps,
8483
8483
  HelixHeadlessStepsVueProps,
8484
8484
  HelixHeadlessStepsVueEvents
8485
8485
  >;
8486
- /** **The name is a button, and only the name.** A stepper is something a reader goes back through, and the thing they reach for is the name of the stage. The marker is 'aria-hidden' decoration, so wrapping it in the button would add hit area and no meaning: the accessible name would be the text either way, and what is pressable would stop being the same shape as what is readable. It is 'helix-button', not a '<button>' this element resets and dresses: the hover wash, the focus ring and the disabled treatment are decisions the button already carries. 'ghost' is the quiet shape, transparent until it is pointed at. It says nothing about where a reader may go. Every step is pressable and announces itself with 'helix-select'; refusing the ones ahead is 'disabled', which the consumer sets because only the consumer knows whether a form can be skipped. A component that guessed would be wrong on the first wizard that lets you jump to the end. **The marker is a slot whose fallback is the state.** A finished stage shows a check, the current one a target, and one not yet reached an empty ring: three marks that say what a reader wants to know without a legend. A consumer who wants their own fills the slot instead. The step renders exactly one of the three, chosen from its own two booleans. Rendering all three and hiding two in CSS would work and is worse: the accessibility tree would carry marks that are not there, and the markup would say three things about a stage that is one. Numbering belongs to the tier above. 'numeric' hides the mark and shows a CSS counter, which the browser resolves at layout, so the digit costs no script and is right in the server's markup however the steps are ordered. Nothing here knows which appearance is in use. **'complete' and 'current' are separate, and both can be false.** A stage that is neither is one the reader has not reached, which is the majority of any sequence and therefore the state that needs no attribute. They can also both be true: a reader who steps back into a finished stage is in it and has finished it, and a stepper that forgets the second is one that loses its checks the moment somebody reviews their answers. **The 'content' slot opens on the current step, and only down a column.** A row has no room under a step to put a form in, and stretching one to fit would set every step's height by the tallest. 'current' is what opens it, so the state that says where the reader is is also the state that shows them the stage: one attribute, no second flag to keep in step with it, and it renders open on the server rather than after hydration. */
8486
+ /** **The name is a button, and only the name.** The marker is 'aria-hidden' decoration, so wrapping it would add hit area and no meaning. It is 'helix-button' rather than a '<button>' this element resets: the hover wash, the focus ring and the disabled treatment are decisions the button carries. **It says nothing about where a reader may go.** Every step is pressable and announces itself with 'helix-select'; refusing the ones ahead is 'disabled', which the consumer sets, because only they know whether a form can be skipped. **The marker is a slot whose fallback is the state.** A finished stage shows a check, the current one a target, one not yet reached an empty ring. Exactly one is rendered, chosen from the step's own two booleans, rather than three with two hidden in CSS: the accessibility tree would otherwise carry marks that are not there. */
8487
8487
  "helix-headless-step": DefineCustomElement<
8488
8488
  HelixHeadlessStep,
8489
8489
  HelixHeadlessStepVueProps,
@@ -8573,7 +8573,7 @@ export interface WcToolkitVueGlobalComponents {
8573
8573
  HelixHeadlessTimelineVueProps,
8574
8574
  HelixHeadlessTimelineVueEvents
8575
8575
  >;
8576
- /** An element rather than a slot, because it carries a timeout, a tone, a dismiss control and a lifetime that pauses: behavior, state and API, which is the test for whether a region becomes an element in this library. **The timer is the item's own.** Held by the region, one message arriving would restart another's, so a stack of three would go from three seconds each to nine for the last one. Each message is on its own clock from the moment it is rendered. **A message is the opposite of the page it is over.** Inverted by default, because a thing arriving in a corner has to be told apart from the page in the moment before it is read, and inverting does that without spending a color that means something. It is a flip of 'color-scheme' rather than a named inverse surface: every token in the subtree re-resolves, so a 'Retry' a consumer slots in comes out right without knowing where it is. 'Tooltip' can name the inverse surface directly because it holds no controls; this cannot. Set 'data-color-mode' on the region to take the page's mode instead. **No duration means it stays.** That is the safe default rather than the common one: anything carrying an action has to still be there when the reader decides to take it, and a component cannot tell from the outside whether the message it was given is disposable. **The clock stops while it is being read.** Hover pauses it, and so does focus landing anywhere inside, which is the case that matters more: a keyboard reader tabbing towards the action in a toast is exactly the person a timer must not race. Both resume where they left off rather than starting again, so a message half-read does not get a full second life every time a pointer crosses it. **Going away is a request, never an act.** It dispatches a cancelable 'helix-dismiss' and does nothing else: the consumer owns the list, so the item removing itself would leave the two disagreeing. Auto-dismiss and the dismiss control raise the same event, so a consumer handles one path and not two. */
8576
+ /** An element rather than a slot, because it carries a timeout, a tone, a dismiss control and a lifetime that pauses: behavior, state and API. **The timer is the item's own.** Held by the region, one message arriving would restart another's, so a stack of three would go from three seconds each to nine for the last. **A message is the opposite of the page it is over.** Inverted by default, so a thing arriving in a corner is told apart from the page before it is read, and by a flip of 'color-scheme' rather than by spending a color that means something. */
8577
8577
  "helix-headless-toast-item": DefineCustomElement<
8578
8578
  HelixHeadlessToastItem,
8579
8579
  HelixHeadlessToastItemVueProps,
@@ -8585,7 +8585,7 @@ export interface WcToolkitVueGlobalComponents {
8585
8585
  HelixHeadlessToastVueProps,
8586
8586
  HelixHeadlessToastVueEvents
8587
8587
  >;
8588
- /** The indicator is one element that travels, not a background that appears on whichever item is current. That difference is the whole point: a fill that swaps is two things changing at once and reads as a flicker, while one that moves is a single object the eye follows, and it says where the selection came from as well as where it went. Its whole box is measured from the selected item and written onto the indicator as custom properties, because labels are different lengths and no proportion of the group is the right answer. Measurement is client-side by necessity, so the server renders the indicator hidden and it fades in once it has somewhere to be, which is the harmless direction: it appears rather than jumping from a wrong place. A radio group, and only that. Navigation is links marked with 'aria-current' and styled by the 'data-navigation-item' recipe: radio semantics would announce a set of destinations as a set of choices. A tab controls a panel. Being a radio group, it is a form control, and it says so through 'HelixFormElement': it reports its value under a 'name', comes back to the markup's choice on reset, and restores the one a reader made when the browser brings a form back. The semantics were already right: the host has carried 'role="radiogroup"' and the items 'role="radio"' from the start. What none of it did was reach a form. A button in a shadow root submits nothing whatever its role, so the value has to be reported by the host, which is the only thing a form can see. */
8588
+ /** The indicator is one element that travels, not a background that appears on whichever item is current. A fill that swaps is two things changing at once and reads as a flicker; one that moves is a single object the eye follows, and it says where the selection came from as well as where it went. Its box is measured from the selected item and written onto the indicator as custom properties, labels being different lengths. Measurement is client-side by necessity, so the server renders the indicator hidden and it fades in once it has somewhere to be, which is the harmless direction. */
8589
8589
  "helix-headless-toggle-group": DefineCustomElement<
8590
8590
  HelixHeadlessToggleGroup,
8591
8591
  HelixHeadlessToggleGroupVueProps,
@@ -8603,13 +8603,13 @@ export interface WcToolkitVueGlobalComponents {
8603
8603
  HelixHeadlessToolbarVueProps,
8604
8604
  HelixHeadlessToolbarVueEvents
8605
8605
  >;
8606
- /** The trigger is slotted and stays the consumer's own element. The bubble is rendered here, in the top layer through the popover API, so it clears any ancestor with 'overflow: hidden' without a 'z-index' anywhere. Position comes from 'FloatingController', which flips to another side when the preferred one does not fit and slides along it to stay in view. **Two forms, and they are not equivalent for a screen reader.** Wrapping is the convenience form and is right where the bubble decorates a control that already says what it is. The trigger stays the consumer's own element, slotted, and nothing here can describe it: an IDREF cannot cross a shadow boundary, an element reference is valid only into the same tree or a shadow-including *ancestor* (the bubble's tree is neither, it is inward), and a string on the trigger means writing to markup the consumer server-rendered. All three measured. 'for' is the form that works. The tooltip sits *beside* the trigger in the consumer's own tree and names it by id, so both are in one tree and an IDREF between them is legal in the ordinary way: '''html <helix-button id="save" aria-describedby="save-tip">Save</helix-button> <helix-tooltip id="save-tip" for="save" content="Save your work"></helix-tooltip> ''' Note that the reference points at *this host*, not at the bubble inside its shadow root: the description is computed from the flat tree, which descends into shadow content on its own. **The consumer writes that 'aria-describedby', and nothing here writes it for them.** WebAwesome and Spectrum Web Components both do write it, with 'setAttribute' on the trigger at runtime, which is the one thing this library declines everywhere: React rendered that element on the server without the attribute and reports a mismatch it will not patch up. Two ids in the markup is the whole cost of the version that is correct before any JavaScript runs. Because it is the consumer's line to write, it is also theirs to forget, so 'for' audits itself on first open and says so once, rather than on every open, since a warning that repeats is one people learn to filter out. It shows on hover and on focus, and hides on leave, on blur, on Escape, and once the trigger has been activated: the hint is spent when the control has been used. A tooltip that answers only the pointer is one a keyboard reader never sees. The pointer waits out 'delay' first, or sweeping a row of controls flashes a tooltip for each of them. Focus does not wait: a keyboard reader landing on a control has already committed to it. Open state comes from 'MixinControllerOpen', so it announces 'helix-before-open' and 'helix-before-close' like every other overlay here, and a consumer can drive it with 'open' where they need to. */
8606
+ /** The trigger is slotted and stays the consumer's own element. The bubble is rendered here, in the top layer through the popover API, so it clears any ancestor with 'overflow: hidden' without a 'z-index' anywhere. Position comes from 'FloatingController', which flips to another side when the preferred one does not fit. **Two forms, and they are not equivalent for a screen reader.** Wrapping is the convenience form and is right where the bubble decorates a control that already says what it is: nothing here can describe a slotted trigger, an IDREF being unable to cross a shadow boundary. 'for' is the form that works. The tooltip sits *beside* the trigger in the consumer's own tree and names it by id, so an IDREF between them is legal in the ordinary way: '''html <helix-button id="save" aria-describedby="save-tip">Save</helix-button> <helix-tooltip id="save-tip" for="save" content="Save your work"></helix-tooltip> ''' The reference points at *this host*, not at the bubble in its shadow root: the description is computed from the flat tree, which descends into shadow content on its own. **The consumer writes that 'aria-describedby', and nothing here writes it for them.** Setting it at runtime is a hydration mismatch React will not patch up. 'for' audits itself on first open and warns once where it is missing. It shows on hover and on focus, and hides on leave, on blur, on Escape, and once the trigger has been activated: the hint is spent when the control has been used. */
8607
8607
  "helix-headless-tooltip": DefineCustomElement<
8608
8608
  HelixHeadlessTooltip,
8609
8609
  HelixHeadlessTooltipVueProps,
8610
8610
  HelixHeadlessTooltipVueEvents
8611
8611
  >;
8612
- /** The root owns the order and nothing else. Which stop is current, what Next and Back mean, when the run is finished; the stops own where they point, and the consumer owns every word. **The spotlight is a shadow, not a hole.** The scrim is a box the size of the current target with an enormous spread on its 'box-shadow', so the darkness is what the shadow paints and the target is simply where the box is. A 'clip-path' on a full-screen scrim is the other way to do it and is worse here: it needs the viewport in the same coordinate space as the cut, which stops being true the moment anything between them is transformed. **It measures, and this is a case where measuring is the only option.** The target is an element somewhere else on the page whose position nothing can know statically. That is different from a trail folding by width, where the design rule was expressible and the measurement was answering a question nobody had asked. Here the question is "where is that button", and there is no other way to ask it. **Nothing is measured on the server and nothing needs to be.** A tour starts closed and a closed tour draws nothing, so the server renders the stops as inert markup and the first frame is correct. This is also what makes it acceptable for the root to write 'current' onto stops it does not own: there is no server state for the write to disagree with. **It cannot point inside a modal, and nothing can fix that here.** 'showModal()' makes everything outside the dialog's own subtree inert, so a panel anywhere else is unpressable however the top layer is ordered: this is what modal means rather than a stacking accident. Measured with a modal 'Drawer' open, 'elementsFromPoint' over the tour's own Done button returned the drawer, the press light-dismissed the drawer, and the tour heard nothing at all. Anything a tour points into has to stay reachable, which for 'Drawer' and 'Dialog' means not modal while the tour is running. **Focus is not trapped.** A reader has to be able to look at, and reach, the thing being pointed at. 'Escape' leaves, which is the one binding a tour needs and the one every reader already tries. */
8612
+ /** The root owns the order and nothing else. Which stop is current, what Next and Back mean, when the run is finished; the stops own where they point, and the consumer owns every word. **The spotlight is a shadow, not a hole.** The scrim is a box the size of the current target with an enormous spread on its 'box-shadow', so the darkness is what the shadow paints. A 'clip-path' on a full-screen scrim needs the viewport in the same coordinate space as the cut, which stops being true the moment anything between them is transformed. **It measures, and the target is an element elsewhere on the page whose position nothing can know statically.** Nothing is measured on the server and nothing needs to be: a tour starts when a reader asks for it. */
8613
8613
  "helix-headless-tour": DefineCustomElement<
8614
8614
  HelixHeadlessTour,
8615
8615
  HelixHeadlessTourVueProps,
@@ -8621,7 +8621,7 @@ export interface WcToolkitVueGlobalComponents {
8621
8621
  HelixHeadlessTourStopVueProps,
8622
8622
  HelixHeadlessTourStopVueEvents
8623
8623
  >;
8624
- /** **'open', on 'MixinControllerOpen', rather than a bare boolean of its own.** A branch showing its children is the same state a disclosure holds, and 'AccordionItem' is on that mixin too. A hand-written toggle announces nothing, so a consumer can neither know a branch opened nor own the state. 'aria-expanded' is still what the row renders, because that is the attribute a tree uses; the property is what differs, not the semantics. An element rather than a slot because it owns state: whether it is open, whether it is selected, and whether it is the one holding focus. A branch is any item with something slotted into 'children', detected in 'firstUpdated' rather than declared by the consumer. That is a client-side enhancement by necessity, since the server cannot inspect children, so the server renders the node as a leaf and the twisty appears on hydration. It appears rather than disappearing, which is the harmless direction. **'href' makes the row itself the link.** A tree used as navigation is a list of links, and the row is what a reader presses: with the anchor slotted into the name instead, everything outside the words is dead, Enter does nothing, and there is no middle-click, no status bar and no open-in-new-tab. Given an 'href' the row *is* an '<a>', so the platform provides all of that and the keyboard needs no handler at all. It changes the shape of the ARIA, which is why it is a property and not the default. An anchor cannot contain the group, since the group holds more anchors and nesting them is invalid, so the row carries 'treeitem' and the group becomes its sibling under a presentational wrapper. That is exactly the navigation flavor of the tree pattern, and the arrangement without an 'href' stays as it was: 'treeitem' containing 'group', which is what an '<li>' holding a '<ul>' is. A consumer whose framework owns routing slots its own link instead and leaves 'href' unset. The row follows that one too, so the whole row works either way; what the anchor adds is what only a real anchor can give. The properties below are written by this component and read by whoever draws it. A consumer sets none of them: they carry a number this element computed and CSS cannot, which is what lets a stylesheet on this tier draw the thing without measuring anything itself. */
8624
+ /** **'open', on 'MixinControllerOpen', rather than a bare boolean of its own.** A branch showing its children is the same state a disclosure holds. A hand-written toggle announces nothing, so a consumer could neither know a branch opened nor own the state. 'aria-expanded' is still what the row renders: the property differs, not the semantics. An element rather than a slot, because it owns state: whether it is open, selected, and whether it holds focus. A branch is any item with something slotted into 'children', detected in 'firstUpdated' because the server cannot inspect children. The server renders the node as a leaf and the twisty appears on hydration, which is the harmless direction. **'href' makes the row itself the link.** With the anchor slotted into the name instead, everything outside the words is dead: no Enter, no middle click, no status bar. Given an 'href' the row *is* an '<a>'. */
8625
8625
  "helix-headless-tree-item": DefineCustomElement<
8626
8626
  HelixHeadlessTreeItem,
8627
8627
  HelixHeadlessTreeItemVueProps,
@@ -8645,7 +8645,7 @@ export interface WcToolkitVueGlobalComponents {
8645
8645
  HelixHeadlessVideoPlaylistItemVueProps,
8646
8646
  HelixHeadlessVideoPlaylistItemVueEvents
8647
8647
  >;
8648
- /** The entries again, reachable without leaving the picture. A playlist beside the player is a list a reader scrolls to; the same list in the control bar is one they open over the video, which is what a full-screen player has and what a phone has room for. **It is placed by the consumer, not rendered by the playlist**, and that is forced rather than chosen. A shadow root cannot project into a sibling's light DOM, so nothing the playlist renders can land inside the player's control bar. What can is an element the consumer writes there, which is what 'Video''s 'controls' slot is for: '''html <helix-video-playlist> <helix-video slot="player" controls> <helix-video-playlist-menu slot="controls"></helix-video-playlist-menu> </helix-video> <helix-video-playlist-item value="one" src="/one.mp4">One</helix-video-playlist-item> </helix-video-playlist> ''' So it finds its playlist upward rather than being handed one, and that chain crosses a shadow boundary in the ordinary arrangement, which is why it is 'closestInstance' and not 'closest'. **Choosing dispatches 'helix-select', the same event an entry dispatches.** It never writes 'active' on the playlist, which looks like the shorter path and skips the cancelable request: an assignment applies the change with no 'helix-before-activate' for anyone to refuse. Announcing is what an entry does, the playlist already listens for it, and a consumer refusing a selection gets 'preventDefault()' on one event rather than two paths that behave differently. */
8648
+ /** The entries again, reachable without leaving the picture. A playlist beside the player is a list a reader scrolls to; the same list in the control bar is one they open over the video. **It is placed by the consumer, not rendered by the playlist**, and that is forced rather than chosen: a shadow root cannot project into a sibling's light DOM, so nothing the playlist renders can land inside the player's control bar. What can is an element the consumer writes there, which is what 'Video''s 'controls' slot is for. */
8649
8649
  "helix-headless-video-playlist-menu": DefineCustomElement<
8650
8650
  HelixHeadlessVideoPlaylistMenu,
8651
8651
  HelixHeadlessVideoPlaylistMenuVueProps,
@@ -8657,13 +8657,13 @@ export interface WcToolkitVueGlobalComponents {
8657
8657
  HelixHeadlessVideoPlaylistVueProps,
8658
8658
  HelixHeadlessVideoPlaylistVueEvents
8659
8659
  >;
8660
- /** **The picture is a real '<video>'.** Decoding, buffering, captions, Picture in Picture, AirPlay and the codecs a device happens to have are all the platform's, and none of them can be had from anything else. What is replaced is the control bar, which is the one part of a video element that cannot be styled and looks different in every browser. **Every control is a real control.** The scrubber and the volume are '<input type="range">', so a reader gets arrow keys, Home and End, the platform's own touch target and its screen reader announcement without this element implementing any of it. Drawing a div and listening for pointer moves is how a media player ends up unusable from a keyboard. **Native controls are the fallback, not the default.** Where a consumer sets 'controls', the browser's bar is used and this one is not drawn: two sets of controls over one video is worse than either alone, and a consumer who asked for the native bar has a reason. The keys are the ones every player uses: Space and 'k' to play or pause, the arrows to seek and to change the volume, 'm' to mute, 'f' for fullscreen. They are bound on this element rather than on the document, because a video is not a palette: keys belong to it only while a reader is on it. Where 'controls' is set they are not bound at all, since two handlers on one press is a video that seeks twice, and the focus stop goes with them. **'<source>' and '<track>' are copied into the video, never slotted into it.** A media element runs resource selection over its own child *nodes*, and a slot is one node, so slotted sources reach it as nothing: no text tracks, no current source, 'readyState' zero. A video given sources that way does not merely lack captions, it does not play. They are copied rather than moved, because the consumer's light DOM is theirs and a framework that re-renders it would find its children gone. The copies are rebuilt whenever the slot changes and the video is asked to load again, which is what makes a '<track>' added later take effect. **The lock screen and a headset's buttons work, and one player owns them.** There is one 'navigator.mediaSession' per page, so the rule is stated rather than left to render order: whichever player was started last owns it, which is what a reader means by "this one" when they press play on their headphones. The title comes from 'label' and the artwork from 'poster'. **'remote' sends the picture somewhere else.** The Remote Playback API is what Safari implements for AirPlay and Chrome for Cast, so one control covers both and the platform supplies the device list. It is offered only once something has answered 'watchAvailability': a button that opens an empty list is worse than no button, and a consumer who wants none can set 'disableremoteplayback' on this element. The properties below are written by this component and read by whoever draws it. A consumer sets none of them: they carry a number this element computed and CSS cannot, which is what lets a stylesheet on this tier draw the thing without measuring anything itself. */
8660
+ /** **The picture is a real '<video>'.** Decoding, buffering, captions, Picture in Picture, AirPlay and whatever codecs a device has are the platform's. What is replaced is the control bar, which is the one part that cannot be styled. **Every control is a real control.** The scrubber and the volume are '<input type="range">', so arrow keys, Home and End, the platform's touch target and its screen reader announcement all come for free. **Native controls are the fallback, not the default.** Where a consumer sets 'controls' the browser's bar is used and this one is not drawn, and the keys below are not bound: two handlers on one press is a video that seeks twice. The keys are the ones every player uses: Space and 'k' to play or pause, the arrows to seek and to change volume, 'm' to mute, 'f' for fullscreen. They are bound on this element rather than on the document, because a video is not a palette. **'<source>' and '<track>' are copied into the video, never slotted into it.** A media element runs resource selection over its own child *nodes*, and a slot is one node, so slotted sources reach it as nothing: no text tracks, no current source, 'readyState' zero. A video given sources that way does not merely lack captions, it does not play. They are copied rather than moved, because the consumer's light DOM is theirs, and rebuilt whenever the slot changes. */
8661
8661
  "helix-headless-video": DefineCustomElement<
8662
8662
  HelixHeadlessVideo,
8663
8663
  HelixHeadlessVideoVueProps,
8664
8664
  HelixHeadlessVideoVueEvents
8665
8665
  >;
8666
- /** **The set arrives as data, which is what makes this a virtual list rather than a box that hides children.** 'for' names a '<script type="application/json">' holding an array of objects, and a '<template>' child says what one row looks like: elements carrying 'data-field="name"' are filled with that field of the item. Both halves are markup, and that is the whole reason for the shape. 'VirtualizeController' says why the obvious alternatives are shut: an 'items' property cannot survive React hydration, which skips every property it would have to apply imperatively, and light-DOM children exist by the time anything could decide not to create them. Text in the document is neither. A server emits it, React re-emits it as a child rather than applying it, and it is one node whatever the row count. **The accessible version is not a '<table>'**, the way it is for the charts that share this 'for="<id>"' shape: ten thousand rows in one is the cost this exists to avoid. It is the rendered window plus 'aria-setsize' and 'aria-posinset', which is what says "four thousand of ten thousand" rather than "four of twenty". **Rows are light DOM, deliberately.** They are cloned from the consumer's template and appended to the host, so a document rule reaches them, the base layer applies, and a class in the template means what it says. Cloned into the shadow root they would be markup the consumer wrote and could not style, which is the one thing a row template must not be. **Reading the source is client-only**, because a server has no document to query. The window renders empty from the server and fills on the first update, which is the same client-side enhancement rule every tree-reading component here follows. **On this tier the consumer gives the viewport its box**, and the component cannot do it for them: 'helix/headless' ships no stylesheet, and a height is a decision rather than a fact this could publish. So a base list wants two rules before it is a list at all: '''css helix-headless-virtual-list::part(viewport) { block-size: 12rem; overflow-y: auto; } ''' **Without them it is not a scroller**, and the failure is worse than it sounds. 'scrollTop' is always zero, so nothing ever windows; and the box has no height of its own, so 'clientHeight' is a function of the rows just drawn. Measuring then moves what is being measured, and the two need not converge: measured in Chromium at 500 rows, it settled into a two-cycle between 378 and 379 rows and stayed there, at two and a half thousand passes in eight seconds. 'VirtualizeController' gives up after ten passes and says so, so this now draws a bad window rather than stopping the page, but a bad window is still the answer until the rules are written. The styled tier writes them for you, with '--virtual-list-block-size'. */
8666
+ /** **The set arrives as data, which is what makes this a virtual list rather than a box that hides children.** 'for' names a '<script type="application/json">' holding an array of objects, and a '<template>' child says what one row looks like: elements carrying 'data-field="name"' are filled with that field of the item. Both halves are markup, and that is the reason for the shape. An 'items' property cannot survive React hydration, which skips every property it would have to apply imperatively, and light-DOM children exist by the time anything could decide not to create them. Text in the document is neither, and it is one node whatever the row count. **The accessible version is not a '<table>'**, the way it is for the charts that share this 'for="<id>"' shape: ten thousand rows in one is the cost this exists to avoid. It is the rendered window plus 'aria-setsize'. */
8667
8667
  "helix-headless-virtual-list": DefineCustomElement<
8668
8668
  HelixHeadlessVirtualList,
8669
8669
  HelixHeadlessVirtualListVueProps,
File without changes