@orz/ui-asciinema 0.0.0-stage → 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.
package/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright 2026 the orzui team
4
+ Copyright 2026 the todou team
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -1,3 +1,113 @@
1
- # Temporary Holding Version
1
+ # @orz/ui-asciinema
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [asciinema](https://asciinema.org/) recordings (`.cast` files) played in the page, in the colours
4
+ of `@orz/ui`'s terminal theme slot. The package contributes a renderer to `@orz/ui-content`'s
5
+ registry, so a `.cast` file in a viewer or an attachment plays without importing this package from
6
+ either. The engine is [asciinema-player](https://www.npmjs.com/package/asciinema-player) under a
7
+ `MediaControls`, the control bar from `@orz/ui`.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @orz/ui-asciinema @orz/ui-content @orz/ui react lucide-react
13
+ ```
14
+
15
+ `asciinema-player` is a regular dependency: installing this package is the whole setup.
16
+ `@orz/ui-content` is a peer dependency so that the app and every content package share one registry.
17
+
18
+ ## CSS
19
+
20
+ ```css
21
+ @import "tailwindcss";
22
+ @import "@orz/ui/styles.css";
23
+ @import "@orz/ui-asciinema/styles.css";
24
+ ```
25
+
26
+ Import it after `@orz/ui/styles.css`. It brings asciinema-player's own stylesheet, imported into the
27
+ `components` layer, and the overrides that let the frame own the corners, the play overlay and the
28
+ fullscreen layout. Like `@orz/ui`'s stylesheet, it registers `@source "./dist"` on its own.
29
+
30
+ ## `asciinemaContent`
31
+
32
+ ```tsx
33
+ import { ContentRenderers } from "@orz/ui-content";
34
+ import { asciinemaContent } from "@orz/ui-asciinema";
35
+
36
+ const RENDERERS = [asciinemaContent, markdownContent(), codeContent];
37
+
38
+ <ContentRenderers renderers={RENDERERS}>{app}</ContentRenderers>;
39
+ ```
40
+
41
+ It accepts files named `*.cast` (any case) or typed `application/x-asciicast`, in either placement,
42
+ and never an inline source: a fence stays code. Register it ahead of any renderer of your own that
43
+ would take a `.cast` as text, since the first renderer that accepts wins.
44
+
45
+ A recording plays under a strip holding its `title` (the file name when it has none) and a
46
+ Download link when the source has a `url`. While it loads, the registry's fallback shows; a load
47
+ that fails shows the message and a Retry button. A cast that does not scan — not JSON, an unknown
48
+ version, a malformed event line — goes on to `Next`, the renderers registered after this one, with
49
+ the reason in a muted line under it, so a broken file still offers its download.
50
+
51
+ `CAST_MAX_BYTES` is 16 MiB. A file whose declared `size` is over it is never loaded, and one that
52
+ turns out over it once loaded is not played: both show the fallback link and "Too large to preview
53
+ — download to view." The player parses a whole recording up front, which at the cap already takes
54
+ about a second.
55
+
56
+ ## `AsciinemaPlayer`
57
+
58
+ For a surface that plays a recording outside the registry:
59
+
60
+ ```tsx
61
+ import { AsciinemaPlayer } from "@orz/ui-asciinema";
62
+
63
+ <AsciinemaPlayer cast={text} />
64
+ <AsciinemaPlayer cast={text} header={<MyTitleStrip />} speed={2} className="max-w-3xl" />
65
+ ```
66
+
67
+ | Prop | |
68
+ |---|---|
69
+ | `cast` | The recording's text: asciicast v1, v2 or v3. |
70
+ | `header` | Drawn above the terminal. |
71
+ | `speed` | Initial speed, default 1. The bar offers 0.5×, 1×, 1.5×, 2× and 4×. |
72
+ | `loop` | Restart at the end. Default false; the bar has no toggle for it. |
73
+ | `autoPlay` | Start playing once loaded. Default false. |
74
+ | `useEmbeddedTheme` | Use the palette a cast carries in its header instead of the terminal slot. Default false. |
75
+ | `className` | On the frame. |
76
+
77
+ A cast that does not scan shows the reason in place of the player. Until played, the terminal shows
78
+ the recording's final frame under a play button, and play starts from 0:00. The bar holds play and
79
+ pause, the time, a seek track with a tick per marker (hover for its label, click to jump),
80
+ speed, and fullscreen, which takes the whole frame, bar included. Focused on the terminal, the
81
+ player's own keys work: space, `,` and `.` to step, the arrows to seek, `0`–`9` to jump, `[` and
82
+ `]` between markers; `f` toggles the frame's fullscreen.
83
+
84
+ ## Theme
85
+
86
+ The terminal's foreground, background and 16 ANSI colours come from the terminal slot
87
+ (`useTerminalTheme()`), converted to `#rrggbb` first, since the player reads nothing else: an oklch
88
+ custom theme plays in its own colours. The cursor and selection tokens have no counterpart in the
89
+ player. A palette embedded in a recording is ignored unless `useEmbeddedTheme` is set.
90
+
91
+ asciinema-player reads its palette, speed and fit only when it is created. A new terminal theme, a
92
+ new speed and entering or leaving fullscreen therefore build the player again, at the same position
93
+ and still playing if it was; the terminal can flash for a frame while it does.
94
+
95
+ ## Loading
96
+
97
+ asciinema-player is only reached through `import("asciinema-player")`, from an effect, so a bundler
98
+ splits it (about 90 KB gzip, its VT engine compiled to WASM) into a chunk of its own, fetched the
99
+ first time a page holds a recording, and server rendering never touches it. Until it arrives, a
100
+ placeholder of the terminal's size holds the space and the bar shows the recording's length. An
101
+ import that fails shows the message and a Retry button that imports again.
102
+
103
+ ## Licence
104
+
105
+ This package is BSD-3-Clause. asciinema-player, which it depends on and loads at run time, is
106
+ Apache-2.0.
107
+
108
+ ## Entry points
109
+
110
+ | Import | Contents |
111
+ |---|---|
112
+ | `@orz/ui-asciinema` | `asciinemaContent`, `CAST_MAX_BYTES`, `AsciinemaPlayer`, `scanCast`, `playerThemeVars` |
113
+ | `@orz/ui-asciinema/styles.css` | asciinema-player's stylesheet and the frame's overrides |
@@ -0,0 +1,24 @@
1
+ import { type ReactNode } from "react";
2
+ export type AsciinemaPlayerProps = {
3
+ /** The cast's text. */
4
+ cast: string;
5
+ /** Drawn above the terminal; the content renderer puts the filename and Download here. */
6
+ header?: ReactNode;
7
+ /** Initial speed. Default 1. */
8
+ speed?: number;
9
+ loop?: boolean;
10
+ autoPlay?: boolean;
11
+ /** Keep the palette a cast carries in its header instead of the terminal slot. Default false. */
12
+ useEmbeddedTheme?: boolean;
13
+ className?: string;
14
+ };
15
+ /**
16
+ * An asciinema recording, played by asciinema-player under a control bar of @orz/ui components,
17
+ * in the colours of the terminal theme slot.
18
+ *
19
+ * The player reads its palette once, and can change neither its speed nor its fit afterwards, so
20
+ * a new terminal theme, speed or fullscreen state builds it again, at the same position and still
21
+ * playing if it was.
22
+ */
23
+ export declare function AsciinemaPlayer(props: AsciinemaPlayerProps): import("react").JSX.Element;
24
+ //# sourceMappingURL=asciinema-player.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asciinema-player.d.ts","sourceRoot":"","sources":["../src/asciinema-player.tsx"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,SAAS,EAOf,MAAM,OAAO,CAAC;AAKf,MAAM,MAAM,oBAAoB,GAAG;IACjC,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,0FAA0F;IAC1F,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iGAAiG;IACjG,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAkBF;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAW1D"}
@@ -0,0 +1,277 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from "@orz/ui/components/button";
3
+ import { MediaControls } from "@orz/ui/components/media-controls";
4
+ import { cn } from "@orz/ui/lib/utils";
5
+ import { useTerminalTheme } from "@orz/ui/theme";
6
+ import { Play } from "lucide-react";
7
+ import { useCallback, useEffect, useId, useMemo, useRef, useState, } from "react";
8
+ import { isCastError, scanCast } from "./cast.js";
9
+ import { loadPlayer } from "./load-player.js";
10
+ import { playerThemeVars } from "./player-theme.js";
11
+ /** Keys the player's own handler acts on; after one, the bar reads the position again. */
12
+ const PLAYER_KEYS = /^(?: |,|\.|\[|\]|[0-9]|ArrowLeft|ArrowRight)$/;
13
+ /** A cell's width and height in em, for a placeholder the size the terminal will be. */
14
+ const CELL_WIDTH = 0.6;
15
+ const LINE_HEIGHT = 4 / 3;
16
+ /**
17
+ * An asciinema recording, played by asciinema-player under a control bar of @orz/ui components,
18
+ * in the colours of the terminal theme slot.
19
+ *
20
+ * The player reads its palette once, and can change neither its speed nor its fit afterwards, so
21
+ * a new terminal theme, speed or fullscreen state builds it again, at the same position and still
22
+ * playing if it was.
23
+ */
24
+ export function AsciinemaPlayer(props) {
25
+ const scan = useMemo(() => scanCast(props.cast), [props.cast]);
26
+ if (isCastError(scan)) {
27
+ return (_jsx("p", { className: cn("text-destructive text-sm", props.className), children: scan.error }));
28
+ }
29
+ // A different cast starts over: no position, no poster state, to carry.
30
+ return _jsx(CastPlayer, { ...props, info: scan }, props.cast);
31
+ }
32
+ function CastPlayer({ cast, info, header, speed: initialSpeed = 1, loop = false, autoPlay = false, useEmbeddedTheme = false, className, }) {
33
+ const id = useId();
34
+ const frameRef = useRef(null);
35
+ const hostRef = useRef(null);
36
+ const probeRef = useRef(null);
37
+ const playerRef = useRef(null);
38
+ const resumeRef = useRef(null);
39
+ const { tokens } = useTerminalTheme();
40
+ const vars = useMemo(() => playerThemeVars(tokens), [tokens]);
41
+ const paletteKey = Object.entries(vars)
42
+ .map(([name, value]) => `${name}:${value}`)
43
+ .join(";");
44
+ const [speed, setSpeed] = useState(initialSpeed);
45
+ useEffect(() => setSpeed(initialSpeed), [initialSpeed]);
46
+ const [fullscreen, setFullscreen] = useState(false);
47
+ const [fullscreenEnabled, setFullscreenEnabled] = useState(false);
48
+ const [load, setLoad] = useState("loading");
49
+ const [attempt, setAttempt] = useState(0);
50
+ const [playing, setPlaying] = useState(false);
51
+ const playingRef = useRef(false);
52
+ playingRef.current = playing;
53
+ // Until the first play or seek, the terminal shows the final frame while the
54
+ // bar shows 0:00, where play starts from.
55
+ const [started, setStarted] = useState(autoPlay);
56
+ const startedRef = useRef(autoPlay);
57
+ const markStarted = useCallback(() => {
58
+ startedRef.current = true;
59
+ setStarted(true);
60
+ }, []);
61
+ const [time, setTime] = useState(0);
62
+ const [duration, setDuration] = useState(info.duration);
63
+ const sync = useCallback(() => {
64
+ const player = playerRef.current;
65
+ if (player === null)
66
+ return;
67
+ const reported = player.getDuration();
68
+ if (typeof reported === "number")
69
+ setDuration(reported);
70
+ if (startedRef.current)
71
+ setTime(player.getCurrentTime());
72
+ }, []);
73
+ useEffect(() => {
74
+ // Read only as triggers. The colours reach the player through the <style>
75
+ // below, which is already in the DOM when it reads them; a new attempt is
76
+ // Retry after a failed import.
77
+ void [paletteKey, attempt];
78
+ const host = hostRef.current;
79
+ if (host === null)
80
+ return;
81
+ let live = true;
82
+ let player;
83
+ const resume = resumeRef.current;
84
+ resumeRef.current = null;
85
+ loadPlayer().then(({ create }) => {
86
+ if (!live)
87
+ return;
88
+ const fresh = resume === null || !resume.started;
89
+ const probe = probeRef.current;
90
+ // A v1 cast is one JSON object; the player reads it from text only
91
+ // when it is pretty-printed, and from the object either way.
92
+ const data = info.version === 1 ? JSON.parse(cast) : cast;
93
+ const built = create({ data }, host, {
94
+ controls: false,
95
+ preload: true,
96
+ fit: fullscreen ? "both" : "width",
97
+ // No `auto/` prefix: a palette embedded in the cast is ignored.
98
+ theme: useEmbeddedTheme ? "auto/orz" : "orz",
99
+ speed,
100
+ loop,
101
+ autoPlay: fresh && autoPlay,
102
+ // Past the end rather than at it: the player draws a poster from
103
+ // the events strictly before its time, which would leave the last
104
+ // one out. Play still starts from 0.
105
+ poster: fresh && !autoPlay ? `npt:${info.duration + 1}` : undefined,
106
+ terminalFontFamily: probe === null ? undefined : getComputedStyle(probe).fontFamily,
107
+ });
108
+ player = built;
109
+ playerRef.current = built;
110
+ built.addEventListener("play", () => {
111
+ if (live)
112
+ markStarted();
113
+ });
114
+ built.addEventListener("playing", () => {
115
+ if (!live)
116
+ return;
117
+ setPlaying(true);
118
+ sync();
119
+ });
120
+ for (const stopped of ["pause", "ended"]) {
121
+ built.addEventListener(stopped, () => {
122
+ if (!live)
123
+ return;
124
+ setPlaying(false);
125
+ sync();
126
+ });
127
+ }
128
+ setLoad("ready");
129
+ // Not startAt: the player reads that on the uncapped timeline and
130
+ // shows its frame only once playing, where seek takes the time the
131
+ // player reports and draws it at once.
132
+ if (!fresh) {
133
+ if (resume.time > 0)
134
+ void built.seek(resume.time).then(sync);
135
+ if (resume.playing)
136
+ void built.play();
137
+ }
138
+ if (resume?.focused) {
139
+ built.el
140
+ .querySelector(".ap-term-text")
141
+ ?.focus({ preventScroll: true });
142
+ }
143
+ }, (error) => {
144
+ if (!live)
145
+ return;
146
+ setLoad(error instanceof Error ? error : new Error(String(error)));
147
+ });
148
+ return () => {
149
+ live = false;
150
+ if (player === undefined)
151
+ return;
152
+ resumeRef.current = {
153
+ time: player.getCurrentTime(),
154
+ playing: playingRef.current,
155
+ started: startedRef.current,
156
+ focused: player.el.contains(document.activeElement),
157
+ };
158
+ player.dispose();
159
+ if (playerRef.current === player)
160
+ playerRef.current = null;
161
+ };
162
+ }, [
163
+ cast,
164
+ paletteKey,
165
+ speed,
166
+ fullscreen,
167
+ useEmbeddedTheme,
168
+ attempt,
169
+ loop,
170
+ autoPlay,
171
+ info.duration,
172
+ info.version,
173
+ markStarted,
174
+ sync,
175
+ ]);
176
+ useEffect(() => {
177
+ if (!playing)
178
+ return;
179
+ // The stock control bar's cadence.
180
+ const interval = setInterval(sync, 100);
181
+ return () => clearInterval(interval);
182
+ }, [playing, sync]);
183
+ useEffect(() => {
184
+ setFullscreenEnabled(document.fullscreenEnabled === true);
185
+ const onChange = () => setFullscreen(document.fullscreenElement !== null &&
186
+ document.fullscreenElement === frameRef.current);
187
+ document.addEventListener("fullscreenchange", onChange);
188
+ return () => document.removeEventListener("fullscreenchange", onChange);
189
+ }, []);
190
+ const toggleFullscreen = useCallback(() => {
191
+ const frame = frameRef.current;
192
+ if (frame === null || !document.fullscreenEnabled)
193
+ return;
194
+ // Refused when the call lacks a user gesture, say; the frame just stays as
195
+ // it is.
196
+ const ignore = () => { };
197
+ if (document.fullscreenElement === frame) {
198
+ document.exitFullscreen().catch(ignore);
199
+ }
200
+ else {
201
+ frame.requestFullscreen().catch(ignore);
202
+ }
203
+ }, []);
204
+ // The poster is drawn past the last event, and the player seeks from there
205
+ // incrementally after clearing it: a seek to the end would leave the screen
206
+ // blank. From 0, where the bar says it is, every seek redraws. The player
207
+ // queues its commands, so a seek a key goes on to make runs after this one.
208
+ const leavePoster = (player) => {
209
+ if (startedRef.current)
210
+ return;
211
+ markStarted();
212
+ void player.seek(0);
213
+ };
214
+ const toggle = () => {
215
+ const player = playerRef.current;
216
+ if (player === null)
217
+ return;
218
+ markStarted();
219
+ if (playing)
220
+ void player.pause();
221
+ else
222
+ void player.play();
223
+ };
224
+ const seek = (to) => {
225
+ const player = playerRef.current;
226
+ if (player === null)
227
+ return;
228
+ leavePoster(player);
229
+ setTime(to);
230
+ // Past the end for the end: the player clamps that to its own duration,
231
+ // where a scanned one a float's breadth short would leave out the last
232
+ // frame.
233
+ void player.seek(to >= duration ? duration + 1 : to).then(sync);
234
+ };
235
+ const onKeyDownCapture = (event) => {
236
+ if (event.altKey || event.metaKey || event.ctrlKey)
237
+ return;
238
+ // Caught on the way down, so the player's own handler never sees them: its
239
+ // `f` would fullscreen the terminal alone, without this bar, and `?`, `k`
240
+ // and `m` open overlays this frame does not use.
241
+ if (event.key === "f") {
242
+ event.preventDefault();
243
+ event.stopPropagation();
244
+ toggleFullscreen();
245
+ return;
246
+ }
247
+ if (event.key === "?" || event.key === "k" || event.key === "m") {
248
+ event.stopPropagation();
249
+ return;
250
+ }
251
+ const player = playerRef.current;
252
+ if (PLAYER_KEYS.test(event.key) &&
253
+ player !== null &&
254
+ event.target instanceof Node &&
255
+ player.el.contains(event.target)) {
256
+ leavePoster(player);
257
+ // Its seek and step resolve after the key; while paused nothing else
258
+ // would read the new position.
259
+ setTimeout(sync, 100);
260
+ }
261
+ };
262
+ const css = `[data-orz-cast="${id}"] .ap-player{${Object.entries(vars)
263
+ .map(([name, value]) => `${name}:${value};`)
264
+ .join("")}}`;
265
+ const ready = load === "ready";
266
+ return (_jsxs("div", { ref: frameRef, "data-orz-cast": id, className: cn("overflow-hidden rounded-lg border bg-terminal-background [&:fullscreen]:rounded-none [&:fullscreen]:border-0", className), onKeyDownCapture: onKeyDownCapture, children: [_jsx("style", { children: css }), _jsx("span", { ref: probeRef, "aria-hidden": true, className: "hidden font-mono" }), header, _jsxs("div", { "data-slot": "cast-screen", className: "relative", children: [!ready && (_jsx("div", { className: "grid w-full place-items-center text-terminal-foreground text-sm", style: {
267
+ aspectRatio: `${info.cols * CELL_WIDTH} / ${info.rows * LINE_HEIGHT}`,
268
+ }, children: load instanceof Error && (_jsxs("div", { className: "grid justify-items-center gap-2 p-4 text-center", children: [_jsxs("span", { children: ["Could not load the player: ", load.message] }), _jsx(Button, { type: "button", size: "xs", variant: "outline", onClick: () => {
269
+ setLoad("loading");
270
+ setAttempt((n) => n + 1);
271
+ }, children: "Retry" })] })) })), _jsx("div", { ref: hostRef, "data-slot": "cast-host" }), ready && !started && (_jsx("button", { type: "button", "aria-label": "Play recording",
272
+ // The bar's play button is the keyboard's way in.
273
+ tabIndex: -1, className: "absolute inset-0 grid cursor-pointer place-items-center bg-terminal-background/40 outline-none", onClick: toggle, children: _jsx("span", { className: "grid size-14 place-items-center rounded-full bg-terminal-foreground/85 text-terminal-background shadow-md", children: _jsx(Play, { className: "size-6 translate-x-0.5 fill-current" }) }) }))] }), _jsx(MediaControls, { playing: playing, time: time, duration: duration, markers: info.markers, speed: speed, fullscreen: fullscreenEnabled
274
+ ? { active: fullscreen, onToggle: toggleFullscreen }
275
+ : undefined, disabled: !ready, portal: fullscreen ? frameRef.current : undefined, onToggle: toggle, onSeek: seek, onSpeed: setSpeed })] }));
276
+ }
277
+ //# sourceMappingURL=asciinema-player.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asciinema-player.js","sourceRoot":"","sources":["../src/asciinema-player.tsx"],"names":[],"mappings":"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"}
package/dist/cast.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ /** What the control bar and the content renderer need to know about a cast, read without playing it. */
2
+ export type CastInfo = {
3
+ version: 1 | 2 | 3;
4
+ cols: number;
5
+ rows: number;
6
+ title?: string;
7
+ /** Seconds, after the idle-time cap: the timeline the player shows. */
8
+ duration: number;
9
+ markers: {
10
+ time: number;
11
+ label: string;
12
+ }[];
13
+ };
14
+ export type CastError = {
15
+ error: string;
16
+ };
17
+ /**
18
+ * Reads a cast's size, title, duration and markers, without parsing its output. asciinema-player
19
+ * never reports markers and swallows its own load errors, so this is also where a cast that would
20
+ * not play is caught. An error names the line it stopped at.
21
+ */
22
+ export declare function scanCast(text: string): CastInfo | CastError;
23
+ export declare function isCastError(scan: CastInfo | CastError): scan is CastError;
24
+ //# sourceMappingURL=cast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cast.d.ts","sourceRoot":"","sources":["../src/cast.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AA6H1C;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CA+B3D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,IAAI,IAAI,SAAS,CAEzE"}
package/dist/cast.js ADDED
@@ -0,0 +1,156 @@
1
+ function isRecord(value) {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3
+ }
4
+ function positiveInt(value) {
5
+ return typeof value === "number" && Number.isInteger(value) && value > 0
6
+ ? value
7
+ : undefined;
8
+ }
9
+ function sizeOf(cols, rows) {
10
+ const c = positiveInt(cols);
11
+ const r = positiveInt(rows);
12
+ return c === undefined || r === undefined ? undefined : { cols: c, rows: r };
13
+ }
14
+ function titleOf(header) {
15
+ return typeof header.title === "string" && header.title !== ""
16
+ ? { title: header.title }
17
+ : {};
18
+ }
19
+ /**
20
+ * Applies the idle-time cap the way asciinema-player does: every gap between consecutive events
21
+ * — whatever their code, since the player keeps them all in its timeline — and the gap before
22
+ * the first one, is limited to `limit` seconds.
23
+ */
24
+ function timeline(limit) {
25
+ let previous = 0;
26
+ let shift = 0;
27
+ return (time) => {
28
+ const gap = time - previous;
29
+ previous = time;
30
+ if (limit !== undefined && gap > limit)
31
+ shift += gap - limit;
32
+ return time - shift;
33
+ };
34
+ }
35
+ function idleLimit(header) {
36
+ // Coerced the way the player multiplies it into milliseconds: null caps every
37
+ // gap to 0 and "2" to 2 s there, and anything NaN caps none.
38
+ if (header.idle_time_limit === undefined)
39
+ return undefined;
40
+ const limit = Number(header.idle_time_limit);
41
+ return Number.isNaN(limit) ? undefined : limit;
42
+ }
43
+ function scanV1(header) {
44
+ const size = sizeOf(header.width, header.height);
45
+ if (size === undefined)
46
+ return { error: "Line 1: missing or invalid size" };
47
+ if (!Array.isArray(header.stdout)) {
48
+ return { error: "Line 1: missing stdout" };
49
+ }
50
+ // The player ignores a v1 header's idle_time_limit, so the cap is not applied here either.
51
+ let time = 0;
52
+ for (const [index, frame] of header.stdout.entries()) {
53
+ if (!Array.isArray(frame) || typeof frame[0] !== "number") {
54
+ return {
55
+ error: `Line 1: stdout frame ${index + 1} is not [delay, data]`,
56
+ };
57
+ }
58
+ time += frame[0];
59
+ }
60
+ if (header.stdout.length === 0)
61
+ return { error: "No events" };
62
+ return {
63
+ version: 1,
64
+ ...size,
65
+ ...titleOf(header),
66
+ duration: time,
67
+ markers: [],
68
+ };
69
+ }
70
+ function scanLines(version, header, lines) {
71
+ const term = isRecord(header.term) ? header.term : {};
72
+ const size = version === 2
73
+ ? sizeOf(header.width, header.height)
74
+ : sizeOf(term.cols, term.rows);
75
+ if (size === undefined)
76
+ return { error: "Line 1: missing or invalid size" };
77
+ const capped = timeline(idleLimit(header));
78
+ const markers = [];
79
+ let absolute = 0;
80
+ let duration;
81
+ for (let i = 1; i < lines.length; i++) {
82
+ const line = (lines[i] ?? "").trim();
83
+ if (line === "" || (version === 3 && line.startsWith("#")))
84
+ continue;
85
+ let event;
86
+ try {
87
+ event = JSON.parse(line);
88
+ }
89
+ catch {
90
+ event = undefined;
91
+ }
92
+ if (!Array.isArray(event) ||
93
+ typeof event[0] !== "number" ||
94
+ typeof event[1] !== "string") {
95
+ return { error: `Line ${i + 1}: not an event` };
96
+ }
97
+ // v2 times are absolute, v3 times are the interval since the previous event.
98
+ absolute = version === 2 ? event[0] : absolute + event[0];
99
+ const time = capped(absolute);
100
+ duration = time;
101
+ if (event[1] === "m") {
102
+ markers.push({
103
+ time,
104
+ label: typeof event[2] === "string" ? event[2] : "",
105
+ });
106
+ }
107
+ }
108
+ if (duration === undefined)
109
+ return { error: "No events" };
110
+ return { version, ...size, ...titleOf(header), duration, markers };
111
+ }
112
+ /**
113
+ * Reads a cast's size, title, duration and markers, without parsing its output. asciinema-player
114
+ * never reports markers and swallows its own load errors, so this is also where a cast that would
115
+ * not play is caught. An error names the line it stopped at.
116
+ */
117
+ export function scanCast(text) {
118
+ if (text.trim() === "")
119
+ return { error: "Empty recording" };
120
+ const lines = text.split("\n");
121
+ const first = (lines[0] ?? "").trim();
122
+ let header;
123
+ try {
124
+ header = JSON.parse(first);
125
+ }
126
+ catch {
127
+ // v1 is one JSON object, usually pretty-printed over many lines.
128
+ try {
129
+ header = JSON.parse(text);
130
+ }
131
+ catch {
132
+ return { error: "Line 1: not JSON" };
133
+ }
134
+ if (!isRecord(header) || header.version !== 1) {
135
+ return { error: "Line 1: not an asciicast header" };
136
+ }
137
+ return scanV1(header);
138
+ }
139
+ if (!isRecord(header))
140
+ return { error: "Line 1: not an asciicast header" };
141
+ switch (header.version) {
142
+ case 1:
143
+ return scanV1(header);
144
+ case 2:
145
+ case 3:
146
+ return scanLines(header.version, header, lines);
147
+ default:
148
+ return {
149
+ error: `Line 1: unsupported asciicast version ${JSON.stringify(header.version)}`,
150
+ };
151
+ }
152
+ }
153
+ export function isCastError(scan) {
154
+ return "error" in scan;
155
+ }
156
+ //# sourceMappingURL=cast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cast.js","sourceRoot":"","sources":["../src/cast.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { type ContentRenderer } from "@orz/ui-content";
2
+ /**
3
+ * Above this a cast is offered as a download instead: the player parses the whole recording up
4
+ * front, which at 16 MiB already takes about a second.
5
+ */
6
+ export declare const CAST_MAX_BYTES: number;
7
+ /**
8
+ * The registry entry for asciinema recordings: a `.cast` file, or one typed
9
+ * `application/x-asciicast`, in either placement. Register it ahead of any renderer that would
10
+ * take a `.cast` as text.
11
+ */
12
+ export declare const asciinemaContent: ContentRenderer;
13
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,eAAe,EAGrB,MAAM,iBAAiB,CAAC;AAMzB;;;GAGG;AACH,eAAO,MAAM,cAAc,QAAmB,CAAC;AAsH/C;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,eAK9B,CAAC"}
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from "@orz/ui/components/button";
3
+ import { ContentFallback, useSourceText, } from "@orz/ui-content";
4
+ import { Download } from "lucide-react";
5
+ import { useMemo } from "react";
6
+ import { AsciinemaPlayer } from "./asciinema-player.js";
7
+ import { isCastError, scanCast } from "./cast.js";
8
+ /**
9
+ * Above this a cast is offered as a download instead: the player parses the whole recording up
10
+ * front, which at 16 MiB already takes about a second.
11
+ */
12
+ export const CAST_MAX_BYTES = 16 * 1024 * 1024;
13
+ function isCast(source) {
14
+ if (source.kind === "inline")
15
+ return false;
16
+ return (/\.cast$/i.test(source.name) ||
17
+ source.mime?.toLowerCase() === "application/x-asciicast");
18
+ }
19
+ function TooLargeCast({ source, placement, className }) {
20
+ return (_jsxs("div", { className: className, children: [_jsx(ContentFallback, { source: source, placement: placement }), " ", _jsx("span", { children: "Too large to preview \u2014 download to view." })] }));
21
+ }
22
+ function CastHeader({ source, title }) {
23
+ return (_jsxs("div", { className: "flex items-center gap-2 border-b bg-card py-1 pr-1 pl-3 text-card-foreground text-xs", children: [_jsx("span", { className: "min-w-0 flex-1 truncate", title: source.name, children: title ?? source.name }), source.url !== undefined && (_jsx(Button, { asChild: true, size: "xs", variant: "ghost", children: _jsxs("a", { href: source.url, download: source.name, children: [_jsx(Download, {}), "Download"] }) }))] }));
24
+ }
25
+ function LoadedCast({ source, placement, className, Next }) {
26
+ const text = useSourceText(source);
27
+ const scan = useMemo(() => text.status === "ready" && text.text.length <= CAST_MAX_BYTES
28
+ ? scanCast(text.text)
29
+ : undefined, [text.status, text.text]);
30
+ if (text.status === "loading") {
31
+ return (_jsx(ContentFallback, { source: source, placement: placement, className: className }));
32
+ }
33
+ if (text.status === "error") {
34
+ return (_jsxs("div", { className: className, children: [_jsx(ContentFallback, { source: source, placement: placement }), " ", _jsx("span", { children: text.error.message }), " ", _jsx(Button, { size: "xs", variant: "outline", onClick: text.retry, children: "Retry" })] }));
35
+ }
36
+ // UTF-16 units, not bytes; a cast is JSON-escaped and almost all ASCII.
37
+ if (text.text.length > CAST_MAX_BYTES) {
38
+ return (_jsx(TooLargeCast, { source: source, placement: placement, className: className }));
39
+ }
40
+ if (scan === undefined || isCastError(scan)) {
41
+ // A broken recording still offers what the renderers after this one
42
+ // make of the file: a download link, at least.
43
+ return (_jsxs("div", { className: className, children: [_jsx(Next, { source: source, placement: placement }), scan !== undefined && (_jsx("p", { className: "text-muted-foreground text-xs", children: scan.error }))] }));
44
+ }
45
+ return (_jsx(AsciinemaPlayer, { cast: text.text, header: _jsx(CastHeader, { source: source, title: scan.title }), className: className }));
46
+ }
47
+ function Cast(props) {
48
+ const { source, placement, className } = props;
49
+ if (source.kind !== "file")
50
+ return null;
51
+ if (source.size !== undefined && source.size > CAST_MAX_BYTES) {
52
+ return (_jsx(TooLargeCast, { source: source, placement: placement, className: className }));
53
+ }
54
+ return _jsx(LoadedCast, { ...props, source: source });
55
+ }
56
+ /**
57
+ * The registry entry for asciinema recordings: a `.cast` file, or one typed
58
+ * `application/x-asciicast`, in either placement. Register it ahead of any renderer that would
59
+ * take a `.cast` as text.
60
+ */
61
+ export const asciinemaContent = {
62
+ name: "asciinema",
63
+ accepts: isCast,
64
+ display: "block",
65
+ Component: Cast,
66
+ };
67
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { AsciinemaPlayer, type AsciinemaPlayerProps, } from "./asciinema-player.tsx";
2
+ export { type CastError, type CastInfo, scanCast } from "./cast.ts";
3
+ export { asciinemaContent, CAST_MAX_BYTES } from "./content.tsx";
4
+ export { playerThemeVars } from "./player-theme.ts";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { AsciinemaPlayer, } from "./asciinema-player.js";
2
+ export { scanCast } from "./cast.js";
3
+ export { asciinemaContent, CAST_MAX_BYTES } from "./content.js";
4
+ export { playerThemeVars } from "./player-theme.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,GAEhB,MAAM,uBAAwB,CAAC;AAChC,OAAO,EAAiC,QAAQ,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAe,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export type PlayerModule = typeof import("asciinema-player");
2
+ export declare function loadPlayer(): Promise<PlayerModule>;
3
+ //# sourceMappingURL=load-player.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-player.d.ts","sourceRoot":"","sources":["../src/load-player.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,cAAc,kBAAkB,CAAC,CAAC;AAO7D,wBAAgB,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAQlD"}
@@ -0,0 +1,14 @@
1
+ // The player needs canvas, requestAnimationFrame and ResizeObserver at import
2
+ // time and weighs ~90 KB gzip; it is only ever reached through this dynamic
3
+ // import, from an effect, so neither SSR nor a page without a cast loads it.
4
+ let loaded;
5
+ export function loadPlayer() {
6
+ loaded ??= import("asciinema-player").catch((error) => {
7
+ // A chunk that failed once (a dropped connection) would stay rejected in
8
+ // the memo, and Retry would get the same rejection back.
9
+ loaded = undefined;
10
+ throw error;
11
+ });
12
+ return loaded;
13
+ }
14
+ //# sourceMappingURL=load-player.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-player.js","sourceRoot":"","sources":["../src/load-player.ts"],"names":[],"mappings":"AAEA,8EAA8E;AAC9E,4EAA4E;AAC5E,6EAA6E;AAC7E,IAAI,MAAyC,CAAC;AAE9C,MAAM,UAAU,UAAU;IACxB,MAAM,KAAK,MAAM,CAAC,kBAAkB,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QAC7D,yEAAyE;QACzE,yDAAyD;QACzD,MAAM,GAAG,SAAS,CAAC;QACnB,MAAM,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { TerminalTokens } from "@orz/ui/theme";
2
+ /** The player's `--term-color-0`…`15`: normal 0–7, then bright 8–15, as `xtermTheme` orders them. */
3
+ export declare const ANSI_ORDER: readonly ["ansi-black", "ansi-red", "ansi-green", "ansi-yellow", "ansi-blue", "ansi-magenta", "ansi-cyan", "ansi-white", "ansi-bright-black", "ansi-bright-red", "ansi-bright-green", "ansi-bright-yellow", "ansi-bright-blue", "ansi-bright-magenta", "ansi-bright-cyan", "ansi-bright-white"];
4
+ /**
5
+ * Terminal tokens as asciinema-player's custom properties, each `#rrggbb`. A token that does not
6
+ * parse is left out, so the player keeps its own default for that colour. The cursor and
7
+ * selection tokens have no counterpart in the player.
8
+ */
9
+ export declare function playerThemeVars(tokens: TerminalTokens): Record<string, string>;
10
+ //# sourceMappingURL=player-theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"player-theme.d.ts","sourceRoot":"","sources":["../src/player-theme.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqB,cAAc,EAAE,MAAM,eAAe,CAAC;AAEvE,qGAAqG;AACrG,eAAO,MAAM,UAAU,iSAiB0B,CAAC;AAWlD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,cAAc,GACrB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAYxB"}
@@ -0,0 +1,48 @@
1
+ import { formatColor, parseColor } from "@orz/ui/lib/color";
2
+ /** The player's `--term-color-0`…`15`: normal 0–7, then bright 8–15, as `xtermTheme` orders them. */
3
+ export const ANSI_ORDER = [
4
+ "ansi-black",
5
+ "ansi-red",
6
+ "ansi-green",
7
+ "ansi-yellow",
8
+ "ansi-blue",
9
+ "ansi-magenta",
10
+ "ansi-cyan",
11
+ "ansi-white",
12
+ "ansi-bright-black",
13
+ "ansi-bright-red",
14
+ "ansi-bright-green",
15
+ "ansi-bright-yellow",
16
+ "ansi-bright-blue",
17
+ "ansi-bright-magenta",
18
+ "ansi-bright-cyan",
19
+ "ansi-bright-white",
20
+ ];
21
+ // The player accepts only #rgb / #rrggbb and silently falls back on anything
22
+ // else, so an oklch custom theme would come out in its default colours.
23
+ function hex(token) {
24
+ const parsed = parseColor(token);
25
+ return parsed === null
26
+ ? undefined
27
+ : formatColor(parsed.color, "hex", { alpha: false });
28
+ }
29
+ /**
30
+ * Terminal tokens as asciinema-player's custom properties, each `#rrggbb`. A token that does not
31
+ * parse is left out, so the player keeps its own default for that colour. The cursor and
32
+ * selection tokens have no counterpart in the player.
33
+ */
34
+ export function playerThemeVars(tokens) {
35
+ const vars = {};
36
+ const put = (name, token) => {
37
+ const value = hex(token);
38
+ if (value !== undefined)
39
+ vars[name] = value;
40
+ };
41
+ put("--term-color-foreground", tokens["terminal-foreground"]);
42
+ put("--term-color-background", tokens["terminal-background"]);
43
+ ANSI_ORDER.forEach((name, index) => {
44
+ put(`--term-color-${index}`, tokens[name]);
45
+ });
46
+ return vars;
47
+ }
48
+ //# sourceMappingURL=player-theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"player-theme.js","sourceRoot":"","sources":["../src/player-theme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG5D,qGAAqG;AACrG,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,YAAY;IACZ,UAAU;IACV,YAAY;IACZ,aAAa;IACb,WAAW;IACX,cAAc;IACd,WAAW;IACX,YAAY;IACZ,mBAAmB;IACnB,iBAAiB;IACjB,mBAAmB;IACnB,oBAAoB;IACpB,kBAAkB;IAClB,qBAAqB;IACrB,kBAAkB;IAClB,mBAAmB;CAC4B,CAAC;AAElD,6EAA6E;AAC7E,wEAAwE;AACxE,SAAS,GAAG,CAAC,KAAa;IACxB,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,MAAM,KAAK,IAAI;QACpB,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAsB;IAEtB,MAAM,IAAI,GAA2B,EAAE,CAAC;IACxC,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,KAAa,EAAE,EAAE;QAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IAC9C,CAAC,CAAC;IACF,GAAG,CAAC,yBAAyB,EAAE,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC;IAC9D,GAAG,CAAC,yBAAyB,EAAE,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC;IAC9D,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACjC,GAAG,CAAC,gBAAgB,KAAK,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,58 @@
1
1
  {
2
2
  "name": "@orz/ui-asciinema",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "asciinema recordings played in the active @orz/ui terminal theme, contributed to the @orz/ui-content registry",
5
+ "license": "BSD-3-Clause",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/orzFly/ui.git",
9
+ "directory": "packages/ui-asciinema"
10
+ },
11
+ "type": "module",
12
+ "sideEffects": [
13
+ "**/*.css"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "@orz/source": "./src/index.ts",
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./styles.css": "./styles.css"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "styles.css"
26
+ ],
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "peerDependencies": {
31
+ "lucide-react": "^1",
32
+ "react": "^19",
33
+ "@orz/ui-content": "^0.1.0",
34
+ "@orz/ui": "^0.1.0"
35
+ },
36
+ "dependencies": {
37
+ "asciinema-player": "^3.17.0"
38
+ },
39
+ "devDependencies": {
40
+ "@testing-library/react": "^16.3.2",
41
+ "@types/react": "^19.2.18",
42
+ "@types/react-dom": "^19.2.4",
43
+ "happy-dom": "^21.0.3",
44
+ "lucide-react": "^1.31.0",
45
+ "radix-ui": "^1.6.7",
46
+ "react": "^19.2.8",
47
+ "react-dom": "^19.2.8",
48
+ "vitest": "^5.0.5",
49
+ "@orz/ui": "0.1.0",
50
+ "@orz/themes": "0.1.0",
51
+ "@orz/ui-content": "0.1.0"
52
+ },
53
+ "scripts": {
54
+ "build": "tsc -p tsconfig.build.json",
55
+ "typecheck": "tsc --noEmit",
56
+ "test": "vitest run"
57
+ }
6
58
  }
package/styles.css ADDED
@@ -0,0 +1,36 @@
1
+ /* Into the components layer: unlayered, the player's rules would beat every
2
+ override below and every utility, whatever their specificity. */
3
+ @import "asciinema-player/dist/bundle/asciinema-player.css" layer(components);
4
+
5
+ /* Tailwind resolves an imported file's @source against that file, so a
6
+ consumer importing this stylesheet scans the classes in the package's
7
+ shipped JS without writing an @source of its own. */
8
+ @source "./dist";
9
+
10
+ @layer components {
11
+ /* The frame draws the corners and the play overlay. */
12
+ [data-orz-cast] div.ap-wrapper div.ap-player {
13
+ border-radius: 0;
14
+ max-width: none;
15
+ }
16
+ [data-orz-cast] .ap-player .ap-overlay-start {
17
+ display: none;
18
+ }
19
+
20
+ /* Fullscreen keeps the bar: the terminal takes what is left and the player
21
+ fits it both ways. */
22
+ [data-orz-cast]:fullscreen {
23
+ display: flex;
24
+ flex-direction: column;
25
+ }
26
+ [data-orz-cast]:fullscreen > [data-slot="cast-screen"] {
27
+ flex: 1;
28
+ min-height: 0;
29
+ }
30
+ [data-orz-cast]:fullscreen [data-slot="cast-host"] {
31
+ height: 100%;
32
+ }
33
+ [data-orz-cast]:fullscreen div.ap-wrapper {
34
+ align-items: center;
35
+ }
36
+ }