@fusion.dev/helix-headless 0.58.1 → 0.59.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 (34) hide show
  1. package/custom-elements.json +24 -203
  2. package/dist/button/root/button-root.d.ts +1 -7
  3. package/dist/button/root/button-root.js +2 -3
  4. package/dist/carousel/root/carousel-root.js +2 -2
  5. package/dist/color-picker/root/color-picker-root.d.ts +7 -1
  6. package/dist/color-picker/root/color-picker-root.js +3 -3
  7. package/dist/data-table/root/data-table-root.js +2 -2
  8. package/dist/heatmap/index.d.ts +1 -1
  9. package/dist/heatmap/index.js +1 -1
  10. package/dist/heatmap/root/buckets.d.ts +0 -4
  11. package/dist/heatmap/root/buckets.js +0 -5
  12. package/dist/heatmap/root/heatmap-root.d.ts +5 -3
  13. package/dist/heatmap/root/heatmap-root.js +5 -6
  14. package/dist/legend/root/legend-root.d.ts +2 -2
  15. package/dist/outline/root/outline-root.d.ts +8 -1
  16. package/dist/outline/root/outline-root.js +2 -3
  17. package/dist/scatter-chart/root/scatter-chart-root.js +2 -2
  18. package/dist/skip-link/root/skip-link-root.d.ts +11 -1
  19. package/dist/skip-link/root/skip-link-root.js +2 -4
  20. package/dist/sparkline/root/sparkline-root.js +2 -5
  21. package/dist/splitter/root/splitter-root.js +3 -3
  22. package/dist/table/column/table-column.js +2 -2
  23. package/dist/table/root/table-root.js +2 -24
  24. package/dist/textarea/root/textarea-root.js +2 -2
  25. package/dist/time-input/root/time-input-root.js +10 -11
  26. package/dist/time-picker/root/time-picker-root.d.ts +0 -27
  27. package/dist/time-picker/root/time-picker-root.js +9 -32
  28. package/dist/tooltip/root/tooltip-root.d.ts +3 -10
  29. package/dist/tooltip/root/tooltip-root.js +3 -13
  30. package/dist/tour/stop/tour-stop.js +2 -3
  31. package/dist/video/root/video-root.js +2 -4
  32. package/package.json +5 -5
  33. package/types/jsx.d.ts +64 -68
  34. package/types/vue.d.ts +16 -20
@@ -4,11 +4,11 @@ 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, HelixElement, HelixResizeEvent, hostAria, MixinChartSource, markRegion, } from '@fusion.dev/helix-core';
7
+ import { CHART_PLOT, customElement, drawTooltip, HelixElement, HelixResizeEvent, hostAria, MixinChartSource, markRegion, searchableById, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import '../../tooltip/index.js';
11
- import { fits, numbers, toneOf, words } from './buckets.js';
11
+ import { fits, toneOf } from './buckets.js';
12
12
  let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(HelixElement) {
13
13
  constructor() {
14
14
  super(...arguments);
@@ -45,8 +45,7 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
45
45
  if (this.hydrating) {
46
46
  return false;
47
47
  }
48
- const root = this.getRootNode();
49
- return typeof root.getElementById === 'function';
48
+ return searchableById(this);
50
49
  }
51
50
  get rowCount() {
52
51
  return Math.max(1, Math.floor(this.rows) || 1);
@@ -124,8 +123,8 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
124
123
  this.announced = showing;
125
124
  }
126
125
  render() {
127
- const bounds = numbers(this.thresholds);
128
- const palette = words(this.tones);
126
+ const bounds = writtenNumbers(this.thresholds);
127
+ const palette = writtenList(this.tones);
129
128
  const cells = this.drawn;
130
129
  return html `<div
131
130
  part="strip"
@@ -53,8 +53,8 @@ export declare class HelixHeadlessLegend extends HelixElement {
53
53
  /**
54
54
  * The id of the table to take the names from.
55
55
  *
56
- * Resolved through `getRootNode()`, so a legend inside another component's
57
- * shadow root names a table in that root rather than in the document.
56
+ * Names a table in the same tree, so a legend inside another component's
57
+ * shadow root reads a table in that root rather than one in the document.
58
58
  *
59
59
  * **In React this arrives as a property and not as an attribute**, the way
60
60
  * `Tooltip.for` does, so it does not reflect and nothing styles on it.
@@ -84,7 +84,14 @@ export declare class HelixHeadlessOutline extends HelixElement {
84
84
  private observer;
85
85
  /** The links, in document order. Empty on the server, which has no tree. */
86
86
  private get links();
87
- /** What each link points at, paired with the link, skipping anything missing. */
87
+ /**
88
+ * What each link points at, paired with the link, skipping anything missing.
89
+ *
90
+ * The fragment is decoded first because these are `href`s and not ids. The
91
+ * lookup is the tree's alone: this intercepts no press, so it has no
92
+ * navigation to agree with, and a section it cannot see is one it is not
93
+ * describing.
94
+ */
88
95
  private get sections();
89
96
  /**
90
97
  * Works out which section is being read, and marks its link.
@@ -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, HelixActivateEvent, HelixElement, IndicatorController, } from '@fusion.dev/helix-core';
7
+ import { customElement, elementById, 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 {
@@ -82,10 +82,9 @@ let HelixHeadlessOutline = class HelixHeadlessOutline extends HelixElement {
82
82
  element.getAttribute('href')?.startsWith('#') === true);
83
83
  }
84
84
  get sections() {
85
- const root = this.getRootNode();
86
85
  return this.links.flatMap((link) => {
87
86
  const id = decodeURIComponent(link.getAttribute('href')?.slice(1) ?? '');
88
- const target = id ? root.getElementById?.(id) : null;
87
+ const target = elementById(this, id);
89
88
  return target && target.getClientRects().length > 0 ? [{ link, target }] : [];
90
89
  });
91
90
  }
@@ -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, extent, formatNumbers, HelixElement, linear, MixinChartSource, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, ticks, } from '@fusion.dev/helix-core';
7
+ import { CHART_PLOT, customElement, drawTooltip, extent, formatNumbers, HelixElement, isRtl, linear, MixinChartSource, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, ticks, } 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';
@@ -190,7 +190,7 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
190
190
  return drawTooltip(this.sourceRows, -1, this.tag('tooltip'));
191
191
  }
192
192
  return drawTooltip(this.sourceRows, at, this.tag('tooltip'), {
193
- dir: getComputedStyle(this).direction === 'rtl' ? 'rtl' : 'ltr',
193
+ dir: isRtl(this) ? 'rtl' : 'ltr',
194
194
  fixed: true,
195
195
  placement: 'top',
196
196
  x: `${point.x}%`,
@@ -60,7 +60,17 @@ export declare class HelixHeadlessSkipLink extends HelixElement {
60
60
  * appearing beside it.
61
61
  */
62
62
  label: string;
63
- /** The element `href` names, looked up in the tree this link sits in. */
63
+ /**
64
+ * The element `href` names, looked up the way the browser would.
65
+ *
66
+ * `orDocument`, which no other caller of `elementById` passes and which that
67
+ * option is written for: a press handled here has to land where following the
68
+ * anchor would, and the platform resolves a fragment against the document
69
+ * whatever tree the anchor is in. A skip link inside an app shell's shadow
70
+ * root pointing at the page's `#main` is the arrangement that needs it.
71
+ *
72
+ * The fragment is decoded first because this is an `href` and not an id.
73
+ */
64
74
  private get target();
65
75
  /**
66
76
  * Focuses the target, lending it a `tabindex` only if it needs one.
@@ -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, HelixElement } from '@fusion.dev/helix-core';
7
+ import { customElement, elementById, HelixElement } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessSkipLink = class HelixHeadlessSkipLink extends HelixElement {
@@ -38,9 +38,7 @@ let HelixHeadlessSkipLink = class HelixHeadlessSkipLink extends HelixElement {
38
38
  if (!this.href.startsWith('#') || this.href.length < 2) {
39
39
  return null;
40
40
  }
41
- const id = decodeURIComponent(this.href.slice(1));
42
- const root = this.getRootNode();
43
- return (root.getElementById?.(id) ?? document.getElementById(id));
41
+ return elementById(this, decodeURIComponent(this.href.slice(1)), { orDocument: true });
44
42
  }
45
43
  render() {
46
44
  return html `<a part="link" href=${this.href} @click=${this.skip}><slot>${this.label}</slot></a>`;
@@ -4,13 +4,10 @@ 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, extent, HelixElement, linear, linePath, PLOT_WIDTH, SourceController, writtenList, } from '@fusion.dev/helix-core';
7
+ import { customElement, extent, HelixElement, linear, linePath, PLOT_WIDTH, SourceController, writtenNumbers, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing, svg } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  const STRIP_HEIGHT = 32;
11
- const readValues = (input) => writtenList(input)
12
- .map(Number)
13
- .filter((one) => Number.isFinite(one));
14
11
  let HelixHeadlessSparkline = class HelixHeadlessSparkline extends HelixElement {
15
12
  constructor() {
16
13
  super(...arguments);
@@ -28,7 +25,7 @@ let HelixHeadlessSparkline = class HelixHeadlessSparkline extends HelixElement {
28
25
  const series = this.source.series;
29
26
  return series.length > 0
30
27
  ? (series[0]?.points.map((point) => point.value) ?? [])
31
- : readValues(this.values);
28
+ : writtenNumbers(this.values);
32
29
  }
33
30
  render() {
34
31
  const points = this.points();
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessSplitter_1;
8
- import { capturePointer, closestInstance, customElement, HelixBeforeCollapseEvent, HelixBeforeResizeEvent, HelixElement, inlineStep, requestChange, } from '@fusion.dev/helix-core';
8
+ import { capturePointer, closestInstance, customElement, HelixBeforeCollapseEvent, HelixBeforeResizeEvent, HelixElement, inlineStep, isRtl, requestChange, } from '@fusion.dev/helix-core';
9
9
  import { html } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  const STACK_BELOW = 512;
@@ -51,7 +51,7 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
51
51
  }
52
52
  const along = this.vertical ? event.clientY : event.clientX;
53
53
  const raw = ((along - origin.start) / origin.length) * 100;
54
- const rtl = !this.vertical && getComputedStyle(this).direction === 'rtl';
54
+ const rtl = !this.vertical && isRtl(this);
55
55
  this.move(rtl ? 100 - raw : raw);
56
56
  };
57
57
  this.handlePointerUp = (event) => {
@@ -129,7 +129,7 @@ let HelixHeadlessSplitter = HelixHeadlessSplitter_1 = class HelixHeadlessSplitte
129
129
  if (!origin) {
130
130
  return;
131
131
  }
132
- const rtl = getComputedStyle(this).direction === 'rtl';
132
+ const rtl = isRtl(this);
133
133
  const along = (box, at, inline) => {
134
134
  if (box.length === 0) {
135
135
  return undefined;
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessTableColumn_1;
8
- import { customElement, HelixBeforeResizeEvent, HelixElement, hostAria, inlineStep, requestChange, } from '@fusion.dev/helix-core';
8
+ import { customElement, HelixBeforeResizeEvent, HelixElement, hostAria, inlineStep, isRtl, requestChange, } from '@fusion.dev/helix-core';
9
9
  import { dotsThreeVertical } from '@fusion.dev/helix-icons';
10
10
  import { html, nothing } from 'lit';
11
11
  import { property, state } from 'lit/decorators.js';
@@ -160,7 +160,7 @@ let HelixHeadlessTableColumn = class HelixHeadlessTableColumn extends HelixEleme
160
160
  });
161
161
  }
162
162
  get forward() {
163
- return getComputedStyle(this).direction === 'rtl' ? -1 : 1;
163
+ return isRtl(this) ? -1 : 1;
164
164
  }
165
165
  render() {
166
166
  const name = this.name;
@@ -4,34 +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, HelixElement, MixinCollection } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, MixinCollection, splitValues } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { edgeOf } from '../column/table-column.js';
11
11
  const tracksOf = (list, wanted) => {
12
- const words = [];
13
- let depth = 0;
14
- let word = '';
15
- for (const char of list.trim()) {
16
- if (char === '(' || char === '[') {
17
- depth += 1;
18
- }
19
- else if (char === ')' || char === ']') {
20
- depth -= 1;
21
- }
22
- if (depth === 0 && /\s/.test(char)) {
23
- if (word) {
24
- words.push(word);
25
- }
26
- word = '';
27
- }
28
- else {
29
- word += char;
30
- }
31
- }
32
- if (word) {
33
- words.push(word);
34
- }
12
+ const words = splitValues(list, ' ');
35
13
  const valid = (track) => typeof CSS === 'undefined' || CSS.supports('grid-template-columns', track);
36
14
  const tracks = [];
37
15
  let names = '';
@@ -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 { AriaForwardController, customElement, HelixFormElement, holdCaret, MixinControllerValue, MixinField, restoreValue, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, HelixFormElement, holdCaret, isRtl, MixinControllerValue, MixinField, restoreValue, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessTextarea = class HelixHeadlessTextarea extends MixinControllerValue(MixinField(HelixFormElement)) {
@@ -60,7 +60,7 @@ let HelixHeadlessTextarea = class HelixHeadlessTextarea extends MixinControllerV
60
60
  }
61
61
  const data = new FormData();
62
62
  data.append(this.name, this.value);
63
- data.append(this.dirName, getComputedStyle(control).direction === 'rtl' ? 'rtl' : 'ltr');
63
+ data.append(this.dirName, isRtl(control) ? 'rtl' : 'ltr');
64
64
  return data;
65
65
  }
66
66
  get selectionDirection() {
@@ -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 { AriaForwardController, valueAsTime as asTime, customElement, timeAsValue as fromTime, HelixFormElement, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerEditing, MixinPickerField, MixinTokens, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, valueAsTime as asTime, customElement, timeAsValue as fromTime, HelixFormElement, isTimeValue, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerEditing, MixinPickerField, MixinTokens, valueAsMinutes, writeTime, } from '@fusion.dev/helix-core';
8
8
  import { clock } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -13,7 +13,6 @@ import { html } from 'lit/static-html.js';
13
13
  import '../../icon/index.js';
14
14
  import '../../tag/index.js';
15
15
  import '../../time-picker/index.js';
16
- import { isTime, toMinutes, writeTime } from '../../time-picker/root/time-picker-root.js';
17
16
  let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinPickerEditing(MixinPickerField(MixinControllerOpen(MixinTokens(MixinControllerValue(MixinField(HelixFormElement)))))) {
18
17
  constructor() {
19
18
  super(...arguments);
@@ -34,7 +33,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinPickerEdi
34
33
  return;
35
34
  }
36
35
  const held = this.held;
37
- if (event.key === 'Enter' && isTime(this.draft)) {
36
+ if (event.key === 'Enter' && isTimeValue(this.draft)) {
38
37
  event.preventDefault();
39
38
  if (!held.includes(this.draft)) {
40
39
  this.requestValue([...held, this.draft].sort().join(','));
@@ -61,7 +60,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinPickerEdi
61
60
  .split(',')
62
61
  .map((one) => one.trim())
63
62
  .filter(Boolean);
64
- const whole = held.length > 0 && held.every(isTime);
63
+ const whole = held.length > 0 && held.every(isTimeValue);
65
64
  this.requestValue(whole ? held.join(',') : typed);
66
65
  this.restore();
67
66
  };
@@ -83,7 +82,7 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinPickerEdi
83
82
  .filter(Boolean);
84
83
  }
85
84
  written(at) {
86
- return this.timeStyle === 'iso' || !isTime(at) ? at : writeTime(at, this.locale);
85
+ return this.timeStyle === 'iso' || !isTimeValue(at) ? at : writeTime(at, this.locale);
87
86
  }
88
87
  get display() {
89
88
  if (this.mode === 'multiple') {
@@ -105,21 +104,21 @@ let HelixHeadlessTimeInput = class HelixHeadlessTimeInput extends MixinPickerEdi
105
104
  }
106
105
  get picked() {
107
106
  const held = this.held;
108
- return held.length > 0 && held.every(isTime) ? held.join(',') : '';
107
+ return held.length > 0 && held.every(isTimeValue) ? held.join(',') : '';
109
108
  }
110
109
  validate() {
111
110
  const held = this.held;
112
- const floor = toMinutes(this.min) ?? 0;
113
- const ceiling = toMinutes(this.max) ?? 1439;
114
- if (held.length > 0 && !held.every(isTime)) {
111
+ const floor = valueAsMinutes(this.min) ?? 0;
112
+ const ceiling = valueAsMinutes(this.max) ?? 1439;
113
+ if (held.length > 0 && !held.every(isTimeValue)) {
115
114
  return this.mode === undefined
116
115
  ? 'Enter a time as HH:mm.'
117
116
  : 'Enter times as HH:mm, separated by commas.';
118
117
  }
119
- if (held.some((one) => (toMinutes(one) ?? 0) < floor)) {
118
+ if (held.some((one) => (valueAsMinutes(one) ?? 0) < floor)) {
120
119
  return `Choose a time at or after ${writeTime(this.min, this.locale)}.`;
121
120
  }
122
- if (held.some((one) => (toMinutes(one) ?? 0) > ceiling)) {
121
+ if (held.some((one) => (valueAsMinutes(one) ?? 0) > ceiling)) {
123
122
  return `Choose a time at or before ${writeTime(this.max, this.locale)}.`;
124
123
  }
125
124
  if (this.mode === 'range' && held.length > 2) {
@@ -2,33 +2,6 @@ import { HelixFormElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  import '../../listbox/index.js';
4
4
  import '../../option/index.js';
5
- /**
6
- * Whether a string is a time the rest of this component can reason about.
7
- *
8
- * **Narrower than core's `valueAsTime` on purpose, and the difference is
9
- * seconds.** That one takes them because `<input type="time">` does, at a step
10
- * below a minute. This library has no such step: `step` here is minutes and is
11
- * floored at one, so nothing it offers or writes ever carries a second, and
12
- * `toMinutes` below would drop one silently if this let it through. Widening
13
- * this to the platform's shape would let a value in that the rest of the family
14
- * cannot hold.
15
- */
16
- export declare const isTime: (value: string) => boolean;
17
- /** Minutes since midnight, or `null` where it is not a time. */
18
- export declare const toMinutes: (value: string) => number | null;
19
- /** Minutes since midnight back to `HH:mm`, which is the value shape throughout. */
20
- export declare const fromMinutes: (minutes: number) => string;
21
- /**
22
- * A time written the way the reader's locale writes one.
23
- *
24
- * `Intl` decides between a twelve and a twenty-four hour clock, because that is
25
- * a property of the locale rather than of this component: `en-US` gets
26
- * `2:30 PM` and `en-GB` gets `14:30` without either being configured. There is
27
- * no flag to force one, deliberately. A product that needs to override its
28
- * reader's own convention has a locale problem rather than a component one, and
29
- * the value is 24 hour regardless.
30
- */
31
- export declare const writeTime: (value: string, locale?: string) => string;
32
5
  declare const HelixHeadlessTimePicker_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & typeof HelixFormElement;
33
6
  /**
34
7
  * @summary Picks a time, from a list of them.
@@ -4,35 +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 { valueAsTime as asTime, customElement, timeAsValue as fromTime, HelixFormElement, MixinControllerValue, } from '@fusion.dev/helix-core';
7
+ import { valueAsTime as asTime, customElement, timeAsValue as fromTime, HelixFormElement, MixinControllerValue, minutesAsValue, valueAsMinutes, writeTime, } from '@fusion.dev/helix-core';
8
8
  import { nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { html } from 'lit/static-html.js';
11
11
  import '../../listbox/index.js';
12
12
  import '../../option/index.js';
13
- export const isTime = (value) => /^([01]\d|2[0-3]):[0-5]\d$/.test(value);
14
- export const toMinutes = (value) => {
15
- if (!isTime(value)) {
16
- return null;
17
- }
18
- const [hours = '0', minutes = '0'] = value.split(':');
19
- return Number(hours) * 60 + Number(minutes);
20
- };
21
- export const fromMinutes = (minutes) => {
22
- const whole = ((Math.round(minutes) % 1440) + 1440) % 1440;
23
- return `${String(Math.floor(whole / 60)).padStart(2, '0')}:${String(whole % 60).padStart(2, '0')}`;
24
- };
25
- export const writeTime = (value, locale) => {
26
- const minutes = toMinutes(value);
27
- if (minutes === null) {
28
- return value;
29
- }
30
- const at = new Date(Date.UTC(2000, 0, 1, Math.floor(minutes / 60), minutes % 60));
31
- return new Intl.DateTimeFormat(locale || undefined, {
32
- timeStyle: 'short',
33
- timeZone: 'UTC',
34
- }).format(at);
35
- };
36
13
  const isTwelveHour = (locale) => new Intl.DateTimeFormat(locale || undefined, { timeStyle: 'short' }).resolvedOptions().hour12 ===
37
14
  true;
38
15
  let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControllerValue(HelixFormElement) {
@@ -92,7 +69,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
92
69
  const half = hour % 12;
93
70
  next = (half + (to === 'PM' ? 12 : 0)) * 60 + minute;
94
71
  }
95
- const whole = fromMinutes(next);
72
+ const whole = minutesAsValue(next);
96
73
  if (this.barred(whole)) {
97
74
  return;
98
75
  }
@@ -102,8 +79,8 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
102
79
  this.listRevealed = false;
103
80
  }
104
81
  get times() {
105
- const from = toMinutes(this.min) ?? 0;
106
- const to = toMinutes(this.max) ?? 1439;
82
+ const from = valueAsMinutes(this.min) ?? 0;
83
+ const to = valueAsMinutes(this.max) ?? 1439;
107
84
  const step = Math.max(1, Math.round(this.step));
108
85
  const found = [];
109
86
  for (let at = from; at <= to; at += step) {
@@ -121,11 +98,11 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
121
98
  wholeHourBarred(shown, half) {
122
99
  const whole = half === undefined ? shown : (shown % 12) + (half === 'PM' ? 12 : 0);
123
100
  const within = this.times.filter((at) => Math.floor(at / 60) === whole);
124
- return within.length > 0 && within.every((at) => this.barred(fromMinutes(at)));
101
+ return within.length > 0 && within.every((at) => this.barred(minutesAsValue(at)));
125
102
  }
126
103
  halfBarred(half) {
127
104
  const within = this.times.filter((at) => (half === 'PM') === at >= 720);
128
- return within.length > 0 && within.every((at) => this.barred(fromMinutes(at)));
105
+ return within.length > 0 && within.every((at) => this.barred(minutesAsValue(at)));
129
106
  }
130
107
  get held() {
131
108
  return this.value
@@ -141,7 +118,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
141
118
  return from !== undefined && to !== undefined && at > from && at < to;
142
119
  }
143
120
  get parts() {
144
- const at = toMinutes(this.value) ?? toMinutes(this.min) ?? 0;
121
+ const at = valueAsMinutes(this.value) ?? valueAsMinutes(this.min) ?? 0;
145
122
  const hour = Math.floor(at / 60);
146
123
  return { hour, meridiem: hour < 12 ? 'AM' : 'PM', minute: at % 60 };
147
124
  }
@@ -262,7 +239,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
262
239
  : this.hours;
263
240
  return html `<div part="columns" data-unset=${this.value === '' ? '' : nothing}>
264
241
  ${this.renderColumn('hour', 'Hour', hours.map(String), (one) => (twelve ? one : one.padStart(2, '0')), shownHour, (one) => this.wholeHourBarred(Number(one), twelve ? meridiem : undefined))}
265
- ${this.renderColumn('minute', 'Minute', this.minutes.map(String), (one) => one.padStart(2, '0'), String(minute), (one) => this.barred(fromMinutes(hour * 60 + Number(one))))}
242
+ ${this.renderColumn('minute', 'Minute', this.minutes.map(String), (one) => one.padStart(2, '0'), String(minute), (one) => this.barred(minutesAsValue(hour * 60 + Number(one))))}
266
243
  ${twelve
267
244
  ? this.renderColumn('meridiem', 'Before or after noon', ['AM', 'PM'], (one) => one, meridiem, (one) => this.halfBarred(one))
268
245
  : nothing}
@@ -283,7 +260,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
283
260
  @input=${this.handleListInput}
284
261
  >
285
262
  ${this.times.map((minutes) => {
286
- const at = fromMinutes(minutes);
263
+ const at = minutesAsValue(minutes);
287
264
  const between = this.between(at);
288
265
  return html `<${this.tag('option')}
289
266
  part="option${between ? ' option-between' : ''}"
@@ -100,8 +100,8 @@ export declare class HelixHeadlessTooltip extends HelixHeadlessTooltip_base {
100
100
  * the element it names, and points its bubble there. Leave it unset and the
101
101
  * default slot is the trigger, which is the older form.
102
102
  *
103
- * Resolved through `getRootNode()`, so a tooltip inside another component's
104
- * shadow root names an element in that root rather than in the document.
103
+ * Names an element in the same tree, so a tooltip inside another component's
104
+ * shadow root points at one in that root rather than one in the document.
105
105
  *
106
106
  * **In React this arrives as a property and not as an attribute**, which is
107
107
  * worth knowing before someone reads the DOM and thinks it did not take.
@@ -199,14 +199,7 @@ export declare class HelixHeadlessTooltip extends HelixHeadlessTooltip_base {
199
199
  * until a chart did.
200
200
  */
201
201
  reposition(): void;
202
- /**
203
- * The element `for` names, or null.
204
- *
205
- * `getRootNode()` rather than `document`, which is what makes this work inside
206
- * another component: an id is unique to a tree, and a tooltip in a shadow root
207
- * names an element in that root. Client only, since the server has no tree to
208
- * search.
209
- */
202
+ /** The element `for` names, or null. */
210
203
  private get named();
211
204
  private readonly bubbleLayer;
212
205
  /**
@@ -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 { anchorElement, customElement, HelixElement, hostAria, MixinControllerOpen, PopoverController, } from '@fusion.dev/helix-core';
7
+ import { anchorElement, customElement, elementById, HelixElement, hostAria, MixinControllerOpen, PopoverController, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  const GENERIC = new Set(['div', 'p', 'section', 'span']);
@@ -90,20 +90,10 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
90
90
  this.bubbleLayer.reposition();
91
91
  }
92
92
  get named() {
93
- if (this.for === '') {
94
- return null;
95
- }
96
- const root = this.getRootNode();
97
- return root.getElementById?.(this.for) ?? null;
93
+ return this.for === '' ? null : elementById(this, this.for);
98
94
  }
99
95
  get enclosure() {
100
- if (this.within === '') {
101
- return null;
102
- }
103
- const root = this.getRootNode();
104
- return root instanceof Document || root instanceof ShadowRoot
105
- ? root.getElementById(this.within)
106
- : null;
96
+ return this.within === '' ? null : elementById(this, this.within);
107
97
  }
108
98
  get bubble() {
109
99
  return this.partNamed('tooltip');
@@ -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, HelixElement, HelixTourAskEvent, hostAria, PopoverController, } from '@fusion.dev/helix-core';
7
+ import { customElement, elementById, HelixElement, HelixTourAskEvent, hostAria, PopoverController, } from '@fusion.dev/helix-core';
8
8
  import { property, state } from 'lit/decorators.js';
9
9
  import { html } from 'lit/static-html.js';
10
10
  import '../../button/index.js';
@@ -41,8 +41,7 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
41
41
  });
42
42
  }
43
43
  get target() {
44
- const scope = this.getRootNode();
45
- return this.for === '' ? null : (scope.getElementById?.(this.for) ?? null);
44
+ return this.for === '' ? null : elementById(this, this.for);
46
45
  }
47
46
  willUpdate(changed) {
48
47
  super.willUpdate(changed);
@@ -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 { activeCue, adoptTrack, CueController, captionsAmong, captionTracks, copiesOf, customElement, drawIcon, FullscreenController, HelixElement, HelixNextEvent, HelixPreviousEvent, IdleController, MediaSessionController, onUnlessFalse, RemotePlaybackController, SKIP_SECONDS, sameCopies, showCaption, writtenList, } from '@fusion.dev/helix-core';
7
+ import { activeCue, adoptTrack, CueController, captionsAmong, captionTracks, copiesOf, customElement, drawIcon, FullscreenController, HelixElement, HelixNextEvent, HelixPreviousEvent, IdleController, MediaSessionController, onUnlessFalse, RemotePlaybackController, SKIP_SECONDS, sameCopies, showCaption, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
8
8
  import { airplay, arrowClockwise, arrowCounterClockwise, closedCaptioning, cornersIn, cornersOut, dotsThreeVertical, download, gauge, pause, pictureInPicture, play, skipBack, skipForward, speakerHigh, speakerX, } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -332,9 +332,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
332
332
  this.dispatchEvent(back ? new HelixPreviousEvent() : new HelixNextEvent());
333
333
  }
334
334
  cycleSpeed() {
335
- const speeds = writtenList(this.speeds)
336
- .map(Number)
337
- .filter((one) => one > 0);
335
+ const speeds = writtenNumbers(this.speeds).filter((one) => one > 0);
338
336
  const video = this.video;
339
337
  if (!video || speeds.length === 0) {
340
338
  return;
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.58.1",
4
+ "version": "0.59.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.58.1",
50
- "@fusion.dev/helix-icons": "0.58.1",
49
+ "@fusion.dev/helix-core": "0.59.0",
50
+ "@fusion.dev/helix-icons": "0.59.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.58.1",
56
- "@fusion.dev/helix-testing": "0.58.1",
55
+ "@fusion.dev/helix-cem": "0.59.0",
56
+ "@fusion.dev/helix-testing": "0.59.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",