@promptctl/rich-js 0.24.1 → 0.25.1

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 (156) hide show
  1. package/README.md +7 -0
  2. package/dist/core/anchor.d.ts +5 -0
  3. package/dist/core/anchor.js +11 -4
  4. package/dist/core/animation.d.ts +175 -10
  5. package/dist/core/animation.js +283 -15
  6. package/dist/core/ansi.js +2 -2
  7. package/dist/core/box.d.ts +7 -7
  8. package/dist/core/cells.d.ts +23 -13
  9. package/dist/core/cells.js +58 -48
  10. package/dist/core/clock.d.ts +2 -2
  11. package/dist/core/clock.js +6 -4
  12. package/dist/core/color.d.ts +27 -10
  13. package/dist/core/color.js +24 -33
  14. package/dist/core/console.d.ts +28 -17
  15. package/dist/core/console.js +44 -39
  16. package/dist/core/easing.d.ts +30 -0
  17. package/dist/core/easing.js +65 -20
  18. package/dist/core/emoji.d.ts +1 -1
  19. package/dist/core/emoji.js +1 -2
  20. package/dist/core/export-html.js +2 -1
  21. package/dist/core/export-lines.js +3 -3
  22. package/dist/core/highlighter.d.ts +9 -8
  23. package/dist/core/highlighter.js +3 -2
  24. package/dist/core/json.js +2 -2
  25. package/dist/core/markup.js +16 -12
  26. package/dist/core/measure.js +2 -2
  27. package/dist/core/memo.d.ts +11 -2
  28. package/dist/core/memo.js +16 -0
  29. package/dist/core/oklch.js +17 -6
  30. package/dist/core/osc8.d.ts +1 -1
  31. package/dist/core/osc8.js +4 -5
  32. package/dist/core/paint.d.ts +2 -1
  33. package/dist/core/paint.js +4 -3
  34. package/dist/core/pattern.d.ts +21 -0
  35. package/dist/core/pattern.js +21 -0
  36. package/dist/core/pretty.js +9 -10
  37. package/dist/core/protocol.d.ts +100 -39
  38. package/dist/core/protocol.js +167 -21
  39. package/dist/core/render.d.ts +15 -2
  40. package/dist/core/render.js +25 -5
  41. package/dist/core/segment.d.ts +13 -5
  42. package/dist/core/segment.js +23 -9
  43. package/dist/core/spinnerData.d.ts +1 -1
  44. package/dist/core/state.d.ts +53 -0
  45. package/dist/core/state.js +82 -0
  46. package/dist/core/strip.d.ts +98 -29
  47. package/dist/core/strip.js +211 -150
  48. package/dist/core/style.d.ts +47 -14
  49. package/dist/core/style.js +57 -28
  50. package/dist/core/text.d.ts +5 -8
  51. package/dist/core/text.js +116 -124
  52. package/dist/core/timeline.d.ts +30 -5
  53. package/dist/core/timeline.js +43 -7
  54. package/dist/core/tween.d.ts +1 -1
  55. package/dist/core/tween.js +5 -3
  56. package/dist/core/view.d.ts +92 -0
  57. package/dist/core/view.js +83 -0
  58. package/dist/host/app.d.ts +49 -21
  59. package/dist/host/app.js +71 -37
  60. package/dist/host/input.js +3 -4
  61. package/dist/index.d.ts +24 -12
  62. package/dist/index.js +9 -4
  63. package/dist/node/traceback.js +4 -3
  64. package/dist/renderables/align.d.ts +2 -3
  65. package/dist/renderables/align.js +0 -3
  66. package/dist/renderables/change.d.ts +42 -0
  67. package/dist/renderables/change.js +112 -0
  68. package/dist/renderables/columns.d.ts +3 -4
  69. package/dist/renderables/columns.js +2 -7
  70. package/dist/renderables/constrain.d.ts +2 -3
  71. package/dist/renderables/constrain.js +0 -3
  72. package/dist/renderables/effect.d.ts +158 -63
  73. package/dist/renderables/effect.js +378 -155
  74. package/dist/renderables/effects.d.ts +122 -11
  75. package/dist/renderables/effects.js +238 -94
  76. package/dist/renderables/embed.d.ts +1 -1
  77. package/dist/renderables/embed.js +16 -8
  78. package/dist/renderables/flexStrip.d.ts +2 -3
  79. package/dist/renderables/flexStrip.js +0 -4
  80. package/dist/renderables/frames.d.ts +35 -54
  81. package/dist/renderables/frames.js +43 -58
  82. package/dist/renderables/glyph-effect.d.ts +158 -0
  83. package/dist/renderables/glyph-effect.js +237 -0
  84. package/dist/renderables/glyph-effects.d.ts +147 -0
  85. package/dist/renderables/glyph-effects.js +283 -0
  86. package/dist/renderables/group.d.ts +3 -4
  87. package/dist/renderables/group.js +0 -3
  88. package/dist/renderables/layout.d.ts +12 -12
  89. package/dist/renderables/layout.js +40 -46
  90. package/dist/renderables/lifecycle.d.ts +77 -0
  91. package/dist/renderables/lifecycle.js +132 -0
  92. package/dist/renderables/list-view.d.ts +4 -5
  93. package/dist/renderables/list-view.js +9 -13
  94. package/dist/renderables/live.d.ts +36 -21
  95. package/dist/renderables/live.js +88 -41
  96. package/dist/renderables/markdown-parse.d.ts +4 -4
  97. package/dist/renderables/markdown-parse.js +52 -46
  98. package/dist/renderables/markdown-url.js +11 -11
  99. package/dist/renderables/padding.d.ts +4 -5
  100. package/dist/renderables/padding.js +0 -3
  101. package/dist/renderables/panel.d.ts +14 -16
  102. package/dist/renderables/panel.js +6 -14
  103. package/dist/renderables/progress.d.ts +83 -34
  104. package/dist/renderables/progress.js +182 -95
  105. package/dist/renderables/progressBar.d.ts +22 -12
  106. package/dist/renderables/progressBar.js +32 -9
  107. package/dist/renderables/prompt.js +3 -2
  108. package/dist/renderables/python-space.js +2 -1
  109. package/dist/renderables/scroll.d.ts +50 -0
  110. package/dist/renderables/scroll.js +106 -0
  111. package/dist/renderables/spinner.d.ts +35 -14
  112. package/dist/renderables/spinner.js +65 -25
  113. package/dist/renderables/status.js +1 -1
  114. package/dist/renderables/syntax.d.ts +10 -9
  115. package/dist/renderables/syntax.js +3 -2
  116. package/dist/renderables/table.d.ts +34 -40
  117. package/dist/renderables/table.js +41 -63
  118. package/dist/renderables/traceback.d.ts +2 -2
  119. package/dist/renderables/traceback.js +6 -5
  120. package/dist/renderables/tree.d.ts +27 -25
  121. package/dist/renderables/tree.js +104 -58
  122. package/dist/renderables/viewport.d.ts +75 -44
  123. package/dist/renderables/viewport.js +145 -82
  124. package/dist/template-bindings/animation-funcs.d.ts +78 -9
  125. package/dist/template-bindings/animation-funcs.js +132 -14
  126. package/dist/template-bindings/index.d.ts +5 -5
  127. package/dist/template-bindings/index.js +5 -5
  128. package/dist/themes/ramp.d.ts +46 -3
  129. package/dist/themes/ramp.js +61 -4
  130. package/dist/themes/registry.js +5 -7
  131. package/dist/widgets/button.js +3 -2
  132. package/dist/widgets/checkbox.js +4 -3
  133. package/dist/widgets/dropdown.d.ts +3 -1
  134. package/dist/widgets/dropdown.js +20 -15
  135. package/dist/widgets/event-router.js +4 -2
  136. package/dist/widgets/focus-manager.d.ts +2 -0
  137. package/dist/widgets/focus-manager.js +6 -4
  138. package/dist/widgets/hit.d.ts +6 -0
  139. package/dist/widgets/hit.js +8 -1
  140. package/dist/widgets/observable.d.ts +23 -0
  141. package/dist/widgets/observable.js +43 -0
  142. package/dist/widgets/slider.d.ts +1 -1
  143. package/dist/widgets/slider.js +12 -3
  144. package/dist/widgets/static-item.d.ts +2 -3
  145. package/dist/widgets/static-item.js +0 -3
  146. package/dist/widgets/text-input.d.ts +28 -32
  147. package/dist/widgets/text-input.js +360 -346
  148. package/dist/widgets/themed-widget.js +2 -1
  149. package/dist/widgets/toggle.js +4 -3
  150. package/dist/widgets/types.d.ts +10 -4
  151. package/dist/widgets/types.js +8 -2
  152. package/dist/widgets/widget-app.d.ts +14 -4
  153. package/dist/widgets/widget-app.js +36 -7
  154. package/dist/widgets/widget-base.d.ts +2 -2
  155. package/dist/widgets/widget-base.js +29 -18
  156. package/package.json +8 -1
@@ -41,7 +41,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
41
41
  if (target) Object.defineProperty(target, contextIn.name, descriptor);
42
42
  done = true;
43
43
  };
44
- import { observableRef, action } from "mobx";
44
+ import { action } from "mobx";
45
+ import { observableRef } from "./observable.js";
45
46
  import { Style } from "../core/style.js";
46
47
  import { DEFAULT_TERMINAL_THEME } from "../themes/terminalThemes.js";
47
48
  import { WidgetBase } from "./widget-base.js";
@@ -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 { observable, action, observableRef } from "mobx";
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";
@@ -79,8 +80,8 @@ let Toggle = (() => {
79
80
  return class Toggle extends _classSuper {
80
81
  static {
81
82
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
82
- _label_decorators = [observable];
83
- _on_decorators = [observable];
83
+ _label_decorators = [observableRef];
84
+ _on_decorators = [observableRef];
84
85
  _variant_decorators = [observableRef];
85
86
  _handleKey_decorators = [action];
86
87
  _handleMouse_decorators = [action];
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import type { Renderable, Measurable, RenderOptions } from "../core/protocol.js";
9
9
  import type { Segment } from "../core/segment.js";
10
+ import type { Anchor } from "../core/anchor.js";
10
11
  import type { Unsubscribe } from "../core/subscription.js";
11
12
  import type { KeyPress, ScreenMouseEvent } from "../host/input.js";
12
13
  export declare class KeyEvent implements KeyPress {
@@ -15,8 +16,9 @@ export declare class KeyEvent implements KeyPress {
15
16
  readonly shift: boolean;
16
17
  readonly ctrl: boolean;
17
18
  readonly meta: boolean;
19
+ readonly time: number;
18
20
  private _stopped;
19
- constructor(init: KeyPress);
21
+ constructor(init: KeyPress, time: number);
20
22
  get stopped(): boolean;
21
23
  stop(): void;
22
24
  }
@@ -28,9 +30,12 @@ export type WidgetMouseEvent = Extract<ScreenMouseEvent, {
28
30
  type: "mouse_down" | "mouse_up" | "mouse_move";
29
31
  }> & {
30
32
  readonly over: boolean;
33
+ readonly cell: Anchor | undefined;
34
+ readonly time: number;
31
35
  };
32
36
  export interface WidgetFocusEvent {
33
- type: "focus" | "blur";
37
+ readonly type: "focus" | "blur";
38
+ readonly time: number;
34
39
  }
35
40
  export interface InteractiveWidget extends Renderable, Measurable {
36
41
  readonly id: string;
@@ -42,8 +47,8 @@ export interface InteractiveWidget extends Renderable, Measurable {
42
47
  handleKey(event: KeyEvent): void;
43
48
  handleMouse(event: WidgetMouseEvent): void;
44
49
  handleFocus(event: WidgetFocusEvent): void;
45
- focus(): void;
46
- blur(): void;
50
+ focus(time: number): void;
51
+ blur(time: number): void;
47
52
  setDisabled(value: boolean): void;
48
53
  setHovered(value: boolean): void;
49
54
  onChange(handler: (widget: InteractiveWidget) => void): Unsubscribe;
@@ -66,5 +71,6 @@ export interface FocusManager {
66
71
  export interface FrameSource {
67
72
  readonly focusManager: FocusManager;
68
73
  readonly frame: readonly (readonly Segment[])[];
74
+ readonly frameTime: number;
69
75
  refresh(): void;
70
76
  }
@@ -6,8 +6,12 @@
6
6
  * terminal escape sequences, or their host environment.
7
7
  */
8
8
  // --- Event types ---
9
+ // Every event a widget hears carries `time`: when it was heard, on the clock
10
+ // the frame's cells are read on (`RenderOptions.frame`), so a widget
11
+ // keeping the moment of its last interaction — a caret's blink restarting —
12
+ // compares it with the frame it draws and never reads a clock.
9
13
  // [LAW:types-are-the-program] KeyEvent is a `KeyPress` the app heard, plus
10
- // the say over who else hears it. It carries a single mutable signal
14
+ // when it was heard and the say over who else hears it. It carries a single mutable signal
11
15
  // (`stopped`) that participants in the dispatch chain set by calling
12
16
  // `stop()`. Treating it as an interface would force every handler to
13
17
  // shuttle a return value upward and the router to interpret it — the
@@ -19,13 +23,15 @@ export class KeyEvent {
19
23
  shift;
20
24
  ctrl;
21
25
  meta;
26
+ time;
22
27
  _stopped = false;
23
- constructor(init) {
28
+ constructor(init, time) {
24
29
  this.key = init.key;
25
30
  this.character = init.character;
26
31
  this.shift = init.shift;
27
32
  this.ctrl = init.ctrl;
28
33
  this.meta = init.meta;
34
+ this.time = time;
29
35
  }
30
36
  get stopped() { return this._stopped; }
31
37
  // Claim this key. Halts further chain dispatch — no high/normal handler
@@ -26,8 +26,10 @@
26
26
  * hear the input.
27
27
  */
28
28
  import { Segment } from "../core/segment.js";
29
- import type { Presence } from "../core/protocol.js";
29
+ import type { FrameHost, Presence } from "../core/protocol.js";
30
30
  import type { Unsubscribe } from "../core/subscription.js";
31
+ import type { Clock, FrameRate } from "../core/clock.js";
32
+ import type { Timeline } from "../core/timeline.js";
31
33
  import { type AppOptions, type AppPhase, type Wheel } from "../host/app.js";
32
34
  import type { FocusManager, FrameSource, KeyEvent, KeyHandlerOptions } from "./types.js";
33
35
  import type { AppMouseEvent } from "../host/app.js";
@@ -44,7 +46,7 @@ export interface WidgetPresence {
44
46
  readonly widget: WidgetBase;
45
47
  readonly presence: Presence;
46
48
  }
47
- export declare class WidgetApp implements FrameSource {
49
+ export declare class WidgetApp implements FrameSource, FrameHost {
48
50
  readonly focusManager: FocusManager;
49
51
  private readonly focus;
50
52
  private readonly app;
@@ -53,6 +55,14 @@ export declare class WidgetApp implements FrameSource {
53
55
  private _present;
54
56
  constructor(options: WidgetAppOptions);
55
57
  get phase(): AppPhase;
58
+ /** The clock the app reads the time from: where the host time a `Timeline` operation takes is read. */
59
+ get clock(): Clock;
60
+ /** The map from the clock to the time the view is drawn for; setting it paints the frame for the new time, as `App.timeline` does. */
61
+ get timeline(): Timeline;
62
+ set timeline(timeline: Timeline);
63
+ /** How often the app paints while what it shows moves; setting it goes on at the new rate from the next frame, as `App.rate` does. */
64
+ get rate(): FrameRate;
65
+ set rate(rate: FrameRate);
56
66
  /**
57
67
  * The widgets the view reported for the frame on screen, in document order,
58
68
  * each drawn or held (`RenderOptions.onPresent`). A held widget is one its
@@ -62,6 +72,8 @@ export declare class WidgetApp implements FrameSource {
62
72
  private get widgets();
63
73
  /** The rows on screen now, as painted: what a pointer event lands on. */
64
74
  get frame(): readonly (readonly Segment[])[];
75
+ /** The time a frame drawn now is drawn for, as `App.frameTime` reads it. */
76
+ get frameTime(): number;
65
77
  /**
66
78
  * Take the terminal, paint the first frame, and route input to its widgets
67
79
  * until the app stops — `App.run`, with the input.
@@ -76,8 +88,6 @@ export declare class WidgetApp implements FrameSource {
76
88
  */
77
89
  refresh(): void;
78
90
  suspend(): Promise<void>;
79
- /** Paint at the app's rate until the returned function is called, as `App.animate` does. */
80
- animate(): Unsubscribe;
81
91
  /**
82
92
  * Hear every key the app does not quit on. A `high` handler hears it before the focused widget, a
83
93
  * `normal` one after it — and after Tab has moved focus — and a handler
@@ -28,6 +28,7 @@
28
28
  import { Reaction } from "mobx";
29
29
  import { Segment } from "../core/segment.js";
30
30
  import { render } from "../core/protocol.js";
31
+ import { hostView } from "../core/view.js";
31
32
  import { App } from "../host/app.js";
32
33
  import { DefaultFocusManager } from "./focus-manager.js";
33
34
  import { EventRouter } from "./event-router.js";
@@ -43,11 +44,20 @@ export class WidgetApp {
43
44
  reaction;
44
45
  _present = [];
45
46
  constructor(options) {
46
- const { view, focusOnClick = true } = options;
47
+ const { focusOnClick = true } = options;
48
+ const view = hostView(options.view);
47
49
  this.reaction = new Reaction("WidgetApp", () => this.app.refresh());
50
+ // The view is drawn inside `compose`, where what it reads is tracked —
51
+ // a function view asked there too — and when it changes is its own to
52
+ // say, as it would be to the App.
53
+ // Its frames are its own, drawn for its `frameTime`: what draws in them
54
+ // is told this app is the host, not the App it paints through, so
55
+ // `drawnBy: widgetApp` names the host its frames carry.
48
56
  this.app = new App({
49
57
  ...options,
50
- view: (t) => ({ render: (renderOptions) => this.compose(() => view(t), renderOptions) }),
58
+ view: {
59
+ render: (renderOptions) => this.compose(view, { ...renderOptions, frame: renderOptions.frame && { time: renderOptions.frame.time, host: this } }),
60
+ },
51
61
  });
52
62
  this.focus = new DefaultFocusManager({
53
63
  present: () => this.widgets,
@@ -55,6 +65,7 @@ export class WidgetApp {
55
65
  const owners = ownersOn(this.app.frame);
56
66
  return this.widgets.filter((widget) => owners.has(widget));
57
67
  },
68
+ frameTime: () => this.frameTime,
58
69
  });
59
70
  this.focusManager = this.focus;
60
71
  this.router = new EventRouter({ source: this, focusOnClick });
@@ -74,6 +85,24 @@ export class WidgetApp {
74
85
  get phase() {
75
86
  return this.app.phase;
76
87
  }
88
+ /** The clock the app reads the time from: where the host time a `Timeline` operation takes is read. */
89
+ get clock() {
90
+ return this.app.clock;
91
+ }
92
+ /** The map from the clock to the time the view is drawn for; setting it paints the frame for the new time, as `App.timeline` does. */
93
+ get timeline() {
94
+ return this.app.timeline;
95
+ }
96
+ set timeline(timeline) {
97
+ this.app.timeline = timeline;
98
+ }
99
+ /** How often the app paints while what it shows moves; setting it goes on at the new rate from the next frame, as `App.rate` does. */
100
+ get rate() {
101
+ return this.app.rate;
102
+ }
103
+ set rate(rate) {
104
+ this.app.rate = rate;
105
+ }
77
106
  /**
78
107
  * The widgets the view reported for the frame on screen, in document order,
79
108
  * each drawn or held (`RenderOptions.onPresent`). A held widget is one its
@@ -89,6 +118,10 @@ export class WidgetApp {
89
118
  get frame() {
90
119
  return this.app.frame;
91
120
  }
121
+ /** The time a frame drawn now is drawn for, as `App.frameTime` reads it. */
122
+ get frameTime() {
123
+ return this.app.frameTime;
124
+ }
92
125
  /**
93
126
  * Take the terminal, paint the first frame, and route input to its widgets
94
127
  * until the app stops — `App.run`, with the input.
@@ -122,10 +155,6 @@ export class WidgetApp {
122
155
  suspend() {
123
156
  return this.app.suspend();
124
157
  }
125
- /** Paint at the app's rate until the returned function is called, as `App.animate` does. */
126
- animate() {
127
- return this.app.animate();
128
- }
129
158
  /**
130
159
  * Hear every key the app does not quit on. A `high` handler hears it before the focused widget, a
131
160
  * `normal` one after it — and after Tab has moved focus — and a handler
@@ -163,7 +192,7 @@ export class WidgetApp {
163
192
  // held widget has no footprint on the frame, and so paints none.
164
193
  const drawing = { ...options, onPresent };
165
194
  const lines = tracked(this.reaction, () => {
166
- const base = Segment.splitLines(render(view(), drawing));
195
+ const base = Segment.splitLines(render(view, drawing));
167
196
  for (const widget of present.keys())
168
197
  paintOverlay(base, widget, drawing);
169
198
  return base;
@@ -24,8 +24,8 @@ export declare abstract class WidgetBase implements InteractiveWidget {
24
24
  abstract handleKey(event: KeyEvent): void;
25
25
  handleMouse(_event: WidgetMouseEvent): void;
26
26
  handleFocus(event: WidgetFocusEvent): void;
27
- focus(): void;
28
- blur(): void;
27
+ focus(time: number): void;
28
+ blur(time: number): void;
29
29
  setDisabled(value: boolean): void;
30
30
  setHovered(value: boolean): void;
31
31
  onChange(handler: (widget: InteractiveWidget) => void): Unsubscribe;
@@ -41,9 +41,11 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
41
41
  if (target) Object.defineProperty(target, contextIn.name, descriptor);
42
42
  done = true;
43
43
  };
44
- import { observable, action } from "mobx";
44
+ import { action } from "mobx";
45
+ import { observableRef } from "./observable.js";
45
46
  import { Segment } from "../core/segment.js";
46
47
  import { NULL_STYLE } from "../core/style.js";
48
+ import { State } from "../core/state.js";
47
49
  let WidgetBase = (() => {
48
50
  let _instanceExtraInitializers = [];
49
51
  let _focused_decorators;
@@ -66,10 +68,10 @@ let WidgetBase = (() => {
66
68
  return class WidgetBase {
67
69
  static {
68
70
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
69
- _focused_decorators = [observable];
70
- _hovered_decorators = [observable];
71
- _active_decorators = [observable];
72
- _disabled_decorators = [observable];
71
+ _focused_decorators = [observableRef];
72
+ _hovered_decorators = [observableRef];
73
+ _active_decorators = [observableRef];
74
+ _disabled_decorators = [observableRef];
73
75
  _handleFocus_decorators = [action];
74
76
  _focus_decorators = [action];
75
77
  _blur_decorators = [action];
@@ -98,8 +100,9 @@ let WidgetBase = (() => {
98
100
  #disabled_accessor_storage = (__runInitializers(this, _active_extraInitializers), __runInitializers(this, _disabled_initializers, false));
99
101
  get disabled() { return this.#disabled_accessor_storage; }
100
102
  set disabled(value) { this.#disabled_accessor_storage = value; }
101
- changeHandlers = (__runInitializers(this, _disabled_extraInitializers), new Set());
102
- submitHandlers = new Set();
103
+ // Subscribed and let go between frames; each set is replaced whole.
104
+ changeHandlers = (__runInitializers(this, _disabled_extraInitializers), new State(new Set()));
105
+ submitHandlers = new State(new Set());
103
106
  handleMouse(_event) { }
104
107
  handleFocus(event) {
105
108
  this.focused = event.type === "focus";
@@ -109,11 +112,11 @@ let WidgetBase = (() => {
109
112
  // subclass override (e.g. Dropdown collapsing its overlay on blur)
110
113
  // participates in the same transition path the event router uses. There
111
114
  // is no second code path that flips `focused` directly from the outside.
112
- focus() {
113
- this.handleFocus({ type: "focus" });
115
+ focus(time) {
116
+ this.handleFocus({ type: "focus", time });
114
117
  }
115
- blur() {
116
- this.handleFocus({ type: "blur" });
118
+ blur(time) {
119
+ this.handleFocus({ type: "blur", time });
117
120
  }
118
121
  setDisabled(value) {
119
122
  this.disabled = value;
@@ -127,27 +130,26 @@ let WidgetBase = (() => {
127
130
  }
128
131
  // --- Subscriptions ---
129
132
  onChange(handler) {
130
- this.changeHandlers.add(handler);
131
- return () => this.changeHandlers.delete(handler);
133
+ return subscribe(this.changeHandlers, handler);
132
134
  }
133
135
  onSubmit(handler) {
134
- this.submitHandlers.add(handler);
135
- return () => this.submitHandlers.delete(handler);
136
+ return subscribe(this.submitHandlers, handler);
136
137
  }
137
138
  emitChange() {
138
- for (const handler of this.changeHandlers) {
139
+ for (const handler of this.changeHandlers.get()) {
139
140
  handler(this);
140
141
  }
141
142
  }
142
143
  emitSubmit() {
143
- for (const handler of this.submitHandlers) {
144
+ for (const handler of this.submitHandlers.get()) {
144
145
  handler(this);
145
146
  }
146
147
  }
147
148
  // --- Renderable + Measurable ---
148
149
  // [LAW:single-enforcer] The one place a widget's cells get their anchor, so
149
150
  // a hit on any cell names this widget and the cell's place in its output
150
- // (`widgetAt`). Subclasses draw; none stamps itself. A `RichText` admits no
151
+ // (`widgetAt`). Subclasses draw; none stamps itself — what a draw stamps
152
+ // inside this stamp is its own (`WidgetMouseEvent.cell`). A `RichText` admits no
151
153
  // anchor, so stamping has to follow every text layout `draw` does.
152
154
  // Every row is padded to the widest, so the widget owns its whole rectangle
153
155
  // and a click past the end of a short row still reaches it; every row ends
@@ -167,3 +169,12 @@ let WidgetBase = (() => {
167
169
  };
168
170
  })();
169
171
  export { WidgetBase };
172
+ /** `handler` added to `handlers` until the returned call takes it out. */
173
+ function subscribe(handlers, handler) {
174
+ handlers.set(new Set(handlers.get()).add(handler));
175
+ return () => {
176
+ const left = new Set(handlers.get());
177
+ left.delete(handler);
178
+ handlers.set(left);
179
+ };
180
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptctl/rich-js",
3
- "version": "0.24.1",
3
+ "version": "0.25.1",
4
4
  "description": "Rich text and beautiful formatting in the terminal — a TypeScript port of Python's Rich",
5
5
  "repository": {
6
6
  "type": "git",
@@ -74,6 +74,13 @@
74
74
  "sessions": "node scripts/build-demos.ts && node dist-demo/examples/claude-sessions/node.js",
75
75
  "strip": "node scripts/build-demos.ts && node dist-demo/examples/rich-strip/main.js",
76
76
  "effects-feel": "node scripts/build-demos.ts && node dist-demo/examples/effects-feel/index.js",
77
+ "glyph-effects": "node scripts/build-demos.ts && node dist-demo/examples/glyph-effects/index.js",
78
+ "attribute-effects": "node scripts/build-demos.ts && node dist-demo/examples/attribute-effects/index.js",
79
+ "gradient-effects": "node scripts/build-demos.ts && node dist-demo/examples/gradient-effects/index.js",
80
+ "transitions": "node scripts/build-demos.ts && node dist-demo/examples/transitions/index.js",
81
+ "lifecycle": "node scripts/build-demos.ts && node dist-demo/examples/lifecycle/index.js",
82
+ "animation-playground": "node scripts/build-demos.ts && node dist-demo/examples/animation-playground/main.js",
83
+ "scroll": "node scripts/build-demos.ts && node dist-demo/examples/scroll/index.js",
77
84
  "viewport": "node scripts/build-demos.ts && node dist-demo/examples/rich-viewport/main.js",
78
85
  "markup-plugins": "node scripts/build-demos.ts && node dist-demo/examples/rich-markup-plugins/main.js",
79
86
  "demo-inputs": "node scripts/build-demos.ts && node dist-demo/examples/rich-config/main.js",