@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.
- package/custom-elements.json +234 -234
- package/dist/accordion/item/accordion-item.js +0 -108
- package/dist/accordion/root/accordion-root.js +0 -33
- package/dist/announcement/root/announcement-root.js +1 -76
- package/dist/app-header/root/app-header-root.js +0 -84
- package/dist/avatar/group/avatar-group.js +0 -77
- package/dist/avatar/root/avatar-root.js +0 -72
- package/dist/badge/root/badge-root.js +0 -67
- package/dist/bar-chart/root/bar-chart-root.js +3 -263
- package/dist/blockquote/root/blockquote-root.js +0 -46
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +0 -72
- package/dist/breadcrumbs/root/breadcrumbs-root.js +0 -52
- package/dist/button/root/button-root.js +0 -215
- package/dist/button-group/root/button-group-root.js +0 -30
- package/dist/callout/root/callout-root.js +0 -52
- package/dist/card/root/card-root.js +0 -41
- package/dist/carousel/root/carousel-root.d.ts +2 -2
- package/dist/carousel/root/carousel-root.js +2 -362
- package/dist/chat/composer/chat-composer.js +0 -131
- package/dist/chat/message/chat-message.js +0 -137
- package/dist/chat/root/chat-root.js +0 -193
- package/dist/checkbox/root/checkbox-root.js +0 -113
- package/dist/checkbox-group/root/checkbox-group-root.js +1 -157
- package/dist/code/root/code-root.js +0 -26
- package/dist/code-block/root/code-block-root.js +0 -100
- package/dist/color-input/root/color-input-root.js +0 -217
- package/dist/color-picker/color.js +0 -69
- package/dist/color-picker/root/color-picker-root.js +0 -272
- package/dist/combobox/root/combobox-root.js +0 -428
- package/dist/command/root/command-root.js +0 -177
- package/dist/copy-button/root/copy-button-root.js +0 -134
- package/dist/data-table/root/data-table-root.js +0 -336
- package/dist/data-table/root/order.js +0 -100
- package/dist/date-input/root/date-input-root.js +1 -398
- package/dist/date-picker/root/date-picker-root.js +0 -358
- package/dist/dialog/root/dialog-root.js +0 -189
- package/dist/divider/root/divider-root.js +0 -71
- package/dist/donut-chart/root/donut-chart-root.js +0 -214
- package/dist/drawer/root/drawer-root.js +0 -116
- package/dist/drilldown/item/drilldown-item.js +0 -82
- package/dist/drilldown/root/drilldown-root.js +0 -91
- package/dist/element-events.generated.js +0 -5
- package/dist/empty/root/empty-root.js +0 -35
- package/dist/file-input/root/file-input-root.js +0 -232
- package/dist/format-date/root/format-date-root.js +0 -95
- package/dist/heatmap/root/buckets.js +0 -57
- package/dist/heatmap/root/heatmap-root.js +0 -313
- package/dist/icon/root/icon-root.js +0 -48
- package/dist/icon-button/root/icon-button-root.js +0 -30
- package/dist/image/root/image-root.js +0 -174
- package/dist/index.js +0 -9
- package/dist/input/root/input-root.js +0 -228
- package/dist/kbd/root/kbd-root.js +0 -117
- package/dist/legend/root/legend-root.js +0 -73
- package/dist/line-chart/root/line-chart-root.js +0 -172
- package/dist/list/item/list-item.js +0 -92
- package/dist/list/root/list-root.js +0 -26
- package/dist/listbox/root/listbox-root.js +2 -429
- package/dist/loader/root/loader-root.js +0 -40
- package/dist/megamenu/root/megamenu-root.js +0 -284
- package/dist/menu/item/menu-item.js +1 -221
- package/dist/menu/root/menu-root.js +1 -171
- package/dist/meter/root/meter-root.js +1 -152
- package/dist/navigation/root/navigation-root.js +0 -56
- package/dist/notifications/notification/notification.js +0 -159
- package/dist/notifications/root/notifications-root.js +0 -148
- package/dist/number-input/root/number-input-root.js +1 -263
- package/dist/option/group/option-group.js +0 -26
- package/dist/option/root/option-root.js +0 -157
- package/dist/outline/root/outline-root.js +0 -292
- package/dist/pagination/root/pagination-root.js +0 -361
- package/dist/pagination/size/pagination-size.js +0 -104
- package/dist/pagination/summary/pagination-summary.js +0 -90
- package/dist/pin-input/root/pin-input-root.js +0 -276
- package/dist/popover/root/popover-root.js +0 -103
- package/dist/progress/root/progress-root.js +1 -133
- package/dist/qr-code/root/qr-code-root.js +0 -96
- package/dist/quote/root/quote-root.js +0 -25
- package/dist/radio/group/radio-group.js +0 -81
- package/dist/radio/root/radio-root.js +0 -85
- package/dist/rating/root/rating-root.js +0 -220
- package/dist/scroll-area/root/scroll-area-root.js +0 -100
- package/dist/select/root/select-root.js +2 -218
- package/dist/skeleton/root/skeleton-root.js +0 -20
- package/dist/slider/root/slider-root.js +1 -267
- package/dist/sparkline/root/sparkline-root.js +3 -164
- package/dist/speed-dial/item/speed-dial-item.js +0 -52
- package/dist/speed-dial/root/speed-dial-root.js +1 -116
- package/dist/splitter/root/splitter-root.js +0 -489
- package/dist/stat/group/stat-group.js +0 -18
- package/dist/stat/root/stat-root.js +0 -23
- package/dist/steps/root/steps-root.js +0 -62
- package/dist/steps/step/step.js +0 -131
- package/dist/switch/root/switch-root.js +0 -80
- package/dist/table/cell/table-cell.js +0 -23
- package/dist/table/column/table-column.js +0 -158
- package/dist/table/root/table-root.js +0 -82
- package/dist/table/row/table-row.js +0 -21
- package/dist/tabs/panel/tab-panel.js +0 -49
- package/dist/tabs/root/tabs-root.js +0 -98
- package/dist/tabs/tab/tab.js +0 -59
- package/dist/tag/root/tag-root.js +0 -31
- package/dist/textarea/root/textarea-root.js +0 -162
- package/dist/time-input/root/time-input-root.js +0 -283
- package/dist/time-picker/root/time-picker-root.js +0 -326
- package/dist/timeline/item/timeline-item.js +0 -66
- package/dist/timeline/root/timeline-root.js +0 -41
- package/dist/toast/item/toast-item.js +0 -165
- package/dist/toast/root/toast-root.js +0 -51
- package/dist/toggle/group/toggle-group.js +0 -150
- package/dist/toggle/root/toggle-root.js +0 -78
- package/dist/toolbar/root/toolbar-root.js +0 -116
- package/dist/tooltip/root/tooltip-root.js +0 -427
- package/dist/tour/root/tour-root.js +0 -130
- package/dist/tour/stop/tour-stop.js +1 -127
- package/dist/tree/item/tree-item.js +0 -198
- package/dist/tree/root/tree-root.js +0 -97
- package/dist/trend/root/trend-root.js +0 -53
- package/dist/video/root/video-root.d.ts +8 -12
- package/dist/video/root/video-root.js +5 -756
- package/dist/video-playlist/item/video-playlist-item.js +1 -90
- package/dist/video-playlist/menu/video-playlist-menu.js +1 -132
- package/dist/video-playlist/root/video-playlist-root.js +0 -113
- package/dist/virtual-list/root/virtual-list-root.js +0 -105
- package/jsx.d.ts +1408 -2576
- package/package.json +6 -6
- 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([
|