@dkkoval/tui-preview 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tui-preview contributors
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,78 @@
1
+ # tui-preview
2
+
3
+ Render `wasm32-wasi` terminal apps inside React with a clean, size-aware API.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install tui-preview
9
+ ```
10
+
11
+ ## Modern API (v1)
12
+
13
+ ```tsx
14
+ import { TuiPreview } from "tui-preview";
15
+
16
+ function Demo() {
17
+ return (
18
+ <TuiPreview
19
+ wasm={new URL("./gradient.wasm", import.meta.url)}
20
+ argv={({ cols, rows }) => [
21
+ "Hello!",
22
+ String(cols),
23
+ String(Math.max(1, rows - 1)),
24
+ "--gradient",
25
+ "diagonal",
26
+ "--no-border",
27
+ ]}
28
+ fit="container"
29
+ terminal={{
30
+ fontSize: 14,
31
+ fontFamily: "monospace",
32
+ theme: {
33
+ background: "#1a1b26",
34
+ foreground: "#a9b1d6",
35
+ },
36
+ }}
37
+ interactive
38
+ style={{ width: "100%", height: 400 }}
39
+ />
40
+ );
41
+ }
42
+ ```
43
+
44
+ ## API
45
+
46
+ - `wasm: string | URL`
47
+ - WASM entrypoint compiled for `wasm32-wasi`.
48
+ - `argv?: string[] | ((size) => string[])`
49
+ - CLI args (without argv[0]).
50
+ - For `fit="container"`, size is the fitted terminal size.
51
+ - `fit?: "container" | "none"` (default: `"container"`)
52
+ - `"container"`: auto-size from container.
53
+ - `"none"`: fixed terminal size from `size`.
54
+ - `size?: { cols: number; rows: number }`
55
+ - Required in practice for fixed mode; fallback/initial for container mode.
56
+ - `terminal?: { fontSize, fontFamily, theme, cursorBlink, convertEol }`
57
+ - `interactive?: boolean` (default: `true`)
58
+ - `env?: Record<string, string>`
59
+ - `onExit?: (code: number) => void`
60
+ - `onError?: (error: unknown) => void`
61
+ - `onStatusChange?: ("loading" | "running" | "exited" | "error") => void`
62
+
63
+ ## Legacy Compatibility
64
+
65
+ Legacy props still work:
66
+
67
+ - `app`, `args`
68
+ - `cols`, `rows`
69
+ - `fontSize`, `fontFamily`, `theme`
70
+
71
+ They are translated internally to the modern API and emit a one-time deprecation warning.
72
+
73
+ ## Notes
74
+
75
+ - Package exports:
76
+ - `tui-preview` (React component + public types)
77
+ - `tui-preview/core` (advanced internals)
78
+ - `ghostty-web` is pinned with semver (`^0.4.0`) for predictable behavior.
@@ -0,0 +1,2 @@
1
+ import type { TuiPreviewProps } from "./types.js";
2
+ export declare function TuiPreview(props: TuiPreviewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,257 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { ansiToHtml } from "./core/ansi.js";
4
+ import { loadGhostty } from "./core/ghostty.js";
5
+ import { resolveTuiPreviewProps, warnLegacyPropsOnce } from "./core/normalize.js";
6
+ import { WasiBridge, instantiateApp } from "./core/wasi.js";
7
+ export function TuiPreview(props) {
8
+ const resolved = useMemo(() => resolveTuiPreviewProps(props), [props]);
9
+ const wrapperRef = useRef(null);
10
+ const containerRef = useRef(null);
11
+ const termRef = useRef(null);
12
+ const [status, setStatus] = useState("loading");
13
+ const [errorMsg, setErrorMsg] = useState("");
14
+ const cellSizeRef = useRef(null);
15
+ const [termSize, setTermSize] = useState(resolved.size);
16
+ const [staticHtml, setStaticHtml] = useState(null);
17
+ useEffect(() => {
18
+ warnLegacyPropsOnce(resolved.usedLegacyProps);
19
+ }, [resolved.usedLegacyProps]);
20
+ // ── Terminal mode effects ────────────────────────────────────────────────
21
+ useEffect(() => {
22
+ if (resolved.mode !== "terminal")
23
+ return;
24
+ setTermSize(resolved.size);
25
+ }, [resolved.mode, resolved.fit, resolved.size.cols, resolved.size.rows]);
26
+ useEffect(() => {
27
+ if (resolved.mode !== "terminal")
28
+ return;
29
+ if (resolved.fit !== "container")
30
+ return;
31
+ if (!wrapperRef.current)
32
+ return;
33
+ const observer = new ResizeObserver(([entry]) => {
34
+ const { width, height } = entry.contentRect;
35
+ if (width > 0 && height > 0) {
36
+ const cellW = cellSizeRef.current?.w ?? resolved.terminal.fontSize * 0.6;
37
+ const cellH = cellSizeRef.current?.h ?? resolved.terminal.fontSize * 1.2;
38
+ setTermSize({
39
+ cols: Math.max(1, Math.floor(width / cellW)),
40
+ rows: Math.max(1, Math.floor(height / cellH)),
41
+ });
42
+ }
43
+ });
44
+ observer.observe(wrapperRef.current);
45
+ return () => observer.disconnect();
46
+ }, [resolved.mode, resolved.fit, resolved.terminal.fontSize]);
47
+ useEffect(() => {
48
+ if (resolved.mode !== "terminal")
49
+ return;
50
+ if (!termSize || !containerRef.current)
51
+ return;
52
+ let cancelled = false;
53
+ const container = containerRef.current;
54
+ const activeSize = termSize;
55
+ const setStatusAndNotify = (next) => {
56
+ setStatus(next);
57
+ resolved.onStatusChange?.(next);
58
+ };
59
+ const setError = (err) => {
60
+ setStatusAndNotify("error");
61
+ setErrorMsg(err instanceof Error ? err.message : String(err));
62
+ resolved.onError?.(err);
63
+ };
64
+ setStatusAndNotify("loading");
65
+ setErrorMsg("");
66
+ async function setup() {
67
+ try {
68
+ const ghostty = await loadGhostty();
69
+ if (cancelled)
70
+ return;
71
+ container.innerHTML = "";
72
+ const term = new ghostty.Terminal({
73
+ cols: activeSize.cols,
74
+ rows: activeSize.rows,
75
+ fontSize: resolved.terminal.fontSize,
76
+ fontFamily: resolved.terminal.fontFamily,
77
+ theme: resolved.terminal.theme,
78
+ disableStdin: !resolved.interactive,
79
+ cursorBlink: resolved.terminal.cursorBlink,
80
+ convertEol: resolved.terminal.convertEol,
81
+ });
82
+ termRef.current = term;
83
+ term.open(container);
84
+ let appCols = term.cols;
85
+ let appRows = term.rows;
86
+ if (resolved.fit === "container") {
87
+ const fitAddon = new ghostty.FitAddon();
88
+ term.loadAddon(fitAddon);
89
+ fitAddon.fit();
90
+ appCols = term.cols;
91
+ appRows = term.rows;
92
+ if (wrapperRef.current && appCols > 0 && appRows > 0) {
93
+ cellSizeRef.current = {
94
+ w: wrapperRef.current.clientWidth / appCols,
95
+ h: wrapperRef.current.clientHeight / appRows,
96
+ };
97
+ }
98
+ }
99
+ const resolvedArgs = resolved.resolveArgv({ cols: appCols, rows: appRows });
100
+ const decoder = new TextDecoder();
101
+ const bridge = new WasiBridge({
102
+ args: [resolved.wasm.toString(), ...resolvedArgs],
103
+ env: resolved.env,
104
+ stdout: (data) => term.write(decoder.decode(data)),
105
+ stderr: (data) => term.write(decoder.decode(data)),
106
+ onExit: (code) => {
107
+ if (!cancelled) {
108
+ setStatusAndNotify("exited");
109
+ resolved.onExit?.(code);
110
+ }
111
+ },
112
+ });
113
+ if (resolved.interactive) {
114
+ term.onData((data) => bridge.pushInput(data));
115
+ }
116
+ const wasmApp = await instantiateApp(resolved.wasm, bridge);
117
+ if (cancelled)
118
+ return;
119
+ setStatusAndNotify("running");
120
+ queueMicrotask(() => {
121
+ if (cancelled)
122
+ return;
123
+ void wasmApp.run().catch((runError) => {
124
+ if (!cancelled) {
125
+ setError(runError);
126
+ }
127
+ });
128
+ });
129
+ }
130
+ catch (e) {
131
+ if (!cancelled) {
132
+ setError(e);
133
+ }
134
+ }
135
+ }
136
+ setup();
137
+ return () => {
138
+ cancelled = true;
139
+ termRef.current?.dispose();
140
+ termRef.current = null;
141
+ };
142
+ }, [
143
+ resolved.mode,
144
+ termSize,
145
+ resolved.wasm,
146
+ resolved.resolveArgv,
147
+ resolved.env,
148
+ resolved.fit,
149
+ resolved.interactive,
150
+ resolved.onExit,
151
+ resolved.onError,
152
+ resolved.onStatusChange,
153
+ resolved.terminal.fontSize,
154
+ resolved.terminal.fontFamily,
155
+ resolved.terminal.theme,
156
+ resolved.terminal.cursorBlink,
157
+ resolved.terminal.convertEol,
158
+ ]);
159
+ // ── Static mode effect ───────────────────────────────────────────────────
160
+ useEffect(() => {
161
+ if (resolved.mode !== "static")
162
+ return;
163
+ let cancelled = false;
164
+ const setStatusAndNotify = (next) => {
165
+ setStatus(next);
166
+ resolved.onStatusChange?.(next);
167
+ };
168
+ setStatusAndNotify("loading");
169
+ setErrorMsg("");
170
+ setStaticHtml(null);
171
+ async function run() {
172
+ try {
173
+ const decoder = new TextDecoder();
174
+ const chunks = [];
175
+ const bridge = new WasiBridge({
176
+ args: [resolved.wasm.toString(), ...resolved.resolveArgv(resolved.size)],
177
+ env: resolved.env,
178
+ stdout: (data) => chunks.push(new Uint8Array(data)),
179
+ stderr: () => { },
180
+ onExit: (code) => {
181
+ if (!cancelled) {
182
+ const total = chunks.reduce((n, c) => n + c.length, 0);
183
+ const merged = new Uint8Array(total);
184
+ let offset = 0;
185
+ for (const c of chunks) {
186
+ merged.set(c, offset);
187
+ offset += c.length;
188
+ }
189
+ setStaticHtml(ansiToHtml(decoder.decode(merged)));
190
+ setStatusAndNotify("exited");
191
+ resolved.onExit?.(code);
192
+ }
193
+ },
194
+ });
195
+ const wasmApp = await instantiateApp(resolved.wasm, bridge);
196
+ if (cancelled)
197
+ return;
198
+ setStatusAndNotify("running");
199
+ await wasmApp.run();
200
+ }
201
+ catch (e) {
202
+ if (!cancelled) {
203
+ setStatusAndNotify("error");
204
+ setErrorMsg(e instanceof Error ? e.message : String(e));
205
+ resolved.onError?.(e);
206
+ }
207
+ }
208
+ }
209
+ run();
210
+ return () => { cancelled = true; };
211
+ }, [
212
+ resolved.mode,
213
+ resolved.wasm,
214
+ resolved.resolveArgv,
215
+ resolved.env,
216
+ resolved.size.cols,
217
+ resolved.size.rows,
218
+ resolved.onExit,
219
+ resolved.onError,
220
+ resolved.onStatusChange,
221
+ ]);
222
+ // ── Render ───────────────────────────────────────────────────────────────
223
+ const bg = resolved.terminal.theme?.background ?? "#1a1b26";
224
+ const fg = resolved.terminal.theme?.foreground ?? "#a9b1d6";
225
+ if (resolved.mode === "static") {
226
+ return (_jsxs("div", { className: props.className, style: {
227
+ position: "relative",
228
+ background: bg,
229
+ borderRadius: 6,
230
+ overflow: "hidden",
231
+ ...props.style,
232
+ }, children: [status === "loading" && _jsx("div", { style: overlayStyle, children: "Loading\u2026" }), status === "error" && (_jsxs("div", { style: { ...overlayStyle, color: "#f7768e" }, children: ["Error: ", errorMsg] })), staticHtml !== null && (_jsx("pre", { style: {
233
+ margin: 0,
234
+ padding: "0.5em",
235
+ fontFamily: resolved.terminal.fontFamily,
236
+ fontSize: resolved.terminal.fontSize,
237
+ color: fg,
238
+ lineHeight: 1.2,
239
+ background: "transparent",
240
+ overflow: "auto",
241
+ }, dangerouslySetInnerHTML: { __html: staticHtml } }))] }));
242
+ }
243
+ return (_jsxs("div", { ref: wrapperRef, className: props.className, style: {
244
+ position: "relative",
245
+ display: "inline-block",
246
+ background: bg,
247
+ borderRadius: 6,
248
+ overflow: "hidden",
249
+ ...props.style,
250
+ }, children: [_jsx("div", { ref: containerRef, style: { display: status === "error" ? "none" : undefined } }), status === "loading" && (_jsx("div", { style: overlayStyle, children: "Loading\u2026" })), status === "error" && (_jsxs("div", { style: { ...overlayStyle, color: "#f7768e" }, children: ["Error: ", errorMsg] }))] }));
251
+ }
252
+ const overlayStyle = {
253
+ padding: "1rem",
254
+ fontFamily: "monospace",
255
+ fontSize: 14,
256
+ color: "#a9b1d6",
257
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e={};exports.default=e;
@@ -0,0 +1,4 @@
1
+ const e = {};
2
+ export {
3
+ e as default
4
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Minimal ANSI SGR → HTML converter.
3
+ *
4
+ * Handles the subset used by typical wasm32-wasi TUI apps:
5
+ * - Reset (0)
6
+ * - Bold, italic, underline (1/3/4 and their off codes)
7
+ * - Standard 16-color fg/bg (30–37, 39, 40–47, 49, 90–97, 100–107)
8
+ * - 256-color fg/bg (38;5;N / 48;5;N)
9
+ * - 24-bit truecolor fg/bg (38;2;R;G;B / 48;2;R;G;B)
10
+ *
11
+ * Non-SGR CSI sequences (cursor movement etc.) are silently dropped,
12
+ * which is correct for static capture where only the final text matters.
13
+ */
14
+ /** Convert ANSI escape codes to an HTML string of styled `<span>` elements. */
15
+ export declare function ansiToHtml(input: string): string;
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Minimal ANSI SGR → HTML converter.
3
+ *
4
+ * Handles the subset used by typical wasm32-wasi TUI apps:
5
+ * - Reset (0)
6
+ * - Bold, italic, underline (1/3/4 and their off codes)
7
+ * - Standard 16-color fg/bg (30–37, 39, 40–47, 49, 90–97, 100–107)
8
+ * - 256-color fg/bg (38;5;N / 48;5;N)
9
+ * - 24-bit truecolor fg/bg (38;2;R;G;B / 48;2;R;G;B)
10
+ *
11
+ * Non-SGR CSI sequences (cursor movement etc.) are silently dropped,
12
+ * which is correct for static capture where only the final text matters.
13
+ */
14
+ // Standard xterm 16-color palette
15
+ const ANSI16 = [
16
+ "#000000", "#cd0000", "#00cd00", "#cdcd00",
17
+ "#0000ee", "#cd00cd", "#00cdcd", "#e5e5e5",
18
+ "#7f7f7f", "#ff0000", "#00ff00", "#ffff00",
19
+ "#5c5cff", "#ff00ff", "#00ffff", "#ffffff",
20
+ ];
21
+ function color256(n) {
22
+ if (n < 16)
23
+ return ANSI16[n];
24
+ if (n < 232) {
25
+ const i = n - 16;
26
+ const r = Math.floor(i / 36);
27
+ const g = Math.floor((i % 36) / 6);
28
+ const b = i % 6;
29
+ const v = (x) => (x === 0 ? 0 : x * 40 + 55);
30
+ return `rgb(${v(r)},${v(g)},${v(b)})`;
31
+ }
32
+ const grey = (n - 232) * 10 + 8;
33
+ return `rgb(${grey},${grey},${grey})`;
34
+ }
35
+ function escHtml(s) {
36
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
37
+ }
38
+ /** Convert ANSI escape codes to an HTML string of styled `<span>` elements. */
39
+ export function ansiToHtml(input) {
40
+ // Current SGR state
41
+ let fg = null;
42
+ let bg = null;
43
+ let bold = false;
44
+ let italic = false;
45
+ let underline = false;
46
+ const result = [];
47
+ let buf = "";
48
+ let bufStyle = "";
49
+ function buildStyle() {
50
+ const parts = [];
51
+ if (fg)
52
+ parts.push(`color:${fg}`);
53
+ if (bg)
54
+ parts.push(`background-color:${bg}`);
55
+ if (bold)
56
+ parts.push("font-weight:bold");
57
+ if (italic)
58
+ parts.push("font-style:italic");
59
+ if (underline)
60
+ parts.push("text-decoration:underline");
61
+ return parts.join(";");
62
+ }
63
+ function flush() {
64
+ if (!buf)
65
+ return;
66
+ result.push(bufStyle ? `<span style="${bufStyle}">${escHtml(buf)}</span>` : escHtml(buf));
67
+ buf = "";
68
+ }
69
+ function applySgr(params) {
70
+ flush();
71
+ let i = 0;
72
+ while (i < params.length) {
73
+ const p = params[i];
74
+ if (p === 0) {
75
+ fg = null;
76
+ bg = null;
77
+ bold = false;
78
+ italic = false;
79
+ underline = false;
80
+ }
81
+ else if (p === 1) {
82
+ bold = true;
83
+ }
84
+ else if (p === 22) {
85
+ bold = false;
86
+ }
87
+ else if (p === 3) {
88
+ italic = true;
89
+ }
90
+ else if (p === 23) {
91
+ italic = false;
92
+ }
93
+ else if (p === 4) {
94
+ underline = true;
95
+ }
96
+ else if (p === 24) {
97
+ underline = false;
98
+ }
99
+ else if (p >= 30 && p <= 37) {
100
+ fg = ANSI16[p - 30];
101
+ }
102
+ else if (p === 38) {
103
+ if (params[i + 1] === 2 && i + 4 < params.length) {
104
+ fg = `rgb(${params[i + 2]},${params[i + 3]},${params[i + 4]})`;
105
+ i += 4;
106
+ }
107
+ else if (params[i + 1] === 5 && i + 2 < params.length) {
108
+ fg = color256(params[i + 2]);
109
+ i += 2;
110
+ }
111
+ }
112
+ else if (p === 39) {
113
+ fg = null;
114
+ }
115
+ else if (p >= 40 && p <= 47) {
116
+ bg = ANSI16[p - 40];
117
+ }
118
+ else if (p === 48) {
119
+ if (params[i + 1] === 2 && i + 4 < params.length) {
120
+ bg = `rgb(${params[i + 2]},${params[i + 3]},${params[i + 4]})`;
121
+ i += 4;
122
+ }
123
+ else if (params[i + 1] === 5 && i + 2 < params.length) {
124
+ bg = color256(params[i + 2]);
125
+ i += 2;
126
+ }
127
+ }
128
+ else if (p === 49) {
129
+ bg = null;
130
+ }
131
+ else if (p >= 90 && p <= 97) {
132
+ fg = ANSI16[p - 82];
133
+ }
134
+ else if (p >= 100 && p <= 107) {
135
+ bg = ANSI16[p - 92];
136
+ }
137
+ i++;
138
+ }
139
+ bufStyle = buildStyle();
140
+ }
141
+ let i = 0;
142
+ while (i < input.length) {
143
+ const ch = input[i];
144
+ if (ch === "\x1b" && i + 1 < input.length && input[i + 1] === "[") {
145
+ // CSI sequence: find the final byte (0x40–0x7e)
146
+ const seqStart = i + 2;
147
+ let end = seqStart;
148
+ while (end < input.length && (input.charCodeAt(end) < 0x40 || input.charCodeAt(end) > 0x7e)) {
149
+ end++;
150
+ }
151
+ if (end < input.length) {
152
+ if (input[end] === "m") {
153
+ const seq = input.slice(seqStart, end);
154
+ const params = seq ? seq.split(";").map((s) => parseInt(s, 10) || 0) : [0];
155
+ applySgr(params);
156
+ }
157
+ // Non-SGR CSI (cursor movement etc.) — silently skip
158
+ i = end + 1;
159
+ }
160
+ else {
161
+ // Incomplete sequence at end of input — skip escape char
162
+ i++;
163
+ }
164
+ }
165
+ else if (ch === "\r") {
166
+ i++;
167
+ }
168
+ else {
169
+ // Regular character — append, flushing if style changed
170
+ const style = buildStyle();
171
+ if (style !== bufStyle) {
172
+ flush();
173
+ bufStyle = style;
174
+ }
175
+ buf += ch;
176
+ i++;
177
+ }
178
+ }
179
+ flush();
180
+ return result.join("");
181
+ }
@@ -0,0 +1,2 @@
1
+ /** Load ghostty-web lazily to avoid SSR issues in host apps. */
2
+ export declare function loadGhostty(): Promise<typeof import("ghostty-web")>;
@@ -0,0 +1,11 @@
1
+ let ghosttyReady = null;
2
+ /** Load ghostty-web lazily to avoid SSR issues in host apps. */
3
+ export function loadGhostty() {
4
+ if (!ghosttyReady) {
5
+ ghosttyReady = import("ghostty-web").then(async (mod) => {
6
+ await mod.init();
7
+ return mod;
8
+ });
9
+ }
10
+ return ghosttyReady;
11
+ }
@@ -0,0 +1,4 @@
1
+ export { ansiToHtml } from "./ansi.js";
2
+ export { loadGhostty } from "./ghostty.js";
3
+ export { resolveTuiPreviewProps, warnLegacyPropsOnce } from "./normalize.js";
4
+ export { WasiBridge, WasiExitError, instantiateApp } from "./wasi.js";
@@ -0,0 +1,4 @@
1
+ export { ansiToHtml } from "./ansi.js";
2
+ export { loadGhostty } from "./ghostty.js";
3
+ export { resolveTuiPreviewProps, warnLegacyPropsOnce } from "./normalize.js";
4
+ export { WasiBridge, WasiExitError, instantiateApp } from "./wasi.js";
@@ -0,0 +1,3 @@
1
+ import type { ResolvedTuiPreviewOptions, TuiPreviewProps } from "../types.js";
2
+ export declare function warnLegacyPropsOnce(usedLegacyProps: boolean): void;
3
+ export declare function resolveTuiPreviewProps(props: TuiPreviewProps): ResolvedTuiPreviewOptions;