@tangle-network/sandbox-ui 0.113.4 → 0.113.5

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.
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  RichFileTree
3
- } from "./chunk-PZVUIUUW.js";
3
+ } from "./chunk-ULPLRBMF.js";
4
4
  import {
5
5
  PortsList
6
- } from "./chunk-LDAKIKTV.js";
6
+ } from "./chunk-SX344CDB.js";
7
7
  import {
8
8
  TerminalView
9
9
  } from "./chunk-IACRDXER.js";
@@ -215,16 +215,20 @@ var require_extends = __commonJS({
215
215
  // src/workbench/sandbox-artifact-pane.tsx
216
216
  import * as React9 from "react";
217
217
  import { Code2, GitCompare, Globe, Network, SquareTerminal } from "lucide-react";
218
+ import { focusRingInset } from "@tangle-network/ui/utils";
218
219
 
219
220
  // src/workbench/pill-tabs.tsx
220
221
  import * as React from "react";
222
+ import { focusRing } from "@tangle-network/ui/utils";
221
223
  import { jsx, jsxs } from "react/jsx-runtime";
222
224
  function PillTabs({
223
225
  items,
224
226
  value,
225
227
  onChange,
226
228
  className,
227
- "aria-label": ariaLabel
229
+ "aria-label": ariaLabel,
230
+ idPrefix,
231
+ panelId
228
232
  }) {
229
233
  const containerRef = React.useRef(null);
230
234
  const tabRefs = React.useRef(/* @__PURE__ */ new Map());
@@ -281,10 +285,38 @@ function PillTabs({
281
285
  },
282
286
  type: "button",
283
287
  role: "tab",
288
+ id: idPrefix ? `${idPrefix}-${item.value}` : void 0,
289
+ "aria-controls": panelId,
284
290
  "aria-selected": selected,
291
+ tabIndex: selected ? 0 : -1,
285
292
  onClick: () => onChange(item.value),
293
+ onKeyDown: (event) => {
294
+ const index = items.findIndex((candidate) => candidate.value === item.value);
295
+ let nextIndex;
296
+ switch (event.key) {
297
+ case "ArrowRight":
298
+ nextIndex = (index + 1) % items.length;
299
+ break;
300
+ case "ArrowLeft":
301
+ nextIndex = (index - 1 + items.length) % items.length;
302
+ break;
303
+ case "Home":
304
+ nextIndex = 0;
305
+ break;
306
+ case "End":
307
+ nextIndex = items.length - 1;
308
+ break;
309
+ default:
310
+ return;
311
+ }
312
+ event.preventDefault();
313
+ const next = items[nextIndex];
314
+ onChange(next.value);
315
+ tabRefs.current.get(next.value)?.focus();
316
+ },
286
317
  className: cn(
287
318
  "relative z-10 inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium whitespace-nowrap transition-colors",
319
+ focusRing,
288
320
  selected ? "text-foreground" : "text-muted-foreground hover:text-foreground"
289
321
  ),
290
322
  children: [
@@ -388,6 +420,7 @@ function PanelHeader({ filename, gitStatus, stats, copyText, children, className
388
420
 
389
421
  // src/workbench/code-view.tsx
390
422
  import * as React6 from "react";
423
+ import { focusRing as focusRing2 } from "@tangle-network/ui/utils";
391
424
 
392
425
  // node_modules/.pnpm/react-syntax-highlighter@16.1.1_react@19.2.8/node_modules/react-syntax-highlighter/dist/esm/highlight.js
393
426
  var import_objectWithoutProperties = __toESM(require_objectWithoutProperties());
@@ -7217,27 +7250,35 @@ function CodeView({
7217
7250
  showTree = true,
7218
7251
  className
7219
7252
  }) {
7253
+ const [narrowPane, setNarrowPane] = React6.useState("code");
7220
7254
  const treePaths = React6.useMemo(() => {
7221
7255
  const list = paths && paths.length > 0 ? paths : [path];
7222
7256
  return list.includes(path) ? list : [...list, path];
7223
7257
  }, [paths, path]);
7224
- return /* @__PURE__ */ jsxs4("div", { className: cn("flex h-full min-h-0 w-full", className), children: [
7225
- showTree && /* @__PURE__ */ jsx6("div", { className: "sandbox-tree-surface flex w-60 shrink-0 flex-col border-r border-[var(--md3-outline-variant)] bg-surface-container-lowest", children: /* @__PURE__ */ jsx6(
7258
+ return /* @__PURE__ */ jsx6("div", { className: cn("sandbox-code-view h-full min-h-0 w-full", className), children: /* @__PURE__ */ jsxs4("div", { className: "sandbox-code-view-layout flex h-full min-h-0 w-full", children: [
7259
+ showTree && /* @__PURE__ */ jsxs4("div", { className: "sandbox-code-view-mobile-toolbar", role: "group", "aria-label": "Code view pane", children: [
7260
+ /* @__PURE__ */ jsx6("button", { type: "button", "aria-pressed": narrowPane === "files", onClick: () => setNarrowPane("files"), className: focusRing2, children: "Files" }),
7261
+ /* @__PURE__ */ jsx6("button", { type: "button", "aria-pressed": narrowPane === "code", onClick: () => setNarrowPane("code"), className: focusRing2, children: "Code" })
7262
+ ] }),
7263
+ showTree && /* @__PURE__ */ jsx6("div", { className: "sandbox-code-view-tree sandbox-tree-surface flex w-60 shrink-0 flex-col border-r border-[var(--md3-outline-variant)] bg-surface-container-lowest", "data-mobile-open": narrowPane === "files", children: /* @__PURE__ */ jsx6(
7226
7264
  RichFileTree,
7227
7265
  {
7228
7266
  paths: treePaths,
7229
7267
  selectedPath: path,
7230
- onSelect: (next) => onFileSelect?.(next),
7268
+ onSelect: (next) => {
7269
+ onFileSelect?.(next);
7270
+ setNarrowPane("code");
7271
+ },
7231
7272
  gitStatus,
7232
7273
  search: treePaths.length > 12,
7233
7274
  height: "100%"
7234
7275
  }
7235
7276
  ) }),
7236
- /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-1 flex-col bg-surface-container", children: [
7277
+ /* @__PURE__ */ jsxs4("div", { className: "sandbox-code-view-editor flex min-w-0 flex-1 flex-col bg-surface-container", "data-mobile-hidden": showTree && narrowPane === "files", children: [
7237
7278
  /* @__PURE__ */ jsx6(FileBreadcrumb, { path }),
7238
7279
  /* @__PURE__ */ jsx6("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx6(CodeSurface, { code: content, filename, language }) })
7239
7280
  ] })
7240
- ] });
7281
+ ] }) });
7241
7282
  }
7242
7283
 
7243
7284
  // src/workbench/diff-view.tsx
@@ -7319,28 +7360,52 @@ function DiffView({ filename, baseline, current, showFileHeader = true, classNam
7319
7360
  // src/workbench/preview-view.tsx
7320
7361
  import * as React8 from "react";
7321
7362
  import { ExternalLink, RotateCw } from "lucide-react";
7363
+ import { focusField } from "@tangle-network/ui/utils";
7322
7364
  import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
7323
7365
  function PreviewView({ url, className }) {
7324
7366
  const [address, setAddress] = React8.useState(url);
7367
+ const [activeUrl, setActiveUrl] = React8.useState(url);
7325
7368
  const [iframeKey, setIframeKey] = React8.useState(0);
7326
7369
  const [loading, setLoading] = React8.useState(true);
7370
+ const [slow, setSlow] = React8.useState(false);
7371
+ const [addressError, setAddressError] = React8.useState(null);
7372
+ const errorId = React8.useId();
7327
7373
  React8.useEffect(() => {
7328
7374
  setAddress(url);
7375
+ setActiveUrl(url);
7329
7376
  setIframeKey((k) => k + 1);
7330
7377
  setLoading(true);
7378
+ setSlow(false);
7379
+ setAddressError(null);
7331
7380
  }, [url]);
7332
7381
  React8.useEffect(() => {
7333
- const t = setTimeout(() => setLoading(false), 1500);
7382
+ if (!loading) return;
7383
+ const t = setTimeout(() => setSlow(true), 8e3);
7334
7384
  return () => clearTimeout(t);
7335
- }, [iframeKey]);
7385
+ }, [iframeKey, loading]);
7336
7386
  const reload = React8.useCallback(() => {
7337
7387
  setIframeKey((k) => k + 1);
7338
7388
  setLoading(true);
7389
+ setSlow(false);
7339
7390
  }, []);
7340
7391
  const navigate = React8.useCallback(() => {
7392
+ let nextUrl;
7393
+ try {
7394
+ nextUrl = new URL(address, activeUrl);
7395
+ } catch {
7396
+ setAddressError("Enter a valid preview URL.");
7397
+ return;
7398
+ }
7399
+ if (nextUrl.protocol !== "http:" && nextUrl.protocol !== "https:") {
7400
+ setAddressError("Use an HTTP or HTTPS preview URL.");
7401
+ return;
7402
+ }
7403
+ setAddressError(null);
7404
+ setActiveUrl(nextUrl.toString());
7341
7405
  setIframeKey((k) => k + 1);
7342
7406
  setLoading(true);
7343
- }, []);
7407
+ setSlow(false);
7408
+ }, [activeUrl, address]);
7344
7409
  return /* @__PURE__ */ jsxs5("div", { className: cn("flex h-full min-h-0 flex-col bg-surface-container", className), children: [
7345
7410
  /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 border-b border-[var(--md3-outline-variant)] bg-surface-container-low px-2 py-1.5", children: [
7346
7411
  /* @__PURE__ */ jsx8(
@@ -7357,16 +7422,24 @@ function PreviewView({ url, className }) {
7357
7422
  "input",
7358
7423
  {
7359
7424
  value: address,
7360
- onChange: (e) => setAddress(e.target.value),
7361
- onKeyDown: (e) => e.key === "Enter" && navigate(),
7425
+ onChange: (e) => {
7426
+ setAddress(e.target.value);
7427
+ setAddressError(null);
7428
+ },
7429
+ onKeyDown: (e) => {
7430
+ if (e.key === "Enter") navigate();
7431
+ },
7432
+ "aria-label": "Preview address",
7433
+ "aria-invalid": addressError != null,
7434
+ "aria-describedby": addressError ? errorId : void 0,
7362
7435
  spellCheck: false,
7363
- className: "h-7 flex-1 rounded-md border border-[var(--md3-outline-variant)] bg-surface-container px-2.5 font-mono text-xs text-foreground focus:outline-none focus-visible:ring-1 focus-visible:ring-ring"
7436
+ className: `h-7 flex-1 rounded-md border bg-surface-container px-2.5 font-mono text-xs text-foreground ${focusField}`
7364
7437
  }
7365
7438
  ),
7366
7439
  /* @__PURE__ */ jsx8(
7367
7440
  "a",
7368
7441
  {
7369
- href: address,
7442
+ href: activeUrl,
7370
7443
  target: "_blank",
7371
7444
  rel: "noopener noreferrer",
7372
7445
  "aria-label": "Open preview in new tab",
@@ -7375,23 +7448,23 @@ function PreviewView({ url, className }) {
7375
7448
  }
7376
7449
  )
7377
7450
  ] }),
7451
+ addressError && /* @__PURE__ */ jsx8("p", { id: errorId, role: "alert", className: "px-3 py-1 text-xs text-destructive", children: addressError }),
7378
7452
  /* @__PURE__ */ jsxs5("div", { className: "relative min-h-0 flex-1 bg-surface-container-lowest", children: [
7379
7453
  /* @__PURE__ */ jsx8(
7380
7454
  "iframe",
7381
7455
  {
7382
- src: address,
7456
+ src: activeUrl,
7383
7457
  title: "Sandbox preview",
7384
7458
  className: "h-full w-full border-0 bg-white",
7385
7459
  sandbox: "allow-scripts allow-same-origin allow-forms allow-popups",
7386
- onLoad: () => setLoading(false)
7460
+ onLoad: () => {
7461
+ setLoading(false);
7462
+ setSlow(false);
7463
+ }
7387
7464
  },
7388
- `${iframeKey}-${address}`
7465
+ iframeKey
7389
7466
  ),
7390
- loading && /* @__PURE__ */ jsx8("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-surface-container-lowest", children: /* @__PURE__ */ jsxs5("p", { className: "text-xs text-muted-foreground", children: [
7391
- "Loading ",
7392
- address,
7393
- "\u2026"
7394
- ] }) })
7467
+ loading && /* @__PURE__ */ jsx8("div", { role: "status", className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-surface-container-lowest", children: /* @__PURE__ */ jsx8("p", { className: "px-4 text-center text-xs text-muted-foreground", children: slow ? "Preview is still loading. Check the address or open it in a new tab." : `Loading ${activeUrl}\u2026` }) })
7395
7468
  ] })
7396
7469
  ] });
7397
7470
  }
@@ -7441,6 +7514,8 @@ function SandboxArtifactPane({
7441
7514
  const [internalView, setInternalView] = React9.useState(
7442
7515
  () => defaultView && (defaultView !== "diff" || hasDiff) ? defaultView : "code"
7443
7516
  );
7517
+ const tabIdPrefix = React9.useId();
7518
+ const panelId = `${tabIdPrefix}-panel`;
7444
7519
  const controlled = activeView != null;
7445
7520
  const requestedView = controlled ? activeView : internalView;
7446
7521
  const view = available.includes(requestedView) ? requestedView : available[0];
@@ -7488,49 +7563,69 @@ function SandboxArtifactPane({
7488
7563
  copyText
7489
7564
  }
7490
7565
  ),
7491
- /* @__PURE__ */ jsx9("div", { className: "flex items-center border-b border-[var(--md3-outline-variant)] bg-surface-container-lowest px-3 py-1.5", children: /* @__PURE__ */ jsx9(PillTabs, { items: tabs, value: view, onChange: selectView, "aria-label": "Artifact view" }) }),
7492
- /* @__PURE__ */ jsxs6("div", { className: "min-h-0 flex-1", children: [
7493
- view === "code" && /* @__PURE__ */ jsx9(
7494
- CodeView,
7495
- {
7496
- path: artifact.path,
7497
- filename: artifact.filename,
7498
- content: artifact.content,
7499
- language: artifact.language,
7500
- paths,
7501
- gitStatus,
7502
- showTree,
7503
- onFileSelect
7504
- }
7505
- ),
7506
- view === "diff" && hasDiff && /* @__PURE__ */ jsx9(
7507
- DiffView,
7508
- {
7509
- filename: artifact.filename,
7510
- baseline: artifact.baselineContent ?? "",
7511
- current: artifact.content
7512
- }
7513
- ),
7514
- view === "preview" && hasPreview && /* @__PURE__ */ jsx9(PreviewView, { url: previewUrl }),
7515
- view === "ports" && hasPorts && /* @__PURE__ */ jsx9("div", { className: "h-full overflow-auto bg-surface-container p-4", children: /* @__PURE__ */ jsx9(
7516
- PortsList,
7517
- {
7518
- ports,
7519
- onExposePort: onExposePort ?? noop,
7520
- onRemovePort
7521
- }
7522
- ) }),
7523
- view === "terminal" && hasTerminal && /* @__PURE__ */ jsx9("div", { className: "h-full bg-surface-container-lowest", children: /* @__PURE__ */ jsx9(
7524
- TerminalView,
7525
- {
7526
- apiUrl: terminal.apiUrl,
7527
- token: terminal.token,
7528
- connectionId: terminal.connectionId,
7529
- title: terminal.title,
7530
- subtitle: terminal.subtitle
7531
- }
7532
- ) })
7533
- ] })
7566
+ /* @__PURE__ */ jsx9("div", { className: "flex items-center border-b border-[var(--md3-outline-variant)] bg-surface-container-lowest px-3 py-1.5", children: /* @__PURE__ */ jsx9(
7567
+ PillTabs,
7568
+ {
7569
+ items: tabs,
7570
+ value: view,
7571
+ onChange: selectView,
7572
+ "aria-label": "Artifact view",
7573
+ idPrefix: tabIdPrefix,
7574
+ panelId
7575
+ }
7576
+ ) }),
7577
+ /* @__PURE__ */ jsxs6(
7578
+ "div",
7579
+ {
7580
+ id: panelId,
7581
+ role: "tabpanel",
7582
+ "aria-labelledby": `${tabIdPrefix}-${view}`,
7583
+ tabIndex: 0,
7584
+ className: `min-h-0 flex-1 ${focusRingInset}`,
7585
+ children: [
7586
+ view === "code" && /* @__PURE__ */ jsx9(
7587
+ CodeView,
7588
+ {
7589
+ path: artifact.path,
7590
+ filename: artifact.filename,
7591
+ content: artifact.content,
7592
+ language: artifact.language,
7593
+ paths,
7594
+ gitStatus,
7595
+ showTree,
7596
+ onFileSelect
7597
+ }
7598
+ ),
7599
+ view === "diff" && hasDiff && /* @__PURE__ */ jsx9(
7600
+ DiffView,
7601
+ {
7602
+ filename: artifact.filename,
7603
+ baseline: artifact.baselineContent ?? "",
7604
+ current: artifact.content
7605
+ }
7606
+ ),
7607
+ view === "preview" && hasPreview && /* @__PURE__ */ jsx9(PreviewView, { url: previewUrl }),
7608
+ view === "ports" && hasPorts && /* @__PURE__ */ jsx9("div", { className: "h-full overflow-auto bg-surface-container p-4", children: /* @__PURE__ */ jsx9(
7609
+ PortsList,
7610
+ {
7611
+ ports,
7612
+ onExposePort: onExposePort ?? noop,
7613
+ onRemovePort
7614
+ }
7615
+ ) }),
7616
+ view === "terminal" && hasTerminal && /* @__PURE__ */ jsx9("div", { className: "h-full bg-surface-container-lowest", children: /* @__PURE__ */ jsx9(
7617
+ TerminalView,
7618
+ {
7619
+ apiUrl: terminal.apiUrl,
7620
+ token: terminal.token,
7621
+ connectionId: terminal.connectionId,
7622
+ title: terminal.title,
7623
+ subtitle: terminal.subtitle
7624
+ }
7625
+ ) })
7626
+ ]
7627
+ }
7628
+ )
7534
7629
  ]
7535
7630
  }
7536
7631
  );
@@ -7540,6 +7635,7 @@ function SandboxArtifactPane({
7540
7635
  import * as React10 from "react";
7541
7636
  import { GitBranch, RefreshCw } from "lucide-react";
7542
7637
  import { Button, Skeleton, Textarea } from "@tangle-network/ui/primitives";
7638
+ import { focusRing as focusRing3, focusRingInset as focusRingInset2 } from "@tangle-network/ui/utils";
7543
7639
  import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
7544
7640
  var BORDER = "border-[var(--md3-outline-variant)]";
7545
7641
  function splitPath(path) {
@@ -7710,7 +7806,7 @@ function ChangesPane({
7710
7806
  type: "button",
7711
7807
  onClick: onRefresh,
7712
7808
  "aria-label": "Refresh",
7713
- className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-surface-container-high hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60",
7809
+ className: `rounded-md p-1 text-muted-foreground transition-colors hover:bg-surface-container-high hover:text-foreground ${focusRing3}`,
7714
7810
  children: /* @__PURE__ */ jsx10(RefreshCw, { className: cn("h-3.5 w-3.5", loading && "animate-spin") })
7715
7811
  }
7716
7812
  )
@@ -7751,7 +7847,8 @@ function ChangesPane({
7751
7847
  tabIndex: tabbable ? 0 : -1,
7752
7848
  onClick: () => onSelectFile?.(file.path),
7753
7849
  className: cn(
7754
- "flex cursor-pointer items-center gap-2 px-3 py-1.5 text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary/60",
7850
+ "flex cursor-pointer items-center gap-2 px-3 py-1.5 text-xs transition-colors",
7851
+ focusRingInset2,
7755
7852
  isSelected ? "bg-surface-container-high" : "hover:bg-surface-container"
7756
7853
  ),
7757
7854
  children: [