@fusion.dev/helix-core 0.0.0-dev
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.
- package/LICENSE +21 -0
- package/custom-elements.json +8005 -0
- package/dist/controllers/aria-forward-controller/aria-forward-controller.d.ts +67 -0
- package/dist/controllers/aria-forward-controller/aria-forward-controller.js +146 -0
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +79 -0
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +140 -0
- package/dist/controllers/command-controller/command-controller.d.ts +83 -0
- package/dist/controllers/command-controller/command-controller.js +93 -0
- package/dist/controllers/copy-controller/copy-controller.d.ts +58 -0
- package/dist/controllers/copy-controller/copy-controller.js +128 -0
- package/dist/controllers/disabled-cascade-controller/disabled-cascade-controller.d.ts +76 -0
- package/dist/controllers/disabled-cascade-controller/disabled-cascade-controller.js +67 -0
- package/dist/controllers/dismiss-controller/dismiss-controller.d.ts +58 -0
- package/dist/controllers/dismiss-controller/dismiss-controller.js +165 -0
- package/dist/controllers/drilldown-controller/drilldown-controller.d.ts +127 -0
- package/dist/controllers/drilldown-controller/drilldown-controller.js +171 -0
- package/dist/controllers/floating-controller/floating-controller.d.ts +123 -0
- package/dist/controllers/floating-controller/floating-controller.js +227 -0
- package/dist/controllers/focus-trap-controller/focus-trap-controller.d.ts +26 -0
- package/dist/controllers/focus-trap-controller/focus-trap-controller.js +93 -0
- package/dist/controllers/indicator-controller/indicator-controller.d.ts +97 -0
- package/dist/controllers/indicator-controller/indicator-controller.js +164 -0
- package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.d.ts +113 -0
- package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +117 -0
- package/dist/controllers/popover-controller/popover-controller.d.ts +132 -0
- package/dist/controllers/popover-controller/popover-controller.js +150 -0
- package/dist/controllers/source-controller/source-controller.d.ts +55 -0
- package/dist/controllers/source-controller/source-controller.js +94 -0
- package/dist/controllers/token-color-controller/token-color-controller.d.ts +63 -0
- package/dist/controllers/token-color-controller/token-color-controller.js +105 -0
- package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +127 -0
- package/dist/controllers/virtualize-controller/virtualize-controller.js +216 -0
- package/dist/events/element-events.d.ts +4 -0
- package/dist/events/element-events.js +34 -0
- package/dist/events/events.d.ts +743 -0
- package/dist/events/events.js +799 -0
- package/dist/helix-element/helix-element.d.ts +201 -0
- package/dist/helix-element/helix-element.js +695 -0
- package/dist/helix-form-element/helix-form-element.d.ts +152 -0
- package/dist/helix-form-element/helix-form-element.js +194 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +54 -0
- package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +137 -0
- package/dist/mixins/mixin-chart-source/mixin-chart-source.js +140 -0
- package/dist/mixins/mixin-collection/index.d.ts +1 -0
- package/dist/mixins/mixin-collection/index.js +1 -0
- package/dist/mixins/mixin-collection/mixin-collection.d.ts +58 -0
- package/dist/mixins/mixin-collection/mixin-collection.js +154 -0
- package/dist/mixins/mixin-controller-active/mixin-controller-active.d.ts +53 -0
- package/dist/mixins/mixin-controller-active/mixin-controller-active.js +155 -0
- package/dist/mixins/mixin-controller-checked/mixin-controller-checked.d.ts +54 -0
- package/dist/mixins/mixin-controller-checked/mixin-controller-checked.js +157 -0
- package/dist/mixins/mixin-controller-open/mixin-controller-open.d.ts +49 -0
- package/dist/mixins/mixin-controller-open/mixin-controller-open.js +176 -0
- package/dist/mixins/mixin-controller-value/mixin-controller-value.d.ts +73 -0
- package/dist/mixins/mixin-controller-value/mixin-controller-value.js +173 -0
- package/dist/mixins/mixin-dismissible/mixin-dismissible.d.ts +41 -0
- package/dist/mixins/mixin-dismissible/mixin-dismissible.js +79 -0
- package/dist/mixins/mixin-field/mixin-field.d.ts +145 -0
- package/dist/mixins/mixin-field/mixin-field.js +423 -0
- package/dist/utils/anchor-element/anchor-element.d.ts +23 -0
- package/dist/utils/anchor-element/anchor-element.js +39 -0
- package/dist/utils/capture-pointer/capture-pointer.d.ts +22 -0
- package/dist/utils/capture-pointer/capture-pointer.js +30 -0
- package/dist/utils/closest-instance/closest-instance.d.ts +25 -0
- package/dist/utils/closest-instance/closest-instance.js +34 -0
- package/dist/utils/create-sheet/create-sheet.d.ts +16 -0
- package/dist/utils/create-sheet/create-sheet.js +16 -0
- package/dist/utils/custom-element/custom-element.d.ts +26 -0
- package/dist/utils/custom-element/custom-element.js +114 -0
- package/dist/utils/draw-icon/draw-icon.d.ts +38 -0
- package/dist/utils/draw-icon/draw-icon.js +39 -0
- package/dist/utils/draw-tooltip/draw-tooltip.d.ts +56 -0
- package/dist/utils/draw-tooltip/draw-tooltip.js +89 -0
- package/dist/utils/elapsed-time/elapsed-time.d.ts +10 -0
- package/dist/utils/elapsed-time/elapsed-time.js +117 -0
- package/dist/utils/focusable-elements/focusable-elements.d.ts +40 -0
- package/dist/utils/focusable-elements/focusable-elements.js +88 -0
- package/dist/utils/format-numbers/format-numbers.d.ts +1 -0
- package/dist/utils/format-numbers/format-numbers.js +29 -0
- package/dist/utils/host-aria/host-aria.d.ts +38 -0
- package/dist/utils/host-aria/host-aria.js +41 -0
- package/dist/utils/inherit-disabled/inherit-disabled.d.ts +33 -0
- package/dist/utils/inherit-disabled/inherit-disabled.js +55 -0
- package/dist/utils/line-path/line-path.d.ts +23 -0
- package/dist/utils/line-path/line-path.js +107 -0
- package/dist/utils/on-unless-false/on-unless-false.d.ts +28 -0
- package/dist/utils/on-unless-false/on-unless-false.js +32 -0
- package/dist/utils/read-series/read-series.d.ts +78 -0
- package/dist/utils/read-series/read-series.js +187 -0
- package/dist/utils/relative-time/relative-time.d.ts +24 -0
- package/dist/utils/relative-time/relative-time.js +84 -0
- package/dist/utils/request-change/request-change.d.ts +48 -0
- package/dist/utils/request-change/request-change.js +7 -0
- package/dist/utils/resolve-active-item/resolve-active-item.d.ts +28 -0
- package/dist/utils/resolve-active-item/resolve-active-item.js +21 -0
- package/dist/utils/restore-value/restore-value.d.ts +32 -0
- package/dist/utils/restore-value/restore-value.js +84 -0
- package/dist/utils/scale/scale.d.ts +23 -0
- package/dist/utils/scale/scale.js +32 -0
- package/dist/utils/scale/ticks.d.ts +27 -0
- package/dist/utils/scale/ticks.js +52 -0
- package/dist/utils/scroll-item-into-view/scroll-item-into-view.d.ts +10 -0
- package/dist/utils/scroll-item-into-view/scroll-item-into-view.js +98 -0
- package/dist/utils/slot-filled/slot-filled.d.ts +26 -0
- package/dist/utils/slot-filled/slot-filled.js +26 -0
- package/dist/utils/tell-disclosure/tell-disclosure.d.ts +51 -0
- package/dist/utils/tell-disclosure/tell-disclosure.js +76 -0
- package/dist/utils/ticker/ticker.d.ts +8 -0
- package/dist/utils/ticker/ticker.js +55 -0
- package/dist/utils/tone-marks/tone-marks.d.ts +52 -0
- package/dist/utils/tone-marks/tone-marks.js +36 -0
- package/dist/utils/value-as/value-as.d.ts +64 -0
- package/dist/utils/value-as/value-as.js +105 -0
- package/package.json +58 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
2
|
+
/** What the controller needs to know: which element inside is the control. */
|
|
3
|
+
export interface AriaForwardOptions {
|
|
4
|
+
/** The focusable thing in the shadow root, or null before it renders. */
|
|
5
|
+
target: () => HTMLElement | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Carries a host's ARIA references down to the control inside it.
|
|
9
|
+
*
|
|
10
|
+
* **The gap this closes, measured.** `<helix-button aria-describedby="tip">` puts
|
|
11
|
+
* the description on the host, and the host is not what a reader is on: the
|
|
12
|
+
* accessibility tree exposes the `<button>` in the shadow root, and the host
|
|
13
|
+
* beside it is a generic with a description nobody hears. Read from Chrome's own
|
|
14
|
+
* tree:
|
|
15
|
+
*
|
|
16
|
+
* ```
|
|
17
|
+
* host role=generic name="" desc="Helix description"
|
|
18
|
+
* inner button role=button name="Helix" desc=""
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* A plain `<button>` with the identical attribute reads `desc="Helix
|
|
22
|
+
* description"`, so the reference itself is fine and only its destination is
|
|
23
|
+
* wrong. This moves it.
|
|
24
|
+
*
|
|
25
|
+
* **An IDREF cannot make that trip and an element reference can.** The ids are
|
|
26
|
+
* written in the consumer's tree and the control is in a shadow root, so
|
|
27
|
+
* `aria-describedby` copied onto the inner element resolves to nothing.
|
|
28
|
+
* `ariaDescribedByElements` takes the elements themselves, and a reference is
|
|
29
|
+
* valid into the same tree *or a shadow-including ancestor* of it: from inside
|
|
30
|
+
* the shadow root looking out, the document is exactly that. Measured, the
|
|
31
|
+
* assignment reads back with the node intact, where the inward direction, from a
|
|
32
|
+
* light-DOM trigger to a node in a shadow root, reads back empty. This is the
|
|
33
|
+
* one direction that works, which is why `Tooltip` needs `for` and this does
|
|
34
|
+
* not.
|
|
35
|
+
*
|
|
36
|
+
* **The attributes stay on the host.** Nothing is removed and nothing is added:
|
|
37
|
+
* the consumer's markup is exactly what they wrote, which is the same reason
|
|
38
|
+
* `hostAria` exists. What changes is only that the control now also holds the
|
|
39
|
+
* reference, so a screen reader on the focused element hears it.
|
|
40
|
+
*
|
|
41
|
+
* Watched with a `MutationObserver`, because a reference can arrive at any time:
|
|
42
|
+
* a framework rendering a tooltip after this element upgrades, or a consumer
|
|
43
|
+
* pointing `aria-describedby` at a different id, changes an attribute and
|
|
44
|
+
* nothing schedules a Lit update.
|
|
45
|
+
*/
|
|
46
|
+
export declare class AriaForwardController implements ReactiveController {
|
|
47
|
+
private readonly host;
|
|
48
|
+
private readonly target;
|
|
49
|
+
private observer;
|
|
50
|
+
/** Which state attributes this controller wrote, so only those are removed. */
|
|
51
|
+
private readonly forwarded;
|
|
52
|
+
constructor(host: ReactiveControllerHost & HTMLElement, options: AriaForwardOptions);
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the host's references and hands them to the control.
|
|
55
|
+
*
|
|
56
|
+
* Resolved through the host's own root rather than `document`, so a button
|
|
57
|
+
* inside another component's shadow root finds the ids written beside it
|
|
58
|
+
* there. Ids that resolve to nothing are dropped rather than passed on: an
|
|
59
|
+
* `aria-describedby` naming a heading that has been removed is a stale
|
|
60
|
+
* reference, and an empty array clears the property rather than leaving the
|
|
61
|
+
* last good one in place.
|
|
62
|
+
*/
|
|
63
|
+
forward(): void;
|
|
64
|
+
hostConnected(): void;
|
|
65
|
+
hostDisconnected(): void;
|
|
66
|
+
hostUpdated(): void;
|
|
67
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/** The two relationships a consumer writes on a host and means for the control. */
|
|
2
|
+
const RELATIONSHIPS = [
|
|
3
|
+
['aria-describedby', 'ariaDescribedByElements'],
|
|
4
|
+
['aria-labelledby', 'ariaLabelledByElements'],
|
|
5
|
+
];
|
|
6
|
+
/**
|
|
7
|
+
* The state attributes a host may carry and mean for the control inside it.
|
|
8
|
+
*
|
|
9
|
+
* **Copied rather than resolved, which is what makes them a separate list.** A
|
|
10
|
+
* relationship above names ids in the consumer's tree and can only travel as an
|
|
11
|
+
* element reference; these are plain strings and the value means the same thing
|
|
12
|
+
* wherever it is written, so the whole of the work is putting it on the node the
|
|
13
|
+
* accessibility tree actually exposes.
|
|
14
|
+
*
|
|
15
|
+
* **The case that asked for it was a disclosure with a slotted trigger.**
|
|
16
|
+
* `Megamenu` and `SpeedDial` write `aria-expanded` onto whatever a consumer
|
|
17
|
+
* slots, and where that is a `<helix-button>` the attribute landed on a host the
|
|
18
|
+
* tree reports as `generic`: measured through Chrome's own tree, a button named
|
|
19
|
+
* "Mega" with no expanded state at all, on a component whose comment says the
|
|
20
|
+
* write exists so a screen reader is not "told the control was a plain button
|
|
21
|
+
* forever". A plain `<button slot="trigger">` never had the problem, because
|
|
22
|
+
* there the host *is* the focusable node.
|
|
23
|
+
*
|
|
24
|
+
* **Only what this controller put there is ever taken away, and getting that
|
|
25
|
+
* wrong broke three components.** The first version removed any of these from
|
|
26
|
+
* the control whenever the host lacked it, which reads as the obvious way to
|
|
27
|
+
* keep the two in step and quietly deletes what the component renders for
|
|
28
|
+
* itself: `Combobox` writes `aria-controls="listbox"` onto its own input and
|
|
29
|
+
* `Select` writes `aria-expanded` onto its own trigger, and both were wiped on
|
|
30
|
+
* every update by a controller carrying nothing. Their own specs caught it.
|
|
31
|
+
*
|
|
32
|
+
* So the record below holds what was forwarded last time, and a host that stops
|
|
33
|
+
* carrying one removes exactly that and nothing else. A control's own attribute
|
|
34
|
+
* is untouched either way, which is the only rule that can be right: this
|
|
35
|
+
* controller knows what the host said and cannot know what the template meant.
|
|
36
|
+
*/
|
|
37
|
+
const STATES = ['aria-expanded', 'aria-haspopup', 'aria-controls'];
|
|
38
|
+
/**
|
|
39
|
+
* Carries a host's ARIA references down to the control inside it.
|
|
40
|
+
*
|
|
41
|
+
* **The gap this closes, measured.** `<helix-button aria-describedby="tip">` puts
|
|
42
|
+
* the description on the host, and the host is not what a reader is on: the
|
|
43
|
+
* accessibility tree exposes the `<button>` in the shadow root, and the host
|
|
44
|
+
* beside it is a generic with a description nobody hears. Read from Chrome's own
|
|
45
|
+
* tree:
|
|
46
|
+
*
|
|
47
|
+
* ```
|
|
48
|
+
* host role=generic name="" desc="Helix description"
|
|
49
|
+
* inner button role=button name="Helix" desc=""
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* A plain `<button>` with the identical attribute reads `desc="Helix
|
|
53
|
+
* description"`, so the reference itself is fine and only its destination is
|
|
54
|
+
* wrong. This moves it.
|
|
55
|
+
*
|
|
56
|
+
* **An IDREF cannot make that trip and an element reference can.** The ids are
|
|
57
|
+
* written in the consumer's tree and the control is in a shadow root, so
|
|
58
|
+
* `aria-describedby` copied onto the inner element resolves to nothing.
|
|
59
|
+
* `ariaDescribedByElements` takes the elements themselves, and a reference is
|
|
60
|
+
* valid into the same tree *or a shadow-including ancestor* of it: from inside
|
|
61
|
+
* the shadow root looking out, the document is exactly that. Measured, the
|
|
62
|
+
* assignment reads back with the node intact, where the inward direction, from a
|
|
63
|
+
* light-DOM trigger to a node in a shadow root, reads back empty. This is the
|
|
64
|
+
* one direction that works, which is why `Tooltip` needs `for` and this does
|
|
65
|
+
* not.
|
|
66
|
+
*
|
|
67
|
+
* **The attributes stay on the host.** Nothing is removed and nothing is added:
|
|
68
|
+
* the consumer's markup is exactly what they wrote, which is the same reason
|
|
69
|
+
* `hostAria` exists. What changes is only that the control now also holds the
|
|
70
|
+
* reference, so a screen reader on the focused element hears it.
|
|
71
|
+
*
|
|
72
|
+
* Watched with a `MutationObserver`, because a reference can arrive at any time:
|
|
73
|
+
* a framework rendering a tooltip after this element upgrades, or a consumer
|
|
74
|
+
* pointing `aria-describedby` at a different id, changes an attribute and
|
|
75
|
+
* nothing schedules a Lit update.
|
|
76
|
+
*/
|
|
77
|
+
export class AriaForwardController {
|
|
78
|
+
constructor(host, options) {
|
|
79
|
+
/** Which state attributes this controller wrote, so only those are removed. */
|
|
80
|
+
this.forwarded = new Set();
|
|
81
|
+
this.host = host;
|
|
82
|
+
this.target = options.target;
|
|
83
|
+
host.addController(this);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolves the host's references and hands them to the control.
|
|
87
|
+
*
|
|
88
|
+
* Resolved through the host's own root rather than `document`, so a button
|
|
89
|
+
* inside another component's shadow root finds the ids written beside it
|
|
90
|
+
* there. Ids that resolve to nothing are dropped rather than passed on: an
|
|
91
|
+
* `aria-describedby` naming a heading that has been removed is a stale
|
|
92
|
+
* reference, and an empty array clears the property rather than leaving the
|
|
93
|
+
* last good one in place.
|
|
94
|
+
*/
|
|
95
|
+
forward() {
|
|
96
|
+
const target = this.target();
|
|
97
|
+
if (!target) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const root = this.host.getRootNode();
|
|
101
|
+
for (const [attribute, property] of RELATIONSHIPS) {
|
|
102
|
+
if (!(property in target)) {
|
|
103
|
+
// An engine without element reflection. Nothing to do, and nothing that
|
|
104
|
+
// silently half works: assigning to a property it does not implement is
|
|
105
|
+
// a no-op rather than an error, which is what the guard makes visible.
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const ids = (this.host.getAttribute(attribute) ?? '').split(/\s+/).filter(Boolean);
|
|
109
|
+
const elements = ids
|
|
110
|
+
.map((id) => root.getElementById?.(id) ?? null)
|
|
111
|
+
.filter((element) => element !== null);
|
|
112
|
+
target[property] =
|
|
113
|
+
elements.length > 0 ? elements : null;
|
|
114
|
+
}
|
|
115
|
+
for (const attribute of STATES) {
|
|
116
|
+
const value = this.host.getAttribute(attribute);
|
|
117
|
+
if (value === null) {
|
|
118
|
+
// Only if this controller is what put it there. See the note above.
|
|
119
|
+
if (this.forwarded.has(attribute)) {
|
|
120
|
+
target.removeAttribute(attribute);
|
|
121
|
+
this.forwarded.delete(attribute);
|
|
122
|
+
}
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
target.setAttribute(attribute, value);
|
|
126
|
+
this.forwarded.add(attribute);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
hostConnected() {
|
|
130
|
+
this.observer = new MutationObserver(() => this.forward());
|
|
131
|
+
this.observer.observe(this.host, {
|
|
132
|
+
attributeFilter: [...RELATIONSHIPS.map(([attribute]) => attribute), ...STATES],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
hostDisconnected() {
|
|
136
|
+
this.observer?.disconnect();
|
|
137
|
+
this.observer = undefined;
|
|
138
|
+
}
|
|
139
|
+
/* After the render that created the control, because there is nothing to
|
|
140
|
+
forward to before it exists. Every update, not only the first: a component
|
|
141
|
+
that re-renders its control, which `Button` does whenever `href` changes it
|
|
142
|
+
between an anchor and a button, produces a new element with none of this. */
|
|
143
|
+
hostUpdated() {
|
|
144
|
+
this.forward();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
2
|
+
/** What the controller needs of its host, which is a Lit element and a count. */
|
|
3
|
+
export interface ChartPointerHost extends ReactiveControllerHost, HTMLElement {
|
|
4
|
+
/**
|
|
5
|
+
* How many readings there are, so the pointer has something to land on.
|
|
6
|
+
*/
|
|
7
|
+
readonly readings: number;
|
|
8
|
+
/**
|
|
9
|
+
* Where the chart drew itself, which is where the plot is looked for.
|
|
10
|
+
*/
|
|
11
|
+
readonly renderRoot: DocumentFragment | HTMLElement;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Which reading a pointer is nearest, for a chart that draws no markers.
|
|
15
|
+
*
|
|
16
|
+
* **The anchor is a position and not an element, and that is the whole reason
|
|
17
|
+
* this exists.** A bar and a donut slice are elements a tooltip could be hung
|
|
18
|
+
* on; a line is one `<path>` for a whole series, because
|
|
19
|
+
* `preserveAspectRatio="none"` turns a circle into an ellipse and there is no
|
|
20
|
+
* vector-effect that answers for a shape, so `LineChart` deliberately draws no
|
|
21
|
+
* per-reading marker. Anchoring per datum works for two charts of four and
|
|
22
|
+
* cannot be extended to the third, so the resolution is arithmetic instead.
|
|
23
|
+
*
|
|
24
|
+
* **It maps along one axis only.** The nearest reading to a pointer is decided
|
|
25
|
+
* by x alone in a vertical chart, whatever y is doing, because a reader moving
|
|
26
|
+
* across a chart is asking about a column of it rather than about a point. A
|
|
27
|
+
* true nearest-point search would make a two-series chart answer differently
|
|
28
|
+
* depending on which line the pointer drifted toward, which is a tooltip that
|
|
29
|
+
* changes its mind while the reader holds still.
|
|
30
|
+
*
|
|
31
|
+
* **Coarse pointers get nothing.** A touch has no hover, so a tooltip driven by
|
|
32
|
+
* one appears where a finger already covers it and stays after the finger
|
|
33
|
+
* lifts. The table underneath is the readable version and is what a phone
|
|
34
|
+
* should be given, so this listens for `pointermove` and ignores anything whose
|
|
35
|
+
* `pointerType` is not fine.
|
|
36
|
+
*/
|
|
37
|
+
export declare class ChartPointerController implements ReactiveController {
|
|
38
|
+
#private;
|
|
39
|
+
/** The reading under the pointer, or -1 when the pointer is elsewhere. */
|
|
40
|
+
at: number;
|
|
41
|
+
/**
|
|
42
|
+
* Which axis the readings are laid out along.
|
|
43
|
+
*
|
|
44
|
+
* **An axis and not a boolean, because two of the four charts do not use x.**
|
|
45
|
+
* This was `tracks = true`, and every reading was resolved from the pointer's
|
|
46
|
+
* x whatever the chart was doing. A horizontal bar chart runs its categories
|
|
47
|
+
* *down* the box, so moving the pointer from the first row to the last never
|
|
48
|
+
* changed the answer: measured on four rows, all four reported the third,
|
|
49
|
+
* which is the slot the middle of the box falls in. It looked like a tooltip
|
|
50
|
+
* that would not follow and was a tooltip reading the wrong axis.
|
|
51
|
+
*
|
|
52
|
+
* `none` is the ring. Its slices are real elements at every angle, so the
|
|
53
|
+
* reading under the pointer is the one the pointer is over, and arithmetic on
|
|
54
|
+
* either axis would answer with whichever slice shared a coordinate: at three
|
|
55
|
+
* o'clock and at nine o'clock, x alone gives the same slice for opposite
|
|
56
|
+
* sides. The slices call `set` and this only holds the answer.
|
|
57
|
+
*/
|
|
58
|
+
axis: 'block' | 'inline' | 'none';
|
|
59
|
+
constructor(host: ChartPointerHost);
|
|
60
|
+
hostConnected(): void;
|
|
61
|
+
hostDisconnected(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Tells the tooltip its anchor moved, which nothing else would.
|
|
64
|
+
*
|
|
65
|
+
* The mark the tooltip is hung on is one element the chart walks from reading
|
|
66
|
+
* to reading with an inline style, and Floating UI's `autoUpdate` watches
|
|
67
|
+
* scrolling, resizing and layout shift. Moving an absolutely positioned
|
|
68
|
+
* element by writing `inset` is none of the three, so the bubble is placed
|
|
69
|
+
* once on open and then stays where the first reading was while the mark
|
|
70
|
+
* travels out from under it.
|
|
71
|
+
*
|
|
72
|
+
* Here rather than in the chart because the reading is this controller's, and
|
|
73
|
+
* after the update rather than in `set` because the mark is still at the old
|
|
74
|
+
* position until the render that follows.
|
|
75
|
+
*/
|
|
76
|
+
hostUpdated(): void;
|
|
77
|
+
/** Puts the pointer on a reading, for a chart that resolves it some other way. */
|
|
78
|
+
set(at: number): void;
|
|
79
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which reading a pointer is nearest, for a chart that draws no markers.
|
|
3
|
+
*
|
|
4
|
+
* **The anchor is a position and not an element, and that is the whole reason
|
|
5
|
+
* this exists.** A bar and a donut slice are elements a tooltip could be hung
|
|
6
|
+
* on; a line is one `<path>` for a whole series, because
|
|
7
|
+
* `preserveAspectRatio="none"` turns a circle into an ellipse and there is no
|
|
8
|
+
* vector-effect that answers for a shape, so `LineChart` deliberately draws no
|
|
9
|
+
* per-reading marker. Anchoring per datum works for two charts of four and
|
|
10
|
+
* cannot be extended to the third, so the resolution is arithmetic instead.
|
|
11
|
+
*
|
|
12
|
+
* **It maps along one axis only.** The nearest reading to a pointer is decided
|
|
13
|
+
* by x alone in a vertical chart, whatever y is doing, because a reader moving
|
|
14
|
+
* across a chart is asking about a column of it rather than about a point. A
|
|
15
|
+
* true nearest-point search would make a two-series chart answer differently
|
|
16
|
+
* depending on which line the pointer drifted toward, which is a tooltip that
|
|
17
|
+
* changes its mind while the reader holds still.
|
|
18
|
+
*
|
|
19
|
+
* **Coarse pointers get nothing.** A touch has no hover, so a tooltip driven by
|
|
20
|
+
* one appears where a finger already covers it and stays after the finger
|
|
21
|
+
* lifts. The table underneath is the readable version and is what a phone
|
|
22
|
+
* should be given, so this listens for `pointermove` and ignores anything whose
|
|
23
|
+
* `pointerType` is not fine.
|
|
24
|
+
*/
|
|
25
|
+
export class ChartPointerController {
|
|
26
|
+
#host;
|
|
27
|
+
#moved;
|
|
28
|
+
#plot;
|
|
29
|
+
constructor(host) {
|
|
30
|
+
this.#moved = false;
|
|
31
|
+
/** The reading under the pointer, or -1 when the pointer is elsewhere. */
|
|
32
|
+
this.at = -1;
|
|
33
|
+
/**
|
|
34
|
+
* Which axis the readings are laid out along.
|
|
35
|
+
*
|
|
36
|
+
* **An axis and not a boolean, because two of the four charts do not use x.**
|
|
37
|
+
* This was `tracks = true`, and every reading was resolved from the pointer's
|
|
38
|
+
* x whatever the chart was doing. A horizontal bar chart runs its categories
|
|
39
|
+
* *down* the box, so moving the pointer from the first row to the last never
|
|
40
|
+
* changed the answer: measured on four rows, all four reported the third,
|
|
41
|
+
* which is the slot the middle of the box falls in. It looked like a tooltip
|
|
42
|
+
* that would not follow and was a tooltip reading the wrong axis.
|
|
43
|
+
*
|
|
44
|
+
* `none` is the ring. Its slices are real elements at every angle, so the
|
|
45
|
+
* reading under the pointer is the one the pointer is over, and arithmetic on
|
|
46
|
+
* either axis would answer with whichever slice shared a coordinate: at three
|
|
47
|
+
* o'clock and at nine o'clock, x alone gives the same slice for opposite
|
|
48
|
+
* sides. The slices call `set` and this only holds the answer.
|
|
49
|
+
*/
|
|
50
|
+
this.axis = 'inline';
|
|
51
|
+
this.#move = (event) => {
|
|
52
|
+
if (this.axis === 'none' || (event.pointerType !== 'mouse' && event.pointerType !== 'pen')) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const box = this.#box();
|
|
56
|
+
const count = this.#host.readings;
|
|
57
|
+
const along = this.axis === 'block' ? box?.height : box?.width;
|
|
58
|
+
if (!box || !along || count === 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
/* The same slot arithmetic the charts draw with: reading `i` sits in the
|
|
62
|
+
middle of the `i`th of `count` equal slots, so the nearest is the slot
|
|
63
|
+
the pointer is in. Rounding a proportion of `count - 1` instead puts the
|
|
64
|
+
boundaries half a slot off and the first and last reading get half the
|
|
65
|
+
catchment of the others. */
|
|
66
|
+
const from = this.axis === 'block' ? event.clientY - box.top : event.clientX - box.left;
|
|
67
|
+
const slot = Math.floor((from / along) * count);
|
|
68
|
+
const next = Math.min(Math.max(slot, 0), count - 1);
|
|
69
|
+
if (next !== this.at) {
|
|
70
|
+
this.at = next;
|
|
71
|
+
this.#moved = true;
|
|
72
|
+
this.#host.requestUpdate();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
this.#leave = () => {
|
|
76
|
+
if (this.at !== -1) {
|
|
77
|
+
this.at = -1;
|
|
78
|
+
this.#host.requestUpdate();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
this.#host = host;
|
|
82
|
+
host.addController(this);
|
|
83
|
+
}
|
|
84
|
+
hostConnected() {
|
|
85
|
+
this.#host.addEventListener('pointermove', this.#move);
|
|
86
|
+
this.#host.addEventListener('pointerleave', this.#leave);
|
|
87
|
+
}
|
|
88
|
+
hostDisconnected() {
|
|
89
|
+
this.#host.removeEventListener('pointermove', this.#move);
|
|
90
|
+
this.#host.removeEventListener('pointerleave', this.#leave);
|
|
91
|
+
this.at = -1;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Tells the tooltip its anchor moved, which nothing else would.
|
|
95
|
+
*
|
|
96
|
+
* The mark the tooltip is hung on is one element the chart walks from reading
|
|
97
|
+
* to reading with an inline style, and Floating UI's `autoUpdate` watches
|
|
98
|
+
* scrolling, resizing and layout shift. Moving an absolutely positioned
|
|
99
|
+
* element by writing `inset` is none of the three, so the bubble is placed
|
|
100
|
+
* once on open and then stays where the first reading was while the mark
|
|
101
|
+
* travels out from under it.
|
|
102
|
+
*
|
|
103
|
+
* Here rather than in the chart because the reading is this controller's, and
|
|
104
|
+
* after the update rather than in `set` because the mark is still at the old
|
|
105
|
+
* position until the render that follows.
|
|
106
|
+
*/
|
|
107
|
+
hostUpdated() {
|
|
108
|
+
if (!this.#moved) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.#moved = false;
|
|
112
|
+
this.#host.renderRoot
|
|
113
|
+
.querySelector('[part="tooltip-anchor"]')
|
|
114
|
+
?.reposition?.();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The box the readings are laid out in, which is the plot and not the host.
|
|
118
|
+
*
|
|
119
|
+
* The host also holds the row of labels, so measuring against it would map
|
|
120
|
+
* the pointer over a box taller than the drawing. That mattered the moment a
|
|
121
|
+
* chart started measuring down the box rather than across it, which is what
|
|
122
|
+
* `axis` is for: on a horizontal bar chart the labels are beside the plot
|
|
123
|
+
* rather than under it, and the host is wider than the drawing by exactly
|
|
124
|
+
* their column.
|
|
125
|
+
*/
|
|
126
|
+
#box() {
|
|
127
|
+
this.#plot ??= this.#host.renderRoot.querySelector('[part="plot"]') ?? undefined;
|
|
128
|
+
return this.#plot?.getBoundingClientRect();
|
|
129
|
+
}
|
|
130
|
+
/** Puts the pointer on a reading, for a chart that resolves it some other way. */
|
|
131
|
+
set(at) {
|
|
132
|
+
if (at !== this.at) {
|
|
133
|
+
this.at = at;
|
|
134
|
+
this.#moved = true;
|
|
135
|
+
this.#host.requestUpdate();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
#move;
|
|
139
|
+
#leave;
|
|
140
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
2
|
+
/**
|
|
3
|
+
* Whether the browser implements invoker commands.
|
|
4
|
+
*
|
|
5
|
+
* Feature-detected on the prototype rather than on an instance, so it can be
|
|
6
|
+
* answered before anything has rendered, and guarded for a server, which has no
|
|
7
|
+
* `HTMLButtonElement` at all.
|
|
8
|
+
*/
|
|
9
|
+
export declare const supportsInvokers: () => boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The `command` event, for a browser that does not send one.
|
|
12
|
+
*
|
|
13
|
+
* Invoker commands are recent: Chrome 135, Firefox 137, Safari 18.4. Before
|
|
14
|
+
* this, a button in an older browser set `commandForElement` on nothing, the
|
|
15
|
+
* wiring silently did not happen, and the control did nothing at all when
|
|
16
|
+
* clicked. No error, no warning, and every `command` in the library dead at
|
|
17
|
+
* once, which is the worst way for a feature to be missing: the markup is
|
|
18
|
+
* right, the component is right, and the button is simply inert.
|
|
19
|
+
*
|
|
20
|
+
* So the button sends this instead where the platform will not. It carries the
|
|
21
|
+
* two members `CommandController` reads and dispatches at the target, which is
|
|
22
|
+
* where the platform dispatches too, so nothing downstream can tell the
|
|
23
|
+
* difference.
|
|
24
|
+
*
|
|
25
|
+
* **The built-in commands are not reproduced.** `show-modal`, `close` and
|
|
26
|
+
* `toggle-popover` are defined against native elements and applied by the
|
|
27
|
+
* browser itself, and writing our own would mean reimplementing part of the
|
|
28
|
+
* platform against elements we do not own. Nothing in this library uses them:
|
|
29
|
+
* a custom element only ever answers a `--` command, which is the platform's
|
|
30
|
+
* marker for "the target decides", and the target is ours.
|
|
31
|
+
*/
|
|
32
|
+
export declare class HelixFallbackCommandEvent extends Event {
|
|
33
|
+
/** The command asked for, verbatim. */
|
|
34
|
+
readonly command: string;
|
|
35
|
+
/** The element that asked. */
|
|
36
|
+
readonly source: Element | null;
|
|
37
|
+
constructor(command: string, source: Element | null);
|
|
38
|
+
}
|
|
39
|
+
/** What the host answers to, by command name. */
|
|
40
|
+
export interface CommandOptions {
|
|
41
|
+
/** Each command the host implements, keyed by the name a trigger writes. */
|
|
42
|
+
commands: Record<string, (source: Element | null) => void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Lets a control elsewhere on the page operate this one, declaratively.
|
|
46
|
+
*
|
|
47
|
+
* ```html
|
|
48
|
+
* <helix-button commandfor="confirm" command="--show">Delete…</helix-button>
|
|
49
|
+
* <helix-dialog id="confirm">…</helix-dialog>
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* **The platform's own invoker, not a convention of ours.** `commandfor` and
|
|
53
|
+
* `command` are attributes the browser understands: it resolves the id, wires
|
|
54
|
+
* the two together, and dispatches a `command` event at the target when the
|
|
55
|
+
* trigger is activated. That is a real event with a real source, so it works
|
|
56
|
+
* for a click, for Enter, for Space, and for anything else the platform decides
|
|
57
|
+
* counts as activation later.
|
|
58
|
+
*
|
|
59
|
+
* The alternative was a `data-` recipe of our own with a delegated listener on
|
|
60
|
+
* the document, and it is worse in every direction: a global listener a
|
|
61
|
+
* consumer cannot see, an id lookup we would write ourselves, activation
|
|
62
|
+
* semantics we would have to guess at, and a convention that is ours to
|
|
63
|
+
* document and theirs to learn.
|
|
64
|
+
*
|
|
65
|
+
* **Only custom commands reach a custom element.** The built-in ones —
|
|
66
|
+
* `show-modal`, `close`, `toggle-popover` — are defined against native elements
|
|
67
|
+
* and the browser applies them itself; pointed at a custom element they do
|
|
68
|
+
* nothing. So the names here start with `--`, which is the platform's marker
|
|
69
|
+
* for "the target decides what this means", and the target deciding is exactly
|
|
70
|
+
* what this is.
|
|
71
|
+
*
|
|
72
|
+
* Unrecognized commands are left alone rather than swallowed, so a host that
|
|
73
|
+
* answers `--show` does not quietly absorb a `--print` meant for something
|
|
74
|
+
* else.
|
|
75
|
+
*/
|
|
76
|
+
export declare class CommandController implements ReactiveController {
|
|
77
|
+
private readonly host;
|
|
78
|
+
private readonly options;
|
|
79
|
+
constructor(host: ReactiveControllerHost & HTMLElement, options: CommandOptions);
|
|
80
|
+
private readonly handle;
|
|
81
|
+
hostConnected(): void;
|
|
82
|
+
hostDisconnected(): void;
|
|
83
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the browser implements invoker commands.
|
|
3
|
+
*
|
|
4
|
+
* Feature-detected on the prototype rather than on an instance, so it can be
|
|
5
|
+
* answered before anything has rendered, and guarded for a server, which has no
|
|
6
|
+
* `HTMLButtonElement` at all.
|
|
7
|
+
*/
|
|
8
|
+
export const supportsInvokers = () => typeof HTMLButtonElement !== 'undefined' && 'commandForElement' in HTMLButtonElement.prototype;
|
|
9
|
+
/**
|
|
10
|
+
* The `command` event, for a browser that does not send one.
|
|
11
|
+
*
|
|
12
|
+
* Invoker commands are recent: Chrome 135, Firefox 137, Safari 18.4. Before
|
|
13
|
+
* this, a button in an older browser set `commandForElement` on nothing, the
|
|
14
|
+
* wiring silently did not happen, and the control did nothing at all when
|
|
15
|
+
* clicked. No error, no warning, and every `command` in the library dead at
|
|
16
|
+
* once, which is the worst way for a feature to be missing: the markup is
|
|
17
|
+
* right, the component is right, and the button is simply inert.
|
|
18
|
+
*
|
|
19
|
+
* So the button sends this instead where the platform will not. It carries the
|
|
20
|
+
* two members `CommandController` reads and dispatches at the target, which is
|
|
21
|
+
* where the platform dispatches too, so nothing downstream can tell the
|
|
22
|
+
* difference.
|
|
23
|
+
*
|
|
24
|
+
* **The built-in commands are not reproduced.** `show-modal`, `close` and
|
|
25
|
+
* `toggle-popover` are defined against native elements and applied by the
|
|
26
|
+
* browser itself, and writing our own would mean reimplementing part of the
|
|
27
|
+
* platform against elements we do not own. Nothing in this library uses them:
|
|
28
|
+
* a custom element only ever answers a `--` command, which is the platform's
|
|
29
|
+
* marker for "the target decides", and the target is ours.
|
|
30
|
+
*/
|
|
31
|
+
export class HelixFallbackCommandEvent extends Event {
|
|
32
|
+
constructor(command, source) {
|
|
33
|
+
// Matching the platform: fired at the invokee, cancelable, and not
|
|
34
|
+
// bubbling. `CommandController` listens on the target itself, so there is
|
|
35
|
+
// nothing above it that a bubble would reach anyway.
|
|
36
|
+
super('command', { cancelable: true });
|
|
37
|
+
this.command = command;
|
|
38
|
+
this.source = source;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Lets a control elsewhere on the page operate this one, declaratively.
|
|
43
|
+
*
|
|
44
|
+
* ```html
|
|
45
|
+
* <helix-button commandfor="confirm" command="--show">Delete…</helix-button>
|
|
46
|
+
* <helix-dialog id="confirm">…</helix-dialog>
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* **The platform's own invoker, not a convention of ours.** `commandfor` and
|
|
50
|
+
* `command` are attributes the browser understands: it resolves the id, wires
|
|
51
|
+
* the two together, and dispatches a `command` event at the target when the
|
|
52
|
+
* trigger is activated. That is a real event with a real source, so it works
|
|
53
|
+
* for a click, for Enter, for Space, and for anything else the platform decides
|
|
54
|
+
* counts as activation later.
|
|
55
|
+
*
|
|
56
|
+
* The alternative was a `data-` recipe of our own with a delegated listener on
|
|
57
|
+
* the document, and it is worse in every direction: a global listener a
|
|
58
|
+
* consumer cannot see, an id lookup we would write ourselves, activation
|
|
59
|
+
* semantics we would have to guess at, and a convention that is ours to
|
|
60
|
+
* document and theirs to learn.
|
|
61
|
+
*
|
|
62
|
+
* **Only custom commands reach a custom element.** The built-in ones —
|
|
63
|
+
* `show-modal`, `close`, `toggle-popover` — are defined against native elements
|
|
64
|
+
* and the browser applies them itself; pointed at a custom element they do
|
|
65
|
+
* nothing. So the names here start with `--`, which is the platform's marker
|
|
66
|
+
* for "the target decides what this means", and the target deciding is exactly
|
|
67
|
+
* what this is.
|
|
68
|
+
*
|
|
69
|
+
* Unrecognized commands are left alone rather than swallowed, so a host that
|
|
70
|
+
* answers `--show` does not quietly absorb a `--print` meant for something
|
|
71
|
+
* else.
|
|
72
|
+
*/
|
|
73
|
+
export class CommandController {
|
|
74
|
+
constructor(host, options) {
|
|
75
|
+
this.handle = (event) => {
|
|
76
|
+
const command = event.command;
|
|
77
|
+
const run = this.options.commands[command];
|
|
78
|
+
if (!run) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
run(event.source);
|
|
82
|
+
};
|
|
83
|
+
this.host = host;
|
|
84
|
+
this.options = options;
|
|
85
|
+
host.addController(this);
|
|
86
|
+
}
|
|
87
|
+
hostConnected() {
|
|
88
|
+
this.host.addEventListener('command', this.handle);
|
|
89
|
+
}
|
|
90
|
+
hostDisconnected() {
|
|
91
|
+
this.host.removeEventListener('command', this.handle);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
2
|
+
/** What a copy needs to know, asked for rather than assumed. */
|
|
3
|
+
export interface CopyOptions {
|
|
4
|
+
/**
|
|
5
|
+
* How long the confirmation stands, in milliseconds.
|
|
6
|
+
*
|
|
7
|
+
* On a timer rather than on the pointer leaving, so it survives a hand that
|
|
8
|
+
* has already moved on: the press and the reading of it are two moments, and
|
|
9
|
+
* a mark that vanishes with the cursor is one a fast reader never sees.
|
|
10
|
+
*/
|
|
11
|
+
after?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Puts text on the clipboard and holds a confirmation for a moment.
|
|
15
|
+
*
|
|
16
|
+
* A controller rather than a component, because copying is a behavior and not a
|
|
17
|
+
* shape. Color's chip is a swatch that copies, and a copy button is a button
|
|
18
|
+
* that copies, and those two have nothing in common to look at: what they share
|
|
19
|
+
* is the write, the confirmation that follows it, the timer that ends it and the
|
|
20
|
+
* event that announces it. Written twice, the second one would get the failure
|
|
21
|
+
* case wrong.
|
|
22
|
+
*
|
|
23
|
+
* **There are two routes and both are tried**, because the modern one is gated
|
|
24
|
+
* in more places than it looks: an insecure origin, an unfocused document, and a
|
|
25
|
+
* Permissions Policy defaulting to `self`, which a sandboxed iframe is not. The
|
|
26
|
+
* component using this reported as not working, and a control that does nothing
|
|
27
|
+
* is indistinguishable from a broken one. See `write` above.
|
|
28
|
+
*
|
|
29
|
+
* **Where neither route is allowed, the failure is silence.** The confirmation
|
|
30
|
+
* does not appear, which is the honest report: a message about a clipboard is
|
|
31
|
+
* worse than a chip that did not flash, and there is nothing a reader could do
|
|
32
|
+
* about a permission policy anyway.
|
|
33
|
+
*
|
|
34
|
+
* **The event is announced after the write, not before it.** It is not
|
|
35
|
+
* cancelable for the same reason: by the time anything could be said, the text
|
|
36
|
+
* is already on the clipboard, and an event that cannot prevent what it
|
|
37
|
+
* describes should not pretend otherwise.
|
|
38
|
+
*
|
|
39
|
+
* The timer is cleared when the host leaves the document, or a component removed
|
|
40
|
+
* mid-confirmation asks a detached element to re-render.
|
|
41
|
+
*/
|
|
42
|
+
export declare class CopyController implements ReactiveController {
|
|
43
|
+
private readonly host;
|
|
44
|
+
private readonly options;
|
|
45
|
+
/** Whether the last write succeeded and is still being confirmed. */
|
|
46
|
+
copied: boolean;
|
|
47
|
+
private timer;
|
|
48
|
+
constructor(host: ReactiveControllerHost & EventTarget, options?: CopyOptions);
|
|
49
|
+
/**
|
|
50
|
+
* Writes to the clipboard, and confirms it if the platform allowed it.
|
|
51
|
+
*
|
|
52
|
+
* Empty text is not copied. An empty clipboard is not a thing a reader ever
|
|
53
|
+
* asks for, and confirming one is worse than doing nothing.
|
|
54
|
+
*/
|
|
55
|
+
copy(text: string): Promise<void>;
|
|
56
|
+
hostDisconnected(): void;
|
|
57
|
+
private reset;
|
|
58
|
+
}
|