@promptctl/rich-js 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +3 -2
  2. package/dist/core/color.d.ts +1 -0
  3. package/dist/core/color.js +3 -2
  4. package/dist/core/noise.d.ts +31 -0
  5. package/dist/core/noise.js +94 -0
  6. package/dist/core/paint.js +13 -2
  7. package/dist/core/pretty.d.ts +23 -2
  8. package/dist/core/pretty.js +54 -18
  9. package/dist/core/protocol.d.ts +2 -0
  10. package/dist/core/protocol.js +3 -2
  11. package/dist/core/selection.d.ts +12 -0
  12. package/dist/core/selection.js +14 -0
  13. package/dist/core/strip.d.ts +103 -72
  14. package/dist/core/strip.js +240 -212
  15. package/dist/core/style.js +9 -7
  16. package/dist/host/app.d.ts +68 -2
  17. package/dist/host/app.js +125 -15
  18. package/dist/host/index.d.ts +3 -1
  19. package/dist/host/index.js +1 -0
  20. package/dist/host/input.d.ts +84 -0
  21. package/dist/host/input.js +498 -0
  22. package/dist/host/terminal-host.d.ts +12 -0
  23. package/dist/host/terminal-host.js +2 -0
  24. package/dist/index.d.ts +9 -4
  25. package/dist/index.js +6 -3
  26. package/dist/node/terminal-host.d.ts +7 -0
  27. package/dist/node/terminal-host.js +7 -0
  28. package/dist/renderables/effect.d.ts +23 -8
  29. package/dist/renderables/effect.js +48 -26
  30. package/dist/renderables/effects.d.ts +252 -0
  31. package/dist/renderables/effects.js +565 -0
  32. package/dist/renderables/embed.d.ts +11 -5
  33. package/dist/renderables/embed.js +13 -6
  34. package/dist/renderables/list-view.d.ts +42 -0
  35. package/dist/renderables/list-view.js +78 -0
  36. package/dist/renderables/progress.d.ts +6 -0
  37. package/dist/renderables/progress.js +11 -4
  38. package/dist/renderables/progressBar.d.ts +22 -2
  39. package/dist/renderables/progressBar.js +51 -7
  40. package/dist/renderables/rule.d.ts +2 -2
  41. package/dist/renderables/rule.js +12 -10
  42. package/dist/renderables/table.js +5 -7
  43. package/dist/renderables/viewport.d.ts +30 -0
  44. package/dist/renderables/viewport.js +59 -3
  45. package/dist/widgets/dropdown.js +3 -3
  46. package/dist/widgets/event-router.d.ts +31 -40
  47. package/dist/widgets/event-router.js +52 -483
  48. package/dist/widgets/index.d.ts +2 -1
  49. package/dist/widgets/types.d.ts +8 -25
  50. package/dist/widgets/types.js +8 -0
  51. package/dist/widgets/widget-app.d.ts +22 -7
  52. package/dist/widgets/widget-app.js +22 -12
  53. package/package.json +22 -18
@@ -20,12 +20,21 @@
20
20
  *
21
21
  * [LAW:locality-or-seam] `App` knows nothing of widgets — `host/` depends on
22
22
  * `core/` alone — and this reaches it only through the seams it already has:
23
- * the view is a renderable, and `onPaint` hears the frame.
23
+ * the view is a renderable, `onPaint` hears the frame, and `onKey`/`onMouse`
24
+ * hear the input.
24
25
  */
25
26
  import { Segment } from "../core/segment.js";
26
27
  import type { Unsubscribe } from "../core/subscription.js";
27
- import { type AppOptions, type AppPhase } from "../host/app.js";
28
- import type { FocusManager, FrameSource, KeyEvent, KeyHandlerOptions, ScreenMouseEvent } from "./types.js";
28
+ import { type AppOptions, type AppPhase, type Wheel } from "../host/app.js";
29
+ import type { FocusManager, FrameSource, KeyEvent, KeyHandlerOptions } from "./types.js";
30
+ import type { AppMouseEvent } from "../host/app.js";
31
+ export interface WidgetAppOptions extends AppOptions {
32
+ /**
33
+ * Whether a left-button press focuses the widget under it, before it hears the
34
+ * press. `true` unless given.
35
+ */
36
+ readonly focusOnClick?: boolean;
37
+ }
29
38
  export declare class WidgetApp implements FrameSource {
30
39
  readonly focusManager: FocusManager;
31
40
  private readonly focus;
@@ -33,7 +42,7 @@ export declare class WidgetApp implements FrameSource {
33
42
  private readonly router;
34
43
  private readonly reaction;
35
44
  private drawn;
36
- constructor(options: AppOptions);
45
+ constructor(options: WidgetAppOptions);
37
46
  get phase(): AppPhase;
38
47
  /** The rows on screen now, as painted: what a pointer event lands on. */
39
48
  get frame(): readonly (readonly Segment[])[];
@@ -54,12 +63,18 @@ export declare class WidgetApp implements FrameSource {
54
63
  /** Paint at the app's rate until the returned function is called, as `App.animate` does. */
55
64
  animate(): Unsubscribe;
56
65
  /**
57
- * Hear every key. A `high` handler hears it before the focused widget, a
66
+ * Hear every key the app does not quit on. A `high` handler hears it before the focused widget, a
58
67
  * `normal` one after it — and after Tab has moved focus — and a handler
59
68
  * that calls `event.stop()` ends the key there.
60
69
  */
61
70
  onKey(handler: (event: KeyEvent) => void, options?: KeyHandlerOptions): Unsubscribe;
62
- /** Hear every pointer event, in terminal cells, before any widget does. */
63
- onMouse(handler: (event: ScreenMouseEvent) => void): Unsubscribe;
71
+ /** Hear every pointer event but the vertical wheel, in terminal cells, before any widget does. */
72
+ onMouse(handler: (event: AppMouseEvent) => void): Unsubscribe;
73
+ /**
74
+ * Offer each wheel turn the viewport under the pointer did not move with to
75
+ * `handler`, as `App.onWheel` does: it returns whether it moved something,
76
+ * and a turn nothing moved with is passed outward.
77
+ */
78
+ onWheel(handler: (wheel: Wheel) => boolean): Unsubscribe;
64
79
  private compose;
65
80
  }
@@ -20,7 +20,8 @@
20
20
  *
21
21
  * [LAW:locality-or-seam] `App` knows nothing of widgets — `host/` depends on
22
22
  * `core/` alone — and this reaches it only through the seams it already has:
23
- * the view is a renderable, and `onPaint` hears the frame.
23
+ * the view is a renderable, `onPaint` hears the frame, and `onKey`/`onMouse`
24
+ * hear the input.
24
25
  */
25
26
  import { Reaction } from "mobx";
26
27
  import { Segment } from "../core/segment.js";
@@ -40,7 +41,7 @@ export class WidgetApp {
40
41
  // The widgets the view drew for the frame on screen, in document order.
41
42
  drawn = [];
42
43
  constructor(options) {
43
- const { view } = options;
44
+ const { view, focusOnClick = true } = options;
44
45
  this.reaction = new Reaction("WidgetApp", () => this.app.refresh());
45
46
  this.app = new App({
46
47
  ...options,
@@ -51,7 +52,12 @@ export class WidgetApp {
51
52
  return this.drawn.filter((widget) => owners.has(widget));
52
53
  });
53
54
  this.focusManager = this.focus;
54
- this.router = new EventRouter({ source: this, host: options.host });
55
+ this.router = new EventRouter({ source: this, focusOnClick });
56
+ // [LAW:single-enforcer] The app reads the terminal; the router routes
57
+ // what it hears, after the app's quit keys.
58
+ this.app.onKey((press) => this.router.key(press));
59
+ this.app.onMouse((event) => this.router.mouse(event));
60
+ this.app.onWheel((wheel) => this.router.wheel(wheel));
55
61
  this.app.onPaint(() => {
56
62
  this.focus.settle(this.drawn);
57
63
  this.router.framePainted();
@@ -72,18 +78,14 @@ export class WidgetApp {
72
78
  * until the app stops — `App.run`, with the input.
73
79
  */
74
80
  async run() {
75
- // App refuses a second run; this one must not end the input of the first.
81
+ // App refuses a second run; this one must not end the state of the first.
76
82
  if (this.app.phase !== "idle")
77
83
  return this.app.run();
78
- // Listening before the app starts, so a first frame that throws — and
79
- // stops the host — is not followed by a subscription that restarts it.
80
- this.router.start();
81
- const running = this.app.run();
82
84
  try {
83
- await running;
85
+ await this.app.run();
84
86
  }
85
87
  finally {
86
- this.router.stop();
88
+ this.router.reset();
87
89
  this.reaction.dispose();
88
90
  }
89
91
  }
@@ -109,17 +111,25 @@ export class WidgetApp {
109
111
  return this.app.animate();
110
112
  }
111
113
  /**
112
- * Hear every key. A `high` handler hears it before the focused widget, a
114
+ * Hear every key the app does not quit on. A `high` handler hears it before the focused widget, a
113
115
  * `normal` one after it — and after Tab has moved focus — and a handler
114
116
  * that calls `event.stop()` ends the key there.
115
117
  */
116
118
  onKey(handler, options) {
117
119
  return this.router.onKey(handler, options);
118
120
  }
119
- /** Hear every pointer event, in terminal cells, before any widget does. */
121
+ /** Hear every pointer event but the vertical wheel, in terminal cells, before any widget does. */
120
122
  onMouse(handler) {
121
123
  return this.router.onMouse(handler);
122
124
  }
125
+ /**
126
+ * Offer each wheel turn the viewport under the pointer did not move with to
127
+ * `handler`, as `App.onWheel` does: it returns whether it moved something,
128
+ * and a turn nothing moved with is passed outward.
129
+ */
130
+ onWheel(handler) {
131
+ return this.app.onWheel(handler);
132
+ }
123
133
  compose(view, options) {
124
134
  const drawn = new Set();
125
135
  // [LAW:parse-dont-validate] An owner is any object; only a `WidgetBase`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptctl/rich-js",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
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",
@@ -61,26 +61,27 @@
61
61
  "build": "npm run clean && tsc",
62
62
  "test": "vitest run",
63
63
  "test:watch": "vitest",
64
+ "prelint": "node scripts/stage-repo-files.ts",
64
65
  "lint": "tsc --noEmit && tsc -p tsconfig.scripts.json && tsc -p tsconfig.test.json",
65
66
  "clean": "rm -rf dist",
66
- "docs:dev": "npm run demos:build && vitepress dev docs",
67
- "docs:build": "npm run demos:build && vitepress build docs",
67
+ "predocs:dev": "node scripts/stage-repo-files.ts",
68
+ "docs:dev": "vitepress dev docs",
69
+ "predocs:build": "node scripts/stage-repo-files.ts",
70
+ "docs:build": "vitepress build docs",
68
71
  "docs:preview": "vitepress preview docs",
69
- "demo": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-explore/index.js",
70
- "demo:build": "tsc -p tsconfig.demo.json",
71
- "sessions": "tsc -p tsconfig.demo.json && node dist-demo/examples/claude-sessions/index.js",
72
- "strip": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-strip/index.js",
73
- "effects-feel": "tsc -p tsconfig.demo.json && node dist-demo/examples/effects-feel/index.js",
74
- "viewport": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-viewport/index.js",
75
- "markup-plugins": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-markup-plugins/index.js",
76
- "demo-inputs": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-config/index.js",
77
- "demo:dropdown": "tsc -p tsconfig.demo.json && node dist-demo/examples/dropdown-demo/index.js",
78
- "dash": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-dash/index.js",
79
- "template-bindings": "tsc -p tsconfig.demo.json && node dist-demo/examples/rich-template-bindings/index.js",
80
- "themes-and-color-studio": "tsc -p tsconfig.demo.json && node dist-demo/examples/themes-and-color-studio/index.js",
81
- "harness:browser-host": "tsc -p tsconfig.demo.json && echo 'Serving harness at http://localhost:8080/examples/browser-terminal-host-harness/' && npx --yes http-server@14.1.1 -p 8080 -c-1 -s .",
82
- "demos:build": "tsc -p tsconfig.demo.json && vite build --config vite.config.demos.ts",
83
- "demos:preview": "vite preview --config vite.config.demos.ts",
72
+ "demo": "node scripts/build-demos.ts && node dist-demo/examples/rich-explore/node.js",
73
+ "demo:build": "node scripts/build-demos.ts",
74
+ "sessions": "node scripts/build-demos.ts && node dist-demo/examples/claude-sessions/node.js",
75
+ "strip": "node scripts/build-demos.ts && node dist-demo/examples/rich-strip/main.js",
76
+ "effects-feel": "node scripts/build-demos.ts && node dist-demo/examples/effects-feel/index.js",
77
+ "viewport": "node scripts/build-demos.ts && node dist-demo/examples/rich-viewport/main.js",
78
+ "markup-plugins": "node scripts/build-demos.ts && node dist-demo/examples/rich-markup-plugins/main.js",
79
+ "demo-inputs": "node scripts/build-demos.ts && node dist-demo/examples/rich-config/main.js",
80
+ "demo:dropdown": "node scripts/build-demos.ts && node dist-demo/examples/dropdown-demo/main.js",
81
+ "dash": "node scripts/build-demos.ts && node dist-demo/examples/rich-dash/node.js",
82
+ "template-bindings": "node scripts/build-demos.ts && node dist-demo/examples/rich-template-bindings/main.js",
83
+ "themes-and-color-studio": "node scripts/build-demos.ts && node dist-demo/examples/themes-and-color-studio/node.js",
84
+ "terminal-host": "node scripts/build-demos.ts && node dist-demo/examples/terminal-host/main.js",
84
85
  "test:demos": "playwright test"
85
86
  },
86
87
  "keywords": [
@@ -97,6 +98,9 @@
97
98
  "cli"
98
99
  ],
99
100
  "license": "MIT",
101
+ "devEngines": {
102
+ "runtime": { "name": "node", "version": "^22.18.0 || >=23.6.0", "onFail": "error" }
103
+ },
100
104
  "engines": {
101
105
  "node": ">=20"
102
106
  },