@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
@@ -9,203 +9,20 @@ import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import '../../tooltip/index.js';
11
11
  import { fits, numbers, toneOf, words } from './buckets.js';
12
- /**
13
- * @summary A strip of cells colored by how each one went: uptime, contributions, activity.
14
- *
15
- * @description
16
- * **One family covers both readings, and the split people expect does not
17
- * exist.** The argument for two is that a contribution graph needs a continuous
18
- * ramp where uptime needs a category. GitHub's contribution graph is five
19
- * discrete levels rather than a gradient, so a cell that takes a tone covers
20
- * both: `rows="1"` is a status strip, `rows="7"` is a contribution graph, and
21
- * heatmap is the name of that.
22
- *
23
- * **What it cannot be is a bar chart with flat values**, and the data model is
24
- * what closes that rather than taste: chart color is per *series* everywhere
25
- * else here, `--chart-series-1` through `-15`, so ninety differently colored
26
- * cells would be ninety series.
27
- *
28
- * The data is a real `<table>` named by `for`, the same convention every chart
29
- * in this library uses, which also answers the accessibility question without
30
- * inventing anything: **the table is the accessible representation**, it is
31
- * already in the markup, and the drawing is a picture of it. Hide it with
32
- * `data-hidden-visually` and the reading is still there for anyone who needs it.
33
- *
34
- * A cell takes its tone from its own `data-tone`, or from `thresholds` and
35
- * `tones` bucketing its value. The first is what a status page wants and the
36
- * second is what a contribution graph wants; the cell's own wins, so a page that
37
- * already knows a day was degraded does not have to encode that as a number for
38
- * this component to decode again.
39
- *
40
- * **A tone is a name this tier never resolves.** `tone="success"` writes
41
- * `--heatmap-tone: var(--heatmap-tone-success)` onto the cell and stops there,
42
- * so what a name is worth is the answering tier's to say: `helix/styled` points
43
- * the semantic names at the theme's status colors and the numbered ones at a
44
- * ramp, and a consumer on `headless` declares the same names as whatever they
45
- * like. Reaching for `var(--helix-color-${tone})` here is the dependency the
46
- * tiers exist to prevent, and it fails loudly rather than quietly: off-tier that
47
- * property is not undefined, it is *invalid*, so
48
- * `background-color: var(--heatmap-tone, …)` declines its own fallback and every
49
- * cell comes out transparent, leaving a blank page under a row of footer text.
50
- * `shared/chart/series.css` records the same fault in Bar Chart.
51
- *
52
- * **On a narrow container it draws fewer cells rather than thinner ones.**
53
- * Ninety cells in 320 pixels is three and a half pixels each, which is a smear
54
- * and not a strip. The cells it drops are the oldest, and it reflects `visible`
55
- * and fires `helix-resize` when the number changes, because **a summary saying
56
- * "99.2% over 90 days" above a strip drawing 45 of them is telling the reader
57
- * something false about the picture in front of them** and nothing about the
58
- * picture says so.
59
- *
60
- * @since 0.1.0
61
- * @status experimental
62
- * @category charts
63
- * @aliases Contribution graph, Uptime strip, Calendar heatmap, Activity graph
64
- * @documentation https://helix.fusion.dev/components/heatmap
65
- *
66
- * @slot end - Below the strip, at the trailing edge: usually the newest date.
67
- * @slot start - Below the strip, at the leading edge: usually the oldest date.
68
- * @slot summary - Below the strip, in the middle: the reading over the range drawn.
69
- *
70
- * @event {HelixResizeEvent} helix-resize - Fired when the number of cells drawn changes, carrying that number.
71
- *
72
- * @csspart cell - One reading.
73
- * @csspart empty - Shown when the table has no readings in it.
74
- * @csspart end - The trailing slot's box.
75
- * @csspart footer - The three-slot row under the strip.
76
- * @csspart mark - The invisible point the tooltip is anchored to.
77
- * @csspart start - The leading slot's box.
78
- * @csspart strip - The grid of cells.
79
- * @csspart summary - The middle slot's box.
80
- * @csspart tooltip - The bubble, forwarded from `Tooltip`.
81
- * @csspart tooltip-anchor - The `Tooltip` element itself.
82
- * @csspart tooltip-cell - One column's name and value inside the tooltip.
83
- * @csspart tooltip-label - The reading's own name inside the tooltip.
84
- * @csspart tooltip-name - A column's name inside the tooltip.
85
- * @csspart tooltip-value - A column's value inside the tooltip.
86
- *
87
- * @cssproperty --heatmap-cell-height - How tall a cell is. A row of a tall thin cell is what a status page draws.
88
- * @cssproperty --heatmap-cell-inline-size - How wide a cell is. The strip fits as many as the container allows.
89
- * @cssproperty --heatmap-cell-radius - How far a cell's corners are rounded.
90
- * @cssproperty --heatmap-cell-size - The size a cell wants on both axes, where neither is given its own.
91
- * @cssproperty --heatmap-gap - Space between cells. Zero is a continuous field rather than a grid of tiles.
92
- * @cssproperty --heatmap-rows - How many rows the strip lays out. Written by the component from the data, read by the tier that draws it.
93
- * @cssproperty --heatmap-tone - The tone of the cell this is set on. Written by the component, read by the sheet that paints.
94
- * @cssproperty --heatmap-tone-1 - The quietest step of the magnitude ramp, reached by `tones="1 …"`.
95
- * @cssproperty --heatmap-tone-2 - The second step of the magnitude ramp.
96
- * @cssproperty --heatmap-tone-3 - The third step of the magnitude ramp.
97
- * @cssproperty --heatmap-tone-4 - The fourth step of the magnitude ramp.
98
- * @cssproperty --heatmap-tone-5 - The loudest step of the magnitude ramp.
99
- * @cssproperty --heatmap-tone-accent - What `accent` is worth.
100
- * @cssproperty --heatmap-tone-danger - What `danger` is worth.
101
- * @cssproperty --heatmap-tone-info - What `info` is worth.
102
- * @cssproperty --heatmap-tone-muted - What `muted` is worth: the quiet ground, for a bucket that means nothing happened.
103
- * @cssproperty --heatmap-tone-primary - What `primary` is worth.
104
- * @cssproperty --heatmap-tone-success - What `success` is worth, as a name or a `data-tone`.
105
- * @cssproperty --heatmap-tone-warning - What `warning` is worth.
106
- */
107
12
  let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(HelixElement) {
108
13
  constructor() {
109
14
  super(...arguments);
110
- /**
111
- * Shown when the table has no readings in it.
112
- */
113
15
  this.emptyText = 'No readings';
114
- /**
115
- * Space between cells, in CSS pixels, for the same reason `min-cell` is a
116
- * number.
117
- */
118
16
  this.gap = 2;
119
- /**
120
- * The smallest a cell may get before the strip draws fewer of them, in CSS pixels.
121
- *
122
- * **Set this only to overrule what the strip actually draws.** The arithmetic
123
- * needs a width in pixels and the width is a length the stylesheet resolves,
124
- * so a number written here that disagrees with the sheet is a promise the
125
- * consumer has to keep by hand, and the one number a summary is told to trust
126
- * is the one it gets wrong.
127
- *
128
- * The strip measures itself, so `--heatmap-cell-inline-size` and `--heatmap-gap` are
129
- * enough on their own and these two are the estimate used until the first
130
- * measurement lands. Writing the attribute pins the value and stops the
131
- * measurement being consulted, which is what a container query or a transition
132
- * on the cell size wants, where what is drawn this frame is not what the
133
- * arithmetic should plan around.
134
- *
135
- * Zero turns the dropping off altogether, which is right for a strip in a
136
- * container that cannot get narrow.
137
- */
138
17
  this.minCell = 8;
139
- /**
140
- * How many rows the strip has.
141
- *
142
- * `1` is a status strip and `7` is a calendar week, which are the two readings
143
- * this covers. Cells fill down the column and then across, which is what a
144
- * calendar heatmap does: a week is a column, so the strip reads left to right
145
- * in weeks and top to bottom in days.
146
- */
147
18
  this.rows = 1;
148
- /**
149
- * Lower bounds for the tone buckets, in the order the tones are given.
150
- *
151
- * `thresholds="1 3 6 9"` with `tones="1 2 3 4"` puts 1 and 2 in the ramp's
152
- * first step, 3 to 5 in the second, 6 to 8 in the third, and 9 upward in the
153
- * fourth. A value below the first bound gets no tone at all: an empty day in a
154
- * contribution graph is a day with nothing in it, and painting it as the
155
- * lowest bucket claims a reading that does not exist.
156
- */
157
19
  this.thresholds = '';
158
- /**
159
- * Tone per bucket, in the same order as `thresholds`.
160
- *
161
- * A name, resolved as `--heatmap-tone-<name>` by whichever tier answers.
162
- * **`"1"` through `"5"` are a ramp and everything else is a category**, which
163
- * is the distinction a heatmap lives or dies on: magnitude has to be one hue
164
- * getting louder, because a reader compares two cells by weight and cannot
165
- * compare two hues at all. `tones="surface info accent success primary"` for a
166
- * commit count is five unrelated hues for five amounts, which is confetti with
167
- * no order in it.
168
- */
169
20
  this.tones = '';
170
- /**
171
- * The width last measured, or nothing before the first measurement.
172
- *
173
- * Undefined rather than zero, because the two mean different things and the
174
- * difference is a whole strip: unmeasured has to show everything, since a
175
- * first paint that shows one cell and then expands is a flicker on every load.
176
- */
177
21
  this.width = undefined;
178
- /**
179
- * The cell and gap the strip is drawing, in pixels, once it has been read.
180
- *
181
- * **The arithmetic's inputs are a stylesheet's decisions, so the honest way to
182
- * learn them is to ask the stylesheet.** A cell's size is a length that only
183
- * the cascade can resolve: it may be a `rem` against a root font size, a
184
- * percentage of a container, or whatever a container query chose this frame.
185
- * Every one of those is invisible to a number typed into an attribute, and
186
- * each of `--heatmap-cell-inline-size`, `--heatmap-cell-height` and `--heatmap-gap`
187
- * added one more way for the two to drift apart silently.
188
- *
189
- * Reading it back is not styling and does not breach the tier: this asks what
190
- * was decided rather than deciding, which is the same direction as the facts
191
- * this component writes out as custom properties, run backwards.
192
- */
193
22
  this.metrics = undefined;
194
- /**
195
- * Which drawn cell the pointer is on, as an index into `drawn`, or -1 for
196
- * none.
197
- */
198
23
  this.hovered = -1;
199
24
  this.observer = undefined;
200
25
  this.announced = undefined;
201
- /**
202
- * Which drawn cell the pointer is over, or -1.
203
- *
204
- * `pointerover` on the strip rather than a listener per cell: the event
205
- * bubbles, so one handler covers however many cells there are, and unlike
206
- * `mouseover` it is delivered for a pen and for the touch that follows a long
207
- * press.
208
- */
209
26
  this.onPointerOver = (event) => {
210
27
  const cell = event.target?.closest?.('[part="cell"]');
211
28
  const index = Number(cell?.getAttribute('data-index') ?? -1);
@@ -215,78 +32,24 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
215
32
  this.hovered = -1;
216
33
  };
217
34
  }
218
- /**
219
- * How many cells are actually drawn.
220
- *
221
- * **This is the number a summary has to agree with**, and the reason it is
222
- * published at all: a consumer's own summary is their markup and this element
223
- * cannot rewrite it, so the only honest arrangement is to hand over the count
224
- * and say when it changed.
225
- *
226
- * **A plain getter, and the attribute is written in `updated`, because
227
- * `@property({ reflect: true })` on a getter with no setter reflects once and
228
- * then goes stale.** Lit reflects a property that is in `changedProperties`,
229
- * and nothing puts a derived getter there after the first update: the
230
- * attribute would be right on load and wrong from the first resize, which is
231
- * the exact failure the count exists to prevent, arriving through the
232
- * mechanism meant to prevent it.
233
- */
234
35
  get visible() {
235
36
  return Math.min(this.points.length, this.room);
236
37
  }
237
- /**
238
- * Every reading in the table, oldest first, as the table is written.
239
- *
240
- * **Not `readings`, which the mixin already has and which means a count.**
241
- * Two members of the same name meaning a number and an array is the kind of
242
- * collision that type-checks in one direction and reads wrong in both.
243
- */
244
38
  get points() {
245
39
  return this.series[0]?.points ?? [];
246
40
  }
247
- /**
248
- * `rows` as a usable count, which is not always what the attribute says.
249
- *
250
- * One place rather than two: the fit arithmetic and the grid template both
251
- * need it, and a `rows="0"` or a `rows="2.5"` that reached one of them and not
252
- * the other would draw a strip whose columns and whose capacity disagreed.
253
- */
254
41
  get rowCount() {
255
42
  return Math.max(1, Math.floor(this.rows) || 1);
256
43
  }
257
- /**
258
- * The cell width the arithmetic should plan around, in pixels.
259
- *
260
- * The attribute wins where a consumer wrote one, because that is what writing
261
- * it is for; otherwise what the strip draws, and the declared default only
262
- * until the first measurement lands.
263
- */
264
44
  get cellPx() {
265
45
  return this.hasAttribute('min-cell') ? this.minCell : (this.metrics?.cell ?? this.minCell);
266
46
  }
267
- /** The gap the arithmetic should plan around, on the same terms as `cellPx`. */
268
47
  get gapPx() {
269
48
  return this.hasAttribute('gap') ? this.gap : (this.metrics?.gap ?? this.gap);
270
49
  }
271
- /** How many cells the container has room for, as a whole number of columns. */
272
50
  get room() {
273
51
  return fits(this.points.length, this.width ?? Number.NaN, this.cellPx, this.gapPx, this.rowCount);
274
52
  }
275
- /**
276
- * Reads back the cell and the gap the strip resolved.
277
- *
278
- * `gridAutoColumns` and `columnGap` rather than a cell's own box, and the
279
- * difference matters on the frame it matters: a cell exists only once some
280
- * have been drawn, and how many are drawn is what this is measuring *for*.
281
- * Asking the track instead breaks the circle, since a track is sized whether
282
- * or not anything sits in it. Neither property forces layout, so this stays a
283
- * style read on every update rather than a reflow.
284
- *
285
- * Anything that does not parse to a positive number leaves the estimate alone:
286
- * `columnGap` computes to `normal` where no gap is set, and a track given a
287
- * keyword or a `repeat()` has no single width to report. A wrong number here
288
- * is worse than no number, because it is the one the count is published from.
289
- */
290
53
  measure() {
291
54
  const strip = this.renderRoot?.querySelector('[part="strip"]');
292
55
  if (!strip || typeof getComputedStyle !== 'function') {
@@ -299,32 +62,16 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
299
62
  return;
300
63
  }
301
64
  if (this.metrics?.cell !== cell || this.metrics?.gap !== gap) {
302
- /* A new object rather than a mutation, because `@state` compares by
303
- identity and a mutated one schedules nothing. */
304
65
  this.metrics = { cell, gap };
305
66
  }
306
- /* **Whether there is anything between the cells, which CSS cannot ask.**
307
- A sheet can set the gap and cannot read it back, so a rule that wants to
308
- know whether a strip is a grid of tiles or one continuous field has no
309
- selector for it. The measurement is already here, so saying so costs
310
- nothing. It is a fact rather than a decision, which is the line a base
311
- component's custom properties are on the right side of too. */
312
67
  strip.toggleAttribute('data-spaced', gap > 0);
313
68
  }
314
- /**
315
- * The readings actually drawn, newest kept.
316
- *
317
- * The tail rather than the head: a status strip that drops the newest days to
318
- * keep the oldest is answering a question nobody asked.
319
- */
320
69
  get drawn() {
321
70
  const all = this.points;
322
71
  return all.slice(Math.max(0, all.length - this.room));
323
72
  }
324
73
  connectedCallback() {
325
74
  super.connectedCallback();
326
- /* Guarded because Lit SSR provides no `ResizeObserver`, and a component that
327
- measures itself to render has to render without the measurement first. */
328
75
  if (typeof ResizeObserver !== 'undefined') {
329
76
  this.observer = new ResizeObserver((entries) => {
330
77
  const measured = entries[0]?.contentRect.width;
@@ -344,27 +91,9 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
344
91
  super.willUpdate(changed);
345
92
  hostAria(this.internals, { label: this.label || 'Heatmap', role: 'img' });
346
93
  }
347
- /**
348
- * Says on the footer whether any of its three slots holds anything.
349
- *
350
- * **A stylesheet cannot ask.** `[part="footer"]:not(:has(slot:has(*)))` is two
351
- * faults at once: `:has()` may not be nested inside `:has()`, so the selector
352
- * fails to parse and the rule is discarded whole; and a slot's assigned nodes
353
- * are not its DOM children, so `slot:has(*)` reports fallback content rather
354
- * than the fill, and is false either way. Both verified in a browser.
355
- */
356
94
  markEmpty() {
357
95
  this.markRegion('footer', ['start', 'summary', 'end']);
358
96
  }
359
- /**
360
- * One region, hidden when its slot holds nothing and nothing of ours fills it.
361
- *
362
- * `hidden` beside the `data-empty` mirror: `data-empty` is the hook the styled
363
- * sheet reads, and this tier ships no sheet, so an empty region left in the box
364
- * tree would be a gap a headless consumer cannot close without knowing to look
365
- * for it. The styled rule stays too, because that sheet sets `display` on these
366
- * parts and therefore outranks the UA's `[hidden]` one.
367
- */
368
97
  markRegion(part, slots, occupied) {
369
98
  const region = this.renderRoot?.querySelector(`[part="${part}"]`);
370
99
  const empty = !slots.some((name) => slotFilled(this.renderRoot?.querySelector(`slot[name="${name}"]`))) && !(occupied && this.renderRoot?.querySelector(occupied));
@@ -375,14 +104,6 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
375
104
  super.updated(changed);
376
105
  this.markEmpty();
377
106
  this.measure();
378
- /* Written here rather than reflected, and announced only on a change: a
379
- consumer recomputing a summary from this is doing work, and an event per
380
- resize frame is work per frame.
381
-
382
- **Nothing measured yet is the absence of the attribute.** `updated` runs
383
- in the same microtask checkpoint as `firstUpdated`, before React need have
384
- reached the node, so writing `visible="0"` here is an attribute no server
385
- sent. That is the fault `data-settled` was found for. */
386
107
  const showing = this.visible;
387
108
  const written = showing > 0 ? String(showing) : null;
388
109
  if (this.getAttribute('visible') !== written) {
@@ -413,14 +134,6 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
413
134
  ? nothing
414
135
  : cells.map((reading, index) => {
415
136
  const tone = toneOf(reading, bounds, palette);
416
- /* **Two templates rather than one with a conditional style, and
417
- the reason is that an absent tone has to mean an absent
418
- declaration.** `--heatmap-tone: var(--heatmap-tone-undefined)`
419
- is not an unset property, it is a property set to the
420
- guaranteed-invalid value, and `var(--heatmap-tone, …)` then
421
- declines to use its own fallback because the property *is*
422
- set. An untoned cell came out transparent rather than taking
423
- the quiet ground the sheet has for it. */
424
137
  return tone === undefined
425
138
  ? html `<span part="cell" data-index=${index}></span>`
426
139
  : html `<span
@@ -439,37 +152,11 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
439
152
  <span part="end"><slot name="end"></slot></span>
440
153
  </div>`;
441
154
  }
442
- /**
443
- * The bubble over the cell a pointer is resting on.
444
- *
445
- * **One tooltip that moves, which is what every other chart here does.** Bar
446
- * Chart, Line Chart and Donut Chart each render a single anchor against an
447
- * invisible mark and move the mark, so the count is one however many readings
448
- * there are, rather than one element per cell. `drawTooltip` is their shared
449
- * markup and this is the fourth caller.
450
- *
451
- * What it buys over a native `title` is the row rather than the reading.
452
- * `sourceRows` carries every column of the table including the ones nothing
453
- * was plotted from, so a cell reads "11 Jun 2026 / Uptime 81.2% / Note Partial
454
- * outage" where `title` could only ever say the two values this component
455
- * happens to hold. It is also styleable, appears at once rather than after the
456
- * platform's second of delay, and cannot be truncated by the window edge.
457
- *
458
- * The mark is placed in pixels rather than in percent, which is the one thing
459
- * this does differently from its siblings: they divide a plot into equal
460
- * slots, and a strip's cells are a fixed size with a fixed gap inside a box
461
- * that is usually wider, so a percentage of the box lands between cells. The
462
- * pitch is `cell + gap` and both are measured.
463
- */
464
155
  renderTooltip() {
465
156
  const at = this.hovered;
466
157
  if (at < 0) {
467
158
  return nothing;
468
159
  }
469
- /* `sourceRows` is indexed over every reading and the strip draws a tail of
470
- them, so the two agree only when nothing was dropped. Off by the length of
471
- what was cut, a tooltip names the wrong day, and it names one that is
472
- still on screen, so nothing about it looks wrong. */
473
160
  const offset = this.points.length - this.drawn.length;
474
161
  const cell = this.cellPx;
475
162
  const gap = this.gapPx;
@@ -9,65 +9,17 @@ import { icons } 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 Renders a named icon from the Helix set.
14
- *
15
- * @description
16
- * The set is Phosphor, drawn on a 256 box at stroke-width 16, and each glyph is
17
- * rendered as its own inner markup rather than as a single path. Most icons are
18
- * several elements at once, each with its own stroke and fill, so a one-path
19
- * model would draw a fraction of the set and quietly drop the rest.
20
- *
21
- * **The markup is trusted by construction.** It is generated at build time from
22
- * SVG files in this repository, never from anything a user supplies, which is
23
- * what makes `unsafeSVG` the right tool rather than a risk.
24
- *
25
- * The `<svg>` carries `fill="currentColor"` because the generator keeps only
26
- * the inner markup and discards the source's own root tag, and that tag is
27
- * where Phosphor puts its fill. Without it, every solid glyph in the set, 65 of
28
- * them, falls back to the SVG default of black and disappears on a dark page.
29
- * Shapes that set their own `fill` or `stroke` are unaffected, which is why the
30
- * outline icons looked fine and hid this.
31
- *
32
- * A name with no match renders nothing at all instead of an empty box, so a
33
- * typo cannot leave a hole in a layout that looks deliberate.
34
- *
35
- * An icon is decorative by default and hidden from assistive technology. Give
36
- * it a `label` only when the icon is the sole carrier of meaning, such as
37
- * inside a button with no text.
38
- *
39
- * @since 0.1.0
40
- * @status stable
41
- * @category media
42
- * @aliases Glyph, Symbol
43
- * @documentation https://helix.fusion.dev/components/icon
44
- *
45
- * @csspart svg - The rendered SVG.
46
- */
47
12
  let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
48
13
  constructor() {
49
14
  super(...arguments);
50
- /**
51
- * Accessible name, for an icon that is not decorative.
52
- */
53
15
  this.label = '';
54
- /**
55
- * Name of the icon in the Helix set.
56
- */
57
16
  this.name = '';
58
17
  }
59
18
  render() {
60
- /* Own keys only. A truthiness check on a plain-object index lets an
61
- inherited `Object.prototype` key through, and the component renders a
62
- sized, empty `<svg viewBox="">` rather than nothing at all. */
63
19
  const icon = Object.hasOwn(icons, this.name) ? icons[this.name] : undefined;
64
20
  if (!icon) {
65
21
  return nothing;
66
22
  }
67
- // A labeled icon is content and needs a role to carry the label. An
68
- // unlabelled one is decoration sitting beside text that already says the
69
- // same thing, so it is removed from the accessibility tree rather than
70
- // announced twice.
71
23
  const labeled = this.label.length > 0;
72
24
  return html `<svg
73
25
  part="svg"
@@ -6,36 +6,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  import { customElement } from '@fusion.dev/helix-core';
8
8
  import { HelixHeadlessButton } from '../../button/root/button-root.js';
9
- /**
10
- * @summary A button whose content is an icon, and which must be named.
11
- *
12
- * @description
13
- * Everything a button does, with one thing added and it is the reason this is
14
- * an element rather than a note on `Button`: **an icon-only control has no
15
- * accessible name unless it is given one.** A button whose content is a glyph
16
- * announces as "button" and nothing else, which is the single most common way a
17
- * toolbar becomes unusable without sight. `label` is not optional in practice,
18
- * and having a component that says so is what makes it reviewable.
19
- *
20
- * **Registered, so it can be rendered and not only subclassed.** Anything in
21
- * this tier needing an icon button, such as an announcement's dismiss control,
22
- * renders `helix-headless-icon-button` rather than reaching into the styled tier
23
- * for a tag that would never upgrade.
24
- *
25
- * Top-level rather than `Button.Icon`, because a namespace means composition
26
- * and an icon-only button is not part of a button. It is a button with
27
- * different content and a different shape, which makes it its own component.
28
- *
29
- * @since 0.1.0
30
- * @status stable
31
- * @category actions
32
- * @aliases Icon button, Square button
33
- * @documentation https://helix.fusion.dev/components/icon-button
34
- *
35
- * @slot - The icon.
36
- *
37
- * @csspart button - The inner button or anchor.
38
- */
39
9
  let HelixHeadlessIconButton = class HelixHeadlessIconButton extends HelixHeadlessButton {
40
10
  };
41
11
  HelixHeadlessIconButton = __decorate([