@fusion.dev/helix-headless 0.151.0 → 0.153.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 (54) hide show
  1. package/custom-elements.json +1332 -37
  2. package/dist/app-header/root/app-header-root.d.ts +75 -0
  3. package/dist/app-header/root/app-header-root.js +140 -5
  4. package/dist/area-chart/root/area-chart-root.d.ts +16 -1
  5. package/dist/area-chart/root/area-chart-root.js +3 -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/breadcrumbs/breadcrumb/breadcrumb.d.ts +11 -2
  9. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +11 -2
  10. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +14 -2
  11. package/dist/breadcrumbs/root/breadcrumbs-root.js +16 -3
  12. package/dist/dialog/description.d.ts +10 -0
  13. package/dist/dialog/description.js +22 -8
  14. package/dist/dialog/root/dialog-root.d.ts +17 -1
  15. package/dist/dialog/root/dialog-root.js +2 -2
  16. package/dist/drawer/root/drawer-root.d.ts +38 -2
  17. package/dist/drawer/root/drawer-root.js +33 -6
  18. package/dist/heatmap/root/heatmap-root.d.ts +3 -0
  19. package/dist/heatmap/root/heatmap-root.js +2 -8
  20. package/dist/lightbox/root/lightbox-root.d.ts +44 -6
  21. package/dist/lightbox/root/lightbox-root.js +59 -11
  22. package/dist/lightbox/zoom-controller.d.ts +5 -0
  23. package/dist/lightbox/zoom-controller.js +8 -0
  24. package/dist/line-chart/root/line-chart-root.d.ts +16 -1
  25. package/dist/line-chart/root/line-chart-root.js +3 -3
  26. package/dist/loader/root/loader-root.d.ts +5 -0
  27. package/dist/menu/root/menu-root.js +2 -0
  28. package/dist/navigation/root/navigation-root.d.ts +11 -1
  29. package/dist/navigation/root/navigation-root.js +10 -5
  30. package/dist/notifications/root/notifications-root.d.ts +17 -0
  31. package/dist/notifications/root/notifications-root.js +21 -1
  32. package/dist/popover/root/popover-root.d.ts +18 -7
  33. package/dist/popover/root/popover-root.js +8 -1
  34. package/dist/progress/root/progress-root.d.ts +16 -1
  35. package/dist/progress/root/progress-root.js +12 -4
  36. package/dist/scatter-chart/root/scatter-chart-root.d.ts +16 -1
  37. package/dist/scatter-chart/root/scatter-chart-root.js +3 -3
  38. package/dist/splitter/root/splitter-root.d.ts +41 -0
  39. package/dist/splitter/root/splitter-root.js +77 -0
  40. package/dist/tabs/root/tabs-root.d.ts +25 -0
  41. package/dist/tabs/root/tabs-root.js +28 -1
  42. package/dist/tour/naming.d.ts +9 -3
  43. package/dist/tour/naming.js +9 -3
  44. package/dist/tour/root/tour-root.d.ts +29 -5
  45. package/dist/tour/root/tour-root.js +13 -1
  46. package/dist/tour/stop/tour-stop.d.ts +2 -1
  47. package/dist/tour/stop/tour-stop.js +4 -1
  48. package/dist/tree/root/tree-root.d.ts +13 -0
  49. package/dist/tree/root/tree-root.js +42 -2
  50. package/lint.json +1 -1
  51. package/package.json +5 -5
  52. package/types/jsx.d.ts +1456 -36
  53. package/types/vue.d.ts +277 -24
  54. package/types/words.d.ts +64 -0
@@ -19,6 +19,20 @@ import { type PropertyValues, type TemplateResult } from 'lit';
19
19
  * The page is the window unless `scroller` names the element that scrolls, for
20
20
  * an app shell whose content scrolls inside a container.
21
21
  *
22
+ * **Set `hide-on-scroll` for a bar that moves out of the way.** Scrolling down
23
+ * past the bar's own height hides it, and scrolling up shows it again, as does
24
+ * focus moving into it. The custom state `hidden` is on the host while it is
25
+ * hidden, and the styled tier slides a `sticky` bar up out of view.
26
+ *
27
+ * **Set `fold-below` to fold the middle region on a narrow screen.** Below that
28
+ * many pixels of the bar's own width, the middle region is hidden behind a
29
+ * toggle at the end of the bar, named by `menu-label`. The toggle says whether
30
+ * the region is open with `aria-expanded`, and the region opens below the row.
31
+ * Escape closes it and puts focus back on the toggle. The custom state
32
+ * `folded` is on the host while the bar is that narrow, and `open` while the
33
+ * folded region is open. A server serves the bar unfolded, and the browser
34
+ * folds it once it has measured it.
35
+ *
22
36
  * The host is the page's `banner` landmark. Placed inside `main`, `article`,
23
37
  * `aside`, `nav` or `section`, it is a plain box instead, as a native
24
38
  * `<header>` is there, so a header inside a card or a pane does not add a
@@ -36,6 +50,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
36
50
  *
37
51
  * @slot - The middle region, usually a `Navigation`.
38
52
  * @slot end - Trailing content.
53
+ * @slot menu-icon - Replaces the glyph inside the toggle that opens a folded middle region.
39
54
  * @slot start - Leading content, usually a wordmark.
40
55
  *
41
56
  * @event {HelixScrolledEvent} helix-scrolled - Fired when the page crosses the
@@ -48,10 +63,20 @@ import { type PropertyValues, type TemplateResult } from 'lit';
48
63
  * @csspart end - The trailing region.
49
64
  * @csspart row - The centered row inside the banner.
50
65
  * @csspart start - The leading region.
66
+ * @csspart toggle - The control that opens and closes the folded middle region, drawn while the bar is narrower than `fold-below`.
67
+ * @csspart toggle-mark - The glyph on the toggle, where none is slotted.
51
68
  *
69
+ * @cssstate folded - While the bar is narrower than `fold-below`, and the middle region is behind the toggle.
70
+ * @cssstate hidden - While `hide-on-scroll` has the bar out of the way, after a scroll down.
71
+ * @cssstate open - While the folded middle region is open.
52
72
  * @cssstate scrolled - While the page is scrolled down from the top, which is when the bar lifts off it.
53
73
  */
54
74
  export declare class HelixHeadlessAppHeader extends HelixElement {
75
+ /**
76
+ * The key this family's words are registered under, its styled tag:
77
+ * `registerWords('de', { 'helix-app-header': { menuLabel: 'Menü' } })`.
78
+ */
79
+ static words: string;
55
80
  /**
56
81
  * Where the middle region sits in the bar.
57
82
  *
@@ -68,6 +93,47 @@ export declare class HelixHeadlessAppHeader extends HelixElement {
68
93
  * the property carries the part's name, `content`.
69
94
  */
70
95
  contentAlign?: 'start';
96
+ /**
97
+ * The width of the bar, in pixels, below which the middle region folds behind
98
+ * a toggle. Unset, it never folds.
99
+ */
100
+ foldBelow?: number;
101
+ /**
102
+ * Hides the bar as the page scrolls down past it, and shows it again as the
103
+ * page scrolls up.
104
+ */
105
+ hideOnScroll: boolean;
106
+ /** Whether the bar is narrower than `fold-below`, which only the observer sets. */
107
+ private folded;
108
+ /** Whether the folded middle region is open. */
109
+ private unfolded;
110
+ /** Watches the bar's own width while `fold-below` is set. */
111
+ private widths;
112
+ /** Starts or stops watching the width, according to `fold-below`. */
113
+ private watchWidth;
114
+ /** Folds the middle region where the bar is narrower than `fold-below`. */
115
+ private fold;
116
+ /** Opens or closes the folded middle region. */
117
+ private readonly handleToggle;
118
+ /** Escape in the open region closes it and puts focus back on the toggle. */
119
+ private readonly handleKeydown;
120
+ /** Whether the bar is out of the way, under `hide-on-scroll`. */
121
+ private away;
122
+ /** Where the page was last scrolled to, to tell down from up. */
123
+ private lastY;
124
+ /** Shows a hidden bar again when focus moves into it. */
125
+ private readonly handleFocusIn;
126
+ /** Hides the bar or shows it again, under `hide-on-scroll`. */
127
+ private hide;
128
+ /**
129
+ * Hides the bar on a scroll down past its own height, and shows it on a
130
+ * scroll up. A few pixels either way are not a direction, so a bar does not
131
+ * flicker under a hand resting on a trackpad. Not while focus is in the bar
132
+ * or its folded region is open, which a reader is using.
133
+ */
134
+ private follow;
135
+ /** The name of the toggle that opens a folded middle region. */
136
+ menuLabel: string;
71
137
  /**
72
138
  * The id of the element whose scrolling lifts the bar, for an app shell that
73
139
  * scrolls a container instead of the window. Unset is the window.
@@ -111,7 +177,16 @@ export declare class HelixHeadlessAppHeader extends HelixElement {
111
177
  /** Whether this header is the page's banner, read from where it sits. */
112
178
  private readLandmark;
113
179
  disconnectedCallback(): void;
180
+ /** The `fold-below` the width watch was started for, so a change is measured at once. */
181
+ private watchedFor;
182
+ protected firstUpdated(changed: PropertyValues): void;
114
183
  protected updated(changed: PropertyValues): void;
184
+ /**
185
+ * The glyph on the toggle that opens a folded middle region, behind a seam:
186
+ * slot fallback, so a consumer replaces it by slotting, and a method, so a
187
+ * tier above replaces it by overriding.
188
+ */
189
+ protected renderMenuIcon(): TemplateResult;
115
190
  protected render(): TemplateResult;
116
191
  }
117
192
  declare global {
@@ -4,18 +4,41 @@ 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, elementById, HelixElement, HelixScrolledEvent, hostAria, } from '@fusion.dev/helix-core';
8
- import { html } from 'lit';
9
- import { property } from 'lit/decorators.js';
7
+ import { customElement, deepActiveElement, deepContains, elementById, HelixElement, HelixScrolledEvent, hostAria, words, } from '@fusion.dev/helix-core';
8
+ import { list } from '@fusion.dev/helix-icons';
9
+ import { html, nothing } from 'lit';
10
+ import { property, state } from 'lit/decorators.js';
11
+ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
10
12
  import { inSectioningContent } from '../sectioning.js';
11
13
  let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
12
14
  constructor() {
13
15
  super(...arguments);
16
+ this.hideOnScroll = false;
17
+ this.folded = false;
18
+ this.unfolded = false;
19
+ this.handleToggle = () => {
20
+ this.unfolded = !this.unfolded;
21
+ };
22
+ this.handleKeydown = (event) => {
23
+ if (event.key !== 'Escape' || !this.unfolded || event.defaultPrevented) {
24
+ return;
25
+ }
26
+ event.preventDefault();
27
+ this.unfolded = false;
28
+ void this.updateComplete.then(() => this.partNamed('toggle')?.focus());
29
+ };
30
+ this.away = false;
31
+ this.lastY = 0;
32
+ this.handleFocusIn = () => {
33
+ this.hide(false);
34
+ };
35
+ this.menuLabel = 'Menu';
14
36
  this.landmark = 'banner';
15
37
  this.scrolled = false;
16
38
  this.handleScroll = () => {
17
39
  const source = this.source ?? window;
18
40
  const y = source instanceof Element ? source.scrollTop : window.scrollY;
41
+ this.follow(y);
19
42
  const next = this.scrolled ? y > 2 : y > 8;
20
43
  if (next === this.scrolled) {
21
44
  return;
@@ -25,6 +48,57 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
25
48
  this.dispatchEvent(new HelixScrolledEvent({ scrolled: next }));
26
49
  };
27
50
  }
51
+ static { this.words = 'helix-app-header'; }
52
+ watchWidth() {
53
+ if (!this.foldBelow || typeof ResizeObserver === 'undefined') {
54
+ this.widths?.disconnect();
55
+ this.widths = undefined;
56
+ this.folded = false;
57
+ return;
58
+ }
59
+ if (this.widths) {
60
+ return;
61
+ }
62
+ this.widths = new ResizeObserver(([entry]) => {
63
+ const width = entry?.borderBoxSize?.[0]?.inlineSize ?? entry?.contentRect.width ?? 0;
64
+ if (width > 0) {
65
+ this.fold(width);
66
+ }
67
+ });
68
+ this.widths.observe(this);
69
+ }
70
+ fold(width) {
71
+ const folded = this.foldBelow !== undefined && width < this.foldBelow;
72
+ if (folded !== this.folded) {
73
+ this.folded = folded;
74
+ this.unfolded = false;
75
+ }
76
+ }
77
+ hide(away) {
78
+ if (away === this.away) {
79
+ return;
80
+ }
81
+ this.away = away;
82
+ this.toggleState('hidden', away);
83
+ }
84
+ follow(y) {
85
+ const moved = y - this.lastY;
86
+ this.lastY = y;
87
+ if (!this.hideOnScroll) {
88
+ this.hide(false);
89
+ return;
90
+ }
91
+ const height = this.getBoundingClientRect().height;
92
+ if (y <= height) {
93
+ this.hide(false);
94
+ }
95
+ else if (moved > 4 && !this.unfolded && !deepContains(this, deepActiveElement())) {
96
+ this.hide(true);
97
+ }
98
+ else if (moved < -4) {
99
+ this.hide(false);
100
+ }
101
+ }
28
102
  findSource() {
29
103
  return (this.scroller ? elementById(this, this.scroller) : null) ?? window;
30
104
  }
@@ -44,6 +118,8 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
44
118
  willUpdate(changed) {
45
119
  super.willUpdate(changed);
46
120
  hostAria(this.internals, { role: this.landmark });
121
+ this.toggleState('folded', this.folded);
122
+ this.toggleState('open', this.folded && this.unfolded);
47
123
  }
48
124
  connectedCallback() {
49
125
  super.connectedCallback();
@@ -54,6 +130,11 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
54
130
  }
55
131
  });
56
132
  this.listen();
133
+ this.addEventListener('focusin', this.handleFocusIn);
134
+ this.addEventListener('keydown', this.handleKeydown);
135
+ if (this.hasUpdated) {
136
+ this.watchWidth();
137
+ }
57
138
  }
58
139
  readLandmark() {
59
140
  this.landmark = inSectioningContent(this) ? '' : 'banner';
@@ -63,19 +144,57 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
63
144
  super.disconnectedCallback();
64
145
  this.source?.removeEventListener('scroll', this.handleScroll);
65
146
  this.source = undefined;
147
+ this.removeEventListener('focusin', this.handleFocusIn);
148
+ this.removeEventListener('keydown', this.handleKeydown);
149
+ this.widths?.disconnect();
150
+ this.widths = undefined;
151
+ }
152
+ firstUpdated(changed) {
153
+ super.firstUpdated(changed);
154
+ this.watchedFor = this.foldBelow;
155
+ this.watchWidth();
66
156
  }
67
157
  updated(changed) {
68
158
  super.updated(changed);
69
159
  if (changed.has('scroller') && this.isConnected) {
70
160
  this.listen();
71
161
  }
162
+ if (this.foldBelow !== this.watchedFor) {
163
+ this.watchedFor = this.foldBelow;
164
+ this.watchWidth();
165
+ const width = this.getBoundingClientRect().width;
166
+ if (this.widths && width > 0) {
167
+ this.fold(width);
168
+ }
169
+ }
170
+ if (changed.has('hideOnScroll') && !this.hideOnScroll) {
171
+ this.hide(false);
172
+ }
173
+ }
174
+ renderMenuIcon() {
175
+ return html `<slot name="menu-icon">
176
+ <svg part="toggle-mark" fill="currentColor" viewBox=${list.viewBox} aria-hidden="true">
177
+ ${unsafeSVG(list.content)}
178
+ </svg>
179
+ </slot>`;
72
180
  }
73
181
  render() {
74
182
  return html `
75
183
  <div part="row">
76
184
  <div part="start"><slot name="start"></slot></div>
77
- <div part="content"><slot></slot></div>
78
- <div part="end"><slot name="end"></slot></div>
185
+ <div part="content" id="content" ?hidden=${this.folded && !this.unfolded}><slot></slot></div>
186
+ <div part="end"><slot name="end"></slot>${this.folded
187
+ ? html `<button
188
+ part="toggle"
189
+ type="button"
190
+ aria-controls="content"
191
+ aria-expanded=${this.unfolded ? 'true' : 'false'}
192
+ aria-label=${this.menuLabel}
193
+ @click=${this.handleToggle}
194
+ >
195
+ ${this.renderMenuIcon()}
196
+ </button>`
197
+ : nothing}</div>
79
198
  </div>
80
199
  `;
81
200
  }
@@ -83,6 +202,22 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
83
202
  __decorate([
84
203
  property({ attribute: 'content-align', reflect: true })
85
204
  ], HelixHeadlessAppHeader.prototype, "contentAlign", void 0);
205
+ __decorate([
206
+ property({ attribute: 'fold-below', type: Number })
207
+ ], HelixHeadlessAppHeader.prototype, "foldBelow", void 0);
208
+ __decorate([
209
+ property({ attribute: 'hide-on-scroll', type: Boolean })
210
+ ], HelixHeadlessAppHeader.prototype, "hideOnScroll", void 0);
211
+ __decorate([
212
+ state()
213
+ ], HelixHeadlessAppHeader.prototype, "folded", void 0);
214
+ __decorate([
215
+ state()
216
+ ], HelixHeadlessAppHeader.prototype, "unfolded", void 0);
217
+ __decorate([
218
+ words,
219
+ property({ attribute: 'menu-label' })
220
+ ], HelixHeadlessAppHeader.prototype, "menuLabel", void 0);
86
221
  __decorate([
87
222
  property()
88
223
  ], HelixHeadlessAppHeader.prototype, "scroller", void 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())
@@ -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));
@@ -1,5 +1,5 @@
1
1
  import { HelixElement } from '@fusion.dev/helix-core';
2
- import { type PropertyValues, type TemplateResult } from 'lit';
2
+ import { type CSSResultGroup, type PropertyValues, type TemplateResult } from 'lit';
3
3
  /**
4
4
  * @summary One step in a breadcrumb trail.
5
5
  *
@@ -18,7 +18,10 @@ import { type PropertyValues, type TemplateResult } from 'lit';
18
18
  * including your own anchor.
19
19
  *
20
20
  * The current page is plain text, and `aria-current="page"` on the host conveys
21
- * the position.
21
+ * the position. In a trail where no crumb is `current`, the last one is said
22
+ * to be the current page, and keeps its link. An `aria-current` you write on
23
+ * the host, such as `aria-current="location"`, replaces the one the crumb
24
+ * gives itself.
22
25
  *
23
26
  * @since 0.1.0
24
27
  * @status stable
@@ -42,6 +45,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
42
45
  * the label and what follows it into one control.
43
46
  */
44
47
  export declare class HelixHeadlessBreadcrumb extends HelixElement {
48
+ static styles: CSSResultGroup;
45
49
  /**
46
50
  * Marks this item as the page you are on.
47
51
  */
@@ -65,6 +69,11 @@ export declare class HelixHeadlessBreadcrumb extends HelixElement {
65
69
  * of `breadcrumbs/links` lost its padding at once.
66
70
  */
67
71
  href?: string;
72
+ /**
73
+ * @internal Set by the trail: this is its last crumb and no crumb is
74
+ * `current`, so this one is said to be the current page.
75
+ */
76
+ implied: boolean;
68
77
  /**
69
78
  * The link's `rel` value, which describes how the linked page relates to this
70
79
  * one.
@@ -6,23 +6,29 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  import { customElement, HelixElement, hostAria, slotFilled, slotNamed, } from '@fusion.dev/helix-core';
8
8
  import { caretRight } from '@fusion.dev/helix-icons';
9
- import { html, nothing } from 'lit';
9
+ import { css, html, nothing, } from 'lit';
10
10
  import { property } from 'lit/decorators.js';
11
11
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
12
  let HelixHeadlessBreadcrumb = class HelixHeadlessBreadcrumb extends HelixElement {
13
13
  constructor() {
14
14
  super(...arguments);
15
15
  this.current = false;
16
+ this.implied = false;
16
17
  this.rel = '';
17
18
  this.target = '';
18
19
  this.handleEnd = () => {
19
20
  this.markEnd();
20
21
  };
21
22
  }
23
+ static { this.styles = css `
24
+ :host(:first-child) [part='separator'] {
25
+ display: none;
26
+ }
27
+ `; }
22
28
  willUpdate(changed) {
23
29
  super.willUpdate(changed);
24
30
  hostAria(this.internals, { role: 'listitem' });
25
- this.internals.ariaCurrent = this.current ? 'page' : null;
31
+ this.internals.ariaCurrent = this.current || this.implied ? 'page' : null;
26
32
  }
27
33
  updated(changed) {
28
34
  super.updated(changed);
@@ -60,6 +66,9 @@ __decorate([
60
66
  __decorate([
61
67
  property({ reflect: true })
62
68
  ], HelixHeadlessBreadcrumb.prototype, "href", void 0);
69
+ __decorate([
70
+ property({ attribute: false })
71
+ ], HelixHeadlessBreadcrumb.prototype, "implied", void 0);
63
72
  __decorate([
64
73
  property()
65
74
  ], HelixHeadlessBreadcrumb.prototype, "rel", void 0);
@@ -1,6 +1,5 @@
1
1
  import { HelixElement } from '@fusion.dev/helix-core';
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
- import '../breadcrumb/breadcrumb.js';
4
3
  /** One step of a trail written as data, for `items`. */
5
4
  export interface BreadcrumbItem {
6
5
  /** The URL the step links to. The last step is the current page and takes none. */
@@ -26,7 +25,8 @@ export interface BreadcrumbItem {
26
25
  * markers.
27
26
  *
28
27
  * The items are slotted directly, so they are `<ol>` children in the flat tree,
29
- * and each item sets `role="listitem"` on itself.
28
+ * and each item sets `role="listitem"` on itself. Where no slotted crumb is
29
+ * `current`, the last one is said to be the current page.
30
30
  *
31
31
  * The trail does not measure itself, and you write its overflow in markup: put
32
32
  * the crumb that holds the folded items where the gap should appear.
@@ -41,6 +41,11 @@ export interface BreadcrumbItem {
41
41
  * to the first crumb it brought back. A new `items` folds again. Slotted crumbs
42
42
  * do not fold; write the folded crumb where the gap should be.
43
43
  *
44
+ * **Keyboard.** Each crumb with an `href` is a link and one tab stop, and
45
+ * Enter follows it. The current page and the separators are not in the tab
46
+ * order. In a folded trail, Enter or Space on the fold's button shows the
47
+ * whole trail and moves focus to the first crumb it brought back.
48
+ *
44
49
  * @since 0.1.0
45
50
  * @status stable
46
51
  * @category navigation
@@ -96,8 +101,15 @@ export declare class HelixHeadlessBreadcrumbs extends HelixElement {
96
101
  /** Shows the whole trail and moves focus to the first crumb it brought back. */
97
102
  private readonly expand;
98
103
  protected willUpdate(changed: PropertyValues): void;
104
+ protected updated(changed: PropertyValues): void;
99
105
  /** How many crumbs drawn from `items` are folded away, or 0. */
100
106
  private get folded();
107
+ /**
108
+ * Says the last slotted crumb is the current page where none is marked
109
+ * `current`, as React Aria's trail does. Through a property the crumb does
110
+ * not reflect, so nothing is written onto your markup.
111
+ */
112
+ private readonly markLast;
101
113
  /** The crumbs `items` describes, each a crumb of this tier. */
102
114
  protected renderItems(): TemplateResult | typeof nothing;
103
115
  protected render(): TemplateResult;
@@ -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 { customElement, formatWords, HelixElement, hostAria, words } from '@fusion.dev/helix-core';
7
+ import { customElement, formatWords, HelixElement, hostAria, whenHydrated, words, } from '@fusion.dev/helix-core';
8
8
  import { nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import { repeat } from 'lit/directives/repeat.js';
11
11
  import { html } from 'lit/static-html.js';
12
- import '../breadcrumb/breadcrumb.js';
12
+ import { HelixHeadlessBreadcrumb } from '../breadcrumb/breadcrumb.js';
13
13
  let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixElement {
14
14
  constructor() {
15
15
  super(...arguments);
@@ -24,6 +24,15 @@ let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixEleme
24
24
  crumb?.shadowRoot?.querySelector('a, [part="link"]')?.focus();
25
25
  });
26
26
  };
27
+ this.markLast = () => {
28
+ const crumbs = (this.renderRoot.querySelector('slot:not([name])')?.assignedElements() ?? []).filter((one) => one instanceof HelixHeadlessBreadcrumb);
29
+ const marked = crumbs.some((one) => one.current);
30
+ crumbs.forEach((crumb, index) => {
31
+ whenHydrated(crumb, () => {
32
+ crumb.implied = !marked && this.items.length === 0 && index === crumbs.length - 1;
33
+ });
34
+ });
35
+ };
27
36
  }
28
37
  static { this.words = 'helix-breadcrumbs'; }
29
38
  willUpdate(changed) {
@@ -33,6 +42,10 @@ let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixEleme
33
42
  this.expanded = false;
34
43
  }
35
44
  }
45
+ updated(changed) {
46
+ super.updated(changed);
47
+ this.markLast();
48
+ }
36
49
  get folded() {
37
50
  const most = this.maxItems ?? 0;
38
51
  const count = this.items.length;
@@ -77,7 +90,7 @@ let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixEleme
77
90
  }
78
91
  render() {
79
92
  return html `<ol part="list" role="list">
80
- ${this.renderItems()}<slot></slot>
93
+ ${this.renderItems()}<slot @slotchange=${this.markLast}></slot>
81
94
  </ol>`;
82
95
  }
83
96
  };
@@ -1,3 +1,13 @@
1
+ /**
2
+ * The elements at the start of a list that hold words and no control, up to the
3
+ * first one that is or holds a control.
4
+ *
5
+ * `skip` is an element whose words are said elsewhere, such as the heading
6
+ * that names a panel. It is left out, and an element holding it is read for
7
+ * its other children, so a card's title names the panel and the card's text
8
+ * describes it.
9
+ */
10
+ export declare const wordsAmong: (elements: readonly Element[], skip?: Element | null) => Element[];
1
11
  /**
2
12
  * The elements at the start of a slot that hold words and no control, up to the
3
13
  * first one that is or holds a control.