@hex1b/web-terminal 0.1.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 (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +243 -0
  3. package/dist/fonts/cascadia-mono-nf/CascadiaMonoNF.woff2 +0 -0
  4. package/dist/fonts/cascadia-mono-nf/LICENSE.txt +94 -0
  5. package/dist/fonts/cascadia-mono-nf/README.md +29 -0
  6. package/dist/history-state.d.ts +27 -0
  7. package/dist/history-state.d.ts.map +1 -0
  8. package/dist/history-state.js +194 -0
  9. package/dist/history-state.js.map +1 -0
  10. package/dist/index.d.ts +5 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +4 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/input-policy.d.ts +42 -0
  15. package/dist/input-policy.d.ts.map +1 -0
  16. package/dist/input-policy.js +143 -0
  17. package/dist/input-policy.js.map +1 -0
  18. package/dist/mouse-input.d.ts +26 -0
  19. package/dist/mouse-input.d.ts.map +1 -0
  20. package/dist/mouse-input.js +356 -0
  21. package/dist/mouse-input.js.map +1 -0
  22. package/dist/protocol.d.ts +17 -0
  23. package/dist/protocol.d.ts.map +1 -0
  24. package/dist/protocol.js +270 -0
  25. package/dist/protocol.js.map +1 -0
  26. package/dist/renderer.d.ts +113 -0
  27. package/dist/renderer.d.ts.map +1 -0
  28. package/dist/renderer.js +595 -0
  29. package/dist/renderer.js.map +1 -0
  30. package/dist/selection-input.d.ts +36 -0
  31. package/dist/selection-input.d.ts.map +1 -0
  32. package/dist/selection-input.js +69 -0
  33. package/dist/selection-input.js.map +1 -0
  34. package/dist/selection-ui.d.ts +24 -0
  35. package/dist/selection-ui.d.ts.map +1 -0
  36. package/dist/selection-ui.js +113 -0
  37. package/dist/selection-ui.js.map +1 -0
  38. package/dist/terminal-font.d.ts +23 -0
  39. package/dist/terminal-font.d.ts.map +1 -0
  40. package/dist/terminal-font.js +85 -0
  41. package/dist/terminal-font.js.map +1 -0
  42. package/dist/terminal-sizing.d.ts +8 -0
  43. package/dist/terminal-sizing.d.ts.map +1 -0
  44. package/dist/terminal-sizing.js +38 -0
  45. package/dist/terminal-sizing.js.map +1 -0
  46. package/dist/terminal-theme.d.ts +2 -0
  47. package/dist/terminal-theme.d.ts.map +1 -0
  48. package/dist/terminal-theme.js +39 -0
  49. package/dist/terminal-theme.js.map +1 -0
  50. package/dist/terminal-worker.d.ts +2 -0
  51. package/dist/terminal-worker.d.ts.map +1 -0
  52. package/dist/terminal-worker.js +285 -0
  53. package/dist/terminal-worker.js.map +1 -0
  54. package/dist/types.d.ts +312 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +2 -0
  57. package/dist/types.js.map +1 -0
  58. package/dist/validation.d.ts +3 -0
  59. package/dist/validation.d.ts.map +1 -0
  60. package/dist/validation.js +7 -0
  61. package/dist/validation.js.map +1 -0
  62. package/dist/web-terminal.d.ts +51 -0
  63. package/dist/web-terminal.d.ts.map +1 -0
  64. package/dist/web-terminal.js +728 -0
  65. package/dist/web-terminal.js.map +1 -0
  66. package/dist/wire-types.d.ts +218 -0
  67. package/dist/wire-types.d.ts.map +1 -0
  68. package/dist/wire-types.js +2 -0
  69. package/dist/wire-types.js.map +1 -0
  70. package/package.json +42 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mitch Denny
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,243 @@
1
+ # @hex1b/web-terminal
2
+
3
+ The first-party WebGPU browser terminal for Hex1b. It renders server-authoritative
4
+ cells and graphics in a module worker, with local input routing, producer-backed
5
+ history and selection, clipboard actions, and primary/secondary view sizing.
6
+ There are no runtime package dependencies.
7
+
8
+ **Experimental and paired with Hex1b:** this client speaks the evolving HWT1
9
+ transport implemented by the matching Hex1b server. HWT1 and internal browser
10
+ modules are not a supported third-party protocol or renderer API. The bootstrap
11
+ npm version `0.1.0` must be paired with the server build from the **same
12
+ implementation commit**; it is not compatible merely by version number with the
13
+ historical Hex1b NuGet `0.1.0`. Subsequent normal CI releases coordinate npm and
14
+ NuGet versions; use matching builds from the same release.
15
+
16
+ ## Install and mount
17
+
18
+ ```sh
19
+ npm install @hex1b/web-terminal
20
+ ```
21
+
22
+ Give the container a nonzero width and height. Mount resolves after a connected
23
+ frame has been presented, or rejects on initialization failure or a 30-second
24
+ first-frame timeout.
25
+
26
+ ```html
27
+ <div id="terminal" style="width: 100%; height: 480px"></div>
28
+ ```
29
+
30
+ ```ts
31
+ import { WebTerminal } from "@hex1b/web-terminal";
32
+
33
+ const container = document.getElementById("terminal");
34
+ if (!container) throw new Error("Missing terminal container");
35
+
36
+ const terminal = await WebTerminal.mount(container, {
37
+ url: "/ws/terminal", // Your matching Hex1b HWT1 WebSocket endpoint.
38
+ sizing: { mode: "auto", fontSize: 16 },
39
+ onStatus(message, level) {
40
+ console.log(level, message);
41
+ }
42
+ });
43
+
44
+ terminal.focus();
45
+ // On component teardown:
46
+ // terminal.dispose();
47
+ ```
48
+
49
+ `url` accepts a string or URL; relative URLs resolve against the page, and
50
+ `http:`/`https:` become `ws:`/`wss:`. An optional `AbortSignal` cancels mounting
51
+ or disposes a mounted view. Disposal removes only the appended element and its
52
+ connection, not the container or server-side shared terminal.
53
+
54
+ ### Browser and deployment requirements
55
+
56
+ Use HTTPS or localhost and a browser with WebGPU, module workers, transferable
57
+ OffscreenCanvas, worker animation frames, ResizeObserver, and CSS Font Loading.
58
+ Clipboard access also requires browser permission and, for relevant actions, a
59
+ user gesture. There is no Canvas2D terminal-rendering fallback.
60
+
61
+ The package contains browser ES modules, not a single bundle. For bare static
62
+ hosting, copy **all of `dist/`**, preserving its directory structure, and import
63
+ `/web-terminal/index.js` from a module script. `dist/web-terminal.js` also remains
64
+ available for relative static imports. Package consumers should import only from
65
+ `@hex1b/web-terminal`; internal protocol/renderer modules are not public exports.
66
+
67
+ The worker is created with
68
+ `new Worker(new URL("./terminal-worker.js", import.meta.url), { type: "module" })`.
69
+ The default font is resolved relative to its module, not the host page.
70
+ Bundlers differ in whether they discover and rewrite assets inside dependencies;
71
+ this package does not claim universal or individually verified bundler support.
72
+ The reliable static deployment layout is the complete emitted tree described
73
+ above.
74
+
75
+ If your bundler does not handle the dependency's worker URL, explicitly provide
76
+ the entry point you deployed:
77
+
78
+ ```ts
79
+ const terminal = await WebTerminal.mount(container, {
80
+ url: "/ws/terminal",
81
+ workerUrl: "/web-terminal/terminal-worker.js"
82
+ });
83
+ ```
84
+
85
+ `workerUrl` accepts a nonempty string or URL and resolves relative strings against
86
+ the page, not the package module. It still creates a **module** worker. Deploy its
87
+ complete relative module tree, or provide a separately bundled worker entry from
88
+ the same package build. The override does not automatically copy fonts: retain
89
+ the default font asset URL or provide explicit `font.faces` URLs as shown below.
90
+ The browser's worker origin and CSP restrictions still apply.
91
+
92
+ Configure your server's JavaScript and WOFF2 MIME types and CSP to allow these
93
+ workers, fonts, and the intended WebSocket endpoint.
94
+
95
+ ## Configuration and state
96
+
97
+ `WebTerminalOptions` includes:
98
+
99
+ | Option | Meaning |
100
+ | --- | --- |
101
+ | `workerUrl` | Optional module-worker entry; useful when worker assets are deployed separately. |
102
+ | `scale` | GPU backing scale `0.5`–`3`, or `"auto"` (default, bounded device pixel ratio). |
103
+ | `font` | One family and optional downloadable font faces; see below. |
104
+ | `sizing` | `{ mode: "auto", fontSize?: number }` or `{ mode: "fixed", columns, rows, fontSize?: number }`. |
105
+ | `readOnly` | Disable application input while retaining history inspection and selection. |
106
+ | `label` | Accessible label for the terminal's hidden keyboard input. |
107
+ | `inputBindings`, `onInput`, `actions` | Per-view input policy and custom actions. |
108
+ | `onSelectionUI` | Synchronous, cancelable UI notification hook. |
109
+
110
+ Font size is an integer from 8–32, defaulting to 16. Import `MIN_FONT_SIZE` and
111
+ `MAX_FONT_SIZE` from `@hex1b/web-terminal` for sizing controls. Requested fixed grids allow
112
+ 20–300 columns and 10–100 rows. The producer still owns actual grid geometry.
113
+ `resize()`, `setSizing()`, and automatic resize requests require primary
114
+ ownership. `requestPrimary()` explicitly requests ownership; inspect `peer` or
115
+ `onRoleChange` to observe the result.
116
+
117
+ The handle exposes `geometry`, `peer`, `connected`, `stats`, `screenText`,
118
+ `sizing`, `viewport`, `selection`, `inputBindings`, and `inputContext`.
119
+ Metrics start empty; check optional fields before using them. History may be
120
+ unavailable, and selection can be unavailable, none, pending, valid, or
121
+ invalidated. Narrow `viewport.available` and `selection.status` before using
122
+ their state-specific values. `screenText` reflects the presented viewport, not
123
+ an independently reconstructed ANSI buffer.
124
+
125
+ Callbacks include `onGeometry`, `onRoleChange`, `onSizingChange`, `onStats`,
126
+ `onViewportChange`, `onSelectionChange`, `onStatus`, and `onInputError`.
127
+
128
+ ## Input and clipboard
129
+
130
+ Import `InputRoute`, `TerminalAction`, and `defaultInputBindings` to inspect and
131
+ customize routing. Defaults preserve browser shortcuts, forward terminal keys,
132
+ copy with Cmd+C or Ctrl+Shift+C, and use a local right-click to copy or paste
133
+ when application mouse capture does not own that gesture. IME composition and
134
+ paste are forwarded through the producer's mode-aware input encoder.
135
+
136
+ Overrides match first. Reuse a default binding's ID to replace it, or specify
137
+ `{ id: "clipboard.context-click", remove: true }` to remove that default.
138
+ `match`, `when`, and `onInput` must finish synchronously. Actions may be async.
139
+
140
+ ```ts
141
+ import { InputRoute, TerminalAction, type WebTerminalOptions } from "@hex1b/web-terminal";
142
+
143
+ const options: WebTerminalOptions = {
144
+ url: "/ws/terminal",
145
+ inputBindings: [
146
+ {
147
+ id: "history.previous-page",
148
+ match: input => input.type === "key" && input.key === "PageUp" && input.shift,
149
+ action: TerminalAction.ScrollLines,
150
+ args: -20
151
+ },
152
+ { id: "clipboard.context-click", remove: true }
153
+ ],
154
+ onInput(input) {
155
+ if (input.type === "key" && input.meta) return InputRoute.Browser;
156
+ return InputRoute.Continue;
157
+ }
158
+ };
159
+ ```
160
+
161
+ Named actions are `copySelection`, `pasteClipboard`, `copyOrPaste`,
162
+ `clearSelection`, `scrollToLive`, and `scrollLines`.
163
+ `terminal.runAction(TerminalAction.ScrollLines, -20)` shares the same
164
+ implementation as bindings and UI controls. Custom `actions` receive
165
+ `(context, args, input)`; their argument/result types are `unknown`, so custom
166
+ handlers validate their own data. Built-in action names cannot be overridden.
167
+
168
+ You can also call `scrollLines()`, `scrollToLive()`, `clearSelection()`,
169
+ `copySelection({ clear: true })`, `paste(text)`, or `pasteClipboard()` directly.
170
+ Copy uses authoritative producer selection text, not rendered cells. Clipboard
171
+ actions reject if selection/input/focus changes before their asynchronous work
172
+ can be applied safely. Errors are surfaced rather than silently reported as
173
+ successful copies or pastes.
174
+
175
+ ## Selection UI hooks
176
+
177
+ `onSelectionUI` receives a typed `SelectionUIEvent`, also dispatched as the
178
+ `selectionui` DOM event on `terminal.element`. Its frozen detail includes
179
+ selection, viewport, geometry, canvas size, connection/read-only state, and:
180
+
181
+ - `overlay`: a stable light-DOM host for custom UI; style your controls and set
182
+ `pointer-events: auto` on interactive descendants.
183
+ - `rects`: selection rectangles in overlay-local CSS pixels.
184
+ - `runAction`: the same typed action API as the terminal handle.
185
+ - `signal`: cleanup lifetime, aborted on disposal.
186
+
187
+ Call `event.preventDefault()` **synchronously** to replace the default Copy
188
+ button. This does not remove selection highlights or transfer ownership of
189
+ selection/clipboard state. The callback must return `undefined`, not a Promise.
190
+ Events coalesce meaningful changes; `refreshSelectionUI()` re-notifies hosts
191
+ after external styling or policy changes. External DOM listeners may also
192
+ cancel the default UI.
193
+
194
+ Inspection UI inherits the embedding page's `--cp-*` theme tokens and otherwise
195
+ uses its own light/dark defaults. Shadow parts include `selection-highlights`,
196
+ `selection-highlight`, and `selection-copy-button`.
197
+
198
+ ## Fonts and licenses
199
+
200
+ The default is the bundled **Cascadia Mono NF** variable WOFF2 font. The package
201
+ includes the unmodified font, SIL Open Font License, and provenance in
202
+ `dist/fonts/cascadia-mono-nf/`; no sample assets are required.
203
+
204
+ ```ts
205
+ const font = {
206
+ family: "My Terminal Font",
207
+ faces: [{ url: "/fonts/my-terminal.woff2", weight: "100 900", style: "normal" }]
208
+ };
209
+ ```
210
+
211
+ Custom face URLs resolve against the host page before the configuration reaches
212
+ the worker. Page-loaded fonts are not inherited by workers: supply face URLs,
213
+ a locally installed family, or a generic family such as `monospace`. A generic
214
+ family cannot have downloadable faces. Font loading failures reject rather
215
+ than silently selecting a different font.
216
+
217
+ Hex1b's code is MIT licensed (`LICENSE`); the bundled font uses its separate
218
+ SIL Open Font License.
219
+
220
+ ## Build, test, and pack
221
+
222
+ From this package directory, with Node.js 22 or later:
223
+
224
+ ```sh
225
+ npm ci
226
+ npm run build
227
+ npm test
228
+ npm pack
229
+ ```
230
+
231
+ The strict TypeScript build emits JavaScript, declarations, declaration maps,
232
+ and source maps (with embedded sources), then copies fonts into `dist/`.
233
+ `npm test` runs zero-dependency `node:test` tests against those emitted modules
234
+ and strict public-consumer declaration checks in NodeNext and bundler modes.
235
+ `npm run typecheck` validates sources without emitting.
236
+
237
+ `prepack` rebuilds for `npm pack` and manual `npm publish` from this directory.
238
+ Only `dist/`, this README, the MIT license, and package metadata are shipped.
239
+ A prepared tarball is self-contained and can be published with
240
+ `npm publish ./hex1b-web-terminal-<version>.tgz --ignore-scripts`; it does not
241
+ need development sources or build scripts. The package name is always
242
+ `@hex1b/web-terminal`, including GitHub Packages. Registry selection is left to
243
+ the caller; no registry is pinned in `package.json`.
@@ -0,0 +1,94 @@
1
+ Copyright (c) 2019 - Present, Microsoft Corporation,
2
+ with Reserved Font Name Cascadia Code.
3
+
4
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
5
+ This license is copied below, and is also available with a FAQ at:
6
+ http://scripts.sil.org/OFL
7
+
8
+
9
+ -----------------------------------------------------------
10
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
11
+ -----------------------------------------------------------
12
+
13
+ PREAMBLE
14
+ The goals of the Open Font License (OFL) are to stimulate worldwide
15
+ development of collaborative font projects, to support the font creation
16
+ efforts of academic and linguistic communities, and to provide a free and
17
+ open framework in which fonts may be shared and improved in partnership
18
+ with others.
19
+
20
+ The OFL allows the licensed fonts to be used, studied, modified and
21
+ redistributed freely as long as they are not sold by themselves. The
22
+ fonts, including any derivative works, can be bundled, embedded,
23
+ redistributed and/or sold with any software provided that any reserved
24
+ names are not used by derivative works. The fonts and derivatives,
25
+ however, cannot be released under any other type of license. The
26
+ requirement for fonts to remain under this license does not apply
27
+ to any document created using the fonts or their derivatives.
28
+
29
+ DEFINITIONS
30
+ "Font Software" refers to the set of files released by the Copyright
31
+ Holder(s) under this license and clearly marked as such. This may
32
+ include source files, build scripts and documentation.
33
+
34
+ "Reserved Font Name" refers to any names specified as such after the
35
+ copyright statement(s).
36
+
37
+ "Original Version" refers to the collection of Font Software components as
38
+ distributed by the Copyright Holder(s).
39
+
40
+ "Modified Version" refers to any derivative made by adding to, deleting,
41
+ or substituting -- in part or in whole -- any of the components of the
42
+ Original Version, by changing formats or by porting the Font Software to a
43
+ new environment.
44
+
45
+ "Author" refers to any designer, engineer, programmer, technical
46
+ writer or other person who contributed to the Font Software.
47
+
48
+ PERMISSION & CONDITIONS
49
+ Permission is hereby granted, free of charge, to any person obtaining
50
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
51
+ redistribute, and sell modified and unmodified copies of the Font
52
+ Software, subject to the following conditions:
53
+
54
+ 1) Neither the Font Software nor any of its individual components,
55
+ in Original or Modified Versions, may be sold by itself.
56
+
57
+ 2) Original or Modified Versions of the Font Software may be bundled,
58
+ redistributed and/or sold with any software, provided that each copy
59
+ contains the above copyright notice and this license. These can be
60
+ included either as stand-alone text files, human-readable headers or
61
+ in the appropriate machine-readable metadata fields within text or
62
+ binary files as long as those fields can be easily viewed by the user.
63
+
64
+ 3) No Modified Version of the Font Software may use the Reserved Font
65
+ Name(s) unless explicit written permission is granted by the corresponding
66
+ Copyright Holder. This restriction only applies to the primary font name as
67
+ presented to the users.
68
+
69
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
70
+ Software shall not be used to promote, endorse or advertise any
71
+ Modified Version, except to acknowledge the contribution(s) of the
72
+ Copyright Holder(s) and the Author(s) or with their explicit written
73
+ permission.
74
+
75
+ 5) The Font Software, modified or unmodified, in part or in whole,
76
+ must be distributed entirely under this license, and must not be
77
+ distributed under any other license. The requirement for fonts to
78
+ remain under this license does not apply to any document created
79
+ using the Font Software.
80
+
81
+ TERMINATION
82
+ This license becomes null and void if any of the above conditions are
83
+ not met.
84
+
85
+ DISCLAIMER
86
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
87
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
88
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
89
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
90
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
91
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
92
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
93
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
94
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # Cascadia Mono NF
2
+
3
+ The web terminal's default font is Microsoft's Cascadia Mono with Nerd Font
4
+ symbols and without programming ligatures. The font file is unmodified.
5
+
6
+ - Upstream: <https://github.com/microsoft/cascadia-code>
7
+ - Release: [v2407.24](https://github.com/microsoft/cascadia-code/releases/tag/v2407.24)
8
+ - Upstream release path: `woff2/CascadiaMonoNF.woff2`
9
+ - Retrieved from Aspire's copy at commit
10
+ [`1dd4584e3df56f5544a3e7f5fda8aa767f92318e`](https://github.com/microsoft/aspire/tree/1dd4584e3df56f5544a3e7f5fda8aa767f92318e/src/Aspire.Dashboard/wwwroot/fonts/cascadia-mono-nf)
11
+ - Size: 976,460 bytes
12
+ - SHA-256: `bd42b0c992de9c42d8a770112e8140d67d6437798be39d49e57f4954e2f6e8e2`
13
+
14
+ ## License and redistribution
15
+
16
+ Copyright (c) 2019 - Present, Microsoft Corporation,
17
+ with Reserved Font Name Cascadia Code.
18
+
19
+ The font is licensed under the **SIL Open Font License 1.1**, not Hex1b's code
20
+ license. The full, unmodified license and copyright notice are in
21
+ [`LICENSE.txt`](LICENSE.txt). Include that notice and license whenever
22
+ redistributing the font, including future embedded-resource or npm packaging.
23
+ OFL permits bundling and embedding alongside software; the font cannot be sold
24
+ on its own. Modified versions must respect the reserved-font-name and other
25
+ OFL conditions.
26
+
27
+ To update, take the unmodified WOFF2 from an official upstream release, retain
28
+ the accompanying license, and update the version, provenance, size, and hash
29
+ above. Do not subset or patch the asset as an incidental packaging step.
@@ -0,0 +1,27 @@
1
+ /** Commands contain producer identities; text and ranges are never inferred from painted cells. */
2
+ export declare class HistoryState {
3
+ #private;
4
+ constructor(send: (command: TerminalCommand) => void, change?: () => void);
5
+ get viewport(): HistoryViewport;
6
+ get selection(): HistorySelectionState;
7
+ accept(history: HistoryMetadata | null, revision: number): boolean;
8
+ begin(point: TerminalPoint, { mode, extend }: {
9
+ mode: SelectionMode;
10
+ extend?: boolean;
11
+ }): void;
12
+ extend(point: TerminalPoint): void;
13
+ scroll(delta: number, endpoint?: TerminalPoint): void;
14
+ live(): void;
15
+ clear(): void;
16
+ endGesture(cancelled?: boolean): void;
17
+ cancelCopy(error: unknown): void;
18
+ copy(): Promise<string>;
19
+ disconnect(): void;
20
+ }
21
+ import type { SelectionMode, TerminalPoint, TerminalSelection, TerminalViewport } from "./types.js";
22
+ import type { HistoryMetadata, TerminalCommand } from "./wire-types.js";
23
+ type OmitEach<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
24
+ type HistoryViewport = OmitEach<TerminalViewport, "followTail" | "offset">;
25
+ type HistorySelectionState = OmitEach<TerminalSelection, "active" | "pending" | "copying" | "copyError">;
26
+ export {};
27
+ //# sourceMappingURL=history-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-state.d.ts","sourceRoot":"","sources":["../src/history-state.ts"],"names":[],"mappings":"AAIA,mGAAmG;AACnG,qBAAa,YAAY;;gBAaX,IAAI,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,EAAE,MAAM,GAAE,MAAM,IAAe;IAKnF,IAAI,QAAQ,IAAI,eAAe,CAK9B;IAED,IAAI,SAAS,IAAI,qBAAqB,CAYrC;IAED,MAAM,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAuDlE,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,MAAc,EAAE,EAAE;QAAE,IAAI,EAAE,aAAa,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IAStG,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAalC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,aAAa,GAAG,IAAI;IAcrD,IAAI;IAQJ,KAAK;IAQL,UAAU,CAAC,SAAS,UAAQ;IAc5B,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAEhC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IAyBvB,UAAU;CAIX;AACD,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACpG,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAExE,KAAK,QAAQ,CAAC,CAAC,EAAE,CAAC,SAAS,WAAW,IAAI,CAAC,SAAS,OAAO,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC;AACjF,KAAK,eAAe,GAAG,QAAQ,CAAC,gBAAgB,EAAE,YAAY,GAAG,QAAQ,CAAC,CAAC;AAC3E,KAAK,qBAAqB,GAAG,QAAQ,CAAC,iBAAiB,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC,CAAC"}
@@ -0,0 +1,194 @@
1
+ const unavailable = "Text history is not available for this terminal view";
2
+ const expired = "Selection expired: its text was changed, evicted, reset, or resized. Select the text again.";
3
+ const changedBeforeCopy = "Selection changed before copy completed. Copy the new selection again.";
4
+ /** Commands contain producer identities; text and ranges are never inferred from painted cells. */
5
+ export class HistoryState {
6
+ #send;
7
+ #change;
8
+ #history = null;
9
+ #revision = 0;
10
+ #nextRequest = 0;
11
+ #selectionRequest = 0;
12
+ #viewportRequest = 0;
13
+ #pendingMode = "character";
14
+ #pendingAction = "clear";
15
+ #pendingCopy;
16
+ #deferredEndpoint;
17
+ constructor(send, change = () => { }) {
18
+ this.#send = send;
19
+ this.#change = change;
20
+ }
21
+ get viewport() {
22
+ if (!this.#history)
23
+ return { available: false, following: true, pending: false };
24
+ const { selection, copy, ...viewport } = this.#history;
25
+ return { ...viewport, rowIds: [...viewport.rowIds], available: true, revision: this.#revision,
26
+ pending: viewport.requestId < this.#viewportRequest };
27
+ }
28
+ get selection() {
29
+ if (!this.#history)
30
+ return { status: "unavailable", mode: "character", ranges: [], text: null, message: unavailable };
31
+ const selection = this.#history.selection;
32
+ if (selection.requestId < this.#selectionRequest || this.#deferredEndpoint) {
33
+ return { requestId: this.#selectionRequest, status: "pending", mode: this.#pendingMode,
34
+ canExtend: this.#pendingAction !== "clear",
35
+ ranges: this.#pendingAction === "clear" ? [] : selection.ranges.map(range => ({ ...range })), text: null,
36
+ message: "Resolving selection…" };
37
+ }
38
+ return { ...selection, canExtend: selection.status === "valid",
39
+ ranges: selection.ranges.map(range => ({ ...range })), revision: this.#revision,
40
+ message: selection.status === "invalidated" ? expired : "" };
41
+ }
42
+ accept(history, revision) {
43
+ if (revision <= this.#revision)
44
+ return false;
45
+ const previousGeneration = this.#history?.generation;
46
+ this.#history = history;
47
+ this.#revision = revision;
48
+ if (!history || (previousGeneration && previousGeneration !== history.generation) ||
49
+ history.selection.status === "invalidated") {
50
+ this.endGesture(true);
51
+ this.#rejectCopy(new Error(expired));
52
+ }
53
+ const pending = this.#pendingCopy;
54
+ if (pending && history?.copy?.requestId === pending.requestId) {
55
+ if (history.copy.status !== "valid" || history.selection.status !== "valid" ||
56
+ history.selection.requestId !== pending.selectionRequestId ||
57
+ history.generation !== pending.generation || this.#selectionRequest > pending.selectionRequestId ||
58
+ history.copy.text !== history.selection.text) {
59
+ this.#rejectCopy(new Error(expired));
60
+ }
61
+ else {
62
+ clearTimeout(pending.timer);
63
+ this.#pendingCopy = undefined;
64
+ pending.resolve(history.copy.text);
65
+ }
66
+ }
67
+ if (this.#deferredEndpoint && !this.viewport.pending) {
68
+ const point = this.#deferredEndpoint;
69
+ this.#deferredEndpoint = undefined;
70
+ this.extend(point);
71
+ }
72
+ this.#change();
73
+ return true;
74
+ }
75
+ #requireHistory() {
76
+ if (!this.#history)
77
+ throw new Error(unavailable);
78
+ return this.#history;
79
+ }
80
+ #pointRowId(point) {
81
+ const history = this.#requireHistory();
82
+ if (!point || !Number.isInteger(point.x) || point.x < 0 || point.x > 1023 ||
83
+ !Number.isInteger(point.y) || point.y < 0 ||
84
+ typeof history.rowIds[point.y] !== "string" || !history.rowIds[point.y].length) {
85
+ throw new Error("Terminal viewport is not ready for that selection point. Wait for the next frame and try again.");
86
+ }
87
+ return history.rowIds[point.y];
88
+ }
89
+ #selectionChanged(mode = this.selection.mode, action = "extend") {
90
+ this.#rejectCopy(new Error(changedBeforeCopy));
91
+ this.#pendingMode = mode;
92
+ this.#pendingAction = action;
93
+ this.#selectionRequest = ++this.#nextRequest;
94
+ return this.#selectionRequest;
95
+ }
96
+ begin(point, { mode, extend = false }) {
97
+ const rowId = this.#pointRowId(point);
98
+ this.#deferredEndpoint = undefined;
99
+ const requestId = this.#selectionChanged(mode, extend ? "extend" : "start");
100
+ this.#send({ type: "selection", action: extend ? "extend" : "start", mode, requestId,
101
+ generation: this.#requireHistory().generation, rowId, column: point.x });
102
+ this.#change();
103
+ }
104
+ extend(point) {
105
+ this.#pointRowId(point);
106
+ if (this.viewport.pending) {
107
+ this.#rejectCopy(new Error(changedBeforeCopy));
108
+ this.#pendingMode = this.selection.mode;
109
+ this.#pendingAction = "extend";
110
+ this.#deferredEndpoint = { ...point };
111
+ this.#change();
112
+ return;
113
+ }
114
+ this.begin(point, { mode: this.selection.mode, extend: true });
115
+ }
116
+ scroll(delta, endpoint) {
117
+ this.#requireHistory();
118
+ if (!Number.isSafeInteger(delta) || delta < -2147483648 || delta > 2147483647)
119
+ throw new RangeError("Scroll delta must be a signed 32-bit integer");
120
+ if (endpoint)
121
+ this.#pointRowId(endpoint);
122
+ if (!delta && !endpoint)
123
+ return;
124
+ if (endpoint)
125
+ this.#deferredEndpoint = undefined;
126
+ else
127
+ this.#flushDeferredEndpoint();
128
+ const requestId = endpoint ? this.#selectionChanged() : ++this.#nextRequest;
129
+ this.#viewportRequest = requestId;
130
+ this.#send({ type: "viewport", requestId, delta,
131
+ ...(endpoint ? { extend: { row: endpoint.y, column: endpoint.x } } : {}) });
132
+ this.#change();
133
+ }
134
+ live() {
135
+ this.#requireHistory();
136
+ this.#flushDeferredEndpoint();
137
+ this.#viewportRequest = ++this.#nextRequest;
138
+ this.#send({ type: "viewport", live: true, requestId: this.#viewportRequest });
139
+ this.#change();
140
+ }
141
+ clear() {
142
+ this.#requireHistory();
143
+ this.#deferredEndpoint = undefined;
144
+ const requestId = this.#selectionChanged(this.selection.mode, "clear");
145
+ this.#send({ type: "selection", action: "clear", requestId });
146
+ this.#change();
147
+ }
148
+ endGesture(cancelled = false) {
149
+ if (cancelled && this.#deferredEndpoint) {
150
+ this.#deferredEndpoint = undefined;
151
+ this.#change();
152
+ }
153
+ }
154
+ #flushDeferredEndpoint() {
155
+ if (!this.#deferredEndpoint)
156
+ return;
157
+ const endpoint = this.#deferredEndpoint;
158
+ this.#deferredEndpoint = undefined;
159
+ // Preserve command order if another scroll overtakes the pending presentation.
160
+ this.scroll(0, endpoint);
161
+ }
162
+ cancelCopy(error) { this.#rejectCopy(error); }
163
+ copy() {
164
+ const history = this.#requireHistory();
165
+ const selection = this.selection;
166
+ if (selection.status !== "valid")
167
+ throw new Error(selection.message || "Select text before copying.");
168
+ this.#rejectCopy(new Error("A newer copy request replaced this request."));
169
+ const requestId = ++this.#nextRequest;
170
+ const { promise, resolve, reject } = Promise.withResolvers();
171
+ const timer = setTimeout(() => this.#rejectCopy(new Error("Timed out resolving selection. Copy again.")), 10000);
172
+ this.#pendingCopy = { requestId, selectionRequestId: selection.requestId, generation: history.generation,
173
+ resolve, reject, timer };
174
+ try {
175
+ this.#send({ type: "copy", requestId, selectionRequestId: selection.requestId, generation: history.generation });
176
+ }
177
+ catch (error) {
178
+ this.#rejectCopy(error);
179
+ }
180
+ return promise;
181
+ }
182
+ #rejectCopy(error) {
183
+ if (!this.#pendingCopy)
184
+ return;
185
+ clearTimeout(this.#pendingCopy.timer);
186
+ this.#pendingCopy.reject(error);
187
+ this.#pendingCopy = undefined;
188
+ }
189
+ disconnect() {
190
+ this.#rejectCopy(new Error("Terminal view disconnected before copy completed."));
191
+ this.#deferredEndpoint = undefined;
192
+ }
193
+ }
194
+ //# sourceMappingURL=history-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-state.js","sourceRoot":"","sources":["../src/history-state.ts"],"names":[],"mappings":"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","sourcesContent":["const unavailable = \"Text history is not available for this terminal view\";\nconst expired = \"Selection expired: its text was changed, evicted, reset, or resized. Select the text again.\";\nconst changedBeforeCopy = \"Selection changed before copy completed. Copy the new selection again.\";\n\n/** Commands contain producer identities; text and ranges are never inferred from painted cells. */\nexport class HistoryState {\n #send: (command: TerminalCommand) => void;\n #change: () => void;\n #history: HistoryMetadata | null = null;\n #revision = 0;\n #nextRequest = 0;\n #selectionRequest = 0;\n #viewportRequest = 0;\n #pendingMode: SelectionMode = \"character\";\n #pendingAction = \"clear\";\n #pendingCopy: PendingCopy | undefined;\n #deferredEndpoint: TerminalPoint | undefined;\n\n constructor(send: (command: TerminalCommand) => void, change: () => void = () => {}) {\n this.#send = send;\n this.#change = change;\n }\n\n get viewport(): HistoryViewport {\n if (!this.#history) return { available: false, following: true, pending: false };\n const { selection, copy, ...viewport } = this.#history;\n return { ...viewport, rowIds: [...viewport.rowIds], available: true, revision: this.#revision,\n pending: viewport.requestId < this.#viewportRequest };\n }\n\n get selection(): HistorySelectionState {\n if (!this.#history) return { status: \"unavailable\", mode: \"character\", ranges: [], text: null, message: unavailable };\n const selection = this.#history.selection;\n if (selection.requestId < this.#selectionRequest || this.#deferredEndpoint) {\n return { requestId: this.#selectionRequest, status: \"pending\", mode: this.#pendingMode,\n canExtend: this.#pendingAction !== \"clear\",\n ranges: this.#pendingAction === \"clear\" ? [] : selection.ranges.map(range => ({ ...range })), text: null,\n message: \"Resolving selection…\" };\n }\n return { ...selection, canExtend: selection.status === \"valid\",\n ranges: selection.ranges.map(range => ({ ...range })), revision: this.#revision,\n message: selection.status === \"invalidated\" ? expired : \"\" };\n }\n\n accept(history: HistoryMetadata | null, revision: number): boolean {\n if (revision <= this.#revision) return false;\n const previousGeneration = this.#history?.generation;\n this.#history = history;\n this.#revision = revision;\n if (!history || (previousGeneration && previousGeneration !== history.generation) ||\n history.selection.status === \"invalidated\") {\n this.endGesture(true);\n this.#rejectCopy(new Error(expired));\n }\n const pending = this.#pendingCopy;\n if (pending && history?.copy?.requestId === pending.requestId) {\n if (history.copy.status !== \"valid\" || history.selection.status !== \"valid\" ||\n history.selection.requestId !== pending.selectionRequestId ||\n history.generation !== pending.generation || this.#selectionRequest > pending.selectionRequestId ||\n history.copy.text !== history.selection.text) {\n this.#rejectCopy(new Error(expired));\n } else {\n clearTimeout(pending.timer);\n this.#pendingCopy = undefined;\n pending.resolve(history.copy.text);\n }\n }\n if (this.#deferredEndpoint && !this.viewport.pending) {\n const point = this.#deferredEndpoint;\n this.#deferredEndpoint = undefined;\n this.extend(point);\n }\n this.#change();\n return true;\n }\n\n #requireHistory(): HistoryMetadata {\n if (!this.#history) throw new Error(unavailable);\n return this.#history;\n }\n\n #pointRowId(point: TerminalPoint): string {\n const history = this.#requireHistory();\n if (!point || !Number.isInteger(point.x) || point.x < 0 || point.x > 1023 ||\n !Number.isInteger(point.y) || point.y < 0 ||\n typeof history.rowIds[point.y] !== \"string\" || !history.rowIds[point.y].length) {\n throw new Error(\"Terminal viewport is not ready for that selection point. Wait for the next frame and try again.\");\n }\n return history.rowIds[point.y];\n }\n\n #selectionChanged(mode = this.selection.mode, action = \"extend\") {\n this.#rejectCopy(new Error(changedBeforeCopy));\n this.#pendingMode = mode;\n this.#pendingAction = action;\n this.#selectionRequest = ++this.#nextRequest;\n return this.#selectionRequest;\n }\n\n begin(point: TerminalPoint, { mode, extend = false }: { mode: SelectionMode; extend?: boolean }): void {\n const rowId = this.#pointRowId(point);\n this.#deferredEndpoint = undefined;\n const requestId = this.#selectionChanged(mode, extend ? \"extend\" : \"start\");\n this.#send({ type: \"selection\", action: extend ? \"extend\" : \"start\", mode, requestId,\n generation: this.#requireHistory().generation, rowId, column: point.x });\n this.#change();\n }\n\n extend(point: TerminalPoint): void {\n this.#pointRowId(point);\n if (this.viewport.pending) {\n this.#rejectCopy(new Error(changedBeforeCopy));\n this.#pendingMode = this.selection.mode;\n this.#pendingAction = \"extend\";\n this.#deferredEndpoint = { ...point };\n this.#change();\n return;\n }\n this.begin(point, { mode: this.selection.mode, extend: true });\n }\n\n scroll(delta: number, endpoint?: TerminalPoint): void {\n this.#requireHistory();\n if (!Number.isSafeInteger(delta) || delta < -2147483648 || delta > 2147483647) throw new RangeError(\"Scroll delta must be a signed 32-bit integer\");\n if (endpoint) this.#pointRowId(endpoint);\n if (!delta && !endpoint) return;\n if (endpoint) this.#deferredEndpoint = undefined;\n else this.#flushDeferredEndpoint();\n const requestId = endpoint ? this.#selectionChanged() : ++this.#nextRequest;\n this.#viewportRequest = requestId;\n this.#send({ type: \"viewport\", requestId, delta,\n ...(endpoint ? { extend: { row: endpoint.y, column: endpoint.x } } : {}) });\n this.#change();\n }\n\n live() {\n this.#requireHistory();\n this.#flushDeferredEndpoint();\n this.#viewportRequest = ++this.#nextRequest;\n this.#send({ type: \"viewport\", live: true, requestId: this.#viewportRequest });\n this.#change();\n }\n\n clear() {\n this.#requireHistory();\n this.#deferredEndpoint = undefined;\n const requestId = this.#selectionChanged(this.selection.mode, \"clear\");\n this.#send({ type: \"selection\", action: \"clear\", requestId });\n this.#change();\n }\n\n endGesture(cancelled = false) {\n if (cancelled && this.#deferredEndpoint) {\n this.#deferredEndpoint = undefined;\n this.#change();\n }\n }\n\n #flushDeferredEndpoint() {\n if (!this.#deferredEndpoint) return;\n const endpoint = this.#deferredEndpoint;\n this.#deferredEndpoint = undefined;\n // Preserve command order if another scroll overtakes the pending presentation.\n this.scroll(0, endpoint);\n }\n cancelCopy(error: unknown): void { this.#rejectCopy(error); }\n\n copy(): Promise<string> {\n const history = this.#requireHistory();\n const selection = this.selection;\n if (selection.status !== \"valid\") throw new Error(selection.message || \"Select text before copying.\");\n this.#rejectCopy(new Error(\"A newer copy request replaced this request.\"));\n const requestId = ++this.#nextRequest;\n const { promise, resolve, reject } = Promise.withResolvers<string>();\n const timer = setTimeout(() => this.#rejectCopy(new Error(\"Timed out resolving selection. Copy again.\")), 10000);\n this.#pendingCopy = { requestId, selectionRequestId: selection.requestId, generation: history.generation,\n resolve, reject, timer };\n try {\n this.#send({ type: \"copy\", requestId, selectionRequestId: selection.requestId, generation: history.generation });\n } catch (error) {\n this.#rejectCopy(error);\n }\n return promise;\n }\n\n #rejectCopy(error: unknown): void {\n if (!this.#pendingCopy) return;\n clearTimeout(this.#pendingCopy.timer);\n this.#pendingCopy.reject(error);\n this.#pendingCopy = undefined;\n }\n\n disconnect() {\n this.#rejectCopy(new Error(\"Terminal view disconnected before copy completed.\"));\n this.#deferredEndpoint = undefined;\n }\n}\nimport type { SelectionMode, TerminalPoint, TerminalSelection, TerminalViewport } from \"./types.js\";\nimport type { HistoryMetadata, TerminalCommand } from \"./wire-types.js\";\n\ntype OmitEach<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;\ntype HistoryViewport = OmitEach<TerminalViewport, \"followTail\" | \"offset\">;\ntype HistorySelectionState = OmitEach<TerminalSelection, \"active\" | \"pending\" | \"copying\" | \"copyError\">;\ninterface PendingCopy {\n requestId: number; selectionRequestId: number; generation: string;\n resolve: (text: string) => void; reject: (error: unknown) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n"]}
@@ -0,0 +1,5 @@
1
+ export { WebTerminal } from "./web-terminal.js";
2
+ export { InputRoute, TerminalAction, defaultInputBindings } from "./input-policy.js";
3
+ export { MIN_FONT_SIZE, MAX_FONT_SIZE } from "./terminal-sizing.js";
4
+ export type * from "./types.js";
5
+ //# sourceMappingURL=index.d.ts.map