@fusion.dev/helix-core 0.143.0 → 0.144.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 (95) hide show
  1. package/custom-elements.json +9098 -3887
  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 +5 -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/delay-controller/delay-controller.d.ts +50 -0
  11. package/dist/controllers/delay-controller/delay-controller.js +54 -0
  12. package/dist/controllers/focus-scope-controller/focus-scope-controller.d.ts +56 -0
  13. package/dist/controllers/focus-scope-controller/focus-scope-controller.js +71 -0
  14. package/dist/controllers/open-phase-controller/open-phase-controller.d.ts +49 -0
  15. package/dist/controllers/open-phase-controller/open-phase-controller.js +41 -0
  16. package/dist/controllers/source-controller/source-controller.d.ts +16 -0
  17. package/dist/controllers/source-controller/source-controller.js +44 -1
  18. package/dist/controllers/status-controller/status-controller.d.ts +47 -0
  19. package/dist/controllers/status-controller/status-controller.js +34 -0
  20. package/dist/controllers/swipe-controller/swipe-controller.d.ts +61 -0
  21. package/dist/controllers/swipe-controller/swipe-controller.js +106 -0
  22. package/dist/events/events.d.ts +35 -0
  23. package/dist/events/events.js +12 -0
  24. package/dist/index.d.ts +28 -8
  25. package/dist/index.js +27 -7
  26. package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +7 -2
  27. package/dist/mixins/mixin-chart-source/mixin-chart-source.js +5 -1
  28. package/dist/mixins/mixin-controller-checked/mixin-controller-checked.d.ts +10 -0
  29. package/dist/mixins/mixin-controller-checked/mixin-controller-checked.js +9 -0
  30. package/dist/mixins/mixin-dismissible/mixin-dismissible.d.ts +2 -0
  31. package/dist/mixins/mixin-dismissible/mixin-dismissible.js +7 -3
  32. package/dist/mixins/mixin-field/mixin-field.d.ts +2 -0
  33. package/dist/mixins/mixin-field/mixin-field.js +8 -0
  34. package/dist/mixins/mixin-picker-editing/mixin-picker-editing.d.ts +16 -6
  35. package/dist/mixins/mixin-picker-editing/mixin-picker-editing.js +19 -1
  36. package/dist/mixins/mixin-picker-field/mixin-picker-field.d.ts +5 -4
  37. package/dist/mixins/mixin-picker-field/mixin-picker-field.js +12 -1
  38. package/dist/mixins/mixin-segmented-field/mixin-segmented-field.d.ts +67 -0
  39. package/dist/mixins/mixin-segmented-field/mixin-segmented-field.js +243 -0
  40. package/dist/utils/arrow/arrow.d.ts +15 -0
  41. package/dist/utils/arrow/arrow.js +2 -0
  42. package/dist/utils/audit-name/audit-name.d.ts +28 -0
  43. package/dist/utils/audit-name/audit-name.js +95 -0
  44. package/dist/utils/calendar/calendar.d.ts +117 -0
  45. package/dist/utils/calendar/calendar.js +96 -0
  46. package/dist/utils/chord/chord.d.ts +43 -0
  47. package/dist/utils/chord/chord.js +53 -0
  48. package/dist/utils/clock/clock.d.ts +87 -0
  49. package/dist/utils/clock/clock.js +68 -0
  50. package/dist/utils/color-name/color-name.d.ts +28 -0
  51. package/dist/utils/color-name/color-name.js +150 -0
  52. package/dist/utils/count-label/count-label.d.ts +13 -0
  53. package/dist/utils/count-label/count-label.js +5 -0
  54. package/dist/utils/css-color/css-color.d.ts +12 -0
  55. package/dist/utils/css-color/css-color.js +2 -2
  56. package/dist/utils/date-segments/date-segments.d.ts +122 -0
  57. package/dist/utils/date-segments/date-segments.js +206 -0
  58. package/dist/utils/draw-dismiss/draw-dismiss.d.ts +30 -0
  59. package/dist/utils/draw-dismiss/draw-dismiss.js +16 -0
  60. package/dist/utils/draw-tooltip/draw-tooltip.d.ts +15 -21
  61. package/dist/utils/draw-tooltip/draw-tooltip.js +11 -5
  62. package/dist/utils/draw-tooltip/spoken-reading.d.ts +10 -0
  63. package/dist/utils/draw-tooltip/spoken-reading.js +10 -0
  64. package/dist/utils/follow-link/follow-link.d.ts +46 -0
  65. package/dist/utils/follow-link/follow-link.js +37 -0
  66. package/dist/utils/heading-in/heading-in.d.ts +19 -0
  67. package/dist/utils/heading-in/heading-in.js +14 -0
  68. package/dist/utils/number-format/number-format.d.ts +23 -0
  69. package/dist/utils/number-format/number-format.js +55 -0
  70. package/dist/utils/platform/platform.d.ts +23 -0
  71. package/dist/utils/platform/platform.js +11 -0
  72. package/dist/utils/read-color/read-color.d.ts +14 -0
  73. package/dist/utils/read-color/read-color.js +13 -0
  74. package/dist/utils/read-series/read-series.d.ts +6 -0
  75. package/dist/utils/read-series/read-series.js +1 -0
  76. package/dist/utils/reflect-elements/reflect-elements.d.ts +37 -0
  77. package/dist/utils/reflect-elements/reflect-elements.js +40 -0
  78. package/dist/utils/relative-time/relative-time.d.ts +13 -1
  79. package/dist/utils/relative-time/relative-time.js +7 -4
  80. package/dist/utils/report-runtime/report-runtime.d.ts +2 -0
  81. package/dist/utils/roving-stop/roving-stop.d.ts +20 -0
  82. package/dist/utils/roving-stop/roving-stop.js +3 -0
  83. package/dist/utils/swipe/swipe.d.ts +63 -0
  84. package/dist/utils/swipe/swipe.js +48 -0
  85. package/dist/utils/tab-stops/tab-stops.d.ts +16 -0
  86. package/dist/utils/tab-stops/tab-stops.js +22 -0
  87. package/dist/utils/value-as/value-as.d.ts +20 -0
  88. package/dist/utils/value-as/value-as.js +16 -0
  89. package/dist/utils/write-time/write-time.d.ts +11 -1
  90. package/dist/utils/write-time/write-time.js +7 -7
  91. package/dist/utils/written-list/written-list.d.ts +18 -0
  92. package/dist/utils/written-list/written-list.js +8 -0
  93. package/dist/version.d.ts +1 -1
  94. package/dist/version.js +1 -1
  95. package/package.json +4 -4
@@ -0,0 +1,71 @@
1
+ import { deepActiveElement, deepContains, focusableElements, } from '../../utils/focusable-elements/focusable-elements.js';
2
+ const takesFocus = (element) => element instanceof HTMLElement &&
3
+ (element.matches('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [contenteditable]:not([contenteditable="false"]), [tabindex]:not([tabindex="-1"])') ||
4
+ element.shadowRoot?.delegatesFocus === true);
5
+ export const controlsIn = (elements) => elements.flatMap((one) => [
6
+ ...(takesFocus(one) ? [one] : []),
7
+ ...(one instanceof HTMLElement ? focusableElements(one) : []),
8
+ ]);
9
+ export class FocusScopeController {
10
+ constructor(host, options) {
11
+ this.handleKeydown = (event) => {
12
+ if (event.key !== 'Tab' || event.defaultPrevented || !this.options.contain?.()) {
13
+ return;
14
+ }
15
+ const controls = this.controls;
16
+ const active = deepActiveElement();
17
+ const first = controls[0];
18
+ const last = controls[controls.length - 1];
19
+ if (!first || !last) {
20
+ event.preventDefault();
21
+ this.options.surface()?.focus({ preventScroll: true });
22
+ return;
23
+ }
24
+ const at = (control) => active !== null && deepContains(control, active);
25
+ if (event.shiftKey && (at(first) || active === this.options.surface())) {
26
+ event.preventDefault();
27
+ last.focus();
28
+ }
29
+ else if (!event.shiftKey && at(last)) {
30
+ event.preventDefault();
31
+ first.focus();
32
+ }
33
+ };
34
+ this.host = host;
35
+ this.options = options;
36
+ host.addController(this);
37
+ }
38
+ hostConnected() {
39
+ this.host.addEventListener('keydown', this.handleKeydown);
40
+ }
41
+ hostDisconnected() {
42
+ this.host.removeEventListener('keydown', this.handleKeydown);
43
+ }
44
+ get holdsFocus() {
45
+ const active = deepActiveElement();
46
+ const surface = this.options.surface();
47
+ return (active !== null &&
48
+ ((surface !== null && surface !== undefined && deepContains(surface, active)) ||
49
+ this.options.contents().some((one) => deepContains(one, active))));
50
+ }
51
+ get controls() {
52
+ return controlsIn(this.options.contents());
53
+ }
54
+ enter() {
55
+ const active = deepActiveElement();
56
+ if (active instanceof HTMLElement && !this.holdsFocus) {
57
+ this.origin = active;
58
+ }
59
+ const first = this.options.start === 'surface' ? undefined : this.controls[0];
60
+ (first ?? this.options.surface())?.focus({ preventScroll: true });
61
+ }
62
+ leave() {
63
+ const origin = this.origin;
64
+ this.origin = undefined;
65
+ if (!origin?.isConnected || !this.holdsFocus) {
66
+ return false;
67
+ }
68
+ origin.focus({ preventScroll: true });
69
+ return true;
70
+ }
71
+ }
@@ -0,0 +1,49 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /** The two journeys an overlay makes. */
3
+ export type OpenPhase = 'entering' | 'exiting';
4
+ /** What an `OpenPhaseController` reads of its host and writes onto it. */
5
+ export interface OpenPhaseOptions {
6
+ /**
7
+ * Records each phase on the host, or takes it off: the host's own
8
+ * `toggleState`, passed in because it is protected, and one callback per
9
+ * phase so each state's name is written out where it is recorded.
10
+ */
11
+ mark: Record<OpenPhase, (on: boolean) => void>;
12
+ /** Whether the host is open, read after every update. */
13
+ open: () => boolean;
14
+ /** The element whose animations are the entrance and the exit. */
15
+ surface: () => Element | null | undefined;
16
+ }
17
+ /**
18
+ * Resolves once an element's animations have finished, or two frames on where
19
+ * it has none.
20
+ *
21
+ * Two frames first, so an entrance that starts on the frame after the change,
22
+ * as a `@starting-style` transition does once the element is shown, is
23
+ * running by the time the element is asked. `subtree`, so a `::backdrop` and
24
+ * anything inside the surface count too.
25
+ */
26
+ export declare const animationsSettled: (element: Element | null | undefined) => Promise<void>;
27
+ /**
28
+ * Says on the host that an overlay is on its way in or out, until its
29
+ * animations have finished, as `:state(entering)` and `:state(exiting)`.
30
+ *
31
+ * `:state(open)` turns at once, which is right for what the overlay is and too
32
+ * early for a stylesheet that wants to draw the journey: an exit has to know it
33
+ * is an exit while the panel is still on screen.
34
+ *
35
+ * **Never on the first update.** An overlay served open has made no journey,
36
+ * and marking one would put a state on a page that nobody moved.
37
+ *
38
+ * A turn counter, because a close that arrives during an entrance starts a new
39
+ * phase, and the entrance settling late must not clear the exit.
40
+ */
41
+ export declare class OpenPhaseController implements ReactiveController {
42
+ private readonly options;
43
+ /** What `open` was after the last update, and `undefined` before the first. */
44
+ private last;
45
+ /** Which phase is the latest. */
46
+ private turn;
47
+ constructor(host: ReactiveControllerHost, options: OpenPhaseOptions);
48
+ hostUpdated(): void;
49
+ }
@@ -0,0 +1,41 @@
1
+ export const animationsSettled = async (element) => {
2
+ const frame = () => new Promise((resolve) => {
3
+ if (typeof requestAnimationFrame === 'function') {
4
+ requestAnimationFrame(() => resolve());
5
+ }
6
+ else {
7
+ setTimeout(resolve, 16);
8
+ }
9
+ });
10
+ await frame();
11
+ await frame();
12
+ if (!element || typeof element.getAnimations !== 'function') {
13
+ return;
14
+ }
15
+ await Promise.allSettled(element.getAnimations({ subtree: true }).map((one) => one.finished));
16
+ };
17
+ export class OpenPhaseController {
18
+ constructor(host, options) {
19
+ this.turn = 0;
20
+ this.options = options;
21
+ host.addController(this);
22
+ }
23
+ hostUpdated() {
24
+ const open = this.options.open();
25
+ const last = this.last;
26
+ this.last = open;
27
+ if (last === undefined || last === open) {
28
+ return;
29
+ }
30
+ this.turn += 1;
31
+ const turn = this.turn;
32
+ this.options.mark.entering(open);
33
+ this.options.mark.exiting(!open);
34
+ void animationsSettled(this.options.surface()).then(() => {
35
+ if (turn === this.turn) {
36
+ this.options.mark.entering(false);
37
+ this.options.mark.exiting(false);
38
+ }
39
+ });
40
+ }
41
+ }
@@ -100,6 +100,22 @@ export declare class SourceController implements ReactiveController {
100
100
  get rows(): Row[];
101
101
  /** A held parse where it still describes the table, or a fresh one. */
102
102
  private parsed;
103
+ /**
104
+ * The series hidden on the table, by lowercased name. Empty where there is
105
+ * no table, which is every server render.
106
+ */
107
+ get hidden(): ReadonlySet<string>;
108
+ /**
109
+ * Hides these series on the table and shows every other, for every chart
110
+ * and legend reading it, which then redraw.
111
+ *
112
+ * Names are matched without regard to case or surrounding space, as a
113
+ * column's heading is everywhere else. Nothing happens where `for` names no
114
+ * table, or the set is already this one.
115
+ */
116
+ hide(names: Iterable<string>): void;
117
+ /** Counts this controller among the readers of the table it watches, or of none. */
118
+ private enlist;
103
119
  hostConnected(): void;
104
120
  hostUpdated(): void;
105
121
  hostDisconnected(): void;
@@ -1,5 +1,8 @@
1
1
  import { elementById } from '../../utils/element-by-id/element-by-id.js';
2
- import { readRows, readSeries } from '../../utils/read-series/read-series.js';
2
+ import { readRows, readSeries, seriesKey, } from '../../utils/read-series/read-series.js';
3
+ const HIDDEN = new WeakMap();
4
+ const READERS = new WeakMap();
5
+ const NONE = new Set();
3
6
  export class SourceController {
4
7
  constructor(host) {
5
8
  this.watched = null;
@@ -47,6 +50,43 @@ export class SourceController {
47
50
  }
48
51
  return { element, value: read(element), version: this.version };
49
52
  }
53
+ get hidden() {
54
+ const element = this.element;
55
+ return element ? (HIDDEN.get(element) ?? NONE) : NONE;
56
+ }
57
+ hide(names) {
58
+ const element = this.resolve();
59
+ if (!element) {
60
+ return;
61
+ }
62
+ const next = new Set([...names].map(seriesKey).filter(Boolean));
63
+ const now = HIDDEN.get(element) ?? NONE;
64
+ if (next.size === now.size && [...next].every((one) => now.has(one))) {
65
+ return;
66
+ }
67
+ if (next.size === 0) {
68
+ HIDDEN.delete(element);
69
+ }
70
+ else {
71
+ HIDDEN.set(element, next);
72
+ }
73
+ for (const reader of READERS.get(element) ?? []) {
74
+ reader.host.requestUpdate();
75
+ }
76
+ }
77
+ enlist(element) {
78
+ if (this.watched) {
79
+ READERS.get(this.watched)?.delete(this);
80
+ }
81
+ if (element) {
82
+ let readers = READERS.get(element);
83
+ if (!readers) {
84
+ readers = new Set();
85
+ READERS.set(element, readers);
86
+ }
87
+ readers.add(this);
88
+ }
89
+ }
50
90
  hostConnected() {
51
91
  this.watch();
52
92
  }
@@ -57,6 +97,8 @@ export class SourceController {
57
97
  }
58
98
  }
59
99
  hostDisconnected() {
100
+ this.enlist(null);
101
+ this.watched = null;
60
102
  this.observer?.disconnect();
61
103
  this.observer = undefined;
62
104
  this.tree?.disconnect();
@@ -67,6 +109,7 @@ export class SourceController {
67
109
  this.tree?.disconnect();
68
110
  this.version += 1;
69
111
  const element = this.resolve();
112
+ this.enlist(element);
70
113
  this.watched = element;
71
114
  if (typeof MutationObserver === 'undefined') {
72
115
  return;
@@ -0,0 +1,47 @@
1
+ import { nothing, type ReactiveController, type ReactiveControllerHost, type TemplateResult } from 'lit';
2
+ /** How `say` speaks. */
3
+ export interface SayOptions {
4
+ /**
5
+ * Whether the region is emptied before the words land, so the same words
6
+ * said twice are heard twice. Defaults to `true`.
7
+ */
8
+ clear?: boolean;
9
+ }
10
+ /**
11
+ * A live status region an element renders only once it has something to say,
12
+ * and the words it says.
13
+ *
14
+ * **Not rendered until the first time**, so an element that never speaks
15
+ * carries no live region: a button that never waits, a file field that never
16
+ * refuses a file. `spoken` says whether it is rendered and `said` what it
17
+ * holds, and `drawStatus` draws it.
18
+ *
19
+ * **The words land a frame after the region**, through `say`, because a region
20
+ * that arrives with its words is read inconsistently, and emptied first, so
21
+ * the same words twice are a change both times. `sayNow` puts them in at once,
22
+ * for a region already rendered that is only changing what it says.
23
+ *
24
+ * `ArrivalController` is the other half of the same idea, for an element that
25
+ * is itself the news.
26
+ */
27
+ export declare class StatusController implements ReactiveController {
28
+ private readonly host;
29
+ /** Whether the region is rendered: from the first time anything is said. */
30
+ spoken: boolean;
31
+ /** What the region says now. */
32
+ said: string;
33
+ constructor(host: ReactiveControllerHost);
34
+ hostConnected(): void;
35
+ /** Says `words` a frame from now, rendering the region at once where it is not yet. */
36
+ say(words: string, { clear }?: SayOptions): void;
37
+ /** Says `words` now, in the same update that renders the region. */
38
+ sayNow(words: string): void;
39
+ /** Records what the region holds, and asks for an update only where that moved. */
40
+ private hold;
41
+ }
42
+ /**
43
+ * The region a `StatusController` speaks in, or nothing before it has spoken:
44
+ * `<span part="live" role="status">`, which the element documents as its
45
+ * `live` part.
46
+ */
47
+ export declare const drawStatus: (status: StatusController) => TemplateResult | typeof nothing;
@@ -0,0 +1,34 @@
1
+ import { html, nothing, } from 'lit';
2
+ export class StatusController {
3
+ constructor(host) {
4
+ this.host = host;
5
+ this.spoken = false;
6
+ this.said = '';
7
+ host.addController(this);
8
+ }
9
+ hostConnected() { }
10
+ say(words, { clear = true } = {}) {
11
+ this.hold(true, clear ? '' : this.said);
12
+ const fill = () => {
13
+ this.hold(true, words);
14
+ };
15
+ if (typeof requestAnimationFrame === 'function') {
16
+ requestAnimationFrame(fill);
17
+ }
18
+ else {
19
+ fill();
20
+ }
21
+ }
22
+ sayNow(words) {
23
+ this.hold(true, words);
24
+ }
25
+ hold(spoken, said) {
26
+ if (spoken === this.spoken && said === this.said) {
27
+ return;
28
+ }
29
+ this.spoken = spoken;
30
+ this.said = said;
31
+ this.host.requestUpdate();
32
+ }
33
+ }
34
+ export const drawStatus = (status) => status.spoken ? html `<span part="live" role="status">${status.said}</span>` : nothing;
@@ -0,0 +1,61 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ import type { SwipeAxis } from '../../utils/swipe/swipe.js';
3
+ export interface SwipeOptions {
4
+ /** The axis a swipe is read along, or `undefined` while swiping is off. */
5
+ axis: () => SwipeAxis | undefined;
6
+ /**
7
+ * Called on the release, with how far the pointer went and how fast it was
8
+ * going, in pixels per millisecond, on each axis.
9
+ */
10
+ end: (dx: number, dy: number, vx: number, vy: number) => void;
11
+ /** Called as the pointer moves, with how far it has gone from the press. */
12
+ move: (dx: number, dy: number) => void;
13
+ /**
14
+ * Whether a press may start a swipe, given the element it landed on. Every
15
+ * press may where this is not given. A field, a scroller that has its own use
16
+ * for the drag, or a mouse where dragging selects text, says no.
17
+ */
18
+ start?: (target: Element, event: PointerEvent) => boolean;
19
+ /** The element a swipe starts on, which carries `data-swiping` while one runs. */
20
+ surface: () => HTMLElement | null | undefined;
21
+ }
22
+ /**
23
+ * Follows a pointer dragged across a surface along one axis, for a surface the
24
+ * reader swipes away or along: a drawer, a toast, a lightbox's pictures.
25
+ *
26
+ * It decides only that a swipe is happening and reports where the pointer is.
27
+ * What a swipe means is the host's, which is why `move` and `end` are told the
28
+ * distance and nothing else: a drawer settles it against snap points, a toast
29
+ * against half its width, and a lightbox turns it into next or previous.
30
+ *
31
+ * **A press is a swipe only once it has moved `SLOP` pixels, mostly along the
32
+ * axis.** Before that it is still a press, so a tap on a button inside the
33
+ * surface is a tap, and a drag across the axis, a reader scrolling, is left to
34
+ * the page. Once it is a swipe, the click that ends it is swallowed, so a
35
+ * swipe that started on a button does not also press it.
36
+ *
37
+ * The move and the release are heard on the window, so a pointer that leaves
38
+ * the surface mid-swipe is still followed.
39
+ */
40
+ export declare class SwipeController implements ReactiveController {
41
+ private readonly host;
42
+ private readonly options;
43
+ /** Where the press began, while one is down. */
44
+ private origin;
45
+ /** The last two readings, for the speed. */
46
+ private samples;
47
+ /** Whether the press has become a swipe. */
48
+ private swiping;
49
+ /** The pointer being followed. */
50
+ private pointer;
51
+ constructor(host: ReactiveControllerHost & HTMLElement, options: SwipeOptions);
52
+ hostConnected(): void;
53
+ hostDisconnected(): void;
54
+ private readonly handleDown;
55
+ private readonly handleMove;
56
+ private readonly handleUp;
57
+ /** A swipe the browser took over, for a scroll of its own, goes back to where it began. */
58
+ private readonly handleCancel;
59
+ /** Forgets the press and stops listening for it. */
60
+ private stop;
61
+ }
@@ -0,0 +1,106 @@
1
+ const SLOP = 8;
2
+ export class SwipeController {
3
+ constructor(host, options) {
4
+ this.samples = [];
5
+ this.swiping = false;
6
+ this.handleDown = (event) => {
7
+ const surface = this.options.surface();
8
+ const path = event.composedPath();
9
+ const target = path[0];
10
+ if (event.button !== 0 ||
11
+ this.options.axis() === undefined ||
12
+ !surface ||
13
+ !(target instanceof Element) ||
14
+ !path.includes(surface) ||
15
+ (this.options.start && !this.options.start(target, event))) {
16
+ return;
17
+ }
18
+ this.pointer = event.pointerId;
19
+ this.origin = { t: event.timeStamp, x: event.clientX, y: event.clientY };
20
+ this.samples = [this.origin];
21
+ this.swiping = false;
22
+ window.addEventListener('pointermove', this.handleMove);
23
+ window.addEventListener('pointerup', this.handleUp);
24
+ window.addEventListener('pointercancel', this.handleCancel);
25
+ };
26
+ this.handleMove = (event) => {
27
+ const origin = this.origin;
28
+ const axis = this.options.axis();
29
+ if (!origin || event.pointerId !== this.pointer || axis === undefined) {
30
+ return;
31
+ }
32
+ const dx = event.clientX - origin.x;
33
+ const dy = event.clientY - origin.y;
34
+ if (!this.swiping) {
35
+ const along = Math.abs(axis === 'x' ? dx : dy);
36
+ const across = Math.abs(axis === 'x' ? dy : dx);
37
+ if (Math.max(along, across) < SLOP) {
38
+ return;
39
+ }
40
+ if (across >= along) {
41
+ this.stop();
42
+ return;
43
+ }
44
+ this.swiping = true;
45
+ this.options.surface()?.setAttribute('data-swiping', '');
46
+ }
47
+ this.samples = [
48
+ ...this.samples.slice(-1),
49
+ { t: event.timeStamp, x: event.clientX, y: event.clientY },
50
+ ];
51
+ this.options.move(dx, dy);
52
+ };
53
+ this.handleUp = (event) => {
54
+ const origin = this.origin;
55
+ if (!origin || event.pointerId !== this.pointer) {
56
+ return;
57
+ }
58
+ const swiped = this.swiping;
59
+ const [before, last] = [this.samples[0], this.samples[this.samples.length - 1]];
60
+ const elapsed = before && last ? last.t - before.t : 0;
61
+ const vx = before && last && elapsed > 0 ? (last.x - before.x) / elapsed : 0;
62
+ const vy = before && last && elapsed > 0 ? (last.y - before.y) / elapsed : 0;
63
+ this.stop();
64
+ if (!swiped) {
65
+ return;
66
+ }
67
+ window.addEventListener('click', swallow, { capture: true, once: true });
68
+ setTimeout(() => window.removeEventListener('click', swallow, { capture: true }), 0);
69
+ this.options.end(event.clientX - origin.x, event.clientY - origin.y, vx, vy);
70
+ };
71
+ this.handleCancel = (event) => {
72
+ if (event.pointerId !== this.pointer) {
73
+ return;
74
+ }
75
+ const swiped = this.swiping;
76
+ this.stop();
77
+ if (swiped) {
78
+ this.options.end(0, 0, 0, 0);
79
+ }
80
+ };
81
+ this.host = host;
82
+ this.options = options;
83
+ host.addController(this);
84
+ }
85
+ hostConnected() {
86
+ this.host.addEventListener('pointerdown', this.handleDown);
87
+ }
88
+ hostDisconnected() {
89
+ this.host.removeEventListener('pointerdown', this.handleDown);
90
+ this.stop();
91
+ }
92
+ stop() {
93
+ window.removeEventListener('pointermove', this.handleMove);
94
+ window.removeEventListener('pointerup', this.handleUp);
95
+ window.removeEventListener('pointercancel', this.handleCancel);
96
+ this.options.surface()?.removeAttribute('data-swiping');
97
+ this.origin = undefined;
98
+ this.pointer = undefined;
99
+ this.samples = [];
100
+ this.swiping = false;
101
+ }
102
+ }
103
+ const swallow = (event) => {
104
+ event.preventDefault();
105
+ event.stopPropagation();
106
+ };
@@ -261,6 +261,34 @@ export declare class HelixCanvasEditEvent extends CustomEvent<{
261
261
  name: string;
262
262
  });
263
263
  }
264
+ /** Why a file field did not take a file. */
265
+ export type Refusal = 'count' | 'invalid' | 'size' | 'type';
266
+ /** One file a file field did not take, and why. */
267
+ export interface RefusedFile {
268
+ /** The file. */
269
+ readonly file: File;
270
+ /** What the field's `validate` said about it, where it was the one refusing. */
271
+ readonly message?: string;
272
+ /** Why it was not taken. */
273
+ readonly reason: Refusal;
274
+ }
275
+ /** The files a file field refused. */
276
+ export interface RejectDetail {
277
+ /** Each refused file with its reason, in the order they arrived. */
278
+ files: RefusedFile[];
279
+ }
280
+ /**
281
+ * Files arrived at a file field and some were refused: by `accept`, by
282
+ * `max-file-size`, by `max-files`, or by the field's own `validate`.
283
+ *
284
+ * A notification and not a request, because the files are already left out;
285
+ * the event is how a page tells the reader why. Bubbles and is composed, so a
286
+ * form around the field hears it.
287
+ */
288
+ export declare class HelixRejectEvent extends CustomEvent<RejectDetail> {
289
+ static readonly eventName = "helix-reject";
290
+ constructor(detail: RejectDetail);
291
+ }
264
292
  /** What a reader asked to take off the board. */
265
293
  export interface RemoveDetail {
266
294
  /** The cards, by name. */
@@ -1037,6 +1065,12 @@ export interface SelectDetail {
1037
1065
  * A flag says it outright.
1038
1066
  */
1039
1067
  alternate?: boolean;
1068
+ /**
1069
+ * The link of a row that is a link, which the browser follows once the event
1070
+ * is done unless it is canceled. A client-side router cancels it and follows
1071
+ * the link itself.
1072
+ */
1073
+ href?: string;
1040
1074
  /** Value of the item the reader chose. */
1041
1075
  value: string;
1042
1076
  }
@@ -1337,6 +1371,7 @@ declare const registry: {
1337
1371
  readonly HelixPreviousEvent: typeof HelixPreviousEvent;
1338
1372
  readonly HelixReadEvent: typeof HelixReadEvent;
1339
1373
  readonly HelixReconnectEvent: typeof HelixReconnectEvent;
1374
+ readonly HelixRejectEvent: typeof HelixRejectEvent;
1340
1375
  readonly HelixRelabelEvent: typeof HelixRelabelEvent;
1341
1376
  readonly HelixRemoveEvent: typeof HelixRemoveEvent;
1342
1377
  readonly HelixReorderEvent: typeof HelixReorderEvent;
@@ -152,6 +152,17 @@ export class HelixCanvasEditEvent extends CustomEvent {
152
152
  });
153
153
  }
154
154
  }
155
+ export class HelixRejectEvent extends CustomEvent {
156
+ static { this.eventName = 'helix-reject'; }
157
+ constructor(detail) {
158
+ super(HelixRejectEvent.eventName, {
159
+ bubbles: true,
160
+ cancelable: false,
161
+ composed: true,
162
+ detail,
163
+ });
164
+ }
165
+ }
155
166
  export class HelixRemoveEvent extends CustomEvent {
156
167
  static { this.eventName = 'helix-remove'; }
157
168
  constructor(detail) {
@@ -778,6 +789,7 @@ const registry = {
778
789
  HelixPreviousEvent,
779
790
  HelixReadEvent,
780
791
  HelixReconnectEvent,
792
+ HelixRejectEvent,
781
793
  HelixRelabelEvent,
782
794
  HelixRemoveEvent,
783
795
  HelixReorderEvent,