@fusion.dev/helix-core 0.143.0 → 0.145.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 (107) hide show
  1. package/custom-elements.json +10266 -4161
  2. package/dist/controllers/aria-forward-controller/aria-forward-controller.d.ts +10 -0
  3. package/dist/controllers/aria-forward-controller/aria-forward-controller.js +6 -0
  4. package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +89 -0
  5. package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +102 -1
  6. package/dist/controllers/chart-pointer-controller/reading-step.d.ts +57 -0
  7. package/dist/controllers/chart-pointer-controller/reading-step.js +45 -0
  8. package/dist/controllers/copy-controller/copy-controller.d.ts +11 -6
  9. package/dist/controllers/copy-controller/copy-controller.js +11 -9
  10. package/dist/controllers/countdown-controller/countdown-controller.d.ts +55 -0
  11. package/dist/controllers/countdown-controller/countdown-controller.js +57 -0
  12. package/dist/controllers/delay-controller/delay-controller.d.ts +50 -0
  13. package/dist/controllers/delay-controller/delay-controller.js +54 -0
  14. package/dist/controllers/focus-scope-controller/focus-scope-controller.d.ts +56 -0
  15. package/dist/controllers/focus-scope-controller/focus-scope-controller.js +71 -0
  16. package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.d.ts +11 -2
  17. package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +4 -1
  18. package/dist/controllers/open-phase-controller/open-phase-controller.d.ts +49 -0
  19. package/dist/controllers/open-phase-controller/open-phase-controller.js +41 -0
  20. package/dist/controllers/source-controller/source-controller.d.ts +16 -0
  21. package/dist/controllers/source-controller/source-controller.js +44 -1
  22. package/dist/controllers/status-controller/status-controller.d.ts +47 -0
  23. package/dist/controllers/status-controller/status-controller.js +34 -0
  24. package/dist/controllers/swipe-controller/swipe-controller.d.ts +61 -0
  25. package/dist/controllers/swipe-controller/swipe-controller.js +106 -0
  26. package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +68 -35
  27. package/dist/controllers/virtualize-controller/virtualize-controller.js +77 -10
  28. package/dist/events/events.d.ts +221 -0
  29. package/dist/events/events.js +150 -0
  30. package/dist/helix-element/helix-element.js +14 -1
  31. package/dist/helix-element/react-bridge.d.ts +9 -4
  32. package/dist/helix-element/react-bridge.js +14 -1
  33. package/dist/index.d.ts +31 -9
  34. package/dist/index.js +29 -7
  35. package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +7 -2
  36. package/dist/mixins/mixin-chart-source/mixin-chart-source.js +5 -1
  37. package/dist/mixins/mixin-controller-checked/mixin-controller-checked.d.ts +10 -0
  38. package/dist/mixins/mixin-controller-checked/mixin-controller-checked.js +9 -0
  39. package/dist/mixins/mixin-dismissible/mixin-dismissible.d.ts +2 -0
  40. package/dist/mixins/mixin-dismissible/mixin-dismissible.js +7 -3
  41. package/dist/mixins/mixin-field/mixin-field.d.ts +2 -0
  42. package/dist/mixins/mixin-field/mixin-field.js +8 -0
  43. package/dist/mixins/mixin-picker-editing/mixin-picker-editing.d.ts +16 -6
  44. package/dist/mixins/mixin-picker-editing/mixin-picker-editing.js +19 -1
  45. package/dist/mixins/mixin-picker-field/mixin-picker-field.d.ts +5 -4
  46. package/dist/mixins/mixin-picker-field/mixin-picker-field.js +12 -1
  47. package/dist/mixins/mixin-segmented-field/mixin-segmented-field.d.ts +67 -0
  48. package/dist/mixins/mixin-segmented-field/mixin-segmented-field.js +243 -0
  49. package/dist/utils/arrow/arrow.d.ts +15 -0
  50. package/dist/utils/arrow/arrow.js +2 -0
  51. package/dist/utils/audit-name/audit-name.d.ts +28 -0
  52. package/dist/utils/audit-name/audit-name.js +95 -0
  53. package/dist/utils/calendar/calendar.d.ts +117 -0
  54. package/dist/utils/calendar/calendar.js +96 -0
  55. package/dist/utils/chord/chord.d.ts +43 -0
  56. package/dist/utils/chord/chord.js +53 -0
  57. package/dist/utils/clock/clock.d.ts +87 -0
  58. package/dist/utils/clock/clock.js +68 -0
  59. package/dist/utils/color-name/color-name.d.ts +28 -0
  60. package/dist/utils/color-name/color-name.js +150 -0
  61. package/dist/utils/count-label/count-label.d.ts +13 -0
  62. package/dist/utils/count-label/count-label.js +5 -0
  63. package/dist/utils/css-color/css-color.d.ts +12 -0
  64. package/dist/utils/css-color/css-color.js +2 -2
  65. package/dist/utils/date-segments/date-segments.d.ts +122 -0
  66. package/dist/utils/date-segments/date-segments.js +206 -0
  67. package/dist/utils/draw-dismiss/draw-dismiss.d.ts +30 -0
  68. package/dist/utils/draw-dismiss/draw-dismiss.js +16 -0
  69. package/dist/utils/draw-icon/draw-icon.d.ts +7 -6
  70. package/dist/utils/draw-tooltip/draw-tooltip.d.ts +15 -21
  71. package/dist/utils/draw-tooltip/draw-tooltip.js +11 -5
  72. package/dist/utils/draw-tooltip/spoken-reading.d.ts +10 -0
  73. package/dist/utils/draw-tooltip/spoken-reading.js +10 -0
  74. package/dist/utils/follow-link/follow-link.d.ts +46 -0
  75. package/dist/utils/follow-link/follow-link.js +37 -0
  76. package/dist/utils/heading-in/heading-in.d.ts +19 -0
  77. package/dist/utils/heading-in/heading-in.js +14 -0
  78. package/dist/utils/number-format/number-format.d.ts +23 -0
  79. package/dist/utils/number-format/number-format.js +55 -0
  80. package/dist/utils/platform/platform.d.ts +23 -0
  81. package/dist/utils/platform/platform.js +11 -0
  82. package/dist/utils/range-add/range-add.d.ts +19 -0
  83. package/dist/utils/range-add/range-add.js +5 -0
  84. package/dist/utils/read-color/read-color.d.ts +14 -0
  85. package/dist/utils/read-color/read-color.js +13 -0
  86. package/dist/utils/read-series/read-series.d.ts +6 -0
  87. package/dist/utils/read-series/read-series.js +1 -0
  88. package/dist/utils/reflect-elements/reflect-elements.d.ts +37 -0
  89. package/dist/utils/reflect-elements/reflect-elements.js +40 -0
  90. package/dist/utils/relative-time/relative-time.d.ts +13 -1
  91. package/dist/utils/relative-time/relative-time.js +7 -4
  92. package/dist/utils/report-runtime/report-runtime.d.ts +5 -1
  93. package/dist/utils/roving-stop/roving-stop.d.ts +20 -0
  94. package/dist/utils/roving-stop/roving-stop.js +3 -0
  95. package/dist/utils/swipe/swipe.d.ts +63 -0
  96. package/dist/utils/swipe/swipe.js +48 -0
  97. package/dist/utils/tab-stops/tab-stops.d.ts +16 -0
  98. package/dist/utils/tab-stops/tab-stops.js +22 -0
  99. package/dist/utils/value-as/value-as.d.ts +20 -0
  100. package/dist/utils/value-as/value-as.js +16 -0
  101. package/dist/utils/write-time/write-time.d.ts +15 -5
  102. package/dist/utils/write-time/write-time.js +7 -7
  103. package/dist/utils/written-list/written-list.d.ts +18 -0
  104. package/dist/utils/written-list/written-list.js +8 -0
  105. package/dist/version.d.ts +1 -1
  106. package/dist/version.js +1 -1
  107. package/package.json +4 -4
@@ -31,6 +31,14 @@ export interface AriaForwardOptions {
31
31
  named?: () => boolean;
32
32
  /** The focusable thing in the shadow root, or null before it renders. */
33
33
  target: () => HTMLElement | null;
34
+ /**
35
+ * How to name the control, said in a warning when it has no accessible
36
+ * name once it has rendered. Absent, the name is not checked.
37
+ *
38
+ * For a control named by its content or a host attribute, as a button is. A
39
+ * field is checked by `MixinField`, which knows its label.
40
+ */
41
+ unnamed?: string;
34
42
  }
35
43
  /**
36
44
  * Carries a host's ARIA references down to the control inside it.
@@ -93,6 +101,8 @@ export declare class AriaForwardController implements ReactiveController {
93
101
  private readonly named;
94
102
  /** What is read after the host's names. See the option. */
95
103
  private readonly after;
104
+ /** How to name the control, where its name is checked. See the option. */
105
+ private readonly unnamed;
96
106
  constructor(host: ReactiveControllerHost & HTMLElement, options: AriaForwardOptions);
97
107
  /**
98
108
  * Whether the control has a name that did not come from this controller: an
@@ -1,3 +1,4 @@
1
+ import { auditName } from '../../utils/audit-name/audit-name.js';
1
2
  import { elementById } from '../../utils/element-by-id/element-by-id.js';
2
3
  const shownText = (node) => {
3
4
  if (node instanceof HTMLSlotElement) {
@@ -16,6 +17,7 @@ const RELATIONSHIPS = [
16
17
  ['aria-labelledby', 'ariaLabelledByElements'],
17
18
  ];
18
19
  const STATES = [
20
+ 'aria-current',
19
21
  'aria-expanded',
20
22
  'aria-haspopup',
21
23
  'aria-controls',
@@ -36,6 +38,7 @@ export class AriaForwardController {
36
38
  this.asked = options.label ?? false;
37
39
  this.named = options.named;
38
40
  this.after = options.labelledByAfter;
41
+ this.unnamed = options.unnamed;
39
42
  host.addController(this);
40
43
  }
41
44
  namedByItself(target) {
@@ -120,5 +123,8 @@ export class AriaForwardController {
120
123
  }
121
124
  hostUpdated() {
122
125
  this.forward();
126
+ if (this.unnamed !== undefined) {
127
+ auditName(this.host, this.target, this.unnamed);
128
+ }
123
129
  }
124
130
  }
@@ -1,4 +1,19 @@
1
1
  import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /**
3
+ * The id every chart puts on its plot, and the boundary a bubble is kept inside.
4
+ *
5
+ * **A name six templates and two files have to agree on is a name that will
6
+ * stop agreeing**, which is `PALETTE`'s argument and it holds harder here: the
7
+ * failure is silent. `within` resolves by id, so a plot renamed on one side
8
+ * returns null, the boundary falls back to the viewport, and the bubble goes
9
+ * back to hanging past the drawing with nothing reported anywhere. The plot is
10
+ * also what takes focus, and this controller finds it by the same id.
11
+ *
12
+ * Fixed rather than generated: it is scoped to one chart's shadow root, where
13
+ * it is the only one. Declared here, beside the keys that read it, and
14
+ * re-exported from `drawTooltip`'s module, where it was first declared.
15
+ */
16
+ export declare const CHART_PLOT = "helix-chart-plot";
2
17
  /** What the controller needs of its host, which is a Lit element and a count. */
3
18
  export interface ChartPointerHost extends ReactiveControllerHost, HTMLElement {
4
19
  /**
@@ -9,6 +24,16 @@ export interface ChartPointerHost extends ReactiveControllerHost, HTMLElement {
9
24
  * Where the chart drew itself, which is where the plot is looked for.
10
25
  */
11
26
  readonly renderRoot: DocumentFragment | HTMLElement;
27
+ /**
28
+ * The positions of the cells in a row that hold numbers, which the series
29
+ * keys move through. Absent, a reading is one series.
30
+ */
31
+ readonly plottedColumns?: number[];
32
+ /**
33
+ * The palette slot for a cell, or `undefined` for a column no series was
34
+ * drawn from, such as a scatter's measure across.
35
+ */
36
+ seriesOf?(cell: number): number | undefined;
12
37
  }
13
38
  /**
14
39
  * Which reading a pointer is nearest, for a chart that draws no markers.
@@ -33,6 +58,21 @@ export interface ChartPointerHost extends ReactiveControllerHost, HTMLElement {
33
58
  * lifts. The table underneath is the readable version and is what a phone
34
59
  * should be given, so this listens for `pointermove` and ignores anything whose
35
60
  * `pointerType` is not fine.
61
+ *
62
+ * **The keyboard moves the same reading, which is what makes it the reading
63
+ * and not the pointer's.** A chart's plot takes focus (`tabindex="0"` on the
64
+ * element carrying `CHART_PLOT`), and while it has it the arrow keys walk the
65
+ * readings, Home and End go to the first and the last, and the other pair of
66
+ * arrows walks the series within a reading. Escape puts the bubble away, as it
67
+ * does a tooltip. The tooltip follows because it already follows `at`, and the
68
+ * chart's readout, a polite live region `drawTooltip` renders, says the
69
+ * reading in words. It speaks only for a key: a pointer crossing a chart is a
70
+ * sighted reader looking, and a region speaking on every slot it crossed would
71
+ * talk over everything else on the page.
72
+ *
73
+ * No peer charting library ships this, which is the reason it is spelled out
74
+ * rather than assumed: the table beside the chart has every number, and this
75
+ * is for a reader who wants the picture's own order, a reading at a time.
36
76
  */
37
77
  export declare class ChartPointerController implements ReactiveController {
38
78
  private host;
@@ -40,6 +80,22 @@ export declare class ChartPointerController implements ReactiveController {
40
80
  private plot;
41
81
  /** The reading under the pointer, or -1 when the pointer is elsewhere. */
42
82
  at: number;
83
+ /**
84
+ * How many readings stand in one column of the drawing, for a chart laid out
85
+ * as a grid.
86
+ *
87
+ * `1` is a row of readings, which is every chart but a heatmap. A heatmap's
88
+ * calendar week stands seven days to a column, so the up and down arrows move
89
+ * a day and the across pair moves a week, which is where the eye goes.
90
+ */
91
+ grid: number;
92
+ /** Whether the reading was last moved by a key, which is when the readout speaks. */
93
+ keyed: boolean;
94
+ /**
95
+ * The series the keyboard is reading within a reading, as its place among
96
+ * the plotted columns, or -1 while the whole reading is read.
97
+ */
98
+ series: number;
43
99
  /**
44
100
  * Which axis the readings are laid out along.
45
101
  *
@@ -60,6 +116,17 @@ export declare class ChartPointerController implements ReactiveController {
60
116
  constructor(host: ChartPointerHost);
61
117
  hostConnected(): void;
62
118
  hostDisconnected(): void;
119
+ /**
120
+ * The cell of the reading the series keys rest on, by its place in the row,
121
+ * or `undefined` while the whole reading is read.
122
+ *
123
+ * The tooltip marks this cell and the readout says it alone. A place in the
124
+ * row and not a series depth, because a row holds columns nothing was drawn
125
+ * from, and the bubble lists every one of them.
126
+ */
127
+ get column(): number | undefined;
128
+ /** The cells a series key can rest on: every column a series was drawn from. */
129
+ private columns;
63
130
  /**
64
131
  * Tells the tooltip its anchor moved, which nothing else would.
65
132
  *
@@ -122,6 +189,28 @@ export declare class ChartPointerController implements ReactiveController {
122
189
  accepts(event: Event): boolean;
123
190
  /** Puts the pointer on a reading, for a chart that resolves it some other way. */
124
191
  set(at: number): void;
192
+ /**
193
+ * Whether an event came from the plot itself, which is the one element in
194
+ * the chart the keys are for.
195
+ *
196
+ * By the id every chart gives its plot, asked of the first node on the
197
+ * event's path: a key pressed in a donut's middle, which a consumer can fill
198
+ * with a link, is that link's and not the chart's.
199
+ */
200
+ private fromPlot;
201
+ /** Puts the reading away, as Escape and leaving the plot both do. */
202
+ private clear;
203
+ private readonly key;
204
+ /**
205
+ * Shows the first reading when the plot is reached from the keyboard, so the
206
+ * bubble and the readout say where the reader has arrived.
207
+ *
208
+ * A pointer pressing the plot focuses it too, and is left alone: the
209
+ * pointer is already showing a reading, and `:focus-visible` is how the
210
+ * platform says which of the two arrived.
211
+ */
212
+ private readonly arrive;
213
+ private readonly depart;
125
214
  private readonly move;
126
215
  private readonly leave;
127
216
  }
@@ -1,9 +1,74 @@
1
1
  import { isRtl } from '../../utils/is-rtl/is-rtl.js';
2
+ import { stepReading } from './reading-step.js';
3
+ export const CHART_PLOT = 'helix-chart-plot';
2
4
  export class ChartPointerController {
3
5
  constructor(host) {
4
6
  this.moved = false;
5
7
  this.at = -1;
8
+ this.grid = 1;
9
+ this.keyed = false;
10
+ this.series = -1;
6
11
  this.axis = 'inline';
12
+ this.key = (event) => {
13
+ if (!this.fromPlot(event) || event.altKey || event.ctrlKey || event.metaKey) {
14
+ return;
15
+ }
16
+ const count = this.host.readings;
17
+ if (count === 0) {
18
+ return;
19
+ }
20
+ if (event.key === 'Escape') {
21
+ if (this.at >= 0) {
22
+ event.preventDefault();
23
+ this.clear();
24
+ }
25
+ return;
26
+ }
27
+ const next = stepReading(event.key, { at: this.at, series: this.series }, {
28
+ axis: this.axis,
29
+ count,
30
+ grid: this.grid,
31
+ rtl: isRtl(this.host),
32
+ series: this.columns().length,
33
+ });
34
+ if (next === undefined) {
35
+ return;
36
+ }
37
+ const { at, series } = next;
38
+ event.preventDefault();
39
+ if (at === this.at && series === this.series && this.keyed) {
40
+ return;
41
+ }
42
+ this.at = at;
43
+ this.series = series;
44
+ this.keyed = true;
45
+ this.moved = true;
46
+ this.host.requestUpdate();
47
+ };
48
+ this.arrive = (event) => {
49
+ if (!this.fromPlot(event) || this.at >= 0) {
50
+ return;
51
+ }
52
+ const plot = event.composedPath()[0];
53
+ let visible = true;
54
+ try {
55
+ visible = plot.matches(':focus-visible');
56
+ }
57
+ catch {
58
+ }
59
+ if (!visible || this.host.readings === 0) {
60
+ return;
61
+ }
62
+ this.at = 0;
63
+ this.keyed = true;
64
+ this.moved = true;
65
+ this.host.requestUpdate();
66
+ };
67
+ this.depart = (event) => {
68
+ if (this.fromPlot(event) && this.keyed) {
69
+ this.clear();
70
+ }
71
+ };
7
72
  this.move = (event) => {
8
73
  if (this.axis === 'none' || !this.accepts(event)) {
9
74
  return;
@@ -23,12 +88,14 @@ export class ChartPointerController {
23
88
  const next = Math.min(Math.max(slot, 0), count - 1);
24
89
  if (next !== this.at) {
25
90
  this.at = next;
91
+ this.keyed = false;
92
+ this.series = -1;
26
93
  this.moved = true;
27
94
  this.host.requestUpdate();
28
95
  }
29
96
  };
30
97
  this.leave = () => {
31
- if (this.at !== -1) {
98
+ if (this.at !== -1 && !this.keyed) {
32
99
  this.at = -1;
33
100
  this.host.requestUpdate();
34
101
  }
@@ -39,11 +106,29 @@ export class ChartPointerController {
39
106
  hostConnected() {
40
107
  this.host.addEventListener('pointermove', this.move);
41
108
  this.host.addEventListener('pointerleave', this.leave);
109
+ this.host.addEventListener('keydown', this.key);
110
+ this.host.addEventListener('focusin', this.arrive);
111
+ this.host.addEventListener('focusout', this.depart);
42
112
  }
43
113
  hostDisconnected() {
44
114
  this.host.removeEventListener('pointermove', this.move);
45
115
  this.host.removeEventListener('pointerleave', this.leave);
116
+ this.host.removeEventListener('keydown', this.key);
117
+ this.host.removeEventListener('focusin', this.arrive);
118
+ this.host.removeEventListener('focusout', this.depart);
46
119
  this.at = -1;
120
+ this.series = -1;
121
+ this.keyed = false;
122
+ }
123
+ get column() {
124
+ return this.series < 0 ? undefined : this.columns()[this.series];
125
+ }
126
+ columns() {
127
+ const host = this.host;
128
+ const all = host.plottedColumns ?? [];
129
+ return host.seriesOf === undefined
130
+ ? all
131
+ : all.filter((one) => host.seriesOf?.(one) !== undefined);
47
132
  }
48
133
  hostUpdated() {
49
134
  if (!this.moved) {
@@ -70,8 +155,24 @@ export class ChartPointerController {
70
155
  set(at) {
71
156
  if (at !== this.at) {
72
157
  this.at = at;
158
+ this.keyed = false;
73
159
  this.moved = true;
74
160
  this.host.requestUpdate();
75
161
  }
76
162
  }
163
+ fromPlot(event) {
164
+ const target = event.composedPath()[0];
165
+ return (target instanceof Element &&
166
+ target.id === CHART_PLOT &&
167
+ target.getRootNode() === this.host.renderRoot);
168
+ }
169
+ clear() {
170
+ const was = this.at;
171
+ this.at = -1;
172
+ this.series = -1;
173
+ this.keyed = false;
174
+ if (was !== -1) {
175
+ this.host.requestUpdate();
176
+ }
177
+ }
77
178
  }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Where a key moves the reading of a chart, as arithmetic with no element in
3
+ * it.
4
+ *
5
+ * `ChartPointerController` hears the key and asks this where it goes, so the
6
+ * map of keys to readings is one function every chart shares, and a spec can
7
+ * hold it without a page.
8
+ */
9
+ /** How the readings of a chart are laid out, which decides what each arrow does. */
10
+ export interface ReadingLayout {
11
+ /**
12
+ * The axis the readings run along: `inline` along the line of text, `block`
13
+ * down the box, as a horizontal bar chart runs its categories, and `none`
14
+ * for a ring or a plane, whose readings are walked in the table's order.
15
+ */
16
+ axis: 'block' | 'inline' | 'none';
17
+ /** How many readings there are. */
18
+ count: number;
19
+ /** How many readings stand in one column, for a grid. `1` is a row of them. */
20
+ grid: number;
21
+ /** Whether the line of text runs right to left, which turns the inline arrows. */
22
+ rtl: boolean;
23
+ /** How many series a reading holds, which the cross pair of arrows walks. */
24
+ series: number;
25
+ }
26
+ /** A reading and the series within it, `-1` for none and for the whole reading. */
27
+ export interface ReadingPlace {
28
+ /** The reading, or `-1` for none. */
29
+ at: number;
30
+ /** The series within it, by its place among the plotted ones, or `-1` for all of them. */
31
+ series: number;
32
+ }
33
+ /**
34
+ * Which way a key moves along the readings: `1`, `-1`, or `0` for a key that
35
+ * moves something else.
36
+ *
37
+ * The arrows along the line on an inline chart, down the box on a block one,
38
+ * and a whole column across on a grid. The cross pair walks the readings too
39
+ * wherever a reading holds one series, so no arrow is dead.
40
+ */
41
+ export declare const alongReadings: (key: string, layout: ReadingLayout) => number;
42
+ /**
43
+ * Which way a key moves among the series of a reading: the pair of arrows
44
+ * that does not walk the readings, and only where a reading holds two or more.
45
+ * Down the bubble's list for the arrow pointing down, or along the line.
46
+ */
47
+ export declare const amongSeries: (key: string, layout: ReadingLayout) => number;
48
+ /**
49
+ * Where a key moves the reading, or `undefined` for a key that moves nothing,
50
+ * which the page keeps.
51
+ *
52
+ * Home and End go to the first and the last. From no reading, an arrow moving
53
+ * on starts at the first and one moving back at the last, so either arrow
54
+ * starts somewhere a reader expects. A series key on no reading starts on the
55
+ * first. Nothing walks off either end.
56
+ */
57
+ export declare const stepReading: (key: string, from: ReadingPlace, layout: ReadingLayout) => ReadingPlace | undefined;
@@ -0,0 +1,45 @@
1
+ const vertical = (key) => (key === 'ArrowDown' ? 1 : key === 'ArrowUp' ? -1 : 0);
2
+ const inline = (key, rtl) => key === 'ArrowRight' ? (rtl ? -1 : 1) : key === 'ArrowLeft' ? (rtl ? 1 : -1) : 0;
3
+ export const alongReadings = (key, layout) => {
4
+ const single = layout.grid > 1 || layout.series < 2;
5
+ if (layout.axis === 'block') {
6
+ const down = vertical(key);
7
+ return down !== 0 || !single ? down : inline(key, layout.rtl);
8
+ }
9
+ const across = inline(key, layout.rtl);
10
+ if (across !== 0) {
11
+ return across * Math.max(1, layout.grid);
12
+ }
13
+ return single ? vertical(key) : 0;
14
+ };
15
+ export const amongSeries = (key, layout) => {
16
+ if (layout.grid > 1 || layout.series < 2) {
17
+ return 0;
18
+ }
19
+ return layout.axis === 'block' ? inline(key, layout.rtl) : vertical(key);
20
+ };
21
+ export const stepReading = (key, from, layout) => {
22
+ const last = layout.count - 1;
23
+ if (last < 0) {
24
+ return undefined;
25
+ }
26
+ if (key === 'Home') {
27
+ return { at: 0, series: from.series };
28
+ }
29
+ if (key === 'End') {
30
+ return { at: last, series: from.series };
31
+ }
32
+ const along = alongReadings(key, layout);
33
+ if (along !== 0) {
34
+ const at = from.at < 0 ? (along > 0 ? 0 : last) : Math.min(Math.max(from.at + along, 0), last);
35
+ return { at, series: from.series };
36
+ }
37
+ const among = amongSeries(key, layout);
38
+ if (among !== 0) {
39
+ return {
40
+ at: Math.max(from.at, 0),
41
+ series: Math.min(Math.max(from.series + among, -1), layout.series - 1),
42
+ };
43
+ }
44
+ return undefined;
45
+ };
@@ -24,10 +24,10 @@ export interface CopyOptions {
24
24
  * in more places than it looks and a control that does nothing is
25
25
  * indistinguishable from a broken one. See `write` above.
26
26
  *
27
- * **Where neither route is allowed, the failure is silence.** The confirmation
28
- * does not appear, which is the honest report: a message about a clipboard is
29
- * worse than a chip that did not flash, and there is nothing a reader could do
30
- * about a permission policy anyway.
27
+ * **Where neither route is allowed, the copy has failed**, and `failed` holds
28
+ * for the moment a confirmation would. A component decides whether to say so:
29
+ * `CopyButton` names itself with its `error-label`, where Color's chip does
30
+ * not flash at all.
31
31
  *
32
32
  * **The event is announced after the write, not before it.** It is not
33
33
  * cancelable for the same reason: by the time anything could be said, the text
@@ -42,15 +42,20 @@ export declare class CopyController implements ReactiveController {
42
42
  private readonly options;
43
43
  /** Whether the last write succeeded and is still being confirmed. */
44
44
  copied: boolean;
45
+ /** Whether the last copy put nothing on the clipboard, and is still being said. */
46
+ failed: boolean;
45
47
  private timer;
46
48
  constructor(host: ReactiveControllerHost & EventTarget, options?: CopyOptions);
47
49
  /**
48
50
  * Writes to the clipboard, and confirms it if the platform allowed it.
51
+ * Resolves to whether the text is on the clipboard.
49
52
  *
50
53
  * Empty text is not copied. An empty clipboard is not a thing a reader ever
51
- * asks for, and confirming one is worse than doing nothing.
54
+ * asks for, and confirming one is worse than doing nothing. Empty text and a
55
+ * refused write each hold `failed` for the same moment a copy holds
56
+ * `copied`, for a component that says so.
52
57
  */
53
- copy(text: string): Promise<void>;
58
+ copy(text: string): Promise<boolean>;
54
59
  hostDisconnected(): void;
55
60
  private reset;
56
61
  }
@@ -34,30 +34,32 @@ export class CopyController {
34
34
  this.host = host;
35
35
  this.options = options;
36
36
  this.copied = false;
37
+ this.failed = false;
37
38
  host.addController(this);
38
39
  }
39
40
  async copy(text) {
40
- if (text === '') {
41
- return;
42
- }
43
- if (!(await write(text))) {
44
- return;
45
- }
41
+ const wrote = text !== '' && (await write(text));
46
42
  if (this.host instanceof HTMLElement && !this.host.isConnected) {
47
- return;
43
+ return wrote;
48
44
  }
49
- this.copied = true;
45
+ this.copied = wrote;
46
+ this.failed = !wrote;
50
47
  this.host.requestUpdate();
51
- this.host.dispatchEvent(new HelixCopyEvent({ value: text }));
48
+ if (wrote) {
49
+ this.host.dispatchEvent(new HelixCopyEvent({ value: text }));
50
+ }
52
51
  this.reset();
53
52
  this.timer = setTimeout(() => {
54
53
  this.copied = false;
54
+ this.failed = false;
55
55
  this.host.requestUpdate();
56
56
  }, this.options.after ?? 1200);
57
+ return wrote;
57
58
  }
58
59
  hostDisconnected() {
59
60
  this.reset();
60
61
  this.copied = false;
62
+ this.failed = false;
61
63
  }
62
64
  reset() {
63
65
  if (this.timer !== undefined) {
@@ -0,0 +1,55 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /** What a `CountdownController` does when its time runs out, and when it stops and starts. */
3
+ export interface CountdownOptions {
4
+ /** Runs once the time is spent. */
5
+ done: () => void;
6
+ /** Runs with `true` when the clock stops partway and `false` when it runs again. */
7
+ paused?: (paused: boolean) => void;
8
+ }
9
+ /**
10
+ * A lifetime that pauses: a message that dismisses itself after a time, and
11
+ * holds that time while a reader is on it.
12
+ *
13
+ * Toast's items and Announcement each dismiss themselves after `duration`,
14
+ * and each stops the clock while the pointer or focus is on the message, so a
15
+ * reader partway through is not cut off. Resuming continues from what was
16
+ * left rather than starting over.
17
+ *
18
+ * **Holds are counted, not flagged.** Hover and focus stop the same clock and
19
+ * are independent: a pointer can leave while focus stays. Uncounted, a reader
20
+ * who tabs into a message and then moves the pointer off it has the clock
21
+ * restarted underneath them. Only the last one out starts it again.
22
+ *
23
+ * **Leaving the page pauses it**, keeping the time already spent, and drops
24
+ * the holds, since a pointer or focus on a detached element never leaves it.
25
+ * Coming back does not resume on its own: the host says whether it should,
26
+ * such as only while it is still showing. `DelayController` is the other wait
27
+ * in core, for a wait that starts over and forgets itself on leaving.
28
+ */
29
+ export declare class CountdownController implements ReactiveController {
30
+ private readonly options;
31
+ /** When the current run began, or nothing while the clock is stopped. */
32
+ private startedAt;
33
+ /** What is left of the time, so resuming continues rather than restarts. */
34
+ private remaining;
35
+ private timer;
36
+ /** How many reasons there are to hold the clock. */
37
+ private holds;
38
+ constructor(host: ReactiveControllerHost, options: CountdownOptions);
39
+ /** Whether the clock is running now. */
40
+ get running(): boolean;
41
+ hostConnected(): void;
42
+ hostDisconnected(): void;
43
+ /** Starts the clock at `ms`. Nothing, zero or less starts nothing. */
44
+ start(ms: number | undefined): void;
45
+ /** Stops the clock and keeps the time that is left. */
46
+ pause(): void;
47
+ /** Runs the clock again from where it stopped. */
48
+ resume(): void;
49
+ /** Stops the clock where it is, as a dismissal does. */
50
+ cancel(): void;
51
+ /** One more reason to hold the clock, such as a pointer arriving. */
52
+ hold(): void;
53
+ /** One reason fewer, and the clock runs again once none is left. */
54
+ release(): void;
55
+ }
@@ -0,0 +1,57 @@
1
+ export class CountdownController {
2
+ constructor(host, options) {
3
+ this.options = options;
4
+ this.holds = 0;
5
+ host.addController(this);
6
+ }
7
+ get running() {
8
+ return this.timer !== undefined;
9
+ }
10
+ hostConnected() { }
11
+ hostDisconnected() {
12
+ this.pause();
13
+ this.holds = 0;
14
+ }
15
+ start(ms) {
16
+ if (ms === undefined || !(ms > 0)) {
17
+ return;
18
+ }
19
+ this.remaining = ms;
20
+ this.resume();
21
+ }
22
+ pause() {
23
+ if (this.timer === undefined || this.startedAt === undefined) {
24
+ return;
25
+ }
26
+ clearTimeout(this.timer);
27
+ this.timer = undefined;
28
+ this.remaining = Math.max((this.remaining ?? 0) - (Date.now() - this.startedAt), 0);
29
+ this.startedAt = undefined;
30
+ this.options.paused?.(true);
31
+ }
32
+ resume() {
33
+ if (this.timer !== undefined || this.remaining === undefined || this.remaining <= 0) {
34
+ return;
35
+ }
36
+ this.startedAt = Date.now();
37
+ this.options.paused?.(false);
38
+ this.timer = setTimeout(() => {
39
+ this.timer = undefined;
40
+ this.options.done();
41
+ }, this.remaining);
42
+ }
43
+ cancel() {
44
+ clearTimeout(this.timer);
45
+ this.timer = undefined;
46
+ }
47
+ hold() {
48
+ this.holds += 1;
49
+ this.pause();
50
+ }
51
+ release() {
52
+ this.holds = Math.max(this.holds - 1, 0);
53
+ if (this.holds === 0) {
54
+ this.resume();
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,50 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /** What a `DelayController` is for. */
3
+ export interface DelayOptions {
4
+ /**
5
+ * Whether the host renders `elapsed`, and so updates when it moves.
6
+ * Defaults to `true`; a wait that only runs `done`, a debounce, leaves its
7
+ * host alone.
8
+ */
9
+ renders?: boolean;
10
+ }
11
+ /**
12
+ * A wait an element starts and restarts: whether it has run out, and what runs
13
+ * when it does.
14
+ *
15
+ * Avatar's `fallback-delay`, Image's `placeholder-delay` and Loader's `delay`
16
+ * each hold a region back until a wait is over, and Listbox's `filter-delay`
17
+ * holds a filter back until typing pauses. Each kept a timer, a flag and the
18
+ * clean-up by hand; this keeps the three together, and clears the timer when
19
+ * the element leaves the page.
20
+ *
21
+ * `elapsed` is what a template reads, and the host updates when it moves,
22
+ * unless `renders` is false.
23
+ * Leaving the page stops the wait without forgetting that it ran out, so a
24
+ * loader moved to another place in the page does not wait again.
25
+ */
26
+ export declare class DelayController implements ReactiveController {
27
+ private readonly host;
28
+ private readonly options;
29
+ /** Whether the last wait started has run out. */
30
+ elapsed: boolean;
31
+ private timer;
32
+ private done;
33
+ constructor(host: ReactiveControllerHost, options?: DelayOptions);
34
+ /** Whether a wait is running. */
35
+ get pending(): boolean;
36
+ hostConnected(): void;
37
+ hostDisconnected(): void;
38
+ /**
39
+ * Starts the wait over: `elapsed` goes back to false, and after `ms`
40
+ * milliseconds it turns true and `done` runs. No `ms` starts no wait, which
41
+ * leaves `elapsed` false.
42
+ */
43
+ start(ms?: number, done?: () => void): void;
44
+ /** Stops a running wait, leaving `elapsed` as it is. */
45
+ cancel(): void;
46
+ /** Ends a running wait now, as though it had run out. Nothing happens where none is running. */
47
+ flush(): void;
48
+ private finish;
49
+ private mark;
50
+ }