api_binding_ui 3.0.0 → 3.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.
@@ -2,6 +2,6 @@
2
2
  <html lang="en">
3
3
  <head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>Bind API</title>
4
4
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@500;600&display=swap" />
5
- <style>html, body, #root { height: 100%; margin: 0; overflow: hidden; background: #f8fafc; }</style><link rel="stylesheet" crossorigin href="./index-aaxy714b.css"><script type="module" crossorigin src="./index-0n23yy5r.js"></script></head>
5
+ <style>html, body, #root { height: 100%; margin: 0; overflow: hidden; background: #f8fafc; }</style><link rel="stylesheet" crossorigin href="./index-aaxy714b.css"><script type="module" crossorigin src="./index-4mgcb4wd.js"></script></head>
6
6
  <body><div id="root"></div></body>
7
7
  </html>
package/dist/cli.js CHANGED
@@ -2,9 +2,8 @@
2
2
 
3
3
  // src/cli/cli.ts
4
4
  import { createServer } from "node:http";
5
- import { promises as fs2 } from "node:fs";
6
- import path2 from "node:path";
7
- import { fileURLToPath } from "node:url";
5
+ import { promises as fs3 } from "node:fs";
6
+ import path3 from "node:path";
8
7
 
9
8
  // src/framework/names.ts
10
9
  function humanize(id) {
@@ -763,16 +762,123 @@ ${apiType.join(`
763
762
  return { files, issues };
764
763
  }
765
764
 
766
- // src/cli/scan.ts
765
+ // src/server/handler.ts
767
766
  import { promises as fs } from "node:fs";
768
767
  import path from "node:path";
768
+ import { fileURLToPath } from "node:url";
769
+ var TYPES = { ".html": "text/html; charset=utf-8", ".js": "text/javascript", ".css": "text/css", ".svg": "image/svg+xml", ".map": "application/json", ".woff2": "font/woff2" };
770
+ var DIST = fileURLToPath(new URL(".", import.meta.url));
771
+ var APP_DIR = path.join(DIST, "app");
772
+ var OVERLAY_FILE = path.join(DIST, "overlay.js");
773
+ var isUrl = (s) => /^https?:\/\//i.test(s);
774
+ async function readDocs(src, base) {
775
+ if (isUrl(src)) {
776
+ const res = await fetch(src);
777
+ if (!res.ok)
778
+ throw new Error(`${src} returned HTTP ${res.status}.`);
779
+ return res.text();
780
+ }
781
+ if (!/\.(ya?ml|json)$/i.test(src))
782
+ throw new Error(`"${src}" is not a .yaml, .yml or .json file.`);
783
+ return fs.readFile(path.resolve(base, src), "utf8");
784
+ }
785
+ async function writeFileAtomic(file, text) {
786
+ const tmp = `${file}.${process.pid}.tmp`;
787
+ await fs.writeFile(tmp, text, "utf8");
788
+ await fs.rename(tmp, file);
789
+ }
790
+ async function readBody(req, limit = 20 * 1024 * 1024) {
791
+ const chunks = [];
792
+ let size = 0;
793
+ for await (const c of req) {
794
+ size += c.length;
795
+ if (size > limit)
796
+ throw new Error("Body too large.");
797
+ chunks.push(c);
798
+ }
799
+ return Buffer.concat(chunks).toString("utf8");
800
+ }
801
+ function send(res, status, body, type = "text/plain; charset=utf-8") {
802
+ res.writeHead(status, { "Content-Type": type, "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" }).end(body);
803
+ }
804
+ function createStudioHandler({ file, prefix, allowed }) {
805
+ const base = path.dirname(file);
806
+ return async (req, res) => {
807
+ const url = new URL(req.url ?? "/", "http://local");
808
+ if (prefix && url.pathname !== prefix && !url.pathname.startsWith(prefix + "/"))
809
+ return false;
810
+ const rel = url.pathname.slice(prefix.length) || "/";
811
+ try {
812
+ if (prefix && url.pathname === prefix) {
813
+ res.writeHead(302, { Location: prefix + "/" + url.search }).end();
814
+ return true;
815
+ }
816
+ if (rel.startsWith("/api/") && !allowed(req)) {
817
+ send(res, 403, "Forbidden.");
818
+ return true;
819
+ }
820
+ if (rel === "/api/bindings") {
821
+ if (req.method === "GET")
822
+ send(res, 200, await fs.readFile(file, "utf8"), "application/json");
823
+ else if (req.method === "PUT") {
824
+ const text = await readBody(req);
825
+ try {
826
+ parseBindingsFile(text);
827
+ } catch (e) {
828
+ send(res, 400, e.message);
829
+ return true;
830
+ }
831
+ await writeFileAtomic(file, text);
832
+ send(res, 204, "");
833
+ } else
834
+ send(res, 405, "Method not allowed.");
835
+ return true;
836
+ }
837
+ if (rel === "/api/openapi" && req.method === "GET") {
838
+ const src = url.searchParams.get("src") ?? "";
839
+ try {
840
+ send(res, 200, await readDocs(src, base), src.endsWith(".json") ? "application/json" : "text/yaml; charset=utf-8");
841
+ } catch (e) {
842
+ send(res, 502, e.message);
843
+ }
844
+ return true;
845
+ }
846
+ if (req.method !== "GET" && req.method !== "HEAD") {
847
+ send(res, 405, "Method not allowed.");
848
+ return true;
849
+ }
850
+ if (rel === "/overlay.js") {
851
+ send(res, 200, await fs.readFile(OVERLAY_FILE), TYPES[".js"]);
852
+ return true;
853
+ }
854
+ const target = path.resolve(APP_DIR, rel === "/" ? "index.html" : decodeURIComponent(rel.slice(1)));
855
+ if (!target.startsWith(APP_DIR + path.sep)) {
856
+ send(res, 404, "Not found.");
857
+ return true;
858
+ }
859
+ const data = await fs.readFile(target).catch(() => null);
860
+ if (!data) {
861
+ send(res, 404, "Not found.");
862
+ return true;
863
+ }
864
+ send(res, 200, data, TYPES[path.extname(target)] ?? "application/octet-stream");
865
+ } catch (e) {
866
+ send(res, 500, e.message);
867
+ }
868
+ return true;
869
+ };
870
+ }
871
+
872
+ // src/cli/scan.ts
873
+ import { promises as fs2 } from "node:fs";
874
+ import path2 from "node:path";
769
875
  var LITERAL = /componentId\s*(?:=\s*\{?\s*|:\s*)(["'`])([a-z0-9.-]+)\1/g;
770
876
  var SKIP = new Set(["node_modules", "dist", "build", ".git", ".next", ".output"]);
771
877
  async function* sources(dir) {
772
- for (const e of await fs.readdir(dir, { withFileTypes: true })) {
878
+ for (const e of await fs2.readdir(dir, { withFileTypes: true })) {
773
879
  if (SKIP.has(e.name))
774
880
  continue;
775
- const p = path.join(dir, e.name);
881
+ const p = path2.join(dir, e.name);
776
882
  if (e.isDirectory())
777
883
  yield* sources(p);
778
884
  else if (/\.(tsx?|jsx?)$/.test(e.name))
@@ -782,9 +888,9 @@ async function* sources(dir) {
782
888
  async function scanIds(dir, file) {
783
889
  const found = new Map;
784
890
  for await (const p of sources(dir))
785
- for (const m of (await fs.readFile(p, "utf8")).matchAll(LITERAL))
891
+ for (const m of (await fs2.readFile(p, "utf8")).matchAll(LITERAL))
786
892
  if (!found.has(m[2]))
787
- found.set(m[2], path.relative(dir, p));
893
+ found.set(m[2], path2.relative(dir, p));
788
894
  const registered = new Set(file.components.map((c) => c.id));
789
895
  const issues = [];
790
896
  for (const [id, where] of found)
@@ -802,6 +908,7 @@ var HELP = `Usage:
802
908
  api-binding generate [bindings.json] [--check] [--src <dir>]
803
909
 
804
910
  studio Opens the binding studio on http://localhost:<port>. Edits are saved to the file.
911
+ Inside a Vite app, the dev plugin (api_binding_ui/vite) serves the same studio instead.
805
912
  generate Writes one React Query hook per screen into output.dir, calling the Orval client.
806
913
  --check writes nothing; exits 1 when the files are out of date (for CI).
807
914
  --src also checks that every registered id appears in the app's source, and the reverse.`;
@@ -819,34 +926,17 @@ function args(argv) {
819
926
  }
820
927
  return { rest, flags };
821
928
  }
822
- var isUrl = (s) => /^https?:\/\//i.test(s);
823
- async function readDocs(src, base) {
824
- if (isUrl(src)) {
825
- const res = await fetch(src);
826
- if (!res.ok)
827
- throw new Error(`${src} returned HTTP ${res.status}.`);
828
- return res.text();
829
- }
830
- if (!/\.(ya?ml|json)$/i.test(src))
831
- throw new Error(`"${src}" is not a .yaml, .yml or .json file.`);
832
- return fs2.readFile(path2.resolve(base, src), "utf8");
833
- }
834
929
  function report(issues) {
835
930
  for (const i of issues)
836
931
  console.log(`${i.severity === "error" ? "error " : "warning"} ${[i.component ?? i.screen, i.slot].filter(Boolean).join(" · ")}: ${i.message}`);
837
932
  }
838
- async function writeFile(file, text) {
839
- const tmp = `${file}.${process.pid}.tmp`;
840
- await fs2.writeFile(tmp, text, "utf8");
841
- await fs2.rename(tmp, file);
842
- }
843
933
  async function runGenerate(filePath, flags) {
844
- const base = path2.dirname(filePath);
845
- const file = parseBindingsFile(await fs2.readFile(filePath, "utf8"));
934
+ const base = path3.dirname(filePath);
935
+ const file = parseBindingsFile(await fs3.readFile(filePath, "utf8"));
846
936
  const catalog = catalogFromText(await readDocs(file.openapi, base), file.openapi);
847
937
  const { files, issues } = generate(file, catalog);
848
938
  if (typeof flags.src === "string")
849
- issues.push(...await scanIds(path2.resolve(flags.src), file));
939
+ issues.push(...await scanIds(path3.resolve(flags.src), file));
850
940
  report(issues);
851
941
  const errors = issues.filter((i) => i.severity === "error").length;
852
942
  if (errors) {
@@ -854,98 +944,45 @@ async function runGenerate(filePath, flags) {
854
944
  ${errors} error(s). Nothing was written.`);
855
945
  return 1;
856
946
  }
857
- const out = path2.resolve(base, file.output.dir);
858
- await fs2.mkdir(out, { recursive: true });
859
- const wanted = new Map(files.map((f) => [path2.join(out, f.path), f.content]));
860
- const existing = (await fs2.readdir(out)).filter((n) => n.endsWith(".ts")).map((n) => path2.join(out, n));
947
+ const out = path3.resolve(base, file.output.dir);
948
+ await fs3.mkdir(out, { recursive: true });
949
+ const wanted = new Map(files.map((f) => [path3.join(out, f.path), f.content]));
950
+ const existing = (await fs3.readdir(out)).filter((n) => n.endsWith(".ts")).map((n) => path3.join(out, n));
861
951
  const changed = [];
862
952
  const stale = [];
863
953
  for (const [p, content] of wanted)
864
- if (await fs2.readFile(p, "utf8").catch(() => null) !== content)
954
+ if (await fs3.readFile(p, "utf8").catch(() => null) !== content)
865
955
  changed.push(p);
866
956
  for (const p of existing)
867
- if (!wanted.has(p) && (await fs2.readFile(p, "utf8")).startsWith(`// ${GENERATED_MARK}`))
957
+ if (!wanted.has(p) && (await fs3.readFile(p, "utf8")).startsWith(`// ${GENERATED_MARK}`))
868
958
  stale.push(p);
869
959
  if (flags.check) {
870
960
  if (changed.length || stale.length) {
871
- console.log(`Out of date: ${[...changed, ...stale].map((p) => path2.relative(process.cwd(), p)).join(", ")}`);
961
+ console.log(`Out of date: ${[...changed, ...stale].map((p) => path3.relative(process.cwd(), p)).join(", ")}`);
872
962
  return 1;
873
963
  }
874
964
  console.log("Generated files are up to date.");
875
965
  return 0;
876
966
  }
877
967
  for (const p of changed)
878
- await writeFile(p, wanted.get(p));
968
+ await writeFileAtomic(p, wanted.get(p));
879
969
  for (const p of stale)
880
- await fs2.unlink(p);
881
- console.log(`${files.length - 2} screen hook(s) in ${path2.relative(process.cwd(), out) || "."}: ${changed.length} written, ${stale.length} removed.`);
970
+ await fs3.unlink(p);
971
+ console.log(`${files.length - 2} screen hook(s) in ${path3.relative(process.cwd(), out) || "."}: ${changed.length} written, ${stale.length} removed.`);
882
972
  return 0;
883
973
  }
884
- var TYPES = { ".html": "text/html; charset=utf-8", ".js": "text/javascript", ".css": "text/css", ".svg": "image/svg+xml", ".map": "application/json", ".woff2": "font/woff2" };
885
- async function body(req, limit = 20 * 1024 * 1024) {
886
- const chunks = [];
887
- let size = 0;
888
- for await (const c of req) {
889
- size += c.length;
890
- if (size > limit)
891
- throw new Error("Body too large.");
892
- chunks.push(c);
893
- }
894
- return Buffer.concat(chunks).toString("utf8");
895
- }
896
974
  async function runStudio(filePath, flags) {
897
975
  const port = Number(flags.port ?? 5180);
898
- const base = path2.dirname(filePath);
899
- parseBindingsFile(await fs2.readFile(filePath, "utf8"));
900
- const app = fileURLToPath(new URL("./app/", import.meta.url));
976
+ parseBindingsFile(await fs3.readFile(filePath, "utf8"));
901
977
  const origins = new Set([`http://localhost:${port}`, `http://127.0.0.1:${port}`]);
902
- const send = (res, status, text, type = "text/plain; charset=utf-8") => {
903
- res.writeHead(status, { "Content-Type": type, "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" }).end(text);
904
- };
978
+ const allowed = (req) => origins.has(`http://${req.headers.host}`) && (!req.headers.origin || origins.has(String(req.headers.origin)));
979
+ const handle = createStudioHandler({ file: filePath, prefix: "", allowed });
905
980
  const server = createServer(async (req, res) => {
906
- try {
907
- const host = req.headers.host ?? "";
908
- if (!origins.has(`http://${host}`))
909
- return send(res, 403, "Forbidden host.");
910
- if (req.headers.origin && !origins.has(req.headers.origin))
911
- return send(res, 403, "Forbidden origin.");
912
- const url = new URL(req.url ?? "/", `http://${host}`);
913
- if (url.pathname === "/api/bindings") {
914
- if (req.method === "GET")
915
- return send(res, 200, await fs2.readFile(filePath, "utf8"), "application/json");
916
- if (req.method === "PUT") {
917
- const text = await body(req);
918
- try {
919
- parseBindingsFile(text);
920
- } catch (e) {
921
- return send(res, 400, e.message);
922
- }
923
- await writeFile(filePath, text);
924
- return send(res, 204, "");
925
- }
926
- return send(res, 405, "Method not allowed.");
927
- }
928
- if (url.pathname === "/api/openapi" && req.method === "GET") {
929
- const src = url.searchParams.get("src") ?? "";
930
- try {
931
- return send(res, 200, await readDocs(src, base), src.endsWith(".json") ? "application/json" : "text/yaml; charset=utf-8");
932
- } catch (e) {
933
- return send(res, 502, e.message);
934
- }
935
- }
936
- if (req.method !== "GET")
937
- return send(res, 405, "Method not allowed.");
938
- const rel = url.pathname === "/" ? "index.html" : decodeURIComponent(url.pathname.slice(1));
939
- const target = path2.resolve(app, rel);
940
- if (!target.startsWith(app))
941
- return send(res, 404, "Not found.");
942
- const data = await fs2.readFile(target).catch(() => null);
943
- if (!data)
944
- return send(res, 404, "Not found.");
945
- res.writeHead(200, { "Content-Type": TYPES[path2.extname(target)] ?? "application/octet-stream", "X-Content-Type-Options": "nosniff" }).end(data);
946
- } catch (e) {
947
- send(res, 500, e.message);
981
+ if (!origins.has(`http://${req.headers.host}`)) {
982
+ res.writeHead(403).end("Forbidden host.");
983
+ return;
948
984
  }
985
+ await handle(req, res);
949
986
  });
950
987
  server.listen(port, "127.0.0.1", () => console.log(`Binding studio: http://localhost:${port}
951
988
  Editing ${filePath}. Ctrl+C to stop.`));
@@ -953,7 +990,7 @@ Editing ${filePath}. Ctrl+C to stop.`));
953
990
  async function main() {
954
991
  const [cmd, ...argv] = process.argv.slice(2);
955
992
  const { rest, flags } = args(argv);
956
- const filePath = path2.resolve(rest[0] ?? "bindings.json");
993
+ const filePath = path3.resolve(rest[0] ?? "bindings.json");
957
994
  if (cmd === "studio")
958
995
  return runStudio(filePath, flags);
959
996
  if (cmd === "generate")
@@ -0,0 +1,24 @@
1
+ function m(o,i){let n;for(let e of i)if(o.startsWith(e+".")&&(!n||e.length>n.length))n=e;return n}function b(o,i){let n=new Map;for(let s of o){let r=m(s,i);if(r)n.set(r,(n.get(r)??0)+1)}let e;for(let[s,r]of n){let l=e?n.get(e):-1;if(r>l||r===l&&s.length>e.length)e=s}return e}var y=new URL(".",import.meta.url).pathname,c="data-component-id",p=null;async function x(){if(p)return p;try{p=((await(await fetch(y+"api/bindings",{cache:"no-store"})).json()).screens??[]).map((i)=>i.id)}catch{p=[]}return p}var f=(o)=>o.getClientRects().length>0&&getComputedStyle(o).visibility!=="hidden";function w(){let o=[...document.querySelectorAll('dialog[open], [role="dialog"], [role="alertdialog"], [aria-modal="true"]')].filter(f);for(let i of o.reverse()){let n=[...i.querySelectorAll(`[${c}]`)].filter(f).map((e)=>e.getAttribute(c));if(n.length)return n}return[...document.querySelectorAll(`[${c}]`)].filter(f).map((i)=>i.getAttribute(c))}var v=`
2
+ :host { all: initial; }
3
+ .fab { position: fixed; z-index: 2147483000; right: max(20px, env(safe-area-inset-right, 0px)); bottom: max(20px, env(safe-area-inset-bottom, 0px));
4
+ display: grid; grid-template-columns: auto 0fr; align-items: center; height: 44px; padding: 0 13px; border-radius: 10px; cursor: pointer;
5
+ border: 1px solid #e2e8f0; background: #fff; color: #0f172a; font: 500 13px/1 Inter, system-ui, -apple-system, sans-serif;
6
+ box-shadow: 0 1px 2px rgba(15,23,42,.06), 0 6px 16px -6px rgba(15,23,42,.18);
7
+ transition: grid-template-columns 240ms cubic-bezier(.2,.7,.2,1), box-shadow 200ms ease, transform 200ms cubic-bezier(.2,.7,.2,1); }
8
+ .fab svg { color: #3b82f6; display: block; }
9
+ .fab span { overflow: hidden; }
10
+ .fab span > span { display: block; padding-left: 8px; white-space: nowrap; opacity: 0; transition: opacity 180ms ease; }
11
+ .fab:hover, .fab:focus-visible { grid-template-columns: auto 1fr; transform: translateY(-1px); box-shadow: 0 1px 2px rgba(15,23,42,.06), 0 10px 24px -8px rgba(15,23,42,.24); }
12
+ .fab:hover span > span, .fab:focus-visible span > span { opacity: 1; transition-delay: 60ms; }
13
+ .fab:focus-visible { outline: 2px solid #3b82f6; outline-offset: 2px; }
14
+ .fab[hidden] { display: none; }
15
+ dialog { width: min(1440px, calc(100vw - 32px)); height: min(920px, calc(100dvh - 32px)); max-width: none; max-height: none; margin: auto; padding: 0;
16
+ border: 0; border-radius: 12px; overflow: hidden; background: #f8fafc; box-shadow: 0 24px 64px -16px rgba(15,23,42,.35); animation: in 240ms cubic-bezier(.2,.7,.2,1); }
17
+ dialog::backdrop { background: rgba(15,23,42,.32); backdrop-filter: blur(2px); }
18
+ iframe { width: 100%; height: 100%; border: 0; display: block; }
19
+ .hl { position: fixed; z-index: 2147482999; pointer-events: none; border: 2px solid #3b82f6; border-radius: 6px; background: rgba(59,130,246,.08); display: none; }
20
+ @keyframes in { from { opacity: 0; transform: translateY(8px) scale(.985); } }
21
+ @media (prefers-reduced-motion: reduce) { * { animation-duration: 1ms !important; transition-duration: 1ms !important; } }
22
+ `,k='<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 7 4 12l5 5M15 7l5 5-5 5"/></svg>';function h(){if(document.getElementById("api-binding-overlay"))return;let o=document.createElement("div");o.id="api-binding-overlay";let i=o.attachShadow({mode:"open"});i.innerHTML=`<style>${v}</style>
23
+ <button class="fab" type="button" aria-label="Bind API" title="Bind API (Alt+click a component to open it)">${k}<span><span>Bind API</span></span></button>
24
+ <dialog aria-label="Bind API"></dialog><div class="hl"></div>`,document.body.appendChild(o);let n=i.querySelector(".fab"),e=i.querySelector("dialog"),s=i.querySelector(".hl"),r=async(t)=>{let a=await x(),d=t?void 0:b(w(),a),u=new URLSearchParams({embed:"1"});if(d)u.set("screen",d);if(t)u.set("component",t);e.innerHTML=`<iframe title="Bind API studio" src="${y}?${u}"></iframe>`,n.hidden=!0,e.showModal()},l=()=>{if(!e.open)return;e.close(),e.innerHTML="",n.hidden=!1,n.focus()};n.addEventListener("click",()=>void r()),e.addEventListener("close",l),window.addEventListener("message",(t)=>{if(t.origin===location.origin&&t.data==="api-binding:close")l()});let g=(t)=>t.altKey&&!e.open?t.target?.closest?.(`[${c}]`)??null:null;document.addEventListener("click",(t)=>{let a=g(t);if(!a)return;t.preventDefault(),t.stopPropagation(),r(a.getAttribute(c))},!0),document.addEventListener("mousemove",(t)=>{let a=g(t);if(!a){s.style.display="none";return}let d=a.getBoundingClientRect();Object.assign(s.style,{display:"block",left:`${d.left-2}px`,top:`${d.top-2}px`,width:`${d.width+4}px`,height:`${d.height+4}px`})},{passive:!0}),document.addEventListener("keyup",(t)=>{if(t.key==="Alt")s.style.display="none"})}if(document.body)h();else document.addEventListener("DOMContentLoaded",h);
@@ -0,0 +1,18 @@
1
+ /** Studio HTTP handling shared by the CLI server and the Vite plugin: the studio page, the bindings file, the docs. */
2
+ import type { IncomingMessage, ServerResponse } from "node:http";
3
+ export interface StudioHandlerOptions {
4
+ /** Absolute path of bindings.json. */
5
+ file: string;
6
+ /** Path prefix the studio is served under, e.g. "" or "/__binding". */
7
+ prefix: string;
8
+ /** True when the request may read and write the file (host and origin checks live with the caller). */
9
+ allowed: (req: IncomingMessage) => boolean;
10
+ }
11
+ export declare const APP_DIR: string;
12
+ export declare const OVERLAY_FILE: string;
13
+ /** The OpenAPI text: fetched for a URL, read for a .yaml/.yml/.json path relative to `base`. */
14
+ export declare function readDocs(src: string, base: string): Promise<string>;
15
+ /** Writes atomically, so an interrupted save never leaves half a file. */
16
+ export declare function writeFileAtomic(file: string, text: string): Promise<void>;
17
+ /** Handles a request under `prefix`. Returns false when the path is not the studio's, so the caller can pass it on. */
18
+ export declare function createStudioHandler({ file, prefix, allowed }: StudioHandlerOptions): (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
@@ -8,9 +8,11 @@ export interface BindingStudioProps {
8
8
  docsUrl?: (openapi: string) => string;
9
9
  /** Screen to open first. */
10
10
  initialScreen?: ScreenId;
11
+ /** Component to open first; its screen is found from its id. */
12
+ initialComponent?: string;
11
13
  /** Shown in the top bar. */
12
14
  title?: string;
13
15
  /** Shows a close button in the top bar. */
14
16
  onClose?: () => void;
15
17
  }
16
- export declare function BindingStudio({ store, docsUrl, initialScreen, title, onClose }: BindingStudioProps): React.JSX.Element;
18
+ export declare function BindingStudio({ store, docsUrl, initialScreen, initialComponent, title, onClose }: BindingStudioProps): React.JSX.Element;
@@ -2,6 +2,7 @@ import {
2
2
  catalogFromText2,
3
3
  loadCatalog2,
4
4
  humanize,
5
+ screenOf2,
5
6
  fromFile2,
6
7
  toFile2,
7
8
  createEngine2,
@@ -2471,7 +2472,7 @@ var EMPTY_HINT = /* @__PURE__ */ jsxs16("aside", {
2471
2472
  })
2472
2473
  ]
2473
2474
  });
2474
- function BindingStudio({ store, docsUrl = same, initialScreen, title = "Bind API", onClose }) {
2475
+ function BindingStudio({ store, docsUrl = same, initialScreen, initialComponent, title = "Bind API", onClose }) {
2475
2476
  const [file, setFile] = React14.useState(null);
2476
2477
  const [fileError, setFileError] = React14.useState();
2477
2478
  const latest = React14.useRef({ file: null, index: {} });
@@ -2626,13 +2627,14 @@ function BindingStudio({ store, docsUrl = same, initialScreen, title = "Bind API
2626
2627
  initial: model.index,
2627
2628
  save,
2628
2629
  title,
2629
- initialScreen,
2630
+ initialScreen: initialComponent ? screenOf2(initialComponent, model.fw.registry.screens.map((s) => s.id)) ?? initialScreen : initialScreen,
2631
+ initialComponent,
2630
2632
  settingsButton
2631
2633
  });
2632
2634
  }
2633
- function Workbench({ fw, initial, save, title, initialScreen, settingsButton }) {
2635
+ function Workbench({ fw, initial, save, title, initialScreen, initialComponent, settingsButton }) {
2634
2636
  const { index, loaded, status, removed, put, drop, undo, dismiss, retry } = useBindings(initial, save);
2635
- const [place, go] = React14.useState({ screen: initialScreen });
2637
+ const [place, go] = React14.useState({ screen: initialScreen, component: initialComponent });
2636
2638
  const engine = React14.useMemo(() => createEngine2(fw, index), [fw, index]);
2637
2639
  const screen = place.screen && engine.SCR.get(place.screen) || fw.registry.screens[0];
2638
2640
  const comp = screen && place.component ? engine.compOf(screen.id, place.component) : undefined;
@@ -2734,5 +2736,5 @@ export {
2734
2736
  BindingStudio
2735
2737
  };
2736
2738
 
2737
- //# debugId=5CC922DF10302EB664756E2164756E21
2739
+ //# debugId=033A885DE9DD742964756E2164756E21
2738
2740
  //# sourceMappingURL=index.js.map