@promptctl/rich-js 0.24.0 → 0.25.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.
- package/README.md +7 -0
- package/THIRD-PARTY-NOTICES +29 -1
- package/dist/core/anchor.d.ts +5 -0
- package/dist/core/anchor.js +11 -4
- package/dist/core/animation.d.ts +243 -0
- package/dist/core/animation.js +375 -0
- package/dist/core/ansi.js +2 -2
- package/dist/core/box.d.ts +7 -7
- package/dist/core/cell-widths.d.ts +9 -0
- package/dist/core/cell-widths.js +140 -0
- package/dist/core/cells.d.ts +40 -24
- package/dist/core/cells.js +151 -66
- package/dist/core/clock.d.ts +2 -2
- package/dist/core/clock.js +6 -4
- package/dist/core/color.d.ts +27 -10
- package/dist/core/color.js +21 -29
- package/dist/core/console.d.ts +28 -17
- package/dist/core/console.js +48 -43
- package/dist/core/easing.d.ts +140 -10
- package/dist/core/easing.js +328 -50
- package/dist/core/emoji.d.ts +1 -1
- package/dist/core/emoji.js +1 -2
- package/dist/core/export-html.js +2 -1
- package/dist/core/export-lines.js +3 -3
- package/dist/core/highlighter.d.ts +9 -8
- package/dist/core/highlighter.js +3 -2
- package/dist/core/json.js +3 -2
- package/dist/core/markup.js +16 -12
- package/dist/core/measure.js +3 -3
- package/dist/core/memo.d.ts +11 -2
- package/dist/core/memo.js +16 -0
- package/dist/core/oklch.js +17 -6
- package/dist/core/osc8.d.ts +1 -1
- package/dist/core/osc8.js +4 -5
- package/dist/core/paint.d.ts +2 -1
- package/dist/core/paint.js +4 -3
- package/dist/core/pattern.d.ts +21 -0
- package/dist/core/pattern.js +21 -0
- package/dist/core/place.js +2 -1
- package/dist/core/pretty.js +11 -12
- package/dist/core/protocol.d.ts +136 -21
- package/dist/core/protocol.js +203 -16
- package/dist/core/render.d.ts +15 -2
- package/dist/core/render.js +27 -6
- package/dist/core/segment.d.ts +31 -6
- package/dist/core/segment.js +68 -53
- package/dist/core/spinnerData.d.ts +1 -1
- package/dist/core/state.d.ts +53 -0
- package/dist/core/state.js +82 -0
- package/dist/core/strip.d.ts +11 -13
- package/dist/core/strip.js +126 -113
- package/dist/core/style.d.ts +47 -14
- package/dist/core/style.js +74 -34
- package/dist/core/text.d.ts +5 -8
- package/dist/core/text.js +133 -133
- package/dist/core/timeline.d.ts +109 -0
- package/dist/core/timeline.js +180 -0
- package/dist/core/tween.d.ts +49 -0
- package/dist/core/tween.js +71 -0
- package/dist/core/view.d.ts +92 -0
- package/dist/core/view.js +83 -0
- package/dist/host/app.d.ts +49 -21
- package/dist/host/app.js +72 -38
- package/dist/host/input.js +3 -4
- package/dist/index.d.ts +30 -15
- package/dist/index.js +20 -6
- package/dist/node/traceback.js +4 -3
- package/dist/renderables/change.d.ts +42 -0
- package/dist/renderables/change.js +112 -0
- package/dist/renderables/columns.d.ts +1 -1
- package/dist/renderables/columns.js +3 -3
- package/dist/renderables/constrain.js +2 -1
- package/dist/renderables/effect.d.ts +155 -92
- package/dist/renderables/effect.js +203 -136
- package/dist/renderables/effects.d.ts +137 -24
- package/dist/renderables/effects.js +276 -113
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +18 -9
- package/dist/renderables/flexStrip.js +2 -1
- package/dist/renderables/frames.d.ts +45 -43
- package/dist/renderables/frames.js +57 -45
- package/dist/renderables/glyph-effect.d.ts +158 -0
- package/dist/renderables/glyph-effect.js +237 -0
- package/dist/renderables/glyph-effects.d.ts +147 -0
- package/dist/renderables/glyph-effects.js +283 -0
- package/dist/renderables/group.d.ts +1 -1
- package/dist/renderables/group.js +2 -2
- package/dist/renderables/html-entities.d.ts +8 -0
- package/dist/renderables/html-entities.js +386 -0
- package/dist/renderables/layout.d.ts +10 -9
- package/dist/renderables/layout.js +42 -35
- package/dist/renderables/lifecycle.d.ts +77 -0
- package/dist/renderables/lifecycle.js +132 -0
- package/dist/renderables/list-view.d.ts +2 -2
- package/dist/renderables/list-view.js +11 -8
- package/dist/renderables/live.d.ts +36 -21
- package/dist/renderables/live.js +89 -42
- package/dist/renderables/markdown-parse.d.ts +109 -0
- package/dist/renderables/markdown-parse.js +1427 -0
- package/dist/renderables/markdown-url.d.ts +19 -0
- package/dist/renderables/markdown-url.js +314 -0
- package/dist/renderables/markdown.d.ts +5 -7
- package/dist/renderables/markdown.js +356 -683
- package/dist/renderables/padding.d.ts +7 -20
- package/dist/renderables/padding.js +29 -50
- package/dist/renderables/panel.d.ts +30 -32
- package/dist/renderables/panel.js +65 -109
- package/dist/renderables/progress.d.ts +83 -32
- package/dist/renderables/progress.js +186 -90
- package/dist/renderables/progressBar.d.ts +22 -12
- package/dist/renderables/progressBar.js +33 -10
- package/dist/renderables/prompt.js +3 -2
- package/dist/renderables/python-space.d.ts +13 -0
- package/dist/renderables/python-space.js +25 -0
- package/dist/renderables/scroll.d.ts +50 -0
- package/dist/renderables/scroll.js +106 -0
- package/dist/renderables/spinner.d.ts +57 -24
- package/dist/renderables/spinner.js +101 -49
- package/dist/renderables/status.js +1 -1
- package/dist/renderables/syntax.d.ts +10 -9
- package/dist/renderables/syntax.js +5 -3
- package/dist/renderables/table.d.ts +48 -46
- package/dist/renderables/table.js +170 -208
- package/dist/renderables/traceback.d.ts +3 -3
- package/dist/renderables/traceback.js +12 -10
- package/dist/renderables/tree.d.ts +28 -25
- package/dist/renderables/tree.js +108 -58
- package/dist/renderables/viewport.d.ts +77 -38
- package/dist/renderables/viewport.js +156 -72
- package/dist/template-bindings/animation-funcs.d.ts +78 -9
- package/dist/template-bindings/animation-funcs.js +132 -14
- package/dist/template-bindings/index.d.ts +5 -5
- package/dist/template-bindings/index.js +7 -6
- package/dist/themes/ramp.d.ts +62 -4
- package/dist/themes/ramp.js +82 -4
- package/dist/themes/registry.js +5 -7
- package/dist/widgets/button.js +3 -2
- package/dist/widgets/checkbox.js +4 -3
- package/dist/widgets/dropdown.d.ts +3 -1
- package/dist/widgets/dropdown.js +20 -15
- package/dist/widgets/event-router.js +4 -2
- package/dist/widgets/focus-manager.d.ts +23 -14
- package/dist/widgets/focus-manager.js +34 -33
- package/dist/widgets/hit.d.ts +6 -0
- package/dist/widgets/hit.js +8 -1
- package/dist/widgets/index.d.ts +1 -1
- package/dist/widgets/observable.d.ts +23 -0
- package/dist/widgets/observable.js +43 -0
- package/dist/widgets/slider.d.ts +1 -1
- package/dist/widgets/slider.js +12 -3
- package/dist/widgets/static-item.d.ts +1 -1
- package/dist/widgets/static-item.js +6 -7
- package/dist/widgets/text-input.d.ts +53 -47
- package/dist/widgets/text-input.js +456 -399
- package/dist/widgets/themed-widget.js +2 -1
- package/dist/widgets/toggle.js +4 -3
- package/dist/widgets/types.d.ts +10 -4
- package/dist/widgets/types.js +8 -2
- package/dist/widgets/widget-app.d.ts +32 -6
- package/dist/widgets/widget-app.js +74 -27
- package/dist/widgets/widget-base.d.ts +2 -2
- package/dist/widgets/widget-base.js +29 -21
- package/package.json +9 -5
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
* - `paletteFuncs(getPalette)` — `color` and `ramp`, the two functions that
|
|
15
15
|
* resolve a palette name. This is the *only* registration that knows a
|
|
16
16
|
* palette exists. [LAW:one-way-deps]
|
|
17
|
-
* - `animationFuncs(
|
|
18
|
-
*
|
|
19
|
-
* template is evaluated at a time, and the time
|
|
20
|
-
* (`./animation-funcs.ts`).
|
|
17
|
+
* - `animationFuncs(frame)` — `frames`, `stepped`, `tween`, `loop` and
|
|
18
|
+
* `glyphs`, drawn for a moment on a timeline. It is the only registration
|
|
19
|
+
* that knows the template is evaluated at a time, and the time and the
|
|
20
|
+
* timeline are the host's to hand in (`./animation-funcs.ts`).
|
|
21
21
|
*
|
|
22
22
|
* `createRichTextEngine()` wires up `richTextFuncs()` only — it cannot supply a
|
|
23
23
|
* palette. Consumers that name theme colours call `paletteFuncs(getPalette)`
|
|
@@ -40,11 +40,12 @@ import { createEngine } from "@promptctl/go-template-js";
|
|
|
40
40
|
import { RichText } from "../core/text.js";
|
|
41
41
|
import { Style } from "../core/style.js";
|
|
42
42
|
import { Segment } from "../core/segment.js";
|
|
43
|
+
import { render } from "../core/protocol.js";
|
|
43
44
|
import { richTextStyleFuncs } from "./style-funcs.js";
|
|
44
45
|
import { colorFuncs } from "./color-funcs.js";
|
|
45
46
|
export { paletteFuncs } from "./palette-funcs.js";
|
|
46
47
|
export { colorFuncs, readableOnFunc } from "./color-funcs.js";
|
|
47
|
-
export { animationFuncs } from "./animation-funcs.js";
|
|
48
|
+
export { animationFuncs, templateFrame } from "./animation-funcs.js";
|
|
48
49
|
/**
|
|
49
50
|
* Funcs registered by the rich-js binding — the colour sinks, the palette-free
|
|
50
51
|
* colour math, text attributes, and the `link` cell-splitter. Nothing here
|
|
@@ -131,7 +132,7 @@ export function renderTemplate(engine, source, scope = {}, options) {
|
|
|
131
132
|
catch (e) {
|
|
132
133
|
return [new Segment(errorLine(e, maxWidth), safeErrorStyle(errorStyle))];
|
|
133
134
|
}
|
|
134
|
-
return Array.from(
|
|
135
|
+
return Array.from(render(text, { isTerminal: true, ...renderOptions, maxWidth }));
|
|
135
136
|
}
|
|
136
137
|
/**
|
|
137
138
|
* A caught error as the one line `renderTemplate` promises. The summary is the
|
package/dist/themes/ramp.d.ts
CHANGED
|
@@ -20,17 +20,38 @@
|
|
|
20
20
|
* theme colors passes through perceptually even steps rather than the muddy
|
|
21
21
|
* midpoints of an sRGB blend.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* A ramp also carries how far it has been moved: `scroll`, in spans, how
|
|
24
|
+
* far its colours have travelled along it, and `hue`, in turns, how far
|
|
25
|
+
* they are rotated round the OKLCH hue circle. A moved ramp is still a
|
|
26
|
+
* `ColorRamp`, so anything that reads a ramp reads a moving one frame by
|
|
27
|
+
* frame (`moving`) with nothing new to learn. [LAW:one-type-per-behavior]
|
|
28
|
+
*
|
|
29
|
+
* [LAW:one-way-deps] Imports `core/color`, `core/easing`, `core/oklch`,
|
|
30
|
+
* `core/animation` for `over` and `moving`, and `core/tween` for `over`; nothing
|
|
31
|
+
* here knows what a palette is. Resolving stop *names* is the template binding's
|
|
25
32
|
* job (`paletteFuncs`), which hands this module resolved colors.
|
|
26
33
|
*/
|
|
34
|
+
import { Animation } from "../core/animation.js";
|
|
27
35
|
import type { ColorRgba } from "../core/color.js";
|
|
28
|
-
import type
|
|
36
|
+
import { type Ease } from "../core/easing.js";
|
|
29
37
|
/** One stop: the color the ramp is exactly `color` at position `at`. */
|
|
30
38
|
export interface ColorStop {
|
|
31
39
|
readonly at: number;
|
|
32
40
|
readonly color: ColorRgba;
|
|
33
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* How a ramp moves over time, each part an `Animation<number>` that is
|
|
44
|
+
* `Animation.constant(0)` when not given. `scroll` is in spans: at 1 the
|
|
45
|
+
* colours have travelled the whole way from the first stop to the last and
|
|
46
|
+
* wrapped back, so `Animation.continuous(Phase.loop(seconds))` scrolls once
|
|
47
|
+
* every `seconds`. `hue` is in turns, CSS's `turn`: at 1 every hue has gone
|
|
48
|
+
* once round the circle. A one-shot that settles is a `tween` to where it
|
|
49
|
+
* rests.
|
|
50
|
+
*/
|
|
51
|
+
export interface RampMotion {
|
|
52
|
+
readonly scroll?: Animation<number>;
|
|
53
|
+
readonly hue?: Animation<number>;
|
|
54
|
+
}
|
|
34
55
|
/**
|
|
35
56
|
* An immutable ramp over stops sorted by position.
|
|
36
57
|
*
|
|
@@ -49,7 +70,11 @@ export interface ColorStop {
|
|
|
49
70
|
export declare class ColorRamp {
|
|
50
71
|
readonly ease: Ease;
|
|
51
72
|
readonly stops: readonly ColorStop[];
|
|
52
|
-
|
|
73
|
+
/** How far the colours have moved along the span, in spans, in [0, 1). */
|
|
74
|
+
readonly scroll: number;
|
|
75
|
+
/** How far every hue is rotated in OKLCH, in turns, in [0, 1). */
|
|
76
|
+
readonly hue: number;
|
|
77
|
+
constructor(ease: Ease, stops: readonly ColorStop[], scroll?: number, hue?: number);
|
|
53
78
|
/**
|
|
54
79
|
* The color at `value`. Below the first stop it is the first color; at or
|
|
55
80
|
* above the last stop it is the last; between two stops the raw progress
|
|
@@ -65,6 +90,39 @@ export declare class ColorRamp {
|
|
|
65
90
|
* `jump-start`/`jump-both` steps, which jump at the start of the interval as
|
|
66
91
|
* CSS defines them. An ease that overshoots [0, 1] holds the end colors
|
|
67
92
|
* where it does: a ramp never paints outside its stops.
|
|
93
|
+
*
|
|
94
|
+
* A scrolled ramp reads, inside its span, the colour `scroll` spans behind
|
|
95
|
+
* `value`, wrapped back round the span as a repeating gradient does —
|
|
96
|
+
* seamless when the first and last stops are one colour; outside the span
|
|
97
|
+
* its end colours hold, however far it has scrolled. A rotated ramp turns
|
|
98
|
+
* the colour read by `hue` at its own lightness and chroma. Unmoved, every
|
|
99
|
+
* read keeps the exactness above.
|
|
68
100
|
*/
|
|
69
101
|
at(value: number): ColorRgba;
|
|
102
|
+
/** The colour at `value` with the ramp unmoved: the stops and the ease alone. */
|
|
103
|
+
private unmoved;
|
|
104
|
+
/**
|
|
105
|
+
* This ramp read over time: its span of positions, from the first stop's
|
|
106
|
+
* to the last's, crossed at a steady rate in `seconds` and held at the last
|
|
107
|
+
* stop from then on, each colour along the way the ramp's own. A colour
|
|
108
|
+
* tween is a two-stop ramp over its seconds — the ramp's ease shaping the
|
|
109
|
+
* move, so `new ColorRamp(ease, [{ at: 0, color: a }, { at: 1, color: b
|
|
110
|
+
* }]).over(seconds)` is `a` eased to `b` — and a ramp of more stops over
|
|
111
|
+
* time is a sweep through all of them. [LAW:one-source-of-truth] The
|
|
112
|
+
* blending is `at`'s; what time adds is `tween`'s (`core/tween.ts`), so a
|
|
113
|
+
* host sampling once a second or sixty times reads the stop colours byte
|
|
114
|
+
* for byte where `at` paints them.
|
|
115
|
+
*/
|
|
116
|
+
over(seconds: number): Animation<ColorRgba>;
|
|
117
|
+
/**
|
|
118
|
+
* This ramp moving: at `t`, scrolled by `motion.scroll` and rotated by
|
|
119
|
+
* `motion.hue` at `t`, on top of however far it was already moved, and
|
|
120
|
+
* changing when either does — so a loop moves every frame and a ramp a
|
|
121
|
+
* tween settles never changes again once it arrives.
|
|
122
|
+
* [LAW:one-source-of-truth] When it next changes is `Animation.all`'s
|
|
123
|
+
* answer over the two parts; nothing here says it a second time. Whether
|
|
124
|
+
* the depth it is drawn at can show the move is decided where it is drawn
|
|
125
|
+
* (`gradient`, through `stillOnSlots`).
|
|
126
|
+
*/
|
|
127
|
+
moving(motion: RampMotion): Animation<ColorRamp>;
|
|
70
128
|
}
|
package/dist/themes/ramp.js
CHANGED
|
@@ -20,11 +20,21 @@
|
|
|
20
20
|
* theme colors passes through perceptually even steps rather than the muddy
|
|
21
21
|
* midpoints of an sRGB blend.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* A ramp also carries how far it has been moved: `scroll`, in spans, how
|
|
24
|
+
* far its colours have travelled along it, and `hue`, in turns, how far
|
|
25
|
+
* they are rotated round the OKLCH hue circle. A moved ramp is still a
|
|
26
|
+
* `ColorRamp`, so anything that reads a ramp reads a moving one frame by
|
|
27
|
+
* frame (`moving`) with nothing new to learn. [LAW:one-type-per-behavior]
|
|
28
|
+
*
|
|
29
|
+
* [LAW:one-way-deps] Imports `core/color`, `core/easing`, `core/oklch`,
|
|
30
|
+
* `core/animation` for `over` and `moving`, and `core/tween` for `over`; nothing
|
|
31
|
+
* here knows what a palette is. Resolving stop *names* is the template binding's
|
|
25
32
|
* job (`paletteFuncs`), which hands this module resolved colors.
|
|
26
33
|
*/
|
|
27
|
-
import {
|
|
34
|
+
import { Animation } from "../core/animation.js";
|
|
35
|
+
import { EASES } from "../core/easing.js";
|
|
36
|
+
import { IDENTITY, Oklch } from "../core/oklch.js";
|
|
37
|
+
import { tween } from "../core/tween.js";
|
|
28
38
|
/**
|
|
29
39
|
* An immutable ramp over stops sorted by position.
|
|
30
40
|
*
|
|
@@ -43,9 +53,15 @@ import { Oklch } from "../core/oklch.js";
|
|
|
43
53
|
export class ColorRamp {
|
|
44
54
|
ease;
|
|
45
55
|
stops;
|
|
46
|
-
|
|
56
|
+
/** How far the colours have moved along the span, in spans, in [0, 1). */
|
|
57
|
+
scroll;
|
|
58
|
+
/** How far every hue is rotated in OKLCH, in turns, in [0, 1). */
|
|
59
|
+
hue;
|
|
60
|
+
constructor(ease, stops, scroll = 0, hue = 0) {
|
|
47
61
|
this.ease = ease;
|
|
48
62
|
this.stops = stops;
|
|
63
|
+
this.scroll = turnOf("scroll", scroll);
|
|
64
|
+
this.hue = turnOf("hue", hue);
|
|
49
65
|
if (stops.length === 0) {
|
|
50
66
|
throw new RangeError("a ColorRamp needs at least one stop");
|
|
51
67
|
}
|
|
@@ -75,11 +91,29 @@ export class ColorRamp {
|
|
|
75
91
|
* `jump-start`/`jump-both` steps, which jump at the start of the interval as
|
|
76
92
|
* CSS defines them. An ease that overshoots [0, 1] holds the end colors
|
|
77
93
|
* where it does: a ramp never paints outside its stops.
|
|
94
|
+
*
|
|
95
|
+
* A scrolled ramp reads, inside its span, the colour `scroll` spans behind
|
|
96
|
+
* `value`, wrapped back round the span as a repeating gradient does —
|
|
97
|
+
* seamless when the first and last stops are one colour; outside the span
|
|
98
|
+
* its end colours hold, however far it has scrolled. A rotated ramp turns
|
|
99
|
+
* the colour read by `hue` at its own lightness and chroma. Unmoved, every
|
|
100
|
+
* read keeps the exactness above.
|
|
78
101
|
*/
|
|
79
102
|
at(value) {
|
|
80
103
|
if (!Number.isFinite(value)) {
|
|
81
104
|
throw new RangeError(`ColorRamp.at needs a finite value, got ${value}`);
|
|
82
105
|
}
|
|
106
|
+
const first = this.stops[0].at;
|
|
107
|
+
const last = this.stops[this.stops.length - 1].at;
|
|
108
|
+
const span = last - first;
|
|
109
|
+
const inside = first <= value && value <= last;
|
|
110
|
+
const moved = inside ? value - this.scroll * span : value;
|
|
111
|
+
const read = this.unmoved(inside && moved < first ? moved + span : moved);
|
|
112
|
+
// Exact when unrotated: the OKLCH round trip can land a channel one off.
|
|
113
|
+
return this.hue === 0 ? read : Oklch.fromRgba(read).applyKey({ ...IDENTITY, hueShift: 360 * this.hue }).toRgba();
|
|
114
|
+
}
|
|
115
|
+
/** The colour at `value` with the ramp unmoved: the stops and the ease alone. */
|
|
116
|
+
unmoved(value) {
|
|
83
117
|
const stops = this.stops;
|
|
84
118
|
// The last stop at or below `value`; -1 when `value` is below them all.
|
|
85
119
|
let lower = -1;
|
|
@@ -100,4 +134,48 @@ export class ColorRamp {
|
|
|
100
134
|
return to.color;
|
|
101
135
|
return Oklch.fromRgba(from.color).mix(Oklch.fromRgba(to.color), t).toRgba();
|
|
102
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* This ramp read over time: its span of positions, from the first stop's
|
|
139
|
+
* to the last's, crossed at a steady rate in `seconds` and held at the last
|
|
140
|
+
* stop from then on, each colour along the way the ramp's own. A colour
|
|
141
|
+
* tween is a two-stop ramp over its seconds — the ramp's ease shaping the
|
|
142
|
+
* move, so `new ColorRamp(ease, [{ at: 0, color: a }, { at: 1, color: b
|
|
143
|
+
* }]).over(seconds)` is `a` eased to `b` — and a ramp of more stops over
|
|
144
|
+
* time is a sweep through all of them. [LAW:one-source-of-truth] The
|
|
145
|
+
* blending is `at`'s; what time adds is `tween`'s (`core/tween.ts`), so a
|
|
146
|
+
* host sampling once a second or sixty times reads the stop colours byte
|
|
147
|
+
* for byte where `at` paints them.
|
|
148
|
+
*/
|
|
149
|
+
over(seconds) {
|
|
150
|
+
const first = this.stops[0].at;
|
|
151
|
+
const last = this.stops[this.stops.length - 1].at;
|
|
152
|
+
return Animation.map(tween(first, last, { seconds, ease: EASES.linear }), (value) => this.at(value));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* This ramp moving: at `t`, scrolled by `motion.scroll` and rotated by
|
|
156
|
+
* `motion.hue` at `t`, on top of however far it was already moved, and
|
|
157
|
+
* changing when either does — so a loop moves every frame and a ramp a
|
|
158
|
+
* tween settles never changes again once it arrives.
|
|
159
|
+
* [LAW:one-source-of-truth] When it next changes is `Animation.all`'s
|
|
160
|
+
* answer over the two parts; nothing here says it a second time. Whether
|
|
161
|
+
* the depth it is drawn at can show the move is decided where it is drawn
|
|
162
|
+
* (`gradient`, through `stillOnSlots`).
|
|
163
|
+
*/
|
|
164
|
+
moving(motion) {
|
|
165
|
+
const still = Animation.constant(0);
|
|
166
|
+
return Animation.map(Animation.all([motion.scroll ?? still, motion.hue ?? still]), ([scroll, hue]) => new ColorRamp(this.ease, this.stops, this.scroll + scroll, this.hue + hue));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* `v` as a fraction of one turn, in [0, 1): a whole turn is no turn, and a
|
|
171
|
+
* negative one counts back from a whole turn. A value so close under a whole
|
|
172
|
+
* turn that the fraction rounds to it is the turn itself. [LAW:single-enforcer]
|
|
173
|
+
* The constructor reads both moves through here, so a non-finite one fails
|
|
174
|
+
* here, loudly, naming which.
|
|
175
|
+
*/
|
|
176
|
+
function turnOf(what, v) {
|
|
177
|
+
if (!Number.isFinite(v))
|
|
178
|
+
throw new RangeError(`ColorRamp ${what} must be finite, got ${v}`);
|
|
179
|
+
const into = v - Math.floor(v);
|
|
180
|
+
return into < 1 ? into : 0;
|
|
103
181
|
}
|
package/dist/themes/registry.js
CHANGED
|
@@ -3,8 +3,11 @@ import { Palette, drawnOn } from "./palette.js";
|
|
|
3
3
|
import { ACCENT_KEYS, accentText } from "./buildPalette.js";
|
|
4
4
|
import { THEMES } from "./data/index.js";
|
|
5
5
|
import { ANSI_SLOTS } from "./data/types.js";
|
|
6
|
+
import { Memo } from "../core/memo.js";
|
|
6
7
|
const THEME_NAMES = Object.freeze(Object.keys(THEMES).sort());
|
|
7
|
-
|
|
8
|
+
// Fewer themes than a Memo holds, so it never clears and a name keeps one
|
|
9
|
+
// Palette — test/themes/registry.test.ts holds that.
|
|
10
|
+
const cache = new Memo();
|
|
8
11
|
/**
|
|
9
12
|
* Returns the static list of available theme names. Pure and synchronous.
|
|
10
13
|
*/
|
|
@@ -14,12 +17,7 @@ export function listThemePalettes() {
|
|
|
14
17
|
export function getThemePalette(name) {
|
|
15
18
|
if (!isThemeName(name))
|
|
16
19
|
return null;
|
|
17
|
-
|
|
18
|
-
if (cached !== undefined)
|
|
19
|
-
return cached;
|
|
20
|
-
const palette = hydrate(THEMES[name]);
|
|
21
|
-
cache.set(name, palette);
|
|
22
|
-
return palette;
|
|
20
|
+
return cache.get(name, () => hydrate(THEMES[name]));
|
|
23
21
|
}
|
|
24
22
|
function isThemeName(name) {
|
|
25
23
|
return Object.hasOwn(THEMES, name);
|
package/dist/widgets/button.js
CHANGED
|
@@ -49,7 +49,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
49
49
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
50
50
|
done = true;
|
|
51
51
|
};
|
|
52
|
-
import {
|
|
52
|
+
import { action } from "mobx";
|
|
53
|
+
import { observableRef } from "./observable.js";
|
|
53
54
|
import { Segment } from "../core/segment.js";
|
|
54
55
|
import { Style } from "../core/style.js";
|
|
55
56
|
import { cellLen } from "../core/cells.js";
|
|
@@ -83,7 +84,7 @@ let Button = (() => {
|
|
|
83
84
|
return class Button extends _classSuper {
|
|
84
85
|
static {
|
|
85
86
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
86
|
-
_label_decorators = [
|
|
87
|
+
_label_decorators = [observableRef];
|
|
87
88
|
_variant_decorators = [observableRef];
|
|
88
89
|
_handleKey_decorators = [action];
|
|
89
90
|
_handleMouse_decorators = [action];
|
package/dist/widgets/checkbox.js
CHANGED
|
@@ -44,7 +44,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
44
44
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
45
45
|
done = true;
|
|
46
46
|
};
|
|
47
|
-
import {
|
|
47
|
+
import { action } from "mobx";
|
|
48
|
+
import { observableRef } from "./observable.js";
|
|
48
49
|
import { Segment } from "../core/segment.js";
|
|
49
50
|
import { Style } from "../core/style.js";
|
|
50
51
|
import { cellLen } from "../core/cells.js";
|
|
@@ -65,8 +66,8 @@ let Checkbox = (() => {
|
|
|
65
66
|
return class Checkbox extends _classSuper {
|
|
66
67
|
static {
|
|
67
68
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
68
|
-
_label_decorators = [
|
|
69
|
-
_checked_decorators = [
|
|
69
|
+
_label_decorators = [observableRef];
|
|
70
|
+
_checked_decorators = [observableRef];
|
|
70
71
|
_handleKey_decorators = [action];
|
|
71
72
|
_handleMouse_decorators = [action];
|
|
72
73
|
__esDecorate(this, null, _label_decorators, { kind: "accessor", name: "label", static: false, private: false, access: { has: obj => "label" in obj, get: obj => obj.label, set: (obj, value) => { obj.label = value; } }, metadata: _metadata }, _label_initializers, _label_extraInitializers);
|
|
@@ -37,7 +37,9 @@ export interface DropdownOptions {
|
|
|
37
37
|
export declare class Dropdown extends ThemedWidget implements OverlayRenderable {
|
|
38
38
|
readonly id: string;
|
|
39
39
|
readonly focusable = true;
|
|
40
|
-
accessor
|
|
40
|
+
private accessor _options;
|
|
41
|
+
get options(): readonly string[];
|
|
42
|
+
set options(options: readonly string[]);
|
|
41
43
|
accessor selectedIndex: number;
|
|
42
44
|
accessor expanded: boolean;
|
|
43
45
|
accessor highlightedIndex: number;
|
package/dist/widgets/dropdown.js
CHANGED
|
@@ -56,7 +56,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
56
56
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
57
57
|
done = true;
|
|
58
58
|
};
|
|
59
|
-
import {
|
|
59
|
+
import { action } from "mobx";
|
|
60
|
+
import { observableRef } from "./observable.js";
|
|
60
61
|
import { Segment } from "../core/segment.js";
|
|
61
62
|
import { Style } from "../core/style.js";
|
|
62
63
|
import { cellLen, setCellSize, splitText, asCellCol } from "../core/cells.js";
|
|
@@ -67,9 +68,9 @@ import { ink } from "./ink.js";
|
|
|
67
68
|
let Dropdown = (() => {
|
|
68
69
|
let _classSuper = ThemedWidget;
|
|
69
70
|
let _instanceExtraInitializers = [];
|
|
70
|
-
let
|
|
71
|
-
let
|
|
72
|
-
let
|
|
71
|
+
let __options_decorators;
|
|
72
|
+
let __options_initializers = [];
|
|
73
|
+
let __options_extraInitializers = [];
|
|
73
74
|
let _selectedIndex_decorators;
|
|
74
75
|
let _selectedIndex_initializers = [];
|
|
75
76
|
let _selectedIndex_extraInitializers = [];
|
|
@@ -88,15 +89,15 @@ let Dropdown = (() => {
|
|
|
88
89
|
return class Dropdown extends _classSuper {
|
|
89
90
|
static {
|
|
90
91
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
91
|
-
|
|
92
|
-
_selectedIndex_decorators = [
|
|
93
|
-
_expanded_decorators = [
|
|
94
|
-
_highlightedIndex_decorators = [
|
|
95
|
-
_filter_decorators = [
|
|
92
|
+
__options_decorators = [observableRef];
|
|
93
|
+
_selectedIndex_decorators = [observableRef];
|
|
94
|
+
_expanded_decorators = [observableRef];
|
|
95
|
+
_highlightedIndex_decorators = [observableRef];
|
|
96
|
+
_filter_decorators = [observableRef];
|
|
96
97
|
_handleKey_decorators = [action];
|
|
97
98
|
_handleMouse_decorators = [action];
|
|
98
99
|
_handleFocus_decorators = [action];
|
|
99
|
-
__esDecorate(this, null,
|
|
100
|
+
__esDecorate(this, null, __options_decorators, { kind: "accessor", name: "_options", static: false, private: false, access: { has: obj => "_options" in obj, get: obj => obj._options, set: (obj, value) => { obj._options = value; } }, metadata: _metadata }, __options_initializers, __options_extraInitializers);
|
|
100
101
|
__esDecorate(this, null, _selectedIndex_decorators, { kind: "accessor", name: "selectedIndex", static: false, private: false, access: { has: obj => "selectedIndex" in obj, get: obj => obj.selectedIndex, set: (obj, value) => { obj.selectedIndex = value; } }, metadata: _metadata }, _selectedIndex_initializers, _selectedIndex_extraInitializers);
|
|
101
102
|
__esDecorate(this, null, _expanded_decorators, { kind: "accessor", name: "expanded", static: false, private: false, access: { has: obj => "expanded" in obj, get: obj => obj.expanded, set: (obj, value) => { obj.expanded = value; } }, metadata: _metadata }, _expanded_initializers, _expanded_extraInitializers);
|
|
102
103
|
__esDecorate(this, null, _highlightedIndex_decorators, { kind: "accessor", name: "highlightedIndex", static: false, private: false, access: { has: obj => "highlightedIndex" in obj, get: obj => obj.highlightedIndex, set: (obj, value) => { obj.highlightedIndex = value; } }, metadata: _metadata }, _highlightedIndex_initializers, _highlightedIndex_extraInitializers);
|
|
@@ -108,10 +109,14 @@ let Dropdown = (() => {
|
|
|
108
109
|
}
|
|
109
110
|
id = __runInitializers(this, _instanceExtraInitializers);
|
|
110
111
|
focusable = true;
|
|
111
|
-
#
|
|
112
|
-
get
|
|
113
|
-
set
|
|
114
|
-
|
|
112
|
+
#_options_accessor_storage = __runInitializers(this, __options_initializers, void 0);
|
|
113
|
+
get _options() { return this.#_options_accessor_storage; }
|
|
114
|
+
set _options(value) { this.#_options_accessor_storage = value; }
|
|
115
|
+
// A copy is held, so the caller changing the array it assigned changes
|
|
116
|
+
// nothing here: the labels change only by assignment, which repaints.
|
|
117
|
+
get options() { return this._options; }
|
|
118
|
+
set options(options) { this._options = [...options]; }
|
|
119
|
+
#selectedIndex_accessor_storage = (__runInitializers(this, __options_extraInitializers), __runInitializers(this, _selectedIndex_initializers, void 0));
|
|
115
120
|
get selectedIndex() { return this.#selectedIndex_accessor_storage; }
|
|
116
121
|
set selectedIndex(value) { this.#selectedIndex_accessor_storage = value; }
|
|
117
122
|
#expanded_accessor_storage = (__runInitializers(this, _selectedIndex_extraInitializers), __runInitializers(this, _expanded_initializers, false));
|
|
@@ -140,7 +145,7 @@ let Dropdown = (() => {
|
|
|
140
145
|
super(options.theme);
|
|
141
146
|
__runInitializers(this, _filter_extraInitializers);
|
|
142
147
|
this.id = options.id ?? `dropdown-${Math.random().toString(36).slice(2, 8)}`;
|
|
143
|
-
this.
|
|
148
|
+
this._options = [...options.options];
|
|
144
149
|
// [LAW:types-are-the-program] selectedIndex must point to a valid
|
|
145
150
|
// option (or be 0 for the empty-options case). Clamp at the trust
|
|
146
151
|
// boundary so headerText / render / commit logic can all assume
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* host's input (`host/input`), and `WidgetApp` hands this what the app hears.
|
|
7
7
|
*/
|
|
8
8
|
import { KeyEvent } from "./types.js";
|
|
9
|
-
import { drew, originOf, scrollTargetAt, widgetAt } from "./hit.js";
|
|
9
|
+
import { cellOf, drew, originOf, scrollTargetAt, widgetAt } from "./hit.js";
|
|
10
10
|
export class EventRouter {
|
|
11
11
|
source;
|
|
12
12
|
focusOnClick;
|
|
@@ -78,7 +78,7 @@ export class EventRouter {
|
|
|
78
78
|
}
|
|
79
79
|
/** Route a key the app heard. */
|
|
80
80
|
key(press) {
|
|
81
|
-
this.dispatchKey(new KeyEvent(press));
|
|
81
|
+
this.dispatchKey(new KeyEvent(press, this.source.frameTime));
|
|
82
82
|
}
|
|
83
83
|
/** Route a pointer event the app heard. */
|
|
84
84
|
mouse(event) {
|
|
@@ -172,6 +172,8 @@ export class EventRouter {
|
|
|
172
172
|
x: event.x - target.originX,
|
|
173
173
|
y: event.y - target.originY,
|
|
174
174
|
over: drew(frame, target.widget, event.x, event.y),
|
|
175
|
+
cell: cellOf(frame, target.widget, event.x, event.y),
|
|
176
|
+
time: this.source.frameTime,
|
|
175
177
|
});
|
|
176
178
|
if (event.type === "mouse_down")
|
|
177
179
|
this.captured = target;
|
|
@@ -6,18 +6,25 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { InteractiveWidget, FocusManager, KeyEvent } from "./types.js";
|
|
8
8
|
import type { Unsubscribe } from "../core/subscription.js";
|
|
9
|
+
/** What focus follows: the frame a `WidgetApp` just painted. */
|
|
10
|
+
export interface FocusView {
|
|
11
|
+
/** Every widget the view holds, in document order, whether or not it has a cell on screen. */
|
|
12
|
+
present(): readonly InteractiveWidget[];
|
|
13
|
+
/** The widgets with a cell on the frame on screen, in document order. */
|
|
14
|
+
onScreen(): readonly InteractiveWidget[];
|
|
15
|
+
/** The time a frame drawn now is drawn for (`RenderOptions.frame`), which a focus change is stamped with. */
|
|
16
|
+
frameTime(): number;
|
|
17
|
+
}
|
|
9
18
|
export declare class DefaultFocusManager implements FocusManager {
|
|
10
|
-
private readonly
|
|
19
|
+
private readonly view;
|
|
11
20
|
accessor currentWidget: InteractiveWidget | null;
|
|
12
21
|
private readonly changeHandlers;
|
|
13
22
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* of the widgets held here would be a second picture of the frame, and
|
|
18
|
-
* wrong from the next paint.
|
|
23
|
+
* [LAW:one-source-of-truth] The view is asked every time rather than kept:
|
|
24
|
+
* a list of the widgets held here would be a second picture of the frame,
|
|
25
|
+
* and wrong from the next paint.
|
|
19
26
|
*/
|
|
20
|
-
constructor(
|
|
27
|
+
constructor(view: FocusView);
|
|
21
28
|
get current(): InteractiveWidget | null;
|
|
22
29
|
get widgets(): readonly InteractiveWidget[];
|
|
23
30
|
next(): void;
|
|
@@ -25,20 +32,22 @@ export declare class DefaultFocusManager implements FocusManager {
|
|
|
25
32
|
focus(widget: InteractiveWidget): void;
|
|
26
33
|
/**
|
|
27
34
|
* Put focus where the frame just painted lets it rest: it stays on a widget
|
|
28
|
-
* the view
|
|
29
|
-
* one on screen that can, or to none.
|
|
35
|
+
* the view holds (`present`) that can take it, and otherwise moves to the
|
|
36
|
+
* first one on screen that can, or to none.
|
|
30
37
|
*
|
|
31
|
-
*
|
|
32
|
-
* shrank, a viewport scrolled, an overlay covers it
|
|
33
|
-
*
|
|
38
|
+
* In the view, not on screen: a widget cropped for the moment — the
|
|
39
|
+
* terminal shrank, a viewport scrolled, an overlay covers it, its container
|
|
40
|
+
* handed it no column — is still in the view, and keeps focus and whatever
|
|
41
|
+
* focus holds, like a half-typed filter.
|
|
34
42
|
*
|
|
35
43
|
* [LAW:single-enforcer] The one place focus follows the frame — onto the
|
|
36
|
-
* first widget when the app starts, off a widget that
|
|
44
|
+
* first widget when the app starts, off a widget that left the view
|
|
37
45
|
* or was disabled.
|
|
38
46
|
*/
|
|
39
|
-
settle(
|
|
47
|
+
settle(): void;
|
|
40
48
|
onChange(handler: (current: InteractiveWidget | null) => void): Unsubscribe;
|
|
41
49
|
handleKey(event: KeyEvent): void;
|
|
50
|
+
private step;
|
|
42
51
|
private clear;
|
|
43
52
|
private setFocus;
|
|
44
53
|
private emitChange;
|
|
@@ -38,7 +38,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
38
38
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
39
39
|
done = true;
|
|
40
40
|
};
|
|
41
|
-
import { action
|
|
41
|
+
import { action } from "mobx";
|
|
42
|
+
import { observableRef } from "./observable.js";
|
|
42
43
|
let DefaultFocusManager = (() => {
|
|
43
44
|
let _instanceExtraInitializers = [];
|
|
44
45
|
let _currentWidget_decorators;
|
|
@@ -69,42 +70,30 @@ let DefaultFocusManager = (() => {
|
|
|
69
70
|
__esDecorate(this, null, _setFocus_decorators, { kind: "method", name: "setFocus", static: false, private: false, access: { has: obj => "setFocus" in obj, get: obj => obj.setFocus }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
70
71
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
+
view = __runInitializers(this, _instanceExtraInitializers);
|
|
73
74
|
#currentWidget_accessor_storage = __runInitializers(this, _currentWidget_initializers, null);
|
|
74
75
|
get currentWidget() { return this.#currentWidget_accessor_storage; }
|
|
75
76
|
set currentWidget(value) { this.#currentWidget_accessor_storage = value; }
|
|
76
77
|
changeHandlers = (__runInitializers(this, _currentWidget_extraInitializers), new Set());
|
|
77
78
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* of the widgets held here would be a second picture of the frame, and
|
|
82
|
-
* wrong from the next paint.
|
|
79
|
+
* [LAW:one-source-of-truth] The view is asked every time rather than kept:
|
|
80
|
+
* a list of the widgets held here would be a second picture of the frame,
|
|
81
|
+
* and wrong from the next paint.
|
|
83
82
|
*/
|
|
84
|
-
constructor(
|
|
85
|
-
this.
|
|
83
|
+
constructor(view) {
|
|
84
|
+
this.view = view;
|
|
86
85
|
}
|
|
87
86
|
get current() {
|
|
88
87
|
return this.currentWidget;
|
|
89
88
|
}
|
|
90
89
|
get widgets() {
|
|
91
|
-
return this.onScreen().filter(takesFocus);
|
|
90
|
+
return this.view.onScreen().filter(takesFocus);
|
|
92
91
|
}
|
|
93
92
|
next() {
|
|
94
|
-
|
|
95
|
-
if (focusable.length === 0)
|
|
96
|
-
return;
|
|
97
|
-
const currentIdx = this.currentWidget ? focusable.indexOf(this.currentWidget) : -1;
|
|
98
|
-
const nextIdx = (currentIdx + 1) % focusable.length;
|
|
99
|
-
this.setFocus(focusable[nextIdx]);
|
|
93
|
+
this.step(this.view.present());
|
|
100
94
|
}
|
|
101
95
|
prev() {
|
|
102
|
-
|
|
103
|
-
if (focusable.length === 0)
|
|
104
|
-
return;
|
|
105
|
-
const currentIdx = this.currentWidget ? focusable.indexOf(this.currentWidget) : -1;
|
|
106
|
-
const prevIdx = currentIdx <= 0 ? focusable.length - 1 : currentIdx - 1;
|
|
107
|
-
this.setFocus(focusable[prevIdx]);
|
|
96
|
+
this.step([...this.view.present()].reverse());
|
|
108
97
|
}
|
|
109
98
|
// A widget not on screen yet can take focus: an app that is about to show
|
|
110
99
|
// it focuses it first, and the frame that shows it keeps it (`settle`).
|
|
@@ -115,20 +104,21 @@ let DefaultFocusManager = (() => {
|
|
|
115
104
|
}
|
|
116
105
|
/**
|
|
117
106
|
* Put focus where the frame just painted lets it rest: it stays on a widget
|
|
118
|
-
* the view
|
|
119
|
-
* one on screen that can, or to none.
|
|
107
|
+
* the view holds (`present`) that can take it, and otherwise moves to the
|
|
108
|
+
* first one on screen that can, or to none.
|
|
120
109
|
*
|
|
121
|
-
*
|
|
122
|
-
* shrank, a viewport scrolled, an overlay covers it
|
|
123
|
-
*
|
|
110
|
+
* In the view, not on screen: a widget cropped for the moment — the
|
|
111
|
+
* terminal shrank, a viewport scrolled, an overlay covers it, its container
|
|
112
|
+
* handed it no column — is still in the view, and keeps focus and whatever
|
|
113
|
+
* focus holds, like a half-typed filter.
|
|
124
114
|
*
|
|
125
115
|
* [LAW:single-enforcer] The one place focus follows the frame — onto the
|
|
126
|
-
* first widget when the app starts, off a widget that
|
|
116
|
+
* first widget when the app starts, off a widget that left the view
|
|
127
117
|
* or was disabled.
|
|
128
118
|
*/
|
|
129
|
-
settle(
|
|
119
|
+
settle() {
|
|
130
120
|
const current = this.currentWidget;
|
|
131
|
-
if (current && takesFocus(current) &&
|
|
121
|
+
if (current && takesFocus(current) && this.view.present().includes(current))
|
|
132
122
|
return;
|
|
133
123
|
const first = this.widgets[0];
|
|
134
124
|
if (first)
|
|
@@ -154,6 +144,16 @@ let DefaultFocusManager = (() => {
|
|
|
154
144
|
event.stop();
|
|
155
145
|
}
|
|
156
146
|
// --- Private ---
|
|
147
|
+
// To the first widget Tab moves between that follows the focused one in
|
|
148
|
+
// `order`, wrapping. It is found by its place in the view, not among those
|
|
149
|
+
// on screen, so Tab from a widget on no cell moves to its neighbour.
|
|
150
|
+
step(order) {
|
|
151
|
+
const tabbable = new Set(this.widgets);
|
|
152
|
+
const at = this.currentWidget ? order.indexOf(this.currentWidget) : -1;
|
|
153
|
+
const target = [...order.slice(at + 1), ...order.slice(0, at + 1)].find((widget) => tabbable.has(widget));
|
|
154
|
+
if (target)
|
|
155
|
+
this.setFocus(target);
|
|
156
|
+
}
|
|
157
157
|
// No focus is where `settle` leaves an app with nothing to focus, and only
|
|
158
158
|
// there: anywhere else the next frame would put focus back on the first
|
|
159
159
|
// widget, so there is no public way in.
|
|
@@ -163,7 +163,7 @@ let DefaultFocusManager = (() => {
|
|
|
163
163
|
// [LAW:single-enforcer] WidgetBase.focus()/blur() route through
|
|
164
164
|
// handleFocus; calling it here as well would dispatch twice and run any
|
|
165
165
|
// subclass side effect (e.g. Dropdown clearing its filter) twice.
|
|
166
|
-
this.currentWidget.blur();
|
|
166
|
+
this.currentWidget.blur(this.view.frameTime());
|
|
167
167
|
this.currentWidget = null;
|
|
168
168
|
this.emitChange();
|
|
169
169
|
}
|
|
@@ -172,10 +172,11 @@ let DefaultFocusManager = (() => {
|
|
|
172
172
|
return;
|
|
173
173
|
// [LAW:single-enforcer] focus()/blur() already dispatch handleFocus —
|
|
174
174
|
// see clear() for the rationale.
|
|
175
|
+
const time = this.view.frameTime();
|
|
175
176
|
if (this.currentWidget)
|
|
176
|
-
this.currentWidget.blur();
|
|
177
|
+
this.currentWidget.blur(time);
|
|
177
178
|
this.currentWidget = widget;
|
|
178
|
-
widget.focus();
|
|
179
|
+
widget.focus(time);
|
|
179
180
|
this.emitChange();
|
|
180
181
|
}
|
|
181
182
|
emitChange() {
|