@fusion.dev/helix-headless 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/custom-elements.json +234 -234
  2. package/dist/accordion/item/accordion-item.js +0 -108
  3. package/dist/accordion/root/accordion-root.js +0 -33
  4. package/dist/announcement/root/announcement-root.js +1 -76
  5. package/dist/app-header/root/app-header-root.js +0 -84
  6. package/dist/avatar/group/avatar-group.js +0 -77
  7. package/dist/avatar/root/avatar-root.js +0 -72
  8. package/dist/badge/root/badge-root.js +0 -67
  9. package/dist/bar-chart/root/bar-chart-root.js +3 -263
  10. package/dist/blockquote/root/blockquote-root.js +0 -46
  11. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +0 -72
  12. package/dist/breadcrumbs/root/breadcrumbs-root.js +0 -52
  13. package/dist/button/root/button-root.js +0 -215
  14. package/dist/button-group/root/button-group-root.js +0 -30
  15. package/dist/callout/root/callout-root.js +0 -52
  16. package/dist/card/root/card-root.js +0 -41
  17. package/dist/carousel/root/carousel-root.d.ts +2 -2
  18. package/dist/carousel/root/carousel-root.js +2 -362
  19. package/dist/chat/composer/chat-composer.js +0 -131
  20. package/dist/chat/message/chat-message.js +0 -137
  21. package/dist/chat/root/chat-root.js +0 -193
  22. package/dist/checkbox/root/checkbox-root.js +0 -113
  23. package/dist/checkbox-group/root/checkbox-group-root.js +1 -157
  24. package/dist/code/root/code-root.js +0 -26
  25. package/dist/code-block/root/code-block-root.js +0 -100
  26. package/dist/color-input/root/color-input-root.js +0 -217
  27. package/dist/color-picker/color.js +0 -69
  28. package/dist/color-picker/root/color-picker-root.js +0 -272
  29. package/dist/combobox/root/combobox-root.js +0 -428
  30. package/dist/command/root/command-root.js +0 -177
  31. package/dist/copy-button/root/copy-button-root.js +0 -134
  32. package/dist/data-table/root/data-table-root.js +0 -336
  33. package/dist/data-table/root/order.js +0 -100
  34. package/dist/date-input/root/date-input-root.js +1 -398
  35. package/dist/date-picker/root/date-picker-root.js +0 -358
  36. package/dist/dialog/root/dialog-root.js +0 -189
  37. package/dist/divider/root/divider-root.js +0 -71
  38. package/dist/donut-chart/root/donut-chart-root.js +0 -214
  39. package/dist/drawer/root/drawer-root.js +0 -116
  40. package/dist/drilldown/item/drilldown-item.js +0 -82
  41. package/dist/drilldown/root/drilldown-root.js +0 -91
  42. package/dist/element-events.generated.js +0 -5
  43. package/dist/empty/root/empty-root.js +0 -35
  44. package/dist/file-input/root/file-input-root.js +0 -232
  45. package/dist/format-date/root/format-date-root.js +0 -95
  46. package/dist/heatmap/root/buckets.js +0 -57
  47. package/dist/heatmap/root/heatmap-root.js +0 -313
  48. package/dist/icon/root/icon-root.js +0 -48
  49. package/dist/icon-button/root/icon-button-root.js +0 -30
  50. package/dist/image/root/image-root.js +0 -174
  51. package/dist/index.js +0 -9
  52. package/dist/input/root/input-root.js +0 -228
  53. package/dist/kbd/root/kbd-root.js +0 -117
  54. package/dist/legend/root/legend-root.js +0 -73
  55. package/dist/line-chart/root/line-chart-root.js +0 -172
  56. package/dist/list/item/list-item.js +0 -92
  57. package/dist/list/root/list-root.js +0 -26
  58. package/dist/listbox/root/listbox-root.js +2 -429
  59. package/dist/loader/root/loader-root.js +0 -40
  60. package/dist/megamenu/root/megamenu-root.js +0 -284
  61. package/dist/menu/item/menu-item.js +1 -221
  62. package/dist/menu/root/menu-root.js +1 -171
  63. package/dist/meter/root/meter-root.js +1 -152
  64. package/dist/navigation/root/navigation-root.js +0 -56
  65. package/dist/notifications/notification/notification.js +0 -159
  66. package/dist/notifications/root/notifications-root.js +0 -148
  67. package/dist/number-input/root/number-input-root.js +1 -263
  68. package/dist/option/group/option-group.js +0 -26
  69. package/dist/option/root/option-root.js +0 -157
  70. package/dist/outline/root/outline-root.js +0 -292
  71. package/dist/pagination/root/pagination-root.js +0 -361
  72. package/dist/pagination/size/pagination-size.js +0 -104
  73. package/dist/pagination/summary/pagination-summary.js +0 -90
  74. package/dist/pin-input/root/pin-input-root.js +0 -276
  75. package/dist/popover/root/popover-root.js +0 -103
  76. package/dist/progress/root/progress-root.js +1 -133
  77. package/dist/qr-code/root/qr-code-root.js +0 -96
  78. package/dist/quote/root/quote-root.js +0 -25
  79. package/dist/radio/group/radio-group.js +0 -81
  80. package/dist/radio/root/radio-root.js +0 -85
  81. package/dist/rating/root/rating-root.js +0 -220
  82. package/dist/scroll-area/root/scroll-area-root.js +0 -100
  83. package/dist/select/root/select-root.js +2 -218
  84. package/dist/skeleton/root/skeleton-root.js +0 -20
  85. package/dist/slider/root/slider-root.js +1 -267
  86. package/dist/sparkline/root/sparkline-root.js +3 -164
  87. package/dist/speed-dial/item/speed-dial-item.js +0 -52
  88. package/dist/speed-dial/root/speed-dial-root.js +1 -116
  89. package/dist/splitter/root/splitter-root.js +0 -489
  90. package/dist/stat/group/stat-group.js +0 -18
  91. package/dist/stat/root/stat-root.js +0 -23
  92. package/dist/steps/root/steps-root.js +0 -62
  93. package/dist/steps/step/step.js +0 -131
  94. package/dist/switch/root/switch-root.js +0 -80
  95. package/dist/table/cell/table-cell.js +0 -23
  96. package/dist/table/column/table-column.js +0 -158
  97. package/dist/table/root/table-root.js +0 -82
  98. package/dist/table/row/table-row.js +0 -21
  99. package/dist/tabs/panel/tab-panel.js +0 -49
  100. package/dist/tabs/root/tabs-root.js +0 -98
  101. package/dist/tabs/tab/tab.js +0 -59
  102. package/dist/tag/root/tag-root.js +0 -31
  103. package/dist/textarea/root/textarea-root.js +0 -162
  104. package/dist/time-input/root/time-input-root.js +0 -283
  105. package/dist/time-picker/root/time-picker-root.js +0 -326
  106. package/dist/timeline/item/timeline-item.js +0 -66
  107. package/dist/timeline/root/timeline-root.js +0 -41
  108. package/dist/toast/item/toast-item.js +0 -165
  109. package/dist/toast/root/toast-root.js +0 -51
  110. package/dist/toggle/group/toggle-group.js +0 -150
  111. package/dist/toggle/root/toggle-root.js +0 -78
  112. package/dist/toolbar/root/toolbar-root.js +0 -116
  113. package/dist/tooltip/root/tooltip-root.js +0 -427
  114. package/dist/tour/root/tour-root.js +0 -130
  115. package/dist/tour/stop/tour-stop.js +1 -127
  116. package/dist/tree/item/tree-item.js +0 -198
  117. package/dist/tree/root/tree-root.js +0 -97
  118. package/dist/trend/root/trend-root.js +0 -53
  119. package/dist/video/root/video-root.d.ts +8 -12
  120. package/dist/video/root/video-root.js +5 -756
  121. package/dist/video-playlist/item/video-playlist-item.js +1 -90
  122. package/dist/video-playlist/menu/video-playlist-menu.js +1 -132
  123. package/dist/video-playlist/root/video-playlist-root.js +0 -113
  124. package/dist/virtual-list/root/virtual-list-root.js +0 -105
  125. package/jsx.d.ts +1408 -2576
  126. package/package.json +6 -6
  127. package/vue.d.ts +1 -1
@@ -7,56 +7,15 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
- /**
11
- * @summary Attaches a count or a presence mark to another element.
12
- *
13
- * @description
14
- * Wraps whatever it decorates and pins the indicator to its corner, so the
15
- * thing being counted stays the thing in the layout: a badge is an annotation,
16
- * not a container that happens to hold an icon.
17
- *
18
- * The indicator says how many, and none is none. A `value` that reads as zero
19
- * shows no mark at all, an absent one shows a bare dot, and any other value
20
- * shows the value. `max` caps the top: past it the indicator reads `{max}+`
21
- * rather than growing to fit, since a corner mark that widens with its value
22
- * pushes the layout around.
23
- *
24
- * **A dot needs a label.** Without one it is a colored circle that announces
25
- * nothing, which tells a sighted reader there is something new and tells
26
- * everyone else there is not. The label is required in that form, and the
27
- * indicator is hidden from assistive technology when a number already says it.
28
- *
29
- * @since 0.1.0
30
- * @status stable
31
- * @category markers
32
- * @aliases Count, Indicator, Pip
33
- * @documentation https://helix.fusion.dev/components/badge
34
- *
35
- * @slot - The element being decorated.
36
- *
37
- * @csspart indicator - The count, or the dot when there is no value.
38
- */
39
10
  let HelixHeadlessBadge = class HelixHeadlessBadge extends HelixElement {
40
11
  constructor() {
41
12
  super(...arguments);
42
- /**
43
- * Accessible name.
44
- *
45
- * Required for a dot, which has no text to read.
46
- */
47
13
  this.label = '';
48
- /**
49
- * The count or short text to show.
50
- *
51
- * Absent shows a dot, zero shows nothing.
52
- */
53
14
  this.value = '';
54
15
  }
55
- /** Whether the value counts nothing, in which case there is no mark. */
56
16
  get blank() {
57
17
  return this.value.trim() !== '' && Number(this.value) === 0;
58
18
  }
59
- /** What the indicator reads, once the cap has been applied. */
60
19
  get text() {
61
20
  const count = Number(this.value);
62
21
  if (this.max !== undefined && Number.isFinite(count) && count > this.max) {
@@ -67,33 +26,7 @@ let HelixHeadlessBadge = class HelixHeadlessBadge extends HelixElement {
67
26
  render() {
68
27
  const blank = this.blank;
69
28
  const text = this.text;
70
- /* A mark with text is content and is announced; a dot is decoration and is
71
- not.
72
- *
73
- * It must not ask for a `label` as well, or a badge showing a count and no
74
- * label is `aria-hidden` and the number it exists to show reaches nobody
75
- * using a screen reader. The number is its own name where none is given,
76
- * which is why `aria-label` is conditional on the label rather than on this:
77
- * a `4` announces as "4" from the text, and `label="4 unread"` says the
78
- * better thing.
79
- *
80
- * A dot with no label still says nothing, which is the deliberate half: a
81
- * dot means "something here" and cannot say what, so naming it is the
82
- * consumer's to do and silence is better than "badge". */
83
29
  const announced = !blank && (text !== '' || this.label !== '');
84
- // The indicator holds the text and nothing else, so an absent value leaves
85
- // it empty and `[part="indicator"]:empty` is what draws the dot. Lit's
86
- // marker comments do not affect `:empty`, but a whitespace text node does,
87
- // which is why the tags close the way they do.
88
- //
89
- // It stays mounted when there is nothing to show, so the styled tier can
90
- // transition it out rather than having it vanish between frames. Both
91
- // markers are on the shadow content, not the host, so they cost no
92
- // attribute a consumer's server markup would have to match.
93
- //
94
- // `single` is what lets a one-character mark be a circle. Whether the text
95
- // fits inside the square is a fact about the string, and CSS cannot count
96
- // characters.
97
30
  return html `<slot></slot>
98
31
  <span
99
32
  part="indicator"
@@ -8,102 +8,26 @@ import { customElement, drawTooltip, extent, HelixElement, linear, linePath, Mix
8
8
  import { html, nothing, svg } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import '../../tooltip/index.js';
11
- /** The drawing's own coordinates. Height only: the width is the element's. */
12
11
  const HEIGHT = 100;
13
- /**
14
- * @summary Categories as columns, read from a table.
15
- *
16
- * @description
17
- * Two decisions are made here rather than left to a consumer.
18
- *
19
- * **It measures from zero and there is no property to change that.** A bar's
20
- * length *is* its value, so a baseline anywhere else makes the lengths
21
- * proportional to nothing. A reader who wants the differences between values
22
- * rather than the values should be drawing a line, which claims only a shape.
23
- *
24
- * **The labels are HTML beside the drawing rather than `<text>` inside it.** An
25
- * `<svg>` that fills its box scales everything in it, including type: the same
26
- * `font-size` comes out at 10px in a narrow chart and 43px in a wide one, and
27
- * there is no non-scaling-font to answer it the way `vector-effect` answers
28
- * strokes. So the columns are drawn and the words are written around them.
29
- *
30
- * The table is the accessible representation and this drawing is `aria-hidden`.
31
- * A reader who wants the numbers gets the numbers, not a described picture of
32
- * them.
33
- *
34
- * The custom properties below are written by this component and read by whoever
35
- * draws it. A consumer sets none of them: they carry a number this element
36
- * computed and CSS cannot.
37
- *
38
- * @since 0.1.0
39
- * @status experimental
40
- * @category charts
41
- * @aliases Column Chart, Categorical Chart
42
- * @documentation https://helix.fusion.dev/components/bar-chart
43
- *
44
- * @csspart axis - One scale's numbers, drawn only when `secondary` splits them.
45
- * @csspart band - The slot the pointer is resting in.
46
- * @csspart bar - One column.
47
- * @csspart bar-wash-end - The far stop of a bar's gradient, where one is used.
48
- * @csspart bar-wash-start - The stop of a bar's gradient at its base.
49
- * @csspart baseline - The rule the columns stand on.
50
- * @csspart gridline - One rule across the plot, under `gridlines`.
51
- * @csspart group - One category's columns, together.
52
- * @csspart label - One category's name.
53
- * @csspart labels - The row of category names.
54
- * @csspart line - A series drawn as a line rather than as columns, under `lines`.
55
- * @csspart mark - The invisible point the tooltip is anchored to.
56
- * @csspart plot - The drawing.
57
- * @csspart svg - The drawing surface the bars and lines sit on.
58
- * @csspart tick - One number on an axis.
59
- * @csspart tooltip - The bubble, forwarded from `Tooltip`.
60
- * @csspart tooltip-anchor - The `Tooltip` element itself.
61
- * @csspart tooltip-cell - One column's name and value inside the tooltip.
62
- * @csspart tooltip-label - The reading's own name inside the tooltip.
63
- * @csspart tooltip-name - A column's name inside the tooltip.
64
- * @csspart tooltip-value - A column's value inside the tooltip.
65
- *
66
- * @cssproperty --bar-chart-axis - The color of a single-series axis, taken from that series.
67
- * @cssproperty --bar-chart-secondary - The color of the secondary axis when one series owns it. It resolves a `--helix-color-series-*` token, so on the headless tier alone it names something that is not there. #138.
68
- * @cssproperty --chart-bar-wash - The gradient a bar would take, named per bar so the
69
- * styled tier can point `fill` at it. Drawn whatever the treatment and used only where
70
- * `wash="gradient"` asks for it.
71
- */
72
12
  let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource(HelixElement) {
73
- /** The series names on the second scale, lowercased and trimmed. */
74
13
  get scaled() {
75
14
  return new Set((this.secondary ?? '')
76
15
  .split(/\s+/)
77
16
  .map((name) => name.trim().toLowerCase())
78
17
  .filter(Boolean));
79
18
  }
80
- /** The series names drawn as a line, lowercased and trimmed for matching. */
81
19
  get lined() {
82
20
  return new Set((this.lines ?? '')
83
21
  .split(/\s+/)
84
22
  .map((name) => name.trim().toLowerCase())
85
23
  .filter(Boolean));
86
24
  }
87
- /** Whether the levels are ruled, which is every value but `category`. */
88
25
  get rulesValue() {
89
26
  return this.gridlines !== undefined && this.gridlines !== 'category';
90
27
  }
91
- /** Whether each category is ruled, which only two values ask for. */
92
28
  get rulesCategory() {
93
29
  return this.gridlines === 'both' || this.gridlines === 'category';
94
30
  }
95
- /**
96
- * Tells the pointer which way the categories run.
97
- *
98
- * In `willUpdate` because `direction` is a property a consumer can change at
99
- * any time, and the controller resolves a reading from whichever axis it was
100
- * last told about: set once at connect, a chart turned sideways after that
101
- * goes on reading x and answers with the same row wherever the pointer goes.
102
- *
103
- * `willUpdate` and not `updated`, because SSR calls the first and not the
104
- * second, and a value the server and the client disagree about is worse than
105
- * one neither has.
106
- */
107
31
  willUpdate(changed) {
108
32
  super.willUpdate(changed);
109
33
  this.pointer.axis = this.direction === 'horizontal' ? 'block' : 'inline';
@@ -114,33 +38,14 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
114
38
  if (categories.length === 0) {
115
39
  return html `<div id="helix-chart-plot" part="plot"></div>`;
116
40
  }
117
- /* Zero at both ends, so a series that goes negative gets a baseline inside
118
- the box and its columns hang below it. `extent` over every series at once
119
- rather than per series, because two series drawn to different scales are
120
- two charts sharing a picture. */
121
41
  const scaled = this.scaled;
122
42
  const own = (one) => scaled.has(one.name.trim().toLowerCase());
123
- /* Two extents where a series asked for its own, and the primary is taken
124
- over the series that did *not*: measuring the primary over everything
125
- would stretch it to hold numbers no column is drawn against, which is the
126
- one thing splitting the axes was meant to stop. Empty on either side
127
- falls back to the whole set, so `secondary` naming a column that is not
128
- there leaves a chart rather than a blank. */
129
43
  const values = series.flatMap((one) => one.points.map((point) => point.value));
130
44
  const on = (which) => {
131
45
  const found = series.filter(which).flatMap((one) => one.points.map((point) => point.value));
132
46
  return found.length > 0 ? found : values.length > 0 ? values : [0];
133
47
  };
134
48
  const [low, high] = extent(scaled.size === 0 ? on(() => true) : on((one) => !own(one)));
135
- /* **The range is inverted for columns and not for bars**, which is the
136
- whole of what makes the two directions different arithmetic rather than
137
- one drawing turned on its side. SVG counts down from the top, so a column
138
- growing upward needs `[HEIGHT, 0]`; a bar growing rightward is already
139
- going the way x counts, so it takes `[0, HEIGHT]`.
140
- *
141
- * Reusing the inverted scale for both puts the longest bar at x=0 and every
142
- shorter one hard against the right edge, growing away from the baseline
143
- instead of out of it. */
144
49
  const domain = [Math.min(0, low), Math.max(0, high)];
145
50
  const range = this.direction === 'horizontal' ? [0, HEIGHT] : [HEIGHT, 0];
146
51
  const y = linear(domain, range);
@@ -149,88 +54,28 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
149
54
  const [second, top] = extent(scaled.size > 0 ? on(own) : [0]);
150
55
  const other = [Math.min(0, second), Math.max(0, top)];
151
56
  const z = scaled.size > 0 ? linear(other, range) : undefined;
152
- /* Widest number on either scale, in characters, so the axis column is the
153
- size of its own contents.
154
- *
155
- * The ticks are absolutely positioned, which is what puts each one on the
156
- * level it names and also what stops any of them contributing a width: an
157
- * axis of six numbers laid out that way is an empty box. The count is known
158
- * here and unknowable to a selector, so it is written as a length and the
159
- stylesheet reads it. */
160
57
  const widest = z === undefined
161
58
  ? 0
162
59
  : Math.max(...[...ticks(domain), ...ticks(other)].map((level) => level.toLocaleString().length));
163
- /* The second axis wears its series' color when exactly one series is on it,
164
- which is the case worth helping and the only one a single color can
165
- describe honestly: with two sharing it, painting the axis either of their
166
- colors says it belongs to that one.
167
- *
168
- **Written onto the axis and not onto the plot.** The axis is the plot's
169
- sibling, being a column of the host grid, and a custom property inherits
170
- down and never sideways: written onto the plot, both axes come out the
171
- neutral. */
172
60
  const lone = series.filter(own);
173
61
  const tint = lone.length === 1 && lone[0] !== undefined
174
62
  ? `--bar-chart-secondary: var(--chart-series-${seriesIndex(series.indexOf(lone[0]))})`
175
63
  : undefined;
176
64
  return html `${z === undefined ? nothing : this.renderAxis(y, domain, 'primary', sideways, widest)}${this.renderPlot(series, categories, y, domain, floor, z)}${z === undefined ? nothing : this.renderAxis(z, other, 'secondary', sideways, widest, tint)}${this.renderLabels(categories)}`;
177
65
  }
178
- /**
179
- * The columns themselves, behind a seam the styled tier can move.
180
- *
181
- * `aria-hidden`, because the table it was read from is the readable version
182
- * and a screen reader hearing both hears the same numbers twice.
183
- */
184
66
  renderPlot(series, categories, y, domain, floor, z) {
185
67
  const sideways = this.direction === 'horizontal';
186
68
  const slot = 100 / categories.length;
187
- /* Split first, because the columns share the slot between themselves and a
188
- line takes none of it. Sized by every series instead, a chart of two
189
- columns and one line draws both columns two thirds as wide and leaves a
190
- third of every slot empty, for a mark that is not in the slot at all. */
191
69
  const lined = this.lined;
192
- /* Kept with the index it has among *all* the series, because a column needs
193
- two different numbers and they stopped being the same the moment one
194
- series could be a line. `depth` is its place in the slot, so the columns
195
- stand shoulder to shoulder with no gap where a line was; `tone` is its
196
- place in the palette, so it keeps the color the legend gives it.
197
- *
198
- Passing the first for both moves a series into its neighbor's color as
199
- well as its slot: with `Target` drawn as a line, a chart of four series
200
- paints three columns in the first three colors and the line in the third
201
- one as well. */
202
70
  const scaled = this.scaled;
203
71
  const own = (one) => scaled.has(one.name.trim().toLowerCase());
204
- /* The scale a series is measured against, resolved once per series rather
205
- than per point. `z` is absent unless `secondary` named something, so the
206
- ordinary chart takes exactly the path it took before this existed. */
207
72
  const against = (one) => z !== undefined && own(one) ? z : y;
208
73
  const columns = series
209
74
  .map((one, index) => ({ one, tone: index }))
210
75
  .filter(({ one }) => !lined.has(one.name.trim().toLowerCase()));
211
- /* **The stride is what the columns share and the thickness is what they
212
- draw.** Back to back, two columns meet on one coordinate and each one's
213
- edge is the other's: an outlined chart then shows a single line between
214
- them rather than two, and a bar looks as though it were missing a side.
215
- A gap is also just the right way to draw a group, which is why the split
216
- stays whether anything is outlined or not.
217
-
218
- Centered in the stride rather than flush against it, so the gap between two
219
- columns is the same size everywhere and the group sits where it did. One
220
- series has no neighbor to be separated from and keeps the full stride. */
221
76
  const stride = (slot * 0.72) / Math.max(columns.length, 1);
222
77
  const gap = columns.length > 1 ? stride * 0.08 : 0;
223
78
  const thickness = stride - gap;
224
- /* **The geometry is computed for the axis it is drawn on, not rotated onto
225
- it.** Turning the whole plot with `rotate: 90deg` is the shorter CSS and
226
- does not work: a rotation is about the element's own center, so the
227
- drawing leaves its grid cell entirely while the labels, which are a
228
- separate row, stay where they were. What that produces is one column
229
- floating beside a list of names.
230
- *
231
- * Swapping the two axes here costs one branch and keeps every other fact
232
- * about the chart the same. `value` runs along x when the bars are
233
- * sideways and along y otherwise; `category` takes the other one. */
234
79
  const bar = (value, index, depth, tone, scale) => {
235
80
  const at = scale(value);
236
81
  const along = Math.min(floor, at);
@@ -266,18 +111,7 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
266
111
  viewBox="0 0 100 ${HEIGHT}"
267
112
  xmlns="http://www.w3.org/2000/svg"
268
113
  >
269
- ${
270
- /* **One gradient per series, rendered whether or not anything points
271
- at it.** A `<defs>` entry draws nothing on its own, and this tier's
272
- business is elements while which fill a bar takes is the styled
273
- tier's: `wash="gradient"` there switches `fill` to
274
- `--chart-bar-wash` and this is what it would find.
275
-
276
- Colored through the series property rather than an attribute, so
277
- the stops read a token this tier never names. The id resolves
278
- within the shadow root, so two charts on a page carrying the same
279
- ids stay independent. */
280
- svg `<defs>
114
+ ${svg `<defs>
281
115
  ${series.map((_, index) => svg `<linearGradient
282
116
  id="helix-bar-wash-${seriesIndex(index)}"
283
117
  style="--chart-series: var(--chart-series-${seriesIndex(index)})"
@@ -295,52 +129,16 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
295
129
  ? svg `<line part="baseline" x1=${floor} x2=${floor} y1="0" y2="100" />`
296
130
  : svg `<line part="baseline" x1="0" x2="100" y1=${floor} y2=${floor} />`}
297
131
  ${this.renderBand(categories.length, sideways)}
298
- ${categories.map((_, index) =>
299
- /* The group the tooltip is about says so, and the stylesheet fades
300
- the rest. A tooltip over a chart of five categories otherwise
301
- makes a reader match its heading against the row of names to find
302
- out which columns it means. */
303
- svg `<g data-active=${this.pointer.at === index ? '' : nothing} part="group">
132
+ ${categories.map((_, index) => svg `<g data-active=${this.pointer.at === index ? '' : nothing} part="group">
304
133
  ${columns.map(({ one, tone }, depth) => bar(one.points[index]?.value ?? 0, index, depth, tone, against(one)))}
305
134
  </g>`)}
306
- ${
307
- /* After the groups, so a line is never drawn under the columns it is
308
- meant to be read against. Outside them too: a group is one
309
- category and a line crosses all of them, so it belongs to the plot
310
- rather than to any one slot. */
311
- series.map((one) => lined.has(one.name.trim().toLowerCase())
135
+ ${series.map((one) => lined.has(one.name.trim().toLowerCase())
312
136
  ? this.renderLine(one, series.indexOf(one), slot, against(one), sideways)
313
137
  : nothing)}
314
138
  </svg>
315
139
  ${this.renderTooltip()}
316
140
  </div>`;
317
141
  }
318
- /**
319
- * The numbers along one scale, drawn only where there are two of them.
320
- *
321
- * **A chart with one scale renders no axis, on purpose.** Columns measured
322
- * against each other need no numbers to be compared, and a row of them beside
323
- * a small chart is furniture that makes the drawing smaller for no reading a
324
- * reader was going to do. With two scales that argument inverts: a reader
325
- * cannot tell there are two, cannot tell which series is on which, and cannot
326
- * recover either from the picture. So this appears with `secondary` and never
327
- * without it.
328
- *
329
- * **Beside the plot and not inside it.** Inside, the numbers are drawn over
330
- * the drawing, and a horizontal chart shows it immediately: its value axis
331
- * runs along the bottom, which is where the last row of bars is. The vertical
332
- * case has the same fault and enough slack to hide it. The plot is also the
333
- * pointer's catchment and the tooltip's containing block, both measured from
334
- * its box, so padding it to make room would move every reading half an axis
335
- * to the right.
336
- *
337
- * A column of the host grid instead, which the stylesheet grows only when
338
- * `secondary` is set. That is what the property reflects for.
339
- *
340
- * The primary hugs the edge the drawing starts at and the secondary the edge
341
- * it ends at: down the two sides for columns, along the bottom and top for
342
- * bars, which is where each axis is when the chart is turned.
343
- */
344
142
  renderAxis(scale, domain, which, sideways, widest, tint) {
345
143
  const sizing = sideways ? undefined : `--bar-chart-axis: ${widest}ch`;
346
144
  return html `<div
@@ -356,25 +154,10 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
356
154
  >`)}
357
155
  </div>`;
358
156
  }
359
- /**
360
- * The grid, behind everything, or nothing at all.
361
- *
362
- * Across the measured axis, which turns with `direction`: a rule belongs to
363
- * the axis the bars are measured on and not the one they are arranged on, so
364
- * columns get horizontal rules and rows get vertical ones. Getting that
365
- * backwards draws a grid that divides the categories, which are already
366
- * divided by being separate bars.
367
- *
368
- * Drawn before the baseline, so where a gridline and zero coincide the
369
- * baseline is what shows: zero is a fact about the data and a gridline is a
370
- * ruler.
371
- */
372
157
  renderGridlines(y, domain, sideways, count) {
373
158
  if (this.gridlines === undefined) {
374
159
  return nothing;
375
160
  }
376
- /* Through the middle of each group, which is where the column stands, and
377
- not on the boundary between two of them. */
378
161
  const slot = 100 / Math.max(count, 1);
379
162
  const categories = this.rulesCategory
380
163
  ? Array.from({ length: count }, (_, index) => (index + 0.5) * slot).map((at) => sideways
@@ -388,20 +171,6 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
388
171
  : [];
389
172
  return svg `${categories}${levels}`;
390
173
  }
391
- /**
392
- * The slot the pointer is resting in, drawn behind the columns.
393
- *
394
- * **The honest version of the fade.** A group under the pointer already sets
395
- * `data-active` and the stylesheet dims the others, which says which group is
396
- * meant by making four of five quieter: it reads as the chart losing
397
- * confidence rather than as one column being pointed at. A band says the same
398
- * thing by lighting the region the pointer is actually in, and the region is
399
- * the whole slot, because that is the catchment the pointer resolves against.
400
- *
401
- * Both are kept. The fade separates the active group from its neighbors and
402
- * the band says where the neighborhood is, and on a chart of two categories
403
- * the fade alone is ambiguous about which half is lit.
404
- */
405
174
  renderBand(count, sideways) {
406
175
  const at = this.pointer.at;
407
176
  if (at < 0 || count === 0) {
@@ -417,31 +186,12 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
417
186
  ${categories.map((one) => html `<span part="label">${one}</span>`)}
418
187
  </div>`;
419
188
  }
420
- /**
421
- * One series drawn as a line across the categories, behind a seam.
422
- *
423
- * Through the middle of each slot, which is where the group stands, so a
424
- * point sits over its own columns rather than over the gap between two of
425
- * them. The same arithmetic the category gridlines use, and for the same
426
- * reason: the reading is the thing, the boundary is only where the pointer
427
- * changes its mind.
428
- *
429
- * `data-series` keeps the index the series has among *all* of them, not among
430
- * the lines, so the color a line takes is the color the legend gives it and
431
- * moving a column between marks does not repaint the chart.
432
- */
433
189
  renderLine(one, depth, slot, y, sideways) {
434
190
  const points = one.points.map((point, index) => {
435
191
  const across = index * slot + slot / 2;
436
192
  const along = y(point.value);
437
193
  return sideways ? [along, across] : [across, along];
438
194
  });
439
- /* Straight between the category centers, and no `curve` property to change
440
- it. A bar chart's other axis is categorical: there is nothing between
441
- Gateway and Billing, so a curve through them draws values at positions
442
- that do not exist and bulges past the readings on the way. `LineChart`
443
- offers the choice because its axis is continuous and the shape between
444
- two readings is a claim somebody may legitimately want to make. */
445
195
  return svg `<path
446
196
  d=${linePath(points, 'straight')}
447
197
  data-series=${seriesIndex(depth)}
@@ -449,22 +199,12 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
449
199
  style="--chart-series: var(--chart-series-${seriesIndex(depth)})"
450
200
  />`;
451
201
  }
452
- /**
453
- * What a pointer resting on a reading shows.
454
- *
455
- * A seam over `drawTooltip`, which holds the markup every chart shares. The
456
- * usual reason to override is arrangement: every cell of the row is already
457
- * there, including the columns nothing was plotted from.
458
- */
459
202
  renderTooltip() {
460
203
  const at = this.pointer.at;
461
204
  const series = this.series;
462
205
  if (at < 0 || series.length === 0) {
463
206
  return drawTooltip(this.sourceRows, at, this.tag('tooltip'));
464
207
  }
465
- /* Off the far end of the tallest bar in the group, so the box never covers
466
- the columns it is describing. Horizontal bars grow rightward, so the far
467
- end is along x and the arrow hangs off the box's start edge. */
468
208
  const values = series.map((one) => one.points[at]?.value ?? 0);
469
209
  const all = series.flatMap((one) => one.points.map((point) => point.value));
470
210
  const [low, high] = extent(all.length > 0 ? all : [0]);
@@ -7,58 +7,15 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement, 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 passage set off from the prose around it.
12
- *
13
- * @description
14
- * Renders a real `<blockquote>` with a `<footer>` for the attribution, which is
15
- * the structure the HTML spec describes for exactly this: the attribution is
16
- * about the quotation rather than part of it, and putting it inside the quoted
17
- * text claims the source said their own name.
18
- *
19
- * A quotation run inside a sentence is a `Quote`, which is a different element
20
- * because it is a different job.
21
- *
22
- * @since 0.1.0
23
- * @status stable
24
- * @category typography
25
- * @aliases Pullquote, Citation, Excerpt
26
- * @documentation https://helix.fusion.dev/components/blockquote
27
- *
28
- * @slot - The quoted passage.
29
- * @slot attribution - Who said it, and where.
30
- *
31
- * @csspart attribution - The footer carrying the source.
32
- * @csspart blockquote - The quotation.
33
- */
34
10
  let HelixHeadlessBlockquote = class HelixHeadlessBlockquote extends HelixElement {
35
11
  constructor() {
36
12
  super(...arguments);
37
- /**
38
- * URL identifying where the passage came from.
39
- */
40
13
  this.cite = '';
41
14
  }
42
- /**
43
- * Says on the footer whether there is an attribution in it.
44
- *
45
- * A `<footer>` with nothing in it is still a block in the quote's flow and
46
- * still takes the container's gap, so every blockquote written without an
47
- * attribution reserved a full space below itself. There is no selector that
48
- * can ask, `slot:empty` being about a slot's fallback rather than its
49
- * assigned nodes, so the component asks and says so on the footer inside this
50
- * shadow root: an attribute on the host is a change to markup the consumer
51
- * rendered, which React reports on hydration.
52
- */
53
15
  markEmpty() {
54
16
  const footer = this.renderRoot?.querySelector('[part="attribution"]');
55
17
  const empty = !slotFilled(this.renderRoot?.querySelector('slot[name="attribution"]'));
56
18
  footer?.toggleAttribute('data-empty', empty);
57
- /* `hidden` beside the mirror, because `data-empty` is a hook the styled
58
- sheet reads and this tier has no sheet: an empty region left in the box
59
- tree is a gap a consumer cannot close without knowing to look for it. The
60
- styled rule stays, because that sheet sets `display` on this part and so
61
- outranks the UA one. */
62
19
  footer?.toggleAttribute('hidden', empty);
63
20
  }
64
21
  updated(changed) {
@@ -66,9 +23,6 @@ let HelixHeadlessBlockquote = class HelixHeadlessBlockquote extends HelixElement
66
23
  this.markEmpty();
67
24
  }
68
25
  render() {
69
- // The footer is a bare slot inside a real `<footer>`: the element is the
70
- // semantics and cannot be omitted, while an unfilled slot inside it is
71
- // `display: contents` and generates no box.
72
26
  return html `<blockquote part="blockquote" cite=${this.cite || nothing}>
73
27
  <slot></slot>
74
28
  <footer part="attribution"><slot name="attribution"></slot></footer>
@@ -9,85 +9,18 @@ import { caretRight } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property } from 'lit/decorators.js';
11
11
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
- /**
13
- * @summary One step in a breadcrumb trail.
14
- *
15
- * @description
16
- * Contributes the separator, the list-item role, and the link itself where one
17
- * is wanted.
18
- *
19
- * **`href` makes the crumb the link.** A router cannot reach an anchor inside a
20
- * shadow root, and the consumer restores the soft navigation by intercepting the
21
- * click on the way out, which is what `Tree.Item` and `List.Item` both do. What
22
- * the anchor buys is the whole crumb as a target, Enter, middle click, the
23
- * status bar, and a trail that works before any JavaScript runs.
24
- *
25
- * **And the parent needs it.** A trail that collapses has to rebuild its hidden
26
- * crumbs somewhere else, which means reading each one's destination. From a
27
- * property that is a read; from a consumer's slotted markup it is a hunt for an
28
- * anchor and a guess about which element is the link.
29
- *
30
- * A crumb with no `href` is unchanged: the consumer slots whatever they like,
31
- * including their own anchor, and this renders a plain `<span>` around it.
32
- *
33
- * Each item renders its own separator and hides it with `:host(:first-child)`,
34
- * so the trail needs no index and no coordination from the parent. `:host()`
35
- * takes a structural pseudo-class, unlike `:has()`, which it rejects.
36
- *
37
- * The current page is text, not a link. Linking a page to itself is a known
38
- * screen-reader annoyance, and `aria-current="page"` on the host is what
39
- * conveys position.
40
- *
41
- * @since 0.1.0
42
- * @status stable
43
- * @aliases Crumb
44
- * @documentation https://helix.fusion.dev/components/breadcrumbs
45
- *
46
- * @slot - The step's label. Without an `href` this may be the consumer's own
47
- * anchor, styled with `data-link="quiet"`, since a slotted anchor belongs to the
48
- * outer tree and cannot be reached from this component's styles.
49
- * @slot end - After it, for a count or a mark.
50
- * @slot separator - Replaces the chevron shown before this item.
51
- * @slot start - Before the label, for an icon.
52
- *
53
- * @csspart link - The crumb itself, which is an `<a>` where `href` is set.
54
- * @csspart separator - The separator preceding the item.
55
- * @csspart separator-mark - The glyph between this crumb and the next, where none is slotted.
56
- */
57
12
  let HelixHeadlessBreadcrumb = class HelixHeadlessBreadcrumb extends HelixElement {
58
13
  constructor() {
59
14
  super(...arguments);
60
- /**
61
- * Whether this item is the page the reader is on.
62
- */
63
15
  this.current = false;
64
- /**
65
- * What the link declares about where it goes.
66
- *
67
- * The consumer's decision.
68
- */
69
16
  this.rel = '';
70
- /**
71
- * Where the link opens, for the rare crumb that leaves the site.
72
- */
73
17
  this.target = '';
74
18
  }
75
19
  willUpdate(changed) {
76
20
  super.willUpdate(changed);
77
- // On the host, because the item is an `<ol>` child in the flat tree and the
78
- // role has to be there for the list to have items. In `willUpdate` rather
79
- // than `connectedCallback`, which the server never calls.
80
21
  hostAria(this.internals, { role: 'listitem' });
81
22
  this.internals.ariaCurrent = this.current ? 'page' : null;
82
23
  }
83
- /**
84
- * The mark between one crumb and the next.
85
- *
86
- * The chevron is the slot's own fallback, so replacing it is a matter of
87
- * filling the slot and costs the consumer no way to opt out of the default.
88
- * The seam answers the other half: a tier drawing its own separator gets it
89
- * without reimplementing the crumb, its link and its current-page branch.
90
- */
91
24
  renderSeparator() {
92
25
  return html `<slot name="separator"
93
26
  ><svg part="separator-mark" fill="currentColor" viewBox=${caretRight.viewBox}>
@@ -96,12 +29,7 @@ let HelixHeadlessBreadcrumb = class HelixHeadlessBreadcrumb extends HelixElement
96
29
  >`;
97
30
  }
98
31
  render() {
99
- /* The label and whatever flanks it, which is the same content whether or
100
- not it is a link. Built once so the two branches below cannot drift. */
101
32
  const label = html `<slot name="start"></slot><slot></slot><slot name="end"></slot>`;
102
- /* The current page is text, not a link, whatever it was given, so `current`
103
- wins over `href` rather than the two contradicting each other in the
104
- markup. */
105
33
  const linked = (this.href ?? '') !== '' && !this.current;
106
34
  return html `<span part="separator" aria-hidden="true"
107
35
  >${this.renderSeparator()}</span