@fusion.dev/helix-headless 0.152.0 → 0.154.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 (41) hide show
  1. package/custom-elements.json +1143 -62
  2. package/dist/area-chart/root/area-chart-root.d.ts +16 -1
  3. package/dist/area-chart/root/area-chart-root.js +3 -3
  4. package/dist/badge/root/badge-root.d.ts +15 -2
  5. package/dist/badge/root/badge-root.js +20 -3
  6. package/dist/bar-chart/root/bar-chart-root.d.ts +16 -1
  7. package/dist/bar-chart/root/bar-chart-root.js +3 -3
  8. package/dist/card/root/card-root.d.ts +31 -11
  9. package/dist/card/root/card-root.js +25 -11
  10. package/dist/code/root/code-root.d.ts +7 -0
  11. package/dist/code/root/code-root.js +1 -1
  12. package/dist/code-block/root/code-block-root.d.ts +37 -1
  13. package/dist/code-block/root/code-block-root.js +38 -5
  14. package/dist/empty/root/empty-root.d.ts +19 -1
  15. package/dist/empty/root/empty-root.js +13 -4
  16. package/dist/heatmap/root/heatmap-root.d.ts +3 -0
  17. package/dist/heatmap/root/heatmap-root.js +2 -8
  18. package/dist/icon/fetch.d.ts +20 -0
  19. package/dist/icon/fetch.js +70 -0
  20. package/dist/icon/root/icon-root.d.ts +27 -5
  21. package/dist/icon/root/icon-root.js +41 -17
  22. package/dist/line-chart/root/line-chart-root.d.ts +16 -1
  23. package/dist/line-chart/root/line-chart-root.js +3 -3
  24. package/dist/loader/root/loader-root.d.ts +5 -0
  25. package/dist/progress/root/progress-root.d.ts +16 -1
  26. package/dist/progress/root/progress-root.js +12 -4
  27. package/dist/quote/root/quote-root.d.ts +17 -1
  28. package/dist/quote/root/quote-root.js +15 -2
  29. package/dist/scatter-chart/root/scatter-chart-root.d.ts +16 -1
  30. package/dist/scatter-chart/root/scatter-chart-root.js +3 -3
  31. package/dist/skeleton/root/skeleton-root.d.ts +7 -0
  32. package/dist/skeleton/root/skeleton-root.js +4 -1
  33. package/dist/stat/group/stat-group.d.ts +23 -4
  34. package/dist/stat/group/stat-group.js +13 -1
  35. package/dist/stat/root/stat-root.d.ts +46 -1
  36. package/dist/stat/root/stat-root.js +29 -5
  37. package/lint.json +1 -1
  38. package/package.json +5 -5
  39. package/types/jsx.d.ts +1434 -62
  40. package/types/vue.d.ts +297 -24
  41. package/types/words.d.ts +69 -0
@@ -17,7 +17,7 @@ export interface AreaStack {
17
17
  /** Each column's own reading, not yet summed. */
18
18
  values: number[][];
19
19
  }
20
- declare const HelixHeadlessAreaChart_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface);
20
+ declare const HelixHeadlessAreaChart_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartStatusInterface);
21
21
  /**
22
22
  * @summary A chart that shows how a total is made up over time, as bands stacked on one another.
23
23
  *
@@ -73,24 +73,39 @@ declare const HelixHeadlessAreaChart_base: typeof HelixElement & (new (...args:
73
73
  * tap or key, and `helix-reading-press` says a reading was clicked, tapped, or
74
74
  * pressed with Enter or Space, for a page that opens what a reading stands for.
75
75
  *
76
+ * **`data` takes the readings as data** where the page has no table to point
77
+ * `for` at, in a table's shape: the column headings, then one row per reading
78
+ * with its label first. A table is still the better source where the page can
79
+ * show one, since it reads before any script runs.
80
+ *
81
+ * **A chart with nothing to draw says so.** A table with no readings shows
82
+ * `empty-text`, "No readings", in the plot, and `loading` shows `loading-text`
83
+ * there until readings arrive. The `empty` and `loading` slots take markup in
84
+ * their place, such as a link to add the first reading.
85
+ *
76
86
  * @since 0.1.0
77
87
  * @status stable
78
88
  * @category charts
79
89
  * @aliases Stacked Area Chart, Composition Chart, Banded Chart
80
90
  * @documentation https://fusion.dev/helix/components/area-chart
81
91
  *
92
+ * @slot empty - Content shown in place of the drawing when the table has no readings, such as a link to add the first. Falls back to `empty-text`.
93
+ * @slot loading - Content shown in place of the drawing while `loading` is set and there are no readings yet. Falls back to `loading-text`.
94
+ *
82
95
  * @event {HelixReadingChangeEvent} helix-reading-change - The reading shown moved, or was put away: `index` is the row, or `-1` for none, with its `label`, the `series` the keys rest on, and the `pointerType` that moved it.
83
96
  * @event {HelixReadingPressEvent} helix-reading-press - A reading was clicked, tapped, or pressed with Enter or Space, with the same detail as `helix-reading-change`.
84
97
  *
85
98
  * @csspart area - One part's band.
86
99
  * @csspart axis - The numbers of the scale beside the plot, under `value-axis`.
87
100
  * @csspart baseline - The rule at zero, which is the floor of the drawing.
101
+ * @csspart empty - The words in place of the drawing when the table has no readings: the `empty` slot, falling back to `empty-text`. Not drawn while the table cannot be read yet, on the server and during hydration.
88
102
  * @csspart gridline - One rule across the plot, under `gridlines`.
89
103
  * @csspart guide - A rule down the reading a pointer is resting on.
90
104
  * @csspart label - One reading's name.
91
105
  * @csspart labels - The row of reading names.
92
106
  * @csspart line - The top edge of one part's band.
93
107
  * @csspart live - The readout: a polite live region that says the reading the keys moved to. Hide it visually.
108
+ * @csspart loading - The words in place of the drawing while `loading` is set and there are no readings: the `loading` slot, falling back to `loading-text`.
94
109
  * @csspart mark - The invisible point the tooltip is anchored to.
95
110
  * @csspart plot - The drawing, which takes focus to be read by key.
96
111
  * @csspart summary - The total and each part's share at the latest reading, written as a sentence for screen readers.
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { CHART_PLOT, customElement, drawTooltip, drawValueAxis, formatNumbers, formatWords, HelixElement, HelixReadingChangeEvent, HelixReadingPressEvent, linear, linePath, MixinChartSource, PLOT_HEIGHT, PLOT_WIDTH, reportRuntime, seriesIndex, ticks, words, wordsLocale, } from '@fusion.dev/helix-core';
7
+ import { CHART_PLOT, customElement, drawTooltip, drawValueAxis, formatNumbers, formatWords, HelixElement, HelixReadingChangeEvent, HelixReadingPressEvent, linear, linePath, MixinChartSource, MixinChartStatus, PLOT_HEIGHT, PLOT_WIDTH, reportRuntime, seriesIndex, ticks, words, wordsLocale, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing, svg } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import '../../tooltip/index.js';
@@ -20,7 +20,7 @@ const percentIn = (locale) => {
20
20
  }
21
21
  return found;
22
22
  };
23
- let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSource(HelixElement) {
23
+ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartStatus(MixinChartSource(HelixElement)) {
24
24
  constructor() {
25
25
  super(...arguments);
26
26
  this.overlap = false;
@@ -96,7 +96,7 @@ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSour
96
96
  const rows = this.sourceRows;
97
97
  const stack = this.stack;
98
98
  if (rows.length === 0 || stack.columns.length === 0) {
99
- return html `<div id=${CHART_PLOT} part="plot"></div>`;
99
+ return html `<div id=${CHART_PLOT} part="plot">${this.renderStatus()}</div>`;
100
100
  }
101
101
  const top = this.overlap
102
102
  ? Math.max(0, ...stack.layers.flat())
@@ -35,6 +35,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
35
35
  * button "Inbox 8 unread", and stays current as the count changes. The styled
36
36
  * tier's `placement="inline"` draws the mark in the line for that case.
37
37
  *
38
+ * **An icon goes inside the mark** through the `icon` slot, before the count,
39
+ * as Web Awesome's badge takes one at its start. With no `value` the icon is
40
+ * the whole mark, such as a check on an avatar, drawn where the dot would be.
41
+ * The icon is hidden from screen readers: the count and `label` are what the
42
+ * mark says.
43
+ *
38
44
  * @since 0.1.0
39
45
  * @status stable
40
46
  * @category markers
@@ -42,12 +48,15 @@ import { type PropertyValues, type TemplateResult } from 'lit';
42
48
  * @documentation https://fusion.dev/helix/components/badge
43
49
  *
44
50
  * @slot - The element being decorated.
51
+ * @slot icon - An icon inside the mark, before the count, or the whole mark when there is no value. Hidden from screen readers.
45
52
  *
46
- * @csspart indicator - The count, or the dot when there is no value.
53
+ * @csspart icon - The slot the icon arrives through, inside the indicator. A slot draws no box, so it passes inherited properties, such as color, to the icon.
54
+ * @csspart indicator - The count, or the dot when there is no value. Carries `data-dot` when there is no value, `data-single` when the count is one character, and `data-blank` when the value is zero.
47
55
  *
48
56
  * @cssstate blank - The value is zero, so there is no mark.
49
57
  * @cssstate capped - The count is past `max`, so the mark shows `{max}+`.
50
- * @cssstate dot - There is no value, so the mark is a dot.
58
+ * @cssstate dot - There is no value and no icon, so the mark is a dot.
59
+ * @cssstate has-icon - Something is slotted into `icon`, so it is drawn inside the mark.
51
60
  */
52
61
  export declare class HelixHeadlessBadge extends HelixElement {
53
62
  /**
@@ -98,6 +107,10 @@ export declare class HelixHeadlessBadge extends HelixElement {
98
107
  * is showing a count must not lose the count for being handed one.
99
108
  */
100
109
  value: number | string;
110
+ /** Whether something is slotted into `icon`, read from the children. */
111
+ private hasIcon;
112
+ /** Reads whether an icon is slotted, from the children where they can be known. */
113
+ private readonly readIcon;
101
114
  /** The value as text, whatever it was set to. */
102
115
  private get given();
103
116
  /** Whether the value counts nothing, in which case there is no mark. */
@@ -6,7 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  import { customElement, focusTarget, HelixElement, wordsLocale } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
- import { property } from 'lit/decorators.js';
9
+ import { property, state } from 'lit/decorators.js';
10
10
  import { badgeBlank, badgeCapped, badgeText, badgeWords } from '../count.js';
11
11
  let HelixHeadlessBadge = class HelixHeadlessBadge extends HelixElement {
12
12
  constructor() {
@@ -14,6 +14,10 @@ let HelixHeadlessBadge = class HelixHeadlessBadge extends HelixElement {
14
14
  this.label = '';
15
15
  this.locale = '';
16
16
  this.value = '';
17
+ this.hasIcon = false;
18
+ this.readIcon = () => {
19
+ this.hasIcon = this.knownChildren.some((child) => child.slot === 'icon');
20
+ };
17
21
  this.handleSlotchange = () => {
18
22
  this.describe();
19
23
  };
@@ -81,9 +85,13 @@ let HelixHeadlessBadge = class HelixHeadlessBadge extends HelixElement {
81
85
  }
82
86
  willUpdate(changed) {
83
87
  super.willUpdate(changed);
88
+ if (!this.hasUpdated) {
89
+ this.readIcon();
90
+ }
84
91
  this.toggleState('capped', this.capped);
85
- this.toggleState('dot', this.given.trim() === '');
92
+ this.toggleState('dot', this.given.trim() === '' && !this.hasIcon);
86
93
  this.toggleState('blank', this.blank);
94
+ this.toggleState('has-icon', this.hasIcon);
87
95
  }
88
96
  updated(changed) {
89
97
  super.updated(changed);
@@ -97,10 +105,16 @@ let HelixHeadlessBadge = class HelixHeadlessBadge extends HelixElement {
97
105
  <span
98
106
  part="indicator"
99
107
  data-blank=${blank ? '' : nothing}
108
+ data-dot=${this.given.trim() === '' ? '' : nothing}
100
109
  data-single=${text.length <= 1 ? '' : nothing}
101
110
  role="status"
102
111
  aria-label=${announced ? this.words : nothing}
103
- >${blank ? html `<span aria-hidden="true">${text}</span>` : text}</span
112
+ ><slot
113
+ aria-hidden="true"
114
+ name="icon"
115
+ part="icon"
116
+ @slotchange=${this.readIcon}
117
+ ></slot>${blank ? html `<span aria-hidden="true">${text}</span>` : text}</span
104
118
  >`;
105
119
  }
106
120
  };
@@ -119,6 +133,9 @@ __decorate([
119
133
  __decorate([
120
134
  property()
121
135
  ], HelixHeadlessBadge.prototype, "value", void 0);
136
+ __decorate([
137
+ state()
138
+ ], HelixHeadlessBadge.prototype, "hasIcon", void 0);
122
139
  HelixHeadlessBadge = __decorate([
123
140
  customElement('helix-headless-badge')
124
141
  ], HelixHeadlessBadge);
@@ -1,7 +1,7 @@
1
1
  import { HelixElement, type Series } from '@fusion.dev/helix-core';
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  import '../../tooltip/index.js';
4
- declare const HelixHeadlessBarChart_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface);
4
+ declare const HelixHeadlessBarChart_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartStatusInterface);
5
5
  /**
6
6
  * @summary A chart that draws a table's categories as columns.
7
7
  *
@@ -35,12 +35,25 @@ declare const HelixHeadlessBarChart_base: typeof HelixElement & (new (...args: a
35
35
  * tap or key, and `helix-reading-press` says a reading was clicked, tapped, or
36
36
  * pressed with Enter or Space, for a page that opens what a reading stands for.
37
37
  *
38
+ * **`data` takes the readings as data** where the page has no table to point
39
+ * `for` at, in a table's shape: the column headings, then one row per reading
40
+ * with its label first. A table is still the better source where the page can
41
+ * show one, since it reads before any script runs.
42
+ *
43
+ * **A chart with nothing to draw says so.** A table with no readings shows
44
+ * `empty-text`, "No readings", in the plot, and `loading` shows `loading-text`
45
+ * there until readings arrive. The `empty` and `loading` slots take markup in
46
+ * their place, such as a link to add the first reading.
47
+ *
38
48
  * @since 0.1.0
39
49
  * @status stable
40
50
  * @category charts
41
51
  * @aliases Column Chart, Categorical Chart
42
52
  * @documentation https://fusion.dev/helix/components/bar-chart
43
53
  *
54
+ * @slot empty - Content shown in place of the drawing when the table has no readings, such as a link to add the first. Falls back to `empty-text`.
55
+ * @slot loading - Content shown in place of the drawing while `loading` is set and there are no readings yet. Falls back to `loading-text`.
56
+ *
44
57
  * @event {HelixReadingChangeEvent} helix-reading-change - The reading shown moved, or was put away: `index` is the row, or `-1` for none, with its `label`, the `series` the keys rest on, and the `pointerType` that moved it.
45
58
  * @event {HelixReadingPressEvent} helix-reading-press - A reading was clicked, tapped, or pressed with Enter or Space, with the same detail as `helix-reading-change`.
46
59
  *
@@ -50,6 +63,7 @@ declare const HelixHeadlessBarChart_base: typeof HelixElement & (new (...args: a
50
63
  * @csspart bar-wash-end - The far stop of a bar's gradient, where one is used.
51
64
  * @csspart bar-wash-start - The stop of a bar's gradient at its base.
52
65
  * @csspart baseline - The line the columns stand on.
66
+ * @csspart empty - The words in place of the drawing when the table has no readings: the `empty` slot, falling back to `empty-text`. Not drawn while the table cannot be read yet, on the server and during hydration.
53
67
  * @csspart gridline - One gridline across the plot, drawn when `gridlines` is set.
54
68
  * @csspart group - One category's columns, together.
55
69
  * @csspart group-active - The group the tooltip describes. Set together with `group`.
@@ -57,6 +71,7 @@ declare const HelixHeadlessBarChart_base: typeof HelixElement & (new (...args: a
57
71
  * @csspart labels - The row of category names.
58
72
  * @csspart line - A series drawn as a line, named in `lines`.
59
73
  * @csspart live - The readout: a polite live region that says the reading the keys moved to. Hide it visually.
74
+ * @csspart loading - The words in place of the drawing while `loading` is set and there are no readings: the `loading` slot, falling back to `loading-text`.
60
75
  * @csspart mark - The invisible point the tooltip is anchored to.
61
76
  * @csspart plot - The drawing, which takes focus to be read by key.
62
77
  * @csspart summary - The chart's name and each series' highest reading, written as a sentence for screen readers. Hide it visually.
@@ -4,12 +4,12 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { byRow, CHART_PLOT, customElement, drawTooltip, extent, formatNumbers, formatWords, HelixElement, HelixReadingChangeEvent, HelixReadingPressEvent, linear, linePath, MixinChartSource, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, seriesKey, seriesNames, ticks, unbroken, words, wordsLocale, } from '@fusion.dev/helix-core';
7
+ import { byRow, CHART_PLOT, customElement, drawTooltip, extent, formatNumbers, formatWords, HelixElement, HelixReadingChangeEvent, HelixReadingPressEvent, linear, linePath, MixinChartSource, MixinChartStatus, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, seriesKey, seriesNames, ticks, unbroken, words, wordsLocale, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing, svg } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { stackEnds, stackedReadings, stackSpans } from '../stack.js';
11
11
  import '../../tooltip/index.js';
12
- let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource(HelixElement) {
12
+ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartStatus(MixinChartSource(HelixElement)) {
13
13
  constructor() {
14
14
  super(...arguments);
15
15
  this.highestLabel = 'Highest';
@@ -44,7 +44,7 @@ let HelixHeadlessBarChart = class HelixHeadlessBarChart extends MixinChartSource
44
44
  const series = this.series;
45
45
  const categories = series.length === 0 ? [] : this.sourceRows.map((row) => row.label);
46
46
  if (categories.length === 0) {
47
- return html `<div id=${CHART_PLOT} part="plot"></div>`;
47
+ return html `<div id=${CHART_PLOT} part="plot">${this.renderStatus()}</div>`;
48
48
  }
49
49
  const scaled = this.scaled;
50
50
  const own = (one) => scaled.has(seriesKey(one.name));
@@ -13,13 +13,17 @@ import { type PropertyValues, type TemplateResult } from 'lit';
13
13
  * through with `::part`.
14
14
  *
15
15
  * Name the card with `label`, as a Stat Group is named, or with `aria-label`
16
- * or `aria-labelledby`. These are the only names that work with server
17
- * rendering, because reading a title from the light DOM needs a DOM, and the
18
- * server has none. `label` is set through `ElementInternals`, so it writes no
19
- * attribute onto the card. **A named card is a group**, so a
16
+ * or `aria-labelledby`. `label` is set through `ElementInternals`, so it
17
+ * writes no attribute onto the card. **A named card is a group**, so a
20
18
  * reader entering it hears its name; an unnamed one has no role, since a group
21
19
  * with no name only adds "group" to what a reader says.
22
20
  *
21
+ * **With none of those, a heading in `header` names the card**, such as the
22
+ * `<h2>` of its title, by reference, so the name follows the heading's words.
23
+ * Reading a heading needs a DOM, so a served card is named by it once the
24
+ * script runs; `label`, `aria-label` and `aria-labelledby` are the names a
25
+ * server writes.
26
+ *
23
27
  * Each region's slot is a part of the same name, `body` for the main content,
24
28
  * as Web Awesome's card names its regions. A slot draws no box, so a part
25
29
  * passes inherited properties to what is slotted, and a page wanting a box
@@ -29,7 +33,9 @@ import { type PropertyValues, type TemplateResult } from 'lit';
29
33
  * header's and the footer's line, as Web Awesome's card does. While one holds
30
34
  * something, its line is a `header-row` or `footer-row` part; otherwise there
31
35
  * is no wrapper and every region is a bare slot. A server cannot see slotted
32
- * content, so served actions sit on their own line until the script runs.
36
+ * content, so a served card has no row; the styled tier sets served actions at
37
+ * the end of their line from the stylesheet alone, so they do not move when
38
+ * the script runs.
33
39
  *
34
40
  * **The card says which regions it holds**, as `:state(has-media)`,
35
41
  * `:state(has-header)` and `:state(has-footer)`, so a sheet can draw a card
@@ -87,20 +93,34 @@ export declare class HelixHeadlessCard extends HelixElement {
87
93
  */
88
94
  mediaPlacement?: 'end';
89
95
  /**
90
- * @internal The host's `aria-labelledby`, observed so the card becomes a
91
- * group once it is named. Not reflected: the attribute is the consumer's.
96
+ * Hears a name written on the card, which no property mirrors.
97
+ *
98
+ * Lit observes the attributes of its own properties only, and `ariaLabel`
99
+ * and `ariaLabelledByElements` belong to the platform. Observed here rather
100
+ * than through a property, so the manifest lists no attribute of the card's
101
+ * own for them.
92
102
  */
93
- protected labelledBy: string | null;
103
+ static get observedAttributes(): string[];
94
104
  /**
95
- * @internal The host's `aria-label`, observed for the same reason as
96
- * `labelledBy`. Not reflected: the attribute is the consumer's.
105
+ * Called by the browser when an observed attribute changes. A name written
106
+ * or taken off asks for an update, because it decides whether the card is a
107
+ * group and whether its heading names it.
97
108
  */
98
- protected namedBy: string | null;
109
+ attributeChangedCallback(name: string, old: string | null, value: string | null): void;
99
110
  /** Whether `footer-actions` holds something, read from the slot. */
100
111
  private footerActions;
101
112
  /** Whether `header-actions` holds something, read from the slot. */
102
113
  private headerActions;
103
114
  protected willUpdate(changed: PropertyValues): void;
115
+ /**
116
+ * Says what the card is and what names it. On the host, since the host is
117
+ * the box, and from `willUpdate`, which the server calls too.
118
+ *
119
+ * **A heading in `header` is the name only where nothing else is**, and it
120
+ * names the card by reference, so a renamed heading renames the card. An
121
+ * engine without element references hears its words.
122
+ */
123
+ private writeName;
104
124
  protected updated(changed: PropertyValues): void;
105
125
  protected firstUpdated(changed: PropertyValues): void;
106
126
  private readonly readRegions;
@@ -4,15 +4,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, HelixElement, hostAria, slotFilled } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, headingIn, hostAria, slotFilled, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  let HelixHeadlessCard = class HelixHeadlessCard extends HelixElement {
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.label = '';
14
- this.labelledBy = null;
15
- this.namedBy = null;
16
14
  this.footerActions = false;
17
15
  this.headerActions = false;
18
16
  this.readRegions = () => {
@@ -22,12 +20,34 @@ let HelixHeadlessCard = class HelixHeadlessCard extends HelixElement {
22
20
  this.toggleState('has-media', filled('media'));
23
21
  this.toggleState('has-header', this.headerActions || filled('header'));
24
22
  this.toggleState('has-footer', this.footerActions || filled('footer'));
23
+ this.writeName();
25
24
  };
26
25
  }
26
+ static get observedAttributes() {
27
+ return [...super.observedAttributes, 'aria-label', 'aria-labelledby'];
28
+ }
29
+ attributeChangedCallback(name, old, value) {
30
+ super.attributeChangedCallback(name, old, value);
31
+ if (name === 'aria-label' || name === 'aria-labelledby') {
32
+ this.requestUpdate();
33
+ }
34
+ }
27
35
  willUpdate(changed) {
28
36
  super.willUpdate(changed);
29
- const named = Boolean(this.label.trim() || this.namedBy?.trim() || this.labelledBy?.trim());
30
- hostAria(this.internals, { label: this.label, role: named ? 'group' : '' });
37
+ this.writeName();
38
+ }
39
+ writeName() {
40
+ const attribute = (name) => Boolean(this.getAttribute?.(name)?.trim());
41
+ const named = this.label.trim() !== '' || attribute('aria-label') || attribute('aria-labelledby');
42
+ const heading = named
43
+ ? null
44
+ : headingIn(this.knownChildren.filter((child) => child.slot === 'header'));
45
+ const references = 'ariaLabelledByElements' in this.internals;
46
+ hostAria(this.internals, {
47
+ label: heading && !references ? (heading.textContent ?? '').trim() : this.label,
48
+ labelledBy: heading ? [heading] : [],
49
+ role: named || heading ? 'group' : '',
50
+ });
31
51
  }
32
52
  updated(changed) {
33
53
  super.updated(changed);
@@ -64,12 +84,6 @@ __decorate([
64
84
  __decorate([
65
85
  property({ attribute: 'media-placement', reflect: true })
66
86
  ], HelixHeadlessCard.prototype, "mediaPlacement", void 0);
67
- __decorate([
68
- property({ attribute: 'aria-labelledby' })
69
- ], HelixHeadlessCard.prototype, "labelledBy", void 0);
70
- __decorate([
71
- property({ attribute: 'aria-label' })
72
- ], HelixHeadlessCard.prototype, "namedBy", void 0);
73
87
  __decorate([
74
88
  state()
75
89
  ], HelixHeadlessCard.prototype, "footerActions", void 0);
@@ -8,6 +8,13 @@ import { type TemplateResult } from 'lit';
8
8
  * tools and document search. A styled span carries no such meaning, and a
9
9
  * screen reader announces nothing about it.
10
10
  *
11
+ * **Set `translate="no"` on the element to keep a page translator off the
12
+ * code.** A translator that rewrites prose would also rewrite an identifier.
13
+ * The slotted text takes its translation mode from the element, its parent in
14
+ * the page, and the element writes nothing onto itself, so the attribute is
15
+ * yours to set. The `<code>` inside carries `translate="no"` for anything the
16
+ * element draws itself.
17
+ *
11
18
  * `Code` is inline only and never scrolls. For a block of code with its own
12
19
  * box, its own overflow, and copying, use `CodeBlock`, which is a separate
13
20
  * family.
@@ -8,7 +8,7 @@ import { customElement, HelixElement } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  let HelixHeadlessCode = class HelixHeadlessCode extends HelixElement {
10
10
  render() {
11
- return html `<code part="code"><slot></slot></code>`;
11
+ return html `<code part="code" translate="no"><slot></slot></code>`;
12
12
  }
13
13
  };
14
14
  HelixHeadlessCode = __decorate([
@@ -38,6 +38,18 @@ import '../../copy-button/index.js';
38
38
  * and its copy button copies the new version of the file. `variant="split"`
39
39
  * shows the old version beside the new, each removal opposite its replacement.
40
40
  *
41
+ * **A block that scrolls is a tab stop.** While its lines run past its width or
42
+ * its cap, the scroller is a region named for the block, such as "Code,
43
+ * app.ts", that Tab reaches, so a keyboard scrolls it with the arrow keys in
44
+ * every browser. Chromium and Firefox make a scrolling box focusable on their
45
+ * own, and Safari does not. A block that fits is no stop at all.
46
+ *
47
+ * **The code the block draws is marked untranslated**, `translate="no"` on
48
+ * its `<pre>`, so a page translator leaves highlighted lines and a diff as
49
+ * written. Code in the slot takes its translation mode from the element, which
50
+ * writes nothing onto itself, so set `translate="no"` on the element to keep a
51
+ * translator off plain code too.
52
+ *
41
53
  * On the server, the slot renders as ordinary code, so a diff first appears as
42
54
  * the patch text in a `<pre>`. The client makes the same first render, which
43
55
  * keeps hydration clean, and draws the diff once it can read the content. Diff
@@ -95,6 +107,18 @@ export declare class HelixHeadlessCodeBlock extends HelixElement {
95
107
  * `registerWords('de', { 'helix-code-block': { copyLabel: 'Code kopieren' } })`.
96
108
  */
97
109
  static words: string;
110
+ /**
111
+ * The name of a block that scrolls, which is a tab stop while it does.
112
+ *
113
+ * Where the block has a name, the scroller says it too, such as "Code,
114
+ * app.ts", as `code-name-label` puts it.
115
+ */
116
+ codeLabel: string;
117
+ /**
118
+ * The name of a block that scrolls, where the block has a name. `{code}` is
119
+ * `code-label` and `{name}` the block's name.
120
+ */
121
+ codeNameLabel: string;
98
122
  /**
99
123
  * The expand button's words while the block shows every line.
100
124
  */
@@ -249,7 +273,7 @@ export declare class HelixHeadlessCodeBlock extends HelixElement {
249
273
  private capped;
250
274
  /** Whether the expand button has shown every line. */
251
275
  private expanded;
252
- /** Watches the block's size, while it is expandable. */
276
+ /** Watches the block's size. */
253
277
  private sizes;
254
278
  /**
255
279
  * Reads whether the cap hides lines: the scroller is taller inside than it
@@ -258,6 +282,18 @@ export declare class HelixHeadlessCodeBlock extends HelixElement {
258
282
  * everything.
259
283
  */
260
284
  private readonly measure;
285
+ /**
286
+ * Makes each box that scrolls a tab stop and a region named for its block,
287
+ * and takes both away from a box that fits.
288
+ *
289
+ * **A reader on a keyboard scrolls the code from the box itself.** Chromium
290
+ * and Firefox make a scrolling box focusable on their own, and Safari does
291
+ * not, so there the arrow keys could not reach the end of a long line. A
292
+ * box counts while its sheet lets it scroll and its content is wider or
293
+ * taller than it is drawn: an unstyled `<pre>` overflows without scrolling,
294
+ * and a stop on it would scroll nothing.
295
+ */
296
+ private markScrollers;
261
297
  protected updated(changed: PropertyValues): void;
262
298
  connectedCallback(): void;
263
299
  disconnectedCallback(): void;
@@ -16,6 +16,8 @@ import { pairLines, parsePatch } from '../patch.js';
16
16
  let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
17
17
  constructor() {
18
18
  super(...arguments);
19
+ this.codeLabel = 'Code';
20
+ this.codeNameLabel = '{code}, {name}';
19
21
  this.collapseLabel = 'Show fewer lines';
20
22
  this.copiedLabel = 'Copied';
21
23
  this.copyLabel = 'Copy code';
@@ -31,6 +33,7 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
31
33
  this.capped = false;
32
34
  this.expanded = false;
33
35
  this.measure = () => {
36
+ this.markScrollers();
34
37
  if (!this.expanded) {
35
38
  this.capped =
36
39
  this.expandable &&
@@ -60,9 +63,31 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
60
63
  .map((node) => node.textContent ?? '')
61
64
  .join('');
62
65
  }
66
+ markScrollers() {
67
+ for (const box of this.renderRoot.querySelectorAll('[part="block"], [part="code"], [part="old"], [part="new"]')) {
68
+ const style = getComputedStyle(box);
69
+ const scrolls = (value) => value === 'auto' || value === 'scroll';
70
+ const overflows = (scrolls(style.overflowX) && box.scrollWidth > box.clientWidth + 1) ||
71
+ (scrolls(style.overflowY) && box.scrollHeight > box.clientHeight + 1);
72
+ if (overflows) {
73
+ const name = box.closest('[part="file"]')?.querySelector('[part="label"]')?.textContent;
74
+ const named = name ?? (this.label || this.language);
75
+ box.tabIndex = 0;
76
+ box.setAttribute('role', 'region');
77
+ box.setAttribute('aria-label', named
78
+ ? formatWords(this, 'codeNameLabel', { code: this.codeLabel, name: named })
79
+ : this.codeLabel);
80
+ }
81
+ else {
82
+ box.removeAttribute('tabindex');
83
+ box.removeAttribute('role');
84
+ box.removeAttribute('aria-label');
85
+ }
86
+ }
87
+ }
63
88
  updated(changed) {
64
89
  super.updated(changed);
65
- if (this.expandable && typeof ResizeObserver !== 'undefined') {
90
+ if (typeof ResizeObserver !== 'undefined') {
66
91
  this.sizes ??= new ResizeObserver(this.measure);
67
92
  this.sizes.observe(this);
68
93
  }
@@ -114,7 +139,7 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
114
139
  const name = this.label || this.language;
115
140
  return html `${this.renderHeader(name)}
116
141
  <div part="frame">
117
- <pre id="block" part="block">${gutter}${code}</pre>
142
+ <pre id="block" part="block" translate="no">${gutter}${code}</pre>
118
143
  ${name ? nothing : this.renderCopy()}
119
144
  </div>${this.renderExpand()}`;
120
145
  }
@@ -179,8 +204,8 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
179
204
  const hunks = file.hunks.map((hunk) => ({ hunk, rows: pairLines(hunk.lines) }));
180
205
  const filler = html `<span part="filler" aria-hidden="true">\n</span>`;
181
206
  return html `<div part="split">
182
- <pre part="old"><code part="code">${hunks.map(({ hunk, rows }) => html `${this.renderHunk(hunk)}${rows.map(({ left }) => left ? this.renderLine(left, width, 'old') : filler)}`)}</code></pre>
183
- <pre part="new"><code part="code">${hunks.map(({ hunk, rows }) => html `${this.renderHunk(hunk, true)}${rows.map(({ right }) => right ? this.renderLine(right, width, 'new') : filler)}`)}</code></pre>
207
+ <pre part="old" translate="no"><code part="code">${hunks.map(({ hunk, rows }) => html `${this.renderHunk(hunk)}${rows.map(({ left }) => left ? this.renderLine(left, width, 'old') : filler)}`)}</code></pre>
208
+ <pre part="new" translate="no"><code part="code">${hunks.map(({ hunk, rows }) => html `${this.renderHunk(hunk, true)}${rows.map(({ right }) => right ? this.renderLine(right, width, 'new') : filler)}`)}</code></pre>
184
209
  </div>`;
185
210
  }
186
211
  renderDiff(files) {
@@ -197,13 +222,21 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
197
222
  <div part="frame">
198
223
  ${this.variant === 'split'
199
224
  ? this.renderSplit(file, width)
200
- : html `<pre part="block"><code part="code">${file.hunks.map((hunk) => html `${this.renderHunk(hunk)}${hunk.lines.map((line) => this.renderLine(line, width))}`)}</code></pre>`}
225
+ : html `<pre part="block" translate="no"><code part="code">${file.hunks.map((hunk) => html `${this.renderHunk(hunk)}${hunk.lines.map((line) => this.renderLine(line, width))}`)}</code></pre>`}
201
226
  ${name ? nothing : this.renderCopy(now)}
202
227
  </div>
203
228
  </div>`;
204
229
  })}<slot hidden @slotchange=${() => this.requestUpdate()}></slot>`;
205
230
  }
206
231
  };
232
+ __decorate([
233
+ words,
234
+ property({ attribute: 'code-label' })
235
+ ], HelixHeadlessCodeBlock.prototype, "codeLabel", void 0);
236
+ __decorate([
237
+ words,
238
+ property({ attribute: 'code-name-label' })
239
+ ], HelixHeadlessCodeBlock.prototype, "codeNameLabel", void 0);
207
240
  __decorate([
208
241
  words,
209
242
  property({ attribute: 'collapse-label' })
@@ -17,7 +17,16 @@ import { type PropertyValues, type TemplateResult } from 'lit';
17
17
  *
18
18
  * Every region is a bare slot. There is no `empty-state-heading` or
19
19
  * `empty-state-actions` element: assign your own heading and actions to the
20
- * slots.
20
+ * slots. The icon, the message and the actions slots are parts of the same
21
+ * names, as Card's regions are. A slot draws no box, so a part passes
22
+ * inherited properties, such as color and font, to what is slotted, and a
23
+ * page wanting a box there gives it one with `display`, such as a row of
24
+ * actions with `display: flex`.
25
+ *
26
+ * **The view says which regions it holds**, as `:state(has-icon)` and
27
+ * `:state(has-actions)`, so a sheet can space a view with no icon or no
28
+ * actions differently. Read in the browser, so a served view carries neither
29
+ * until the script runs.
21
30
  *
22
31
  * **Or give the heading as words.** `heading` draws them in a `heading` part
23
32
  * that is a heading at `heading-level`, 3 where it is not set, so a page
@@ -42,8 +51,14 @@ import { type PropertyValues, type TemplateResult } from 'lit';
42
51
  * @slot heading - The heading, which names what is missing. A heading element, such as an `<h3>`, keeps its level; any other element is read as a heading at `heading-level`. Replaces the one `heading` draws.
43
52
  * @slot icon - An icon above the heading.
44
53
  *
54
+ * @csspart actions - The slot the actions arrive through. A slot draws no box, so it passes inherited properties to the actions; `display: flex` lays them out in a row.
45
55
  * @csspart heading - The heading at `heading-level`: the one `heading` draws while nothing is slotted into `heading`, or the one around a slotted element that is no heading.
56
+ * @csspart icon - The slot the icon arrives through, drawing no box, as `actions` does.
46
57
  * @csspart live - A copy of the heading and the message, in a live region that screen readers announce when the view appears. Visually hidden in the styled tier.
58
+ * @csspart message - The slot the message arrives through, drawing no box, as `actions` does.
59
+ *
60
+ * @cssstate has-actions - Something is slotted into `actions`.
61
+ * @cssstate has-icon - Something is slotted into `icon`.
47
62
  */
48
63
  export declare class HelixHeadlessEmpty extends HelixElement {
49
64
  /**
@@ -75,6 +90,9 @@ export declare class HelixHeadlessEmpty extends HelixElement {
75
90
  private looseHeading;
76
91
  protected willUpdate(changed: PropertyValues): void;
77
92
  protected firstUpdated(changed: PropertyValues): void;
93
+ protected updated(changed: PropertyValues): void;
94
+ /** Says which regions hold something, one call apiece so each pairs with its `@cssstate`. */
95
+ private readonly readRegions;
78
96
  protected readonly arrival: ArrivalController;
79
97
  /**
80
98
  * The `heading` slot, with the heading `heading` draws as its fallback, so