foss-design 0.3.5 → 0.3.6

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.
@@ -14,7 +14,7 @@
14
14
  document.documentElement.dataset.theme = dark ? 'dark' : 'light'
15
15
  } catch (e) {}
16
16
  </script>
17
- <script type="module" crossorigin src="./assets/index-Cbriu8my.js"></script>
17
+ <script type="module" crossorigin src="./assets/index-DmtHtusH.js"></script>
18
18
  <link rel="stylesheet" crossorigin href="./assets/index-YT1s8WdL.css">
19
19
  </head>
20
20
  <body>
@@ -6999,6 +6999,10 @@ function Toaster() {
6999
6999
  name: "alert",
7000
7000
  size: 15,
7001
7001
  className: "text-danger-text"
7002
+ }) : item.tone === "info" ? /* @__PURE__ */ jsx(Icon, {
7003
+ name: "info",
7004
+ size: 15,
7005
+ className: "text-muted"
7002
7006
  }) : /* @__PURE__ */ jsx(Icon, {
7003
7007
  name: "check",
7004
7008
  size: 15,
@@ -7106,6 +7110,31 @@ function frameSrc(url, theme, extra) {
7106
7110
  function absoluteUrl(url) {
7107
7111
  return new URL(url, document.baseURI).toString();
7108
7112
  }
7113
+ /**
7114
+ * A link or form the runtime kept from leaving its frame. A path whose last part names a screen
7115
+ * or URL item on the canvas (`/settings`, `settings.html`) goes there; a link to another site
7116
+ * opens in a new tab; anything else says where it pointed.
7117
+ */
7118
+ function followLink(message, canvas, go) {
7119
+ if (message.form) {
7120
+ toast("Forms are not sent from a screen", void 0, "info");
7121
+ return;
7122
+ }
7123
+ if (message.path === null) {
7124
+ const tab = window.open(message.href, "_blank");
7125
+ if (tab) tab.opener = null;
7126
+ else toast("Link to another site", message.href, "info");
7127
+ return;
7128
+ }
7129
+ const path = message.path.split(/[?#]/)[0].replace(/\/+$/, "");
7130
+ let name = path.slice(path.lastIndexOf("/") + 1).replace(/\.html?$/, "");
7131
+ try {
7132
+ name = decodeURIComponent(name);
7133
+ } catch {}
7134
+ const ids = new Set(canvas.pages.flatMap((page) => page.sections.flatMap((section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url").map((item) => item.id))));
7135
+ if (name && ids.has(name)) go(name);
7136
+ else toast("Not a screen on this canvas", message.path, "info");
7137
+ }
7109
7138
  //#endregion
7110
7139
  //#region viewer/src/components/inspector.tsx
7111
7140
  /**
@@ -8779,7 +8808,7 @@ function FullPage({ canvasId, itemId }) {
8779
8808
  else if (message.type === "go") {
8780
8809
  const id = message.target.includes("/") ? message.target.split("/")[1] : message.target;
8781
8810
  if (items.some((entry) => entry.id === id)) navigate(fullHref(canvasId, id, theme), { replace: true });
8782
- }
8811
+ } else if (message.type === "link" && canvas) followLink(message, canvas, (id) => navigate(fullHref(canvasId, id, theme), { replace: true }));
8783
8812
  });
8784
8813
  }, [url, item?.kind]);
8785
8814
  if (error) return /* @__PURE__ */ jsx(NotFound, {
@@ -11137,6 +11166,9 @@ var FrameItem = memo(function FrameItem({ placed, mode, thumb, theme, store, eve
11137
11166
  case "go":
11138
11167
  events.onGo(item.id, message.target);
11139
11168
  break;
11169
+ case "link":
11170
+ events.onLink(item.id, message);
11171
+ break;
11140
11172
  case "wheel":
11141
11173
  events.onWheel(frame, message);
11142
11174
  break;
@@ -12078,6 +12110,9 @@ function CanvasView({ canvas, page }) {
12078
12110
  }
12079
12111
  console.warn(`[design] go("${target}"): no such screen`);
12080
12112
  },
12113
+ onLink(from, message) {
12114
+ followLink(message, canvas, (id) => this.onGo(from, id));
12115
+ },
12081
12116
  onWheel(frame, message) {
12082
12117
  const root = frame.closest(".canvas-root");
12083
12118
  if (!root) return;
@@ -12509,7 +12544,10 @@ function Player({ canvas, itemId }) {
12509
12544
  const [pagePart, idPart] = message.target.includes("/") ? message.target.split("/") : [null, message.target];
12510
12545
  const target = all.find((e) => e.item.id === idPart && (!pagePart || e.pageId === pagePart));
12511
12546
  if (target) navigate(playHref(canvas.id, target.item.id, target.pageId));
12512
- }
12547
+ } else if (message.type === "link") followLink(message, canvas, (id) => {
12548
+ const target = all.find((e) => e.item.id === id);
12549
+ if (target) navigate(playHref(canvas.id, target.item.id, target.pageId));
12550
+ });
12513
12551
  });
12514
12552
  }, [
12515
12553
  item,
@@ -284,6 +284,18 @@ export type RuntimeMessage = {
284
284
  key: string;
285
285
  type: 'go';
286
286
  target: string;
287
+ }
288
+ /**
289
+ * A link or form in the screen would have taken the frame to another page, which the frame's
290
+ * origin does not serve; the runtime kept the frame put. `path` is set for its own origin.
291
+ */
292
+ | {
293
+ source: 'design-runtime';
294
+ key: string;
295
+ type: 'link';
296
+ href: string;
297
+ path: string | null;
298
+ form: boolean;
287
299
  } | {
288
300
  source: 'design-runtime';
289
301
  key: string;
@@ -1,4 +1,4 @@
1
- import type { RuntimeMessage, Theme, ViewerMessage } from '../../../src/shared/types';
1
+ import type { CanvasDoc, RuntimeMessage, Theme, ViewerMessage } from '../../../src/shared/types';
2
2
  type Handler = (message: RuntimeMessage) => void;
3
3
  export declare function listenToFrame(frame: HTMLIFrameElement, handler: Handler): () => void;
4
4
  type Outgoing = ViewerMessage extends infer M ? M extends {
@@ -22,4 +22,12 @@ export declare const FRAME_ORIGIN: string;
22
22
  export declare function frameSrc(url: string, theme: Theme, extra?: Record<string, string>): string;
23
23
  /** Absolute URL for opening a frame in a new tab. */
24
24
  export declare function absoluteUrl(url: string): string;
25
+ /**
26
+ * A link or form the runtime kept from leaving its frame. A path whose last part names a screen
27
+ * or URL item on the canvas (`/settings`, `settings.html`) goes there; a link to another site
28
+ * opens in a new tab; anything else says where it pointed.
29
+ */
30
+ export declare function followLink(message: Extract<RuntimeMessage, {
31
+ type: 'link';
32
+ }>, canvas: CanvasDoc, go: (id: string) => void): void;
25
33
  export {};
@@ -1,9 +1,12 @@
1
- import type { CanvasSection, ImageItem, NoteItem, ScreenItem, Theme, UrlItem } from '../../../../src/shared/types';
1
+ import type { CanvasSection, ImageItem, NoteItem, RuntimeMessage, ScreenItem, Theme, UrlItem } from '../../../../src/shared/types';
2
2
  import type { Placed } from './layout';
3
3
  import { type ViewStore } from './view-state';
4
4
  import type { FrameMode } from './viewport';
5
5
  export interface FrameEvents {
6
6
  onGo(from: string, target: string): void;
7
+ onLink(from: string, message: Extract<RuntimeMessage, {
8
+ type: 'link';
9
+ }>): void;
7
10
  onWheel(frame: HTMLIFrameElement, message: {
8
11
  deltaX: number;
9
12
  deltaY: number;
@@ -1,4 +1,4 @@
1
- export type ToastTone = 'ok' | 'error';
1
+ export type ToastTone = 'ok' | 'info' | 'error';
2
2
  /** A short confirmation; `error` shows a failure and stays a little longer to be read. */
3
3
  export declare function toast(title: string, text?: string, tone?: ToastTone): void;
4
4
  /** Only the first mounted Toaster shows toasts: an app that embeds the viewer may keep its own. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foss-design",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Design canvases and design systems for coding agents: live, animated, clickable screens on an infinite canvas, served locally from a .design folder.",
5
5
  "type": "module",
6
6
  "license": "MIT",