foss-design 0.5.0 → 0.6.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.
@@ -467,6 +467,77 @@ var FrameInspector = class {
467
467
  };
468
468
  var SWALLOWED = ["pointerdown", "mousedown", "pointerup", "mouseup", "dblclick", "submit"];
469
469
 
470
+ // src/runtime/public.ts
471
+ var ATTRS = ["src", "srcset", "poster", "href", "xlink:href"];
472
+ var LOADS_HREF = /* @__PURE__ */ new Set(["link", "image", "use", "feimage"]);
473
+ function servePublicFolder(folder) {
474
+ if (!folder?.files.length) return;
475
+ const files = new Set(folder.files);
476
+ const base = new URL(folder.base, location.href);
477
+ const resolve = (value) => {
478
+ if (!value.startsWith("/") || value.startsWith("//")) return null;
479
+ const [, pathname = "", suffix = ""] = /^([^?#]*)(.*)$/.exec(value) ?? [];
480
+ let rel;
481
+ try {
482
+ rel = decodeURIComponent(pathname.slice(1));
483
+ } catch {
484
+ return null;
485
+ }
486
+ return files.has(rel) ? new URL(pathname.slice(1), base).href + suffix : null;
487
+ };
488
+ const rewrite = (name, value) => {
489
+ if (name !== "srcset") return resolve(value);
490
+ let changed = false;
491
+ const next = value.split(",").map((candidate) => {
492
+ const [url = "", ...rest] = candidate.trim().split(/\s+/);
493
+ const resolved = resolve(url);
494
+ if (!resolved) return candidate;
495
+ changed = true;
496
+ return [resolved, ...rest].join(" ");
497
+ }).join(", ");
498
+ return changed ? next : null;
499
+ };
500
+ const applies = (element, name) => name.endsWith("href") ? LOADS_HREF.has(element.localName.toLowerCase()) : true;
501
+ const setAttribute = Element.prototype.setAttribute;
502
+ Element.prototype.setAttribute = function(name, value) {
503
+ const lower = name.toLowerCase();
504
+ const next = ATTRS.includes(lower) && applies(this, lower) ? rewrite(lower, String(value)) : null;
505
+ setAttribute.call(this, name, next ?? value);
506
+ };
507
+ const fix = (element) => {
508
+ for (const name of ATTRS) {
509
+ const value = element.getAttribute(name);
510
+ if (value === null || !applies(element, name)) continue;
511
+ const next = rewrite(name, value);
512
+ if (next !== null) setAttribute.call(element, name, next);
513
+ }
514
+ };
515
+ const fixTree = (root) => {
516
+ fix(root);
517
+ for (const element of root.querySelectorAll("[src], [srcset], [poster], [href]")) fix(element);
518
+ };
519
+ new MutationObserver((records) => {
520
+ for (const record of records) {
521
+ if (record.type === "attributes") fix(record.target);
522
+ for (const node of record.addedNodes) if (node.nodeType === Node.ELEMENT_NODE) fixTree(node);
523
+ }
524
+ }).observe(document, {
525
+ subtree: true,
526
+ childList: true,
527
+ attributes: true,
528
+ attributeFilter: ["src", "srcset", "poster", "href"]
529
+ });
530
+ if (document.documentElement) fixTree(document.documentElement);
531
+ const fetch2 = window.fetch.bind(window);
532
+ window.fetch = (input, init) => {
533
+ const raw = typeof input === "string" ? input : input instanceof URL ? input.href : null;
534
+ let path = raw;
535
+ if (raw && input instanceof URL && input.origin === location.origin) path = input.pathname + input.search;
536
+ const resolved = path ? resolve(path) : null;
537
+ return fetch2(resolved ?? input, init);
538
+ };
539
+ }
540
+
470
541
  // src/runtime/typography.tsx
471
542
  import { useEffect, useSyncExternalStore } from "react";
472
543
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -566,6 +637,7 @@ var config = window.__DESIGN__ ?? { key: "", canvas: "", id: "", props: {} };
566
637
  var embedded = window.parent !== window;
567
638
  var query = new URLSearchParams(window.location.search);
568
639
  var capture = query.has("capture");
640
+ servePublicFolder(config.public);
569
641
  var theme = config.theme ?? (query.get("theme") === "dark" ? "dark" : "light");
570
642
  var themeListeners = /* @__PURE__ */ new Set();
571
643
  function applyTheme(next) {
@@ -370,4 +370,11 @@ export type ViewerMessage = {
370
370
  type: 'inspect-select';
371
371
  ref: number | null;
372
372
  };
373
+ /** design.json `public` as a frame sees it. */
374
+ export interface PublicFolder {
375
+ /** Where the folder's files are, relative to the frame's URL. */
376
+ base: string;
377
+ /** Its files, relative to the folder, with forward slashes. */
378
+ files: string[];
379
+ }
373
380
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foss-design",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
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",