@fusion.dev/helix-headless 0.37.0 → 0.39.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.
@@ -28,7 +28,7 @@ declare const HelixHeadlessHeatmap_base: typeof HelixElement & (new (...args: an
28
28
  * here is the dependency the tiers exist to prevent.
29
29
  *
30
30
  * @since 0.1.0
31
- * @status experimental
31
+ * @status stable
32
32
  * @category charts
33
33
  * @aliases Contribution graph, Uptime strip, Calendar heatmap, Activity graph
34
34
  * @documentation https://fusion.dev/helix/components/heatmap
@@ -40,7 +40,7 @@ declare const HelixHeadlessHeatmap_base: typeof HelixElement & (new (...args: an
40
40
  * @event {HelixResizeEvent} helix-resize - Fired when the number of cells drawn changes, carrying that number.
41
41
  *
42
42
  * @csspart cell - One reading.
43
- * @csspart empty - Shown when the table has no readings in it.
43
+ * @csspart empty - Shown when the table has no readings in it, and not before the table can be read at all: on a server, and while hydrating what one sent, there is no document to resolve `for` in and nothing is known yet.
44
44
  * @csspart end - The trailing slot's box.
45
45
  * @csspart footer - The three-slot row under the strip.
46
46
  * @csspart mark - The invisible point the tooltip is anchored to.
@@ -86,6 +86,9 @@ declare const HelixHeadlessHeatmap_base: typeof HelixElement & (new (...args: an
86
86
  export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
87
87
  /**
88
88
  * Shown when the table has no readings in it.
89
+ *
90
+ * Never shown while the table cannot be read at all, which is a server render
91
+ * and the hydrating one after it: see `sourceKnown`.
89
92
  */
90
93
  emptyText: string;
91
94
  /**
@@ -201,6 +204,23 @@ export declare class HelixHeadlessHeatmap extends HelixHeadlessHeatmap_base {
201
204
  * collision that type-checks in one direction and reads wrong in both.
202
205
  */
203
206
  private get points();
207
+ /**
208
+ * Whether this render can tell a table with no readings from one it has not
209
+ * resolved yet.
210
+ *
211
+ * **Unknown is not empty, which is what an empty state must never say.**
212
+ * `for` names an id and resolving an id wants a document, so a server has
213
+ * none and a hydrating render must not look either. Without this the server
214
+ * drew "No readings" over a table holding ninety days of them, a crawler read
215
+ * that sentence, and the strip flipped to a full one on hydration: the fault
216
+ * `MixinCollection` names for a slotted collection, arriving through the one
217
+ * channel that mixin cannot see.
218
+ *
219
+ * The same shape as `childrenKnown`, asked of a document rather than of a
220
+ * tree. An id that resolves to nothing in a browser is still known, and still
221
+ * empty: a `for` naming no table should say so rather than sit blank.
222
+ */
223
+ private get sourceKnown();
204
224
  /**
205
225
  * `rows` as a usable count, which is not always what the attribute says.
206
226
  *
@@ -38,6 +38,16 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
38
38
  get points() {
39
39
  return this.series[0]?.points ?? [];
40
40
  }
41
+ get sourceKnown() {
42
+ if (this.for === '') {
43
+ return true;
44
+ }
45
+ if (this.hydrating) {
46
+ return false;
47
+ }
48
+ const root = this.getRootNode();
49
+ return typeof root.getElementById === 'function';
50
+ }
41
51
  get rowCount() {
42
52
  return Math.max(1, Math.floor(this.rows) || 1);
43
53
  }
@@ -145,7 +155,7 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
145
155
  })}
146
156
  ${this.renderTooltip()}
147
157
  </div>
148
- ${cells.length === 0 ? html `<p part="empty">${this.emptyText}</p>` : nothing}
158
+ ${cells.length === 0 && this.sourceKnown ? html `<p part="empty">${this.emptyText}</p>` : nothing}
149
159
  <div part="footer">
150
160
  <span part="start"><slot name="start"></slot></span>
151
161
  <span part="summary"><slot name="summary"></slot></span>
@@ -21,7 +21,7 @@ declare const HelixHeadlessMegamenu_base: (new (...args: any[]) => import("@fusi
21
21
  * expects to sweep along a row and see each section.
22
22
  *
23
23
  * @since 0.1.0
24
- * @status experimental
24
+ * @status stable
25
25
  * @category navigation
26
26
  * @aliases Mega menu, Flyout, Nav panel
27
27
  * @documentation https://fusion.dev/helix/components/megamenu
@@ -38,6 +38,7 @@ declare const HelixHeadlessMegamenu_base: (new (...args: any[]) => import("@fusi
38
38
  *
39
39
  * @cssproperty --megamenu-top - Where the panel starts, measured from the bar above it.
40
40
  *
41
+ * @cssstate narrow - While the page is under `full-below`, so the panel is a band whether or not one was asked for.
41
42
  * @cssstate open - While the panel is open, which is when the trigger reads as current.
42
43
  */
43
44
  export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
@@ -55,8 +56,16 @@ export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
55
56
  * a band is not a question of paint: a band is not placed against the trigger,
56
57
  * so the controller has to be told not to measure, and that decision is here
57
58
  * whatever the stylesheet says. Writing the width in both places would be one
58
- * number in two languages, drifting the first time either moved. This sets
59
- * `data-narrow` on the host and the stylesheet arranges itself off that.
59
+ * number in two languages, drifting the first time either moved. This records
60
+ * `:state(narrow)` on the host and the stylesheet arranges itself off that.
61
+ *
62
+ * **A custom state and never `data-narrow`, which is what it was.** The width
63
+ * is read in `connectedCallback`, so on a phone this is true on the very first
64
+ * update: an attribute written there is markup a consumer's server never sent,
65
+ * which React reports as a mismatch and answers by rebuilding the subtree. The
66
+ * narrow exception the server specs describe covers a mirror that can only
67
+ * become true long after hydration, and a viewport measured before the first
68
+ * paint is the opposite case. See `MixinControllerOpen` on `open`.
60
69
  */
61
70
  fullBelow: number;
62
71
  /**
@@ -189,7 +189,7 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
189
189
  if (changed.has('fullBelow')) {
190
190
  this.watchWidth();
191
191
  }
192
- this.toggleAttribute('data-narrow', this.narrow);
192
+ this.toggleState('narrow', this.narrow);
193
193
  this.tellTrigger();
194
194
  if (this.open) {
195
195
  this.band();
@@ -54,7 +54,7 @@ interface Row {
54
54
  * gives it and a headless consumer has to.
55
55
  *
56
56
  * @since 0.1.0
57
- * @status experimental
57
+ * @status stable
58
58
  * @category data
59
59
  * @aliases JSON viewer, Object viewer, Inspector, Data explorer
60
60
  * @documentation https://fusion.dev/helix/components/object-inspector
@@ -120,6 +120,13 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
120
120
  *
121
121
  * Negative for the reason Code Block's is: a boolean attribute defaulting to
122
122
  * true could never be turned off from markup.
123
+ *
124
+ * **It does not reflect, where Code Block's does**, and the difference is what
125
+ * the two sheets need. Code Block reserves the button's width with
126
+ * `:host(:not([no-copy]))`, so the attribute has to be there for a consumer
127
+ * who set the property. This button is positioned out of the flow and takes no
128
+ * room, so nothing selects on it, and reflection exists so that a selector can
129
+ * reach a value.
123
130
  */
124
131
  noCopy: boolean;
125
132
  /**
@@ -179,6 +186,16 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
179
186
  private readonly handleKeydown;
180
187
  private handleRowClick;
181
188
  private renderValue;
189
+ /**
190
+ * One entry, named for its state where it holds one.
191
+ *
192
+ * **`row-active` and no attribute beside it.** Which row the keys are on is
193
+ * drawn by a sheet and by nothing else: the accessibility tree hears it from
194
+ * `aria-activedescendant` on the tree, which points at this row's id, so an
195
+ * attribute would be a second spelling with no reader. The name is the one a
196
+ * consumer can reach, nothing being able to follow `::part()` with an
197
+ * attribute selector.
198
+ */
182
199
  private renderRow;
183
200
  /** The copy button, behind the seam Code Block's uses, so each tier renders its own. */
184
201
  protected renderCopyButton(row: Row | undefined): TemplateResult;
@@ -262,7 +262,6 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
262
262
  role="treeitem"
263
263
  id=${`row-${index}`}
264
264
  data-index=${index}
265
- ?data-active=${active}
266
265
  aria-level=${row.depth + 1}
267
266
  aria-posinset=${row.position}
268
267
  aria-setsize=${row.siblings}
@@ -331,7 +330,7 @@ __decorate([
331
330
  property()
332
331
  ], HelixHeadlessObjectInspector.prototype, "label", void 0);
333
332
  __decorate([
334
- property({ attribute: 'no-copy', reflect: true, type: Boolean })
333
+ property({ attribute: 'no-copy', type: Boolean })
335
334
  ], HelixHeadlessObjectInspector.prototype, "noCopy", void 0);
336
335
  __decorate([
337
336
  property({ attribute: false })
@@ -24,7 +24,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
24
24
  *
25
25
  * @slot - The links, as anchors with fragment hrefs.
26
26
  *
27
- * @event {HelixBeforeActivateEvent} helix-before-activate - The section being read has changed, carrying its id as the value. Cancelable: refusing it takes the decision and nothing moves.
27
+ * @event {HelixActivateEvent} helix-activate - The section being read has changed, carrying its id as the value. An announcement: it is read off where the page is scrolled to, so there is no decision here to take.
28
28
  *
29
29
  * @csspart indicator - The mark that travels to the section being read.
30
30
  * @csspart nav - The navigation landmark.
@@ -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 { customElement, HelixBeforeActivateEvent, HelixElement, IndicatorController, } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixActivateEvent, HelixElement, IndicatorController, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
@@ -116,7 +116,7 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
116
116
  const next = active?.target.id ?? '';
117
117
  if (next !== this.reading) {
118
118
  this.reading = next;
119
- this.dispatchEvent(new HelixBeforeActivateEvent({ value: next }));
119
+ this.dispatchEvent(new HelixActivateEvent({ value: next }));
120
120
  }
121
121
  }
122
122
  get atEnd() {
@@ -20,6 +20,16 @@ export type CellReader = (row: Readonly<Record<string, unknown>>) => unknown;
20
20
  * into a table tells a reader the software is broken rather than the row is.
21
21
  */
22
22
  export declare const at: (row: Readonly<Record<string, unknown>>, path: string) => unknown;
23
+ /**
24
+ * The edge a column names, where it names one of the three.
25
+ *
26
+ * **`align` is a string anybody can write**, the union on the property being a
27
+ * compile-time claim and nothing else, and both tables put the value straight
28
+ * into CSS: `helix-table` into a rule in each row's shadow root, `helix-data-table`
29
+ * into the cell it draws. One guard, here with the property, rather than one in
30
+ * each table and only one of them written.
31
+ */
32
+ export declare const edgeOf: (align: string | undefined) => string | undefined;
23
33
  /**
24
34
  * @summary One column of a table: its heading, how wide it is, and what it reads.
25
35
  *
@@ -26,6 +26,8 @@ export const at = (row, path) => {
26
26
  }
27
27
  return cursor;
28
28
  };
29
+ const EDGES = new Set(['center', 'end', 'start']);
30
+ export const edgeOf = (align) => align && EDGES.has(align) ? align : undefined;
29
31
  let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixElement {
30
32
  constructor() {
31
33
  super(...arguments);
@@ -1,6 +1,6 @@
1
1
  import { HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
- import type { HelixHeadlessTableColumn } from '../column/table-column.js';
3
+ import { type HelixHeadlessTableColumn } from '../column/table-column.js';
4
4
  declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.dev/helix-core").CollectionElement) & typeof HelixElement;
5
5
  /**
6
6
  * @summary Tabular data laid out on a grid.
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement, MixinCollection } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
- const EDGES = new Set(['center', 'end', 'start']);
10
+ import { edgeOf } from '../column/table-column.js';
11
11
  const tracksOf = (list, wanted) => {
12
12
  const words = [];
13
13
  let depth = 0;
@@ -119,9 +119,10 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
119
119
  if (typeof CSSStyleSheet === 'undefined' || !('replaceSync' in CSSStyleSheet.prototype)) {
120
120
  return;
121
121
  }
122
- const rules = this.tableColumns.flatMap((column, index) => column.align && EDGES.has(column.align)
123
- ? [`::slotted(:nth-child(${index + 1})) { --table-cell-align: ${column.align}; }`]
124
- : []);
122
+ const rules = this.tableColumns.flatMap((column, index) => {
123
+ const edge = edgeOf(column.align);
124
+ return edge ? [`::slotted(:nth-child(${index + 1})) { --table-cell-align: ${edge}; }`] : [];
125
+ });
125
126
  const text = ['::slotted(*) { --table-cell-align: initial; }', ...rules].join('\n');
126
127
  this.alignment ??= new CSSStyleSheet();
127
128
  if (text !== this.aligned) {
@@ -38,7 +38,7 @@ declare const HelixHeadlessTooltip_base: (new (...args: any[]) => import("@fusio
38
38
  * been used.
39
39
  *
40
40
  * @since 0.1.0
41
- * @status experimental
41
+ * @status stable
42
42
  * @category overlays
43
43
  * @aliases Hint, Popup label
44
44
  * @documentation https://fusion.dev/helix/components/tooltip
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-headless",
3
3
  "description": "Headless component tier. Structure, accessibility, state, and form participation. No styling.",
4
- "version": "0.37.0",
4
+ "version": "0.39.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -46,14 +46,14 @@
46
46
  },
47
47
  "bugs": "https://github.com/project-fusion/fusion/issues",
48
48
  "dependencies": {
49
- "@fusion.dev/helix-core": "0.37.0",
50
- "@fusion.dev/helix-icons": "0.37.0",
49
+ "@fusion.dev/helix-core": "0.39.0",
50
+ "@fusion.dev/helix-icons": "0.39.0",
51
51
  "uqr": "^0.1.3"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@custom-elements-manifest/analyzer": "^0.11.0",
55
- "@fusion.dev/helix-cem": "0.37.0",
56
- "@fusion.dev/helix-testing": "0.37.0",
55
+ "@fusion.dev/helix-cem": "0.39.0",
56
+ "@fusion.dev/helix-testing": "0.39.0",
57
57
  "@fusion.dev/scripts": "0.0.0-dev",
58
58
  "@happy-dom/global-registrator": "^20.0.0",
59
59
  "@lit-labs/ssr": "^4.1.0",