@fusion.dev/helix-headless 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/custom-elements.json +234 -234
  2. package/dist/accordion/item/accordion-item.js +0 -108
  3. package/dist/accordion/root/accordion-root.js +0 -33
  4. package/dist/announcement/root/announcement-root.js +1 -76
  5. package/dist/app-header/root/app-header-root.js +0 -84
  6. package/dist/avatar/group/avatar-group.js +0 -77
  7. package/dist/avatar/root/avatar-root.js +0 -72
  8. package/dist/badge/root/badge-root.js +0 -67
  9. package/dist/bar-chart/root/bar-chart-root.js +3 -263
  10. package/dist/blockquote/root/blockquote-root.js +0 -46
  11. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +0 -72
  12. package/dist/breadcrumbs/root/breadcrumbs-root.js +0 -52
  13. package/dist/button/root/button-root.js +0 -215
  14. package/dist/button-group/root/button-group-root.js +0 -30
  15. package/dist/callout/root/callout-root.js +0 -52
  16. package/dist/card/root/card-root.js +0 -41
  17. package/dist/carousel/root/carousel-root.d.ts +2 -2
  18. package/dist/carousel/root/carousel-root.js +2 -362
  19. package/dist/chat/composer/chat-composer.js +0 -131
  20. package/dist/chat/message/chat-message.js +0 -137
  21. package/dist/chat/root/chat-root.js +0 -193
  22. package/dist/checkbox/root/checkbox-root.js +0 -113
  23. package/dist/checkbox-group/root/checkbox-group-root.js +1 -157
  24. package/dist/code/root/code-root.js +0 -26
  25. package/dist/code-block/root/code-block-root.js +0 -100
  26. package/dist/color-input/root/color-input-root.js +0 -217
  27. package/dist/color-picker/color.js +0 -69
  28. package/dist/color-picker/root/color-picker-root.js +0 -272
  29. package/dist/combobox/root/combobox-root.js +0 -428
  30. package/dist/command/root/command-root.js +0 -177
  31. package/dist/copy-button/root/copy-button-root.js +0 -134
  32. package/dist/data-table/root/data-table-root.js +0 -336
  33. package/dist/data-table/root/order.js +0 -100
  34. package/dist/date-input/root/date-input-root.js +1 -398
  35. package/dist/date-picker/root/date-picker-root.js +0 -358
  36. package/dist/dialog/root/dialog-root.js +0 -189
  37. package/dist/divider/root/divider-root.js +0 -71
  38. package/dist/donut-chart/root/donut-chart-root.js +0 -214
  39. package/dist/drawer/root/drawer-root.js +0 -116
  40. package/dist/drilldown/item/drilldown-item.js +0 -82
  41. package/dist/drilldown/root/drilldown-root.js +0 -91
  42. package/dist/element-events.generated.js +0 -5
  43. package/dist/empty/root/empty-root.js +0 -35
  44. package/dist/file-input/root/file-input-root.js +0 -232
  45. package/dist/format-date/root/format-date-root.js +0 -95
  46. package/dist/heatmap/root/buckets.js +0 -57
  47. package/dist/heatmap/root/heatmap-root.js +0 -313
  48. package/dist/icon/root/icon-root.js +0 -48
  49. package/dist/icon-button/root/icon-button-root.js +0 -30
  50. package/dist/image/root/image-root.js +0 -174
  51. package/dist/index.js +0 -9
  52. package/dist/input/root/input-root.js +0 -228
  53. package/dist/kbd/root/kbd-root.js +0 -117
  54. package/dist/legend/root/legend-root.js +0 -73
  55. package/dist/line-chart/root/line-chart-root.js +0 -172
  56. package/dist/list/item/list-item.js +0 -92
  57. package/dist/list/root/list-root.js +0 -26
  58. package/dist/listbox/root/listbox-root.js +2 -429
  59. package/dist/loader/root/loader-root.js +0 -40
  60. package/dist/megamenu/root/megamenu-root.js +0 -284
  61. package/dist/menu/item/menu-item.js +1 -221
  62. package/dist/menu/root/menu-root.js +1 -171
  63. package/dist/meter/root/meter-root.js +1 -152
  64. package/dist/navigation/root/navigation-root.js +0 -56
  65. package/dist/notifications/notification/notification.js +0 -159
  66. package/dist/notifications/root/notifications-root.js +0 -148
  67. package/dist/number-input/root/number-input-root.js +1 -263
  68. package/dist/option/group/option-group.js +0 -26
  69. package/dist/option/root/option-root.js +0 -157
  70. package/dist/outline/root/outline-root.js +0 -292
  71. package/dist/pagination/root/pagination-root.js +0 -361
  72. package/dist/pagination/size/pagination-size.js +0 -104
  73. package/dist/pagination/summary/pagination-summary.js +0 -90
  74. package/dist/pin-input/root/pin-input-root.js +0 -276
  75. package/dist/popover/root/popover-root.js +0 -103
  76. package/dist/progress/root/progress-root.js +1 -133
  77. package/dist/qr-code/root/qr-code-root.js +0 -96
  78. package/dist/quote/root/quote-root.js +0 -25
  79. package/dist/radio/group/radio-group.js +0 -81
  80. package/dist/radio/root/radio-root.js +0 -85
  81. package/dist/rating/root/rating-root.js +0 -220
  82. package/dist/scroll-area/root/scroll-area-root.js +0 -100
  83. package/dist/select/root/select-root.js +2 -218
  84. package/dist/skeleton/root/skeleton-root.js +0 -20
  85. package/dist/slider/root/slider-root.js +1 -267
  86. package/dist/sparkline/root/sparkline-root.js +3 -164
  87. package/dist/speed-dial/item/speed-dial-item.js +0 -52
  88. package/dist/speed-dial/root/speed-dial-root.js +1 -116
  89. package/dist/splitter/root/splitter-root.js +0 -489
  90. package/dist/stat/group/stat-group.js +0 -18
  91. package/dist/stat/root/stat-root.js +0 -23
  92. package/dist/steps/root/steps-root.js +0 -62
  93. package/dist/steps/step/step.js +0 -131
  94. package/dist/switch/root/switch-root.js +0 -80
  95. package/dist/table/cell/table-cell.js +0 -23
  96. package/dist/table/column/table-column.js +0 -158
  97. package/dist/table/root/table-root.js +0 -82
  98. package/dist/table/row/table-row.js +0 -21
  99. package/dist/tabs/panel/tab-panel.js +0 -49
  100. package/dist/tabs/root/tabs-root.js +0 -98
  101. package/dist/tabs/tab/tab.js +0 -59
  102. package/dist/tag/root/tag-root.js +0 -31
  103. package/dist/textarea/root/textarea-root.js +0 -162
  104. package/dist/time-input/root/time-input-root.js +0 -283
  105. package/dist/time-picker/root/time-picker-root.js +0 -326
  106. package/dist/timeline/item/timeline-item.js +0 -66
  107. package/dist/timeline/root/timeline-root.js +0 -41
  108. package/dist/toast/item/toast-item.js +0 -165
  109. package/dist/toast/root/toast-root.js +0 -51
  110. package/dist/toggle/group/toggle-group.js +0 -150
  111. package/dist/toggle/root/toggle-root.js +0 -78
  112. package/dist/toolbar/root/toolbar-root.js +0 -116
  113. package/dist/tooltip/root/tooltip-root.js +0 -427
  114. package/dist/tour/root/tour-root.js +0 -130
  115. package/dist/tour/stop/tour-stop.js +1 -127
  116. package/dist/tree/item/tree-item.js +0 -198
  117. package/dist/tree/root/tree-root.js +0 -97
  118. package/dist/trend/root/trend-root.js +0 -53
  119. package/dist/video/root/video-root.d.ts +8 -12
  120. package/dist/video/root/video-root.js +5 -756
  121. package/dist/video-playlist/item/video-playlist-item.js +1 -90
  122. package/dist/video-playlist/menu/video-playlist-menu.js +1 -132
  123. package/dist/video-playlist/root/video-playlist-root.js +0 -113
  124. package/dist/virtual-list/root/virtual-list-root.js +0 -105
  125. package/jsx.d.ts +1408 -2576
  126. package/package.json +6 -6
  127. package/vue.d.ts +1 -1
@@ -7,27 +7,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
- /**
11
- * @summary A row of related measurements.
12
- *
13
- * @description
14
- * A group rather than a list, because the figures are read together and their
15
- * order carries no meaning: a list would announce "1 of 4" for a set where the
16
- * position is not information.
17
- *
18
- * @since 0.1.0
19
- * @status stable
20
- * @aliases Metrics, KPIs, Figures
21
- * @documentation https://helix.fusion.dev/components/stat
22
- *
23
- * @slot - The stats.
24
- */
25
10
  let HelixHeadlessStatGroup = class HelixHeadlessStatGroup extends HelixElement {
26
11
  constructor() {
27
12
  super(...arguments);
28
- /**
29
- * Accessible name for the set.
30
- */
31
13
  this.label = '';
32
14
  }
33
15
  willUpdate(changed) {
@@ -7,29 +7,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
- /**
11
- * @summary One measurement, with the word for what it measures.
12
- *
13
- * @description
14
- * Label first in the markup and the value after it, which is the reading order
15
- * a screen reader takes and the order a caption sits above a figure. The eye
16
- * lands on the number either way, because the number is the loud thing; a
17
- * reader hearing it needs to know what it counts before hearing how many.
18
- *
19
- * Both are bare slots. An element whose only job is to assign itself a slot on
20
- * connect gives its own host an attribute after upgrade, which a React
21
- * consumer's server markup never had.
22
- *
23
- * @since 0.1.0
24
- * @status stable
25
- * @category data
26
- * @aliases Metric, KPI, Figure
27
- * @documentation https://helix.fusion.dev/components/stat
28
- *
29
- * @slot - The value.
30
- * @slot label - What the value measures.
31
- * @slot unit - The unit, set beside the value.
32
- */
33
10
  let HelixHeadlessStat = class HelixHeadlessStat extends HelixElement {
34
11
  render() {
35
12
  return html `<slot name="label"></slot>
@@ -7,59 +7,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement, hostAria, MixinControllerActive, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
- /**
11
- * @summary A sequence of stages, and where the reader is in it.
12
- *
13
- * @description
14
- * **`active` names where the reader is. `complete` stays on each step.** They
15
- * are two different facts and only one of them is positional. Deriving both
16
- * from a position is the obvious design and it is wrong the first time anybody
17
- * steps back: return to stage one of three and stages one and two are still
18
- * finished, while "everything before the active one" says only the first is.
19
- * So this element drives `current` and never touches `complete`.
20
- *
21
- * **`active` is optional, and a sequence without it still renders.** Left
22
- * unset, each step's own `current` stands and nothing here overwrites it, which
23
- * is what lets the whole sequence be right in the server's markup: this element
24
- * would have to count its slotted children to know which one is third, and the
25
- * server has no children to count. Set it, and it wins, because it is a
26
- * statement about the sequence where the attribute on a step is a statement
27
- * about one step.
28
- *
29
- * That is also why the numbering is not JavaScript. It is a CSS counter in the
30
- * tier above, which the browser resolves at layout with no script at all, so
31
- * the third step reads "3" in the server's own markup.
32
- *
33
- * **The connector belongs to the step, not to the list.** Each one draws the
34
- * line that arrives at it and hides it when it is first, the same arrangement
35
- * Breadcrumbs uses for its separator and for the same reason: a line drawn
36
- * between items needs either an index or a parent that counts, and drawing it
37
- * on the item needs neither.
38
- *
39
- * `role="list"` on the host rather than an `<ol>` in the shadow root. The steps
40
- * are the consumer's elements in the light DOM, so an `<ol>` here would be a
41
- * list whose items are somewhere else.
42
- *
43
- * @since 0.1.0
44
- * @status experimental
45
- * @category navigation
46
- * @aliases Stepper, Wizard, Progress steps
47
- * @documentation https://helix.fusion.dev/components/steps
48
- *
49
- * @slot - The steps, which are `helix-step` elements.
50
- *
51
- * @event {HelixActivateEvent} helix-activate - Fired once the current one has moved, so the element already holds it.
52
- * @event {HelixBeforeActivateEvent} helix-before-activate - Fired before the active step changes. Cancelable: refusing it takes the decision and nothing moves.
53
- */
54
10
  let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(HelixElement) {
55
11
  constructor() {
56
12
  super(...arguments);
57
- /**
58
- * Accessible name for the sequence.
59
- *
60
- * A list of stages is a landmark a reader arrives at out of order, so it
61
- * needs a name of its own rather than borrowing the heading above it.
62
- */
63
13
  this.label = 'Steps';
64
14
  this.handleSelection = (event) => {
65
15
  this.requestActive(event.detail.value);
@@ -68,21 +18,9 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
68
18
  this.sync();
69
19
  };
70
20
  }
71
- /** The steps, in the order the consumer wrote them. */
72
21
  get steps() {
73
22
  return [...this.querySelectorAll(':scope > *')].filter((one) => 'current' in one);
74
23
  }
75
- /**
76
- * Puts `active` on whichever step owns it.
77
- *
78
- * Returns without touching anything where nothing has set `active`, which is
79
- * the case a server renders: the steps' own `current` is the state then, and
80
- * overwriting it on hydration is exactly the flash this design exists to
81
- * avoid.
82
- *
83
- * `current` only. `complete` is the consumer's on each step, because a reader
84
- * who steps back has finished more stages than they are standing in front of.
85
- */
86
24
  sync() {
87
25
  if (this.active === '') {
88
26
  return;
@@ -9,131 +9,21 @@ import { check, circle, record } from '@fusion.dev/helix-icons';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
11
11
  import { html } from 'lit/static-html.js';
12
- /* Side-effect import, because this renders a button and a custom element
13
- registers on import. The path is relative, so this registers the base button
14
- and the styled file's identical import registers the styled one; `tier` picks
15
- which name the template asks for. */
16
12
  import '../../button/index.js';
17
- /**
18
- * @summary One stage in a sequence.
19
- *
20
- * @description
21
- * **The name is a button, and only the name.** A stepper is something a reader
22
- * goes back through, and the thing they reach for is the name of the stage. The
23
- * marker is `aria-hidden` decoration, so wrapping it in the button would add
24
- * hit area and no meaning: the accessible name would be the text either way,
25
- * and what is pressable would stop being the same shape as what is readable.
26
- *
27
- * It is `helix-button`, not a `<button>` this element resets and dresses: the
28
- * hover wash, the focus ring and the disabled treatment are decisions the
29
- * button already carries. `ghost` is the quiet shape, transparent until it is
30
- * pointed at.
31
- *
32
- * It says nothing about where a reader may go. Every step is pressable and
33
- * announces itself with `helix-select`; refusing the ones ahead is `disabled`,
34
- * which the consumer sets because only the consumer knows whether a form can be
35
- * skipped. A component that guessed would be wrong on the first wizard that
36
- * lets you jump to the end.
37
- *
38
- * **The marker is a slot whose fallback is the state.** A finished stage shows
39
- * a check, the current one a target, and one not yet reached an empty ring:
40
- * three marks that say what a reader wants to know without a legend. A consumer
41
- * who wants their own fills the slot instead.
42
- *
43
- * The step renders exactly one of the three, chosen from its own two booleans.
44
- * Rendering all three and hiding two in CSS would work and is worse: the
45
- * accessibility tree would carry marks that are not there, and the markup would
46
- * say three things about a stage that is one.
47
- *
48
- * Numbering belongs to the tier above. `numeric` hides the mark and shows a CSS
49
- * counter, which the browser resolves at layout, so the digit costs no script
50
- * and is right in the server's markup however the steps are ordered. Nothing
51
- * here knows which appearance is in use.
52
- *
53
- * **`complete` and `current` are separate, and both can be false.** A stage
54
- * that is neither is one the reader has not reached, which is the majority of
55
- * any sequence and therefore the state that needs no attribute. They can also
56
- * both be true: a reader who steps back into a finished stage is in it and has
57
- * finished it, and a stepper that forgets the second is one that loses its
58
- * checks the moment somebody reviews their answers.
59
- *
60
- * **The `content` slot opens on the current step, and only down a column.** A
61
- * row has no room under a step to put a form in, and stretching one to fit
62
- * would set every step's height by the tallest. `current` is what opens it, so
63
- * the state that says where the reader is is also the state that shows them the
64
- * stage: one attribute, no second flag to keep in step with it, and it renders
65
- * open on the server rather than after hydration.
66
- *
67
- * @since 0.1.0
68
- * @status experimental
69
- * @aliases Stage
70
- * @documentation https://helix.fusion.dev/components/steps
71
- *
72
- * @slot - The step's name.
73
- * @slot content - What the stage holds, shown while it is the current one.
74
- * @slot description - A line under the name, for what the stage is for.
75
- * @slot marker - Replaces the number, or the check a complete step shows.
76
- *
77
- * @event {HelixSelectEvent} helix-select - Fired when the step is pressed. Cancelable: refusing it takes the decision and nothing moves.
78
- *
79
- * @csspart body - The name and its description.
80
- * @csspart content - What the stage holds, while it is open.
81
- * @csspart description - The line under it.
82
- * @csspart label - The step's name, which is a `helix-button`.
83
- * @csspart label-button - The inner control.
84
- * @csspart lead - The half line arriving from the step before.
85
- * @csspart mark - The glyph inside the marker: a tick, a dot, or an empty ring.
86
- * @csspart marker - The shape carrying the number or the check.
87
- * @csspart trail - The half line running on to the next.
88
- */
89
13
  let HelixHeadlessStep = class HelixHeadlessStep extends HelixElement {
90
14
  constructor() {
91
15
  super(...arguments);
92
- /**
93
- * Whether the reader has finished this stage.
94
- */
95
16
  this.complete = false;
96
- /**
97
- * Whether this is the stage the reader is in.
98
- */
99
17
  this.current = false;
100
- /**
101
- * Whether the reader may go to this stage.
102
- */
103
18
  this.disabled = false;
104
- /**
105
- * What this stage is called when it announces itself.
106
- */
107
19
  this.value = '';
108
20
  }
109
- /** Announces the step, unless it is refusing. */
110
21
  select() {
111
22
  if (this.disabled) {
112
23
  return;
113
24
  }
114
25
  this.dispatchEvent(new HelixSelectEvent({ value: this.value }));
115
26
  }
116
- /**
117
- * The mark this stage's state calls for.
118
- *
119
- * `complete` first, so a stage a reader has stepped back into keeps its check
120
- * rather than losing it to the target: they did finish it, and the name and
121
- * the rule below still say which one is open.
122
- *
123
- * **Three shapes, and the reader can tell them apart with the color off.**
124
- * An outline circle for a stage nobody has reached, the same circle with a
125
- * center for the one they are standing on, a bare check for one that is done.
126
- * The first two are one shape filling in, which is the progression, and the
127
- * check is the only mark that leaves the family because finishing is the only
128
- * state that is not a position in the sequence. A coin around the check would
129
- * put two of the three inside the same container and leave hue doing the work
130
- * of telling them apart.
131
- *
132
- * The bare check matches `Timeline`, deliberately: the two components report
133
- * the same kind of thing, a sequence with marks down it, and a check on a
134
- * hairline in one beside a check inside a coin in the other is two answers to
135
- * one question.
136
- */
137
27
  renderMarker() {
138
28
  const icon = this.complete ? check : this.current ? record : circle;
139
29
  return html `<slot name="marker"
@@ -147,15 +37,6 @@ let HelixHeadlessStep = class HelixHeadlessStep extends HelixElement {
147
37
  hostAria(this.internals, { role: 'listitem' });
148
38
  this.internals.ariaCurrent = this.current ? 'step' : null;
149
39
  }
150
- /**
151
- * The step's own label, which is a control because a step can be gone to, behind a seam the styled tier can move.
152
- *
153
- * A method rather than a fragment inline, so a tier above can render its own,
154
- * wrap this one, or leave it out, without reimplementing the whole template.
155
- * The tag is interpolated rather than written, so `tier` resolves it: this
156
- * renders the headless element, and a styled subclass that sets `tier`
157
- * renders the styled one from the same template.
158
- */
159
40
  renderLabel() {
160
41
  return html `<${this.tag('button')}
161
42
  part="label"
@@ -168,18 +49,6 @@ let HelixHeadlessStep = class HelixHeadlessStep extends HelixElement {
168
49
  </${this.tag('button')}>`;
169
50
  }
170
51
  render() {
171
- /* Two half lines, one either side of the mark, rather than one line running
172
- the whole way to the next step.
173
- A single line drawn to the next marker has to know how far away that is,
174
- which is the next step's size, and a step knows only its own; drawn
175
- across the gap alone it is a stub. Two halves need neither: each reaches
176
- from the mark to its own step's edge and half the gap beyond, so it meets
177
- the one coming the other way in the middle of the gap and the rule reads
178
- as continuous however wide the steps are or how differently they are
179
- filled.
180
- The first has nothing on its leading side and the last nothing on its
181
- trailing one, which `:host(:first-child)` and `:host(:last-child)` say
182
- with no index and no parent counting its children. */
183
52
  return html `<span part="lead" aria-hidden="true"></span>
184
53
  <span part="trail" aria-hidden="true"></span>
185
54
  <span part="marker" aria-hidden="true">
@@ -7,74 +7,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { AriaForwardController, customElement, HelixFormElement, MixinControllerChecked, MixinField, slotFilled, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
- /**
11
- * @summary A control that turns something on or off.
12
- *
13
- * @description
14
- * **A switch is not a checkbox with a different shape.** A checkbox is an
15
- * answer to a question the form asks when it is submitted; a switch is a thing
16
- * happening now. That is why `role="switch"` exists and why a switch belongs
17
- * outside a form as often as in one: it says "on" and "off" rather than
18
- * "ticked" and "unticked", and a reader expects the effect immediately rather
19
- * than on Save.
20
- *
21
- * Rendered as a real `<input type="checkbox">` carrying `role="switch"`, which
22
- * is the pairing ARIA describes: the input is what a form submits and what a
23
- * label forwards a press to, and the role is what a screen reader announces.
24
- * Neither half can be got from the other.
25
- *
26
- * The label sits beside it, inside the `<label>` that forwards the press, the
27
- * way a checkbox's does. The hint and the message go under the row, because
28
- * there is no above: the switch and the words naming it are one line.
29
- *
30
- * @since 0.1.0
31
- * @status stable
32
- * @category forms
33
- * @aliases Toggle switch, On/off
34
- * @documentation https://helix.fusion.dev/components/switch
35
- *
36
- * @slot - The words naming what this turns on.
37
- *
38
- * @event {Event} change - Relayed from the input once the state has changed.
39
- * @event {HelixBeforeToggleEvent} helix-before-toggle - Fired before the state changes, and cancelable.
40
- * @event {Event} input - Announced once the state has moved.
41
- *
42
- * @csspart control - The input.
43
- * @csspart feedback - What is wrong with it.
44
- * @csspart field - The row: the track and its label.
45
- * @csspart hint - Help under the control, while nothing is wrong.
46
- * @csspart label - The words beside it.
47
- * @csspart thumb - The part that moves.
48
- * @csspart track - The groove the thumb travels in.
49
- */
50
10
  let HelixHeadlessSwitch = class HelixHeadlessSwitch extends MixinControllerChecked(MixinField(HelixFormElement)) {
51
11
  constructor() {
52
12
  super(...arguments);
53
- /* A consumer's `aria-describedby` names ids in their own tree and the control
54
- is in this shadow root, so the reference has to be carried down as an
55
- element rather than copied as an id. See `AriaForwardController`: measured
56
- in Chrome's own tree, the host held the description and the control the
57
- reader is actually on was exposed without it. */
58
13
  this.aria = new AriaForwardController(this, {
59
14
  target: () => this.renderRoot?.querySelector('[part="control"]') ?? null,
60
15
  });
61
- /**
62
- * Value submitted when the switch is on.
63
- */
64
16
  this.value = 'on';
65
17
  this.handleChange = (event) => {
66
18
  event.stopPropagation();
67
19
  const control = event.target;
68
20
  const taken = this.requestChecked(control.checked);
69
- /* The input follows the element, always, not only where the change was
70
- refused.
71
- *
72
- * A controlled switch takes the request and applies nothing: it has
73
- announced where it would go and is waiting to be told. The input had
74
- already flipped itself, so until the consumer answered, the switch showed
75
- one state and held the other, and a consumer who never answers showed the
76
- wrong one for good. Putting it back costs nothing where the change was
77
- taken, since `this.checked` is then the new value anyway. */
78
21
  control.checked = this.checked;
79
22
  if (!taken) {
80
23
  return;
@@ -82,17 +25,11 @@ let HelixHeadlessSwitch = class HelixHeadlessSwitch extends MixinControllerCheck
82
25
  this.dispatchEvent(new Event('change', { bubbles: true, composed: false }));
83
26
  };
84
27
  }
85
- /** Reports what the form would submit, and the verdict on it. */
86
28
  sync() {
87
- // `null` rather than the value when it is off, which is how a checkbox
88
- // behaves: an unticked box submits nothing rather than a blank.
89
29
  this.internals.setFormValue(this.checked ? this.value : null);
90
30
  this.reportRequired(!this.checked, 'Turn this on to continue.', 'control');
91
31
  }
92
- /** Returns the switch to the state its markup declared. */
93
32
  formResetCallback() {
94
- // `resetChecked` drops both halves, the consumer's and the element's, so
95
- // there is nothing to clear before it.
96
33
  this.resetChecked();
97
34
  }
98
35
  updated(changed) {
@@ -100,30 +37,13 @@ let HelixHeadlessSwitch = class HelixHeadlessSwitch extends MixinControllerCheck
100
37
  this.markEmpty();
101
38
  this.sync();
102
39
  }
103
- /**
104
- * Says on the `label` region whether anything is in it.
105
- *
106
- * An empty region is not free: it is still a flex item, so it still takes the
107
- * container's gap. There is no selector that can ask, `slot:empty` being
108
- * about a slot's fallback rather than its assigned nodes, so the component
109
- * asks and says so, on the region inside this shadow root rather than on the
110
- * host: an attribute written onto the host is a change to markup the consumer
111
- * rendered, which React reports on hydration and does not patch up.
112
- */
113
40
  markEmpty() {
114
41
  const region = this.renderRoot?.querySelector('[part="label"]');
115
42
  const empty = !slotFilled(this.renderRoot?.querySelector('[part="label"] slot'));
116
43
  region?.toggleAttribute('data-empty', empty);
117
- /* `hidden` beside the mirror, because `data-empty` is a hook the styled
118
- sheet reads and this tier has no sheet: an empty region left in the box
119
- tree is a gap a consumer cannot close without knowing to look for it. The
120
- styled rule stays, because that sheet sets `display` on this part and so
121
- outranks the UA one. */
122
44
  region?.toggleAttribute('hidden', empty);
123
45
  }
124
46
  render() {
125
- /* A real `<label>` around both, so pressing the words works with no click
126
- handler: the browser does the forwarding. */
127
47
  return html `<label part="field">
128
48
  <span part="track">
129
49
  <input
@@ -7,36 +7,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
- /**
11
- * @summary One cell of a table.
12
- *
13
- * @description
14
- * Carries its own role, because a grid of divs is not a table to anything
15
- * reading it. `header` makes the cell a `columnheader`, which is what lets a
16
- * screen reader announce "Attribute, appearance" rather than just the value.
17
- *
18
- * The role goes on the host rather than inside the shadow root, because the
19
- * cell has to be a child of its row in the flat tree for the table's structure
20
- * to hold together.
21
- *
22
- * @since 0.1.0
23
- * @status experimental
24
- * @aliases Cell, TableCell
25
- * @documentation https://helix.fusion.dev/components/table
26
- *
27
- * @slot - The cell's content.
28
- */
29
10
  let HelixHeadlessTableCell = class HelixHeadlessTableCell extends HelixElement {
30
11
  constructor() {
31
12
  super(...arguments);
32
- /**
33
- * Whether the cell heads its column.
34
- */
35
13
  this.header = false;
36
14
  }
37
15
  willUpdate(changed) {
38
16
  super.willUpdate(changed);
39
- // In `willUpdate`, which the server does call, unlike `connectedCallback`.
40
17
  hostAria(this.internals, { role: this.header ? 'columnheader' : 'cell' });
41
18
  }
42
19
  render() {
@@ -7,15 +7,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
- /**
11
- * A dot path resolved against a row.
12
- *
13
- * Written here rather than taken from a library, because the whole of what is
14
- * needed is "walk the keys and stop at the first thing that is not an object".
15
- * Returns `undefined` for a path that does not resolve, which the table renders
16
- * as an empty cell: a missing key is a hole in the data and drawing `undefined`
17
- * into a table tells a reader the software is broken rather than the row is.
18
- */
19
10
  const at = (row, path) => {
20
11
  if (path === '') {
21
12
  return undefined;
@@ -32,189 +23,40 @@ const at = (row, path) => {
32
23
  }
33
24
  return cursor;
34
25
  };
35
- /**
36
- * @summary One column of a table: its heading, how wide it is, and what it reads.
37
- *
38
- * @description
39
- * **The column is the source of both its heading and its track.** Add one and
40
- * the table has another track; remove one and it does not. Declaring the widths
41
- * separately, as a CSS track list on the table, leaves nothing tying the two
42
- * together: a table with three headings and a two-track list renders without
43
- * complaint, and a track list that is accidentally the headings is invalid CSS
44
- * the grid silently ignores, collapsing the whole table into one column.
45
- *
46
- * **It is also where every per-column fact goes**, which is the reason
47
- * `helix-data-table` needs no column config object. `field` names the key the
48
- * cell reads, `sortable` says a reader may reorder by it, `align` says which
49
- * edge the values line up against, and `cell` takes a function for anything the
50
- * field alone cannot express. The schema is markup and the data is a property:
51
- * a schema is small, declarative, and something a reader should be able to see
52
- * in the HTML, and data is large and windowed.
53
- *
54
- * **The same element serves both tables**, the markup one and the data one. It
55
- * is identified by a flag rather than by `instanceof`, so a `helix-table-column`
56
- * works inside either without the two roots sharing anything else.
57
- *
58
- * **It generates no box of its own**, the same as a row: the heading is a grid
59
- * item of the table, so it lines up with the cells under it rather than sitting
60
- * in a wrapper that has to be kept the same width.
61
- *
62
- * @since 0.1.0
63
- * @status experimental
64
- * @aliases Column header, Th
65
- * @documentation https://helix.fusion.dev/components/table
66
- *
67
- * @slot - The heading.
68
- *
69
- * @csspart column - The heading cell.
70
- */
71
26
  let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixElement {
72
27
  constructor() {
73
28
  super(...arguments);
74
- /**
75
- * Which edge the values line up against.
76
- *
77
- * `end` is what a column of numbers wants, so digits of the same magnitude sit
78
- * above each other. Left unset the cell inherits the table's own alignment,
79
- * which is `start` for text in either writing direction.
80
- */
81
29
  this.align = '';
82
- /**
83
- * How to read the cell, where the field alone will not do.
84
- *
85
- * A property and not an attribute, because it is a function. Set it from a Lit
86
- * template with `.cell=${...}`, or assign it in script. A column with neither
87
- * this nor a `field` renders an empty cell rather than the whole row stringified,
88
- * which is the failure mode of guessing.
89
- */
90
30
  this.cell = undefined;
91
- /**
92
- * How to read the value that gets sorted and filtered, where it is not the cell.
93
- *
94
- * **The one that is easy to leave out and expensive to add later.** A cell
95
- * showing `2.4 MB` sorts as a string unless something says the underlying value
96
- * is a number, so `12 kB` sorts above `2.4 MB` and the column looks broken
97
- * while every value in it is right. Left unset this is the field, which is the
98
- * correct default: the raw value is what an API sorted by.
99
- */
100
31
  this.compare = undefined;
101
- /**
102
- * The key this column reads out of a row.
103
- *
104
- * Dot paths resolve, so `field="owner.name"` reads a nested value: a result
105
- * from an API arrives shaped the way the API shaped it, and requiring a flat
106
- * row would push a mapping pass onto every consumer.
107
- *
108
- * **It is also the sort and filter identity**, which is why sorting is by
109
- * field and not by column index: a reordered or hidden column would otherwise
110
- * change what `sort="2:asc"` means, and a sort that means something different
111
- * after a column moves is a sort a consumer cannot store in a URL.
112
- *
113
- * Empty on a column that is purely presentational, an actions column say, and
114
- * such a column is never sortable however it is marked.
115
- */
116
32
  this.field = '';
117
- /**
118
- * Whether a reader may reorder the set by this column.
119
- *
120
- * Ignored without a `field`, and silently: a column with nothing to sort by
121
- * has no order to offer, and refusing loudly would mean an actions column
122
- * carrying `sortable` breaks a table rather than doing nothing.
123
- */
124
33
  this.sortable = false;
125
- /**
126
- * The track this column takes, as CSS.
127
- *
128
- * A single track value, so anything valid in `grid-template-columns` works:
129
- * `16rem`, `auto`, `minmax(0, 2fr)`. Left unset the column shares what is
130
- * left over equally, as `minmax(max-content, 1fr)`.
131
- *
132
- * **The floor is `max-content`, never `0` and never `min-content`.** A zero
133
- * floor lets a track shrink under its own content: at 375 the columns squeeze
134
- * to 75px and `eu-central-1` wraps across lines, which reads as corrupt data
135
- * rather than as a table that ran out of room. A `min-content` floor still
136
- * breaks it, a hyphen being a wrap opportunity.
137
- *
138
- * The overflow has somewhere to go: `@fusion.dev/helix` scrolls the table's
139
- * own box, so a long value pushes past the container and the table scrolls
140
- * instead of the value being destroyed. Measured at 375: tracks of 192, 89 and
141
- * 102 against a 343 box, so the table scrolls 384 and the page does not move.
142
- *
143
- * The cost is that a column of prose asks for its longest paragraph on one
144
- * line, so a column holding sentences rather than values wants
145
- * `width="minmax(0, 1fr)"` to opt back into wrapping. A headless consumer owns
146
- * the overflow either way, that being paint, and gets the honest track list.
147
- */
148
34
  this.width = '';
149
- /**
150
- * @internal Read by a parent to identify its children across the light DOM,
151
- * which is cheaper than `instanceof` and is why this is public rather than
152
- * protected. Not a consumer's to read or set.
153
- */
154
35
  this.isTableColumn = true;
155
- /**
156
- * @internal Which way this column is currently ordered, written by the table
157
- * that owns the sort. Not a consumer's to set: the sort lives on the table,
158
- * and a column that disagreed with it would announce an order the rows are
159
- * not in.
160
- */
161
36
  this.sorted = undefined;
162
37
  }
163
- /** The track this column contributes, resolved. */
164
38
  get track() {
165
39
  return this.width || 'minmax(max-content, 1fr)';
166
40
  }
167
- /** Whether a reader can actually reorder by this column. */
168
41
  get canSort() {
169
42
  return this.sortable && this.field !== '';
170
43
  }
171
- /**
172
- * The value this column shows for a row.
173
- *
174
- * `cell` first, then the field. An explicit reader that returns `undefined` is
175
- * treated as declining rather than as an answer, so a formatter can hand the
176
- * default back for the rows it has nothing to say about.
177
- */
178
44
  read(row) {
179
45
  const rendered = this.cell?.(row);
180
46
  return rendered === undefined ? at(row, this.field) : rendered;
181
47
  }
182
- /** The value this column sorts and filters by, which is not always what it shows. */
183
48
  order(row) {
184
49
  const compared = this.compare?.(row);
185
50
  return compared === undefined ? at(row, this.field) : compared;
186
51
  }
187
52
  willUpdate(changed) {
188
53
  super.willUpdate(changed);
189
- /* **`aria-sort` is not decoration.** A `columnheader` a reader can reorder
190
- by has to say which way it is ordered and which headers are orderable at
191
- all, or the whole affordance exists only for a reader who can see the rows
192
- move. `none` on the sortable ones that are not currently the sort is the
193
- part that carries the second half: it is what tells a screen reader this
194
- column can be sorted, where an absent attribute says nothing.
195
-
196
- Off the internals rather than the attribute, for the same reason the role
197
- is: it is this element's own semantics rather than markup a consumer
198
- wrote, and an attribute here is one more thing hydration can disagree
199
- about. */
200
54
  hostAria(this.internals, { role: 'columnheader' });
201
- /* Set here rather than through `hostAria`, which carries the four semantics
202
- every family needs and not this one. */
203
55
  this.internals.ariaSort = this.canSort ? (this.sorted ?? 'none') : null;
204
- /* And the same fact as a custom state, because ARIA is not a style hook.
205
- Whoever draws this column draws the caret, and had nothing to draw it
206
- from: no state and no part told the sorted column from the rest, so the
207
- only way to it was listening for `helix-sort` and reading the order back a
208
- microtask later, which is a lot of machinery for one glyph. */
209
56
  for (const [name, on] of [
210
57
  ['sorted-ascending', this.sorted === 'ascending'],
211
58
  ['sorted-descending', this.sorted === 'descending'],
212
59
  ]) {
213
- /* Through `toggleState`, which carries the feature test, the way
214
- `pagination-root.ts` does in this same hook. Safari below 17.4 and
215
- Firefox below 126 have no `CustomStateSet`, and there
216
- `internals.states.delete` is a `TypeError` thrown from `willUpdate`:
217
- the column would fail to render rather than render without a caret. */
218
60
  this.toggleState(name, on);
219
61
  }
220
62
  }