@open-flow/ui 0.3.1 → 0.5.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.
Files changed (65) hide show
  1. package/dist/app.d.ts +2 -1
  2. package/dist/app.js +1 -1
  3. package/dist/canvas-gestures.d.ts +15 -0
  4. package/dist/canvas-gestures.js +1 -0
  5. package/dist/connect-popover.d.ts +9 -0
  6. package/dist/connect-popover.js +1 -0
  7. package/dist/cta.d.ts +17 -0
  8. package/dist/cta.js +1 -0
  9. package/dist/dev-actions.d.ts +2 -1
  10. package/dist/dev-actions.js +1 -1
  11. package/dist/dev-actions.noop.d.ts +2 -1
  12. package/dist/dev-actions.noop.js +1 -1
  13. package/dist/floating.d.ts +35 -0
  14. package/dist/floating.js +1 -0
  15. package/dist/flow-canvas.d.ts +103 -0
  16. package/dist/flow-canvas.js +1 -0
  17. package/dist/flow-edit.d.ts +61 -0
  18. package/dist/flow-edit.js +1 -0
  19. package/dist/flow-frames.d.ts +25 -0
  20. package/dist/flow-frames.js +1 -0
  21. package/dist/flow-graph.d.ts +109 -0
  22. package/dist/flow-graph.js +1 -0
  23. package/dist/flow-layout.d.ts +30 -0
  24. package/dist/flow-layout.js +1 -0
  25. package/dist/flow-start.d.ts +9 -0
  26. package/dist/flow-start.js +1 -0
  27. package/dist/frame-view.js +1 -1
  28. package/dist/frame.d.ts +21 -1
  29. package/dist/frame.js +1 -1
  30. package/dist/index.d.ts +3 -3
  31. package/dist/index.js +1 -1
  32. package/dist/journey-acts.d.ts +19 -0
  33. package/dist/journey-acts.js +1 -0
  34. package/dist/manifest.d.ts +3 -2
  35. package/dist/manifest.js +1 -1
  36. package/dist/manifest.types.d.ts +11 -1
  37. package/dist/manifest.types.js +1 -1
  38. package/dist/mirror.d.ts +6 -1
  39. package/dist/mirror.js +1 -1
  40. package/dist/node-actions.d.ts +24 -0
  41. package/dist/node-actions.js +1 -0
  42. package/dist/node-menu.d.ts +13 -0
  43. package/dist/node-menu.js +1 -0
  44. package/dist/panels/components.js +1 -1
  45. package/dist/panels/elements.js +1 -1
  46. package/dist/panels/flow-canvas-board.d.ts +49 -0
  47. package/dist/panels/flow-canvas-board.js +1 -0
  48. package/dist/panels/flow-cards.d.ts +54 -0
  49. package/dist/panels/flow-cards.js +1 -0
  50. package/dist/panels/flows.d.ts +5 -5
  51. package/dist/panels/flows.js +1 -1
  52. package/dist/pick-highlight.d.ts +26 -0
  53. package/dist/pick-highlight.js +1 -1
  54. package/dist/pin.d.ts +34 -0
  55. package/dist/screen-frame.d.ts +11 -2
  56. package/dist/screen-frame.js +1 -1
  57. package/dist/screen-sketch.d.ts +30 -0
  58. package/dist/screen-sketch.js +1 -0
  59. package/dist/screen-toolbar.d.ts +4 -3
  60. package/dist/screen-toolbar.js +1 -1
  61. package/dist/server.d.ts +2 -1
  62. package/dist/server.js +2 -1
  63. package/dist/workbench-app.d.ts +3 -2
  64. package/dist/workbench-app.js +1 -1
  65. package/package.json +1 -1
package/dist/app.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { NewTicketInput, TicketSummary } from "./pin";
1
+ import type { NewTicketInput, SaveFlowInput, SaveFlowResult, TicketSummary } from "./pin";
2
2
  import type { RegistryEntry } from "./registry";
3
3
  import type { CreateResult } from "./ticket-drawer";
4
4
  export interface WorkbenchClientProps {
@@ -6,5 +6,6 @@ export interface WorkbenchClientProps {
6
6
  registry: RegistryEntry[];
7
7
  onCreateTicket: (input: NewTicketInput) => Promise<CreateResult>;
8
8
  onLoadTickets?: () => Promise<TicketSummary[]>;
9
+ onSaveFlow?: (input: SaveFlowInput) => Promise<SaveFlowResult>;
9
10
  }
10
11
  export declare function WorkbenchClient(props: WorkbenchClientProps): import("react").JSX.Element;
package/dist/app.js CHANGED
@@ -1 +1 @@
1
- "use client";import{jsx as r}from"react/jsx-runtime";import{Suspense as p,useEffect as d,useMemo as l,useState as h}from"react";import{useSearchParams as g}from"next/navigation";import{FrameView as v}from"./frame-view";import{parseManifest as w}from"./parse-manifest";import{WorkbenchApp as b}from"./workbench-app";function V(n){return r(p,{fallback:null,children:r(x,{...n})})}function k(n){const e=o=>n.get(o)??void 0;return{tab:e("tab"),component:e("component"),flow:e("flow"),surface:e("surface"),vp:e("vp"),zoom:e("zoom"),theme:e("theme"),chrome:e("chrome")}}function x({manifest:n,registry:e,onCreateTicket:o,onLoadTickets:i}){const t=g(),[u,c]=h([]),m=l(()=>w(n),[n]);d(()=>{if(!i)return;let s=!0;return i().then(a=>{s&&c(a)}),()=>{s=!1}},[i]);const f=t.get("route")??void 0;return t.get("view")==="frame"||f?r(v,{manifest:m,registry:e,componentId:t.get("component")??void 0,route:f,viewport:t.get("viewport")??void 0,theme:t.get("theme")??void 0,scenario:t.get("scenario")??void 0}):r(b,{manifest:m,registry:e,tickets:u,view:k(t),onCreateTicket:o})}export{V as WorkbenchClient};
1
+ "use client";import{jsx as r}from"react/jsx-runtime";import{Suspense as d,useEffect as l,useMemo as h,useState as v}from"react";import{useSearchParams as g}from"next/navigation";import{FrameView as w}from"./frame-view";import{parseManifest as b}from"./parse-manifest";import{WorkbenchApp as k}from"./workbench-app";function V(n){return r(d,{fallback:null,children:r(x,{...n})})}function S(n){const e=o=>n.get(o)??void 0;return{tab:e("tab"),component:e("component"),flow:e("flow"),surface:e("surface"),vp:e("vp"),zoom:e("zoom"),theme:e("theme"),chrome:e("chrome")}}function x({manifest:n,registry:e,onCreateTicket:o,onLoadTickets:i,onSaveFlow:u}){const t=g(),[c,a]=v([]),m=h(()=>b(n),[n]);l(()=>{if(!i)return;let s=!0;return i().then(p=>{s&&a(p)}),()=>{s=!1}},[i]);const f=t.get("route")??void 0;return t.get("view")==="frame"||f?r(w,{manifest:m,registry:e,componentId:t.get("component")??void 0,route:f,viewport:t.get("viewport")??void 0,theme:t.get("theme")??void 0,scenario:t.get("scenario")??void 0}):r(k,{manifest:m,registry:e,tickets:c,view:S(t),onCreateTicket:o,onSaveFlow:u})}export{V as WorkbenchClient};
@@ -0,0 +1,15 @@
1
+ export interface CanvasGestures {
2
+ zoomAtClient: (clientX: number, clientY: number, deltaY: number, deltaMode: number) => void;
3
+ }
4
+ export declare const ZOOM_ATTR = "data-workbench-zoom";
5
+ export declare const CanvasGesturesContext: import("react").Context<CanvasGestures | null>;
6
+ export declare const ZOOM_VAR = "--wb-z";
7
+ export declare const NODE_WIDTH_VAR = "--wb-node-w";
8
+ export declare const CANVAS_BG_VAR = "--wb-canvas-bg";
9
+ export declare const EDGE_VAR = "--wb-edge";
10
+ export type CanvasDetail = "far" | "near";
11
+ export declare const DETAIL_IN = 0.55;
12
+ export declare const DETAIL_OUT = 0.45;
13
+ export declare function detailAt(z: number, previous: CanvasDetail): CanvasDetail;
14
+ export declare const CanvasDetailContext: import("react").Context<CanvasDetail>;
15
+ export declare const CANVAS_ATTR = "data-workbench-canvas";
@@ -0,0 +1 @@
1
+ "use client";import{createContext as e}from"react";const c="data-workbench-zoom",s=e(null),x="--wb-z",A="--wb-node-w",p="--wb-canvas-bg",_="--wb-edge",n=.55,r=.45;function T(t,o){return t>=n?"near":t<=r?"far":o}const b=e("near"),i="data-workbench-canvas";export{i as CANVAS_ATTR,p as CANVAS_BG_VAR,b as CanvasDetailContext,s as CanvasGesturesContext,n as DETAIL_IN,r as DETAIL_OUT,_ as EDGE_VAR,A as NODE_WIDTH_VAR,c as ZOOM_ATTR,x as ZOOM_VAR,T as detailAt};
@@ -0,0 +1,9 @@
1
+ import type { ConnectAction, ConnectIntent } from "./flow-edit";
2
+ import type { MirrorPick } from "./frame";
3
+ export declare function ConnectPopover({ pick, route, intent, onConfirm, onCancel, }: {
4
+ pick: MirrorPick;
5
+ route: string | null;
6
+ intent: ConnectIntent;
7
+ onConfirm: (action: ConnectAction) => void;
8
+ onCancel: () => void;
9
+ }): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsxs as t,jsx as m}from"react/jsx-runtime";import{useEffect as h,useRef as p}from"react";import{cx as b}from"./cx";import{useFloating as k}from"./floating";import{CHIP as y}from"./screen-toolbar";function j({pick:a,route:o,intent:r,onConfirm:c,onCancel:s}){const e=r.actions[0]??null,n=r.actions[1]??null,l=p(null),d=p(null),{ref:u,placed:i,style:x}=k(a.rect,s,r);return h(()=>{i&&(e?l.current:d.current)?.focus()},[i,e]),t("div",{ref:u,role:"dialog","aria-label":`Conectar \xAB${a.text||"control"}\xBB`,onKeyDown:f=>{f.key==="Escape"&&s()},className:"fixed z-[2147483050] w-72 rounded-md border border-zinc-200 bg-white p-2 shadow-lg dark:border-zinc-700 dark:bg-zinc-900",style:i?x:{...x,visibility:"hidden"},children:[t("p",{className:"truncate text-[12px] font-medium text-zinc-900 dark:text-zinc-100",children:["\xAB",a.text||"sin texto","\xBB",o&&t("span",{className:"ml-1 font-mono text-[11px] font-normal text-zinc-500 dark:text-zinc-400",children:["\u2192 ",o]})]}),t("p",{className:"mt-0.5 text-[11px] text-zinc-500 dark:text-zinc-400",children:[e?e.hint:"No se puede a\xF1adir desde aqu\xED.",!o&&e&&" \xB7 el control no lleva a una ruta de esta app, escr\xEDbela en la tarjeta"]}),r.existing&&t("p",{className:"mt-0.5 text-[11px] text-amber-700 dark:text-amber-300",children:["Esa pantalla ya est\xE1 en el recorrido (paso ",r.existing.number,")."]}),r.note&&m("p",{className:"mt-0.5 text-[11px] text-zinc-500 dark:text-zinc-400",children:r.note}),t("div",{className:"mt-2 flex items-center gap-2",children:[e&&t("button",{ref:l,type:"button",onClick:()=>c(e),className:"shrink-0 whitespace-nowrap rounded-md bg-zinc-900 px-2.5 py-1 text-[11px] font-medium text-white dark:bg-zinc-100 dark:text-zinc-900",children:["+ ",e.button]}),n&&t("button",{type:"button",onClick:()=>c(n),className:"shrink-0 whitespace-nowrap rounded px-1 text-[11px] font-medium text-sky-700 underline decoration-dotted underline-offset-2 hover:text-sky-900 dark:text-sky-300 dark:hover:text-sky-100",title:n.hint,children:["\xBF",n.verb,"?"]}),m("button",{ref:d,type:"button",onClick:s,className:b(y),children:"Cancelar"})]})]})}export{j as ConnectPopover};
package/dist/cta.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ export interface CtaCandidate {
2
+ tag: string;
3
+ role: string | null;
4
+ type: string | null;
5
+ href: boolean;
6
+ onclick: boolean;
7
+ cursor: string;
8
+ name: string;
9
+ share: number;
10
+ disabled: boolean;
11
+ }
12
+ export declare const CTA_MAX_SHARE = 0.5;
13
+ export declare const CTA_MAX_DEPTH = 5;
14
+ export declare function isSemanticCta(node: CtaCandidate): boolean;
15
+ export declare function isStyledCta(node: CtaCandidate): boolean;
16
+ export declare function ctaOf(chain: readonly CtaCandidate[]): CtaCandidate | null;
17
+ export declare function semanticCtaOf(chain: readonly CtaCandidate[]): CtaCandidate | null;
package/dist/cta.js ADDED
@@ -0,0 +1 @@
1
+ const i=new Set(["button","link","menuitem","menuitemcheckbox","tab","option"]),a=new Set(["BUTTON","SUMMARY","SELECT"]),s=new Set(["submit","button","reset","image"]),r=new Set(["HTML","BODY","MAIN","HEADER","FOOTER","NAV","SECTION","FORM"]),u=new Set(["LABEL","OPTION","INPUT","TEXTAREA","SELECT"]),f=.5,n=5;function l(t){return t.disabled||r.has(t.tag)?!1:t.tag==="A"?t.href:a.has(t.tag)?!0:t.tag==="INPUT"?t.type!==null&&s.has(t.type):t.role!==null&&i.has(t.role)?!0:t.onclick}function c(t){return t.disabled||r.has(t.tag)||u.has(t.tag)||t.cursor!=="pointer"||t.name.trim().length===0?!1:t.share<=f}function E(t){const e=t.slice(0,n);return T(e)??e.find(c)??null}function T(t){return t.slice(0,n).find(l)??null}export{n as CTA_MAX_DEPTH,f as CTA_MAX_SHARE,E as ctaOf,l as isSemanticCta,c as isStyledCta,T as semanticCtaOf};
@@ -1,4 +1,4 @@
1
- import type { NewTicketInput, TicketSummary } from "./pin";
1
+ import type { NewTicketInput, SaveFlowInput, TicketSummary } from "./pin";
2
2
  export declare function createTicket(input: NewTicketInput): Promise<{
3
3
  ok: boolean;
4
4
  id?: string;
@@ -7,3 +7,4 @@ export declare function createTicket(input: NewTicketInput): Promise<{
7
7
  error?: string;
8
8
  }>;
9
9
  export declare function loadTickets(): Promise<TicketSummary[]>;
10
+ export declare function saveFlow(input: SaveFlowInput): Promise<import("./pin").SaveFlowResult>;
@@ -1 +1 @@
1
- "use server";import{readTickets as r,writeTicket as t}from"./server";async function i(e){return t(e)}async function n(){return r()}export{i as createTicket,n as loadTickets};
1
+ "use server";import{readTickets as r,saveFlow as t,writeTicket as n}from"./server";async function c(e){return n(e)}async function i(){return r()}async function s(e){return t(e)}export{c as createTicket,i as loadTickets,s as saveFlow};
@@ -1,3 +1,4 @@
1
- import type { NewTicketInput, TicketSummary } from "./pin";
1
+ import type { NewTicketInput, SaveFlowInput, TicketSummary } from "./pin";
2
2
  export declare function createTicket(_input: NewTicketInput): Promise<never>;
3
3
  export declare function loadTickets(): Promise<TicketSummary[]>;
4
+ export declare function saveFlow(_input: SaveFlowInput): Promise<never>;
@@ -1 +1 @@
1
- const e="El taller s\xF3lo escribe tickets en desarrollo.";async function r(t){throw new Error(e)}async function n(){return[]}export{r as createTicket,n as loadTickets};
1
+ const r="El taller s\xF3lo escribe en desarrollo.";async function n(e){throw new Error(r)}async function o(){return[]}async function t(e){throw new Error(r)}export{n as createTicket,o as loadTickets,t as saveFlow};
@@ -0,0 +1,35 @@
1
+ export declare function placePopover(anchor: {
2
+ top: number;
3
+ left: number;
4
+ width: number;
5
+ height: number;
6
+ }, size: {
7
+ width: number;
8
+ height: number;
9
+ }, viewport: {
10
+ width: number;
11
+ height: number;
12
+ }): {
13
+ top: number;
14
+ left: number;
15
+ };
16
+ export declare function sameSpot(a: {
17
+ top: number;
18
+ left: number;
19
+ } | null, b: {
20
+ top: number;
21
+ left: number;
22
+ }): boolean;
23
+ export declare function useFloating(anchor: {
24
+ top: number;
25
+ left: number;
26
+ width: number;
27
+ height: number;
28
+ }, onDismiss: () => void, key?: unknown): {
29
+ ref: import("react").RefObject<HTMLDivElement | null>;
30
+ placed: boolean;
31
+ style: {
32
+ top: number;
33
+ left: number;
34
+ };
35
+ };
@@ -0,0 +1 @@
1
+ "use client";import{useEffect as L,useLayoutEffect as y,useRef as w,useState as M}from"react";import{CANVAS_ATTR as b}from"./canvas-gestures";function A(e,t,c){let r=e.top+e.height+6;r+t.height>c.height-8&&(r=Math.max(8,e.top-t.height-6));const u=Math.min(Math.max(e.left,8),Math.max(8,c.width-t.width-8));return{top:r,left:u}}function S(e,t){return e!==null&&e.top===t.top&&e.left===t.left}function R(e,t,c){const{top:f,left:r,width:u,height:h}=e,s=w(null),g=w(!1),[v,x]=M(null),d=w(t);return L(()=>{d.current=t},[t]),y(()=>{const n=s.current;if(!n)return;const i=A({top:f,left:r,width:u,height:h},{width:n.offsetWidth,height:n.offsetHeight},{width:window.innerWidth,height:window.innerHeight});x(o=>S(o,i)?o:i)},[f,r,u,h,c]),L(()=>{const n=document.activeElement,i=a=>{const m=a.target;(!(m instanceof Node)||!s.current?.contains(m))&&d.current()},o=()=>d.current(),p=s.current,l=()=>{g.current=!0},E=()=>{g.current&&d.current()};return document.addEventListener("pointerdown",i,!0),window.addEventListener("blur",E),window.addEventListener("scroll",o,!0),window.addEventListener("resize",o),p?.addEventListener("focusin",l),window.addEventListener("focus",l),()=>{document.removeEventListener("pointerdown",i,!0),p?.removeEventListener("focusin",l),window.removeEventListener("focus",l),window.removeEventListener("blur",E),window.removeEventListener("scroll",o,!0),window.removeEventListener("resize",o);const a=document.activeElement;if(a!==null&&a!==document.body)return;((n instanceof HTMLElement&&n.isConnected&&n.tagName!=="IFRAME"&&n!==document.body?n:null)??document.querySelector(`[${b}]`))?.focus()}},[]),{ref:s,placed:v!==null,style:v??{top:0,left:0}}}export{A as placePopover,S as sameSpot,R as useFloating};
@@ -0,0 +1,103 @@
1
+ import type { ReactNode } from "react";
2
+ import { type CanvasDetail } from "./canvas-gestures";
3
+ export interface CanvasViewport {
4
+ tx: number;
5
+ ty: number;
6
+ z: number;
7
+ }
8
+ export interface CanvasNode {
9
+ key: string;
10
+ x: number;
11
+ y: number;
12
+ width: number;
13
+ height: number;
14
+ element: ReactNode;
15
+ chrome?: ReactNode;
16
+ alwaysRender?: boolean;
17
+ behind?: boolean;
18
+ chromeLabel?: string;
19
+ onContextMenu?: (at: {
20
+ x: number;
21
+ y: number;
22
+ }) => void;
23
+ }
24
+ export interface CanvasEdgeProp {
25
+ id: string;
26
+ from: {
27
+ x: number;
28
+ y: number;
29
+ };
30
+ to: {
31
+ x: number;
32
+ y: number;
33
+ };
34
+ label: string | null;
35
+ variant: "solid" | "dashed";
36
+ waypoints?: ReadonlyArray<{
37
+ x: number;
38
+ y: number;
39
+ }>;
40
+ }
41
+ export interface CanvasBBox {
42
+ minX: number;
43
+ minY: number;
44
+ maxX: number;
45
+ maxY: number;
46
+ }
47
+ export declare const MIN_ZOOM = 0.1;
48
+ export declare const OPEN_ZOOM = 0.9;
49
+ export declare const MAX_ZOOM = 2;
50
+ export declare const FIT_PAD = 48;
51
+ export declare function zoomAt(viewport: CanvasViewport, cx: number, cy: number, k: number, min?: number, max?: number): CanvasViewport;
52
+ export declare function wheelZoomFactor(deltaY: number, deltaMode?: number): number;
53
+ export declare function fitViewport(host: {
54
+ width: number;
55
+ height: number;
56
+ }, bbox: CanvasBBox, pad?: number): CanvasViewport;
57
+ export declare function openViewport(host: {
58
+ width: number;
59
+ height: number;
60
+ }, bbox: CanvasBBox, first: {
61
+ width: number;
62
+ height: number;
63
+ } | null, pad?: number): CanvasViewport;
64
+ export declare function revealViewport(viewport: CanvasViewport, host: {
65
+ width: number;
66
+ height: number;
67
+ }, box: {
68
+ x: number;
69
+ y: number;
70
+ width: number;
71
+ height: number;
72
+ }, pad?: number): CanvasViewport;
73
+ interface Point {
74
+ x: number;
75
+ y: number;
76
+ }
77
+ export declare function edgePath(from: Point, to: Point, waypoints?: readonly Point[]): string;
78
+ export declare function edgeLabelPoint(from: Point, to: Point, waypoints?: readonly Point[]): {
79
+ x: number;
80
+ y: number;
81
+ };
82
+ export declare function FlowCanvas({ nodes, edges, bbox, fitKey, reveal, onViewportChange, onDetailChange, label, className, }: {
83
+ nodes: readonly CanvasNode[];
84
+ edges: readonly CanvasEdgeProp[];
85
+ bbox: CanvasBBox;
86
+ fitKey?: string;
87
+ reveal?: {
88
+ key: string;
89
+ tick: number;
90
+ } | null;
91
+ onViewportChange?: (viewport: CanvasViewport) => void;
92
+ onDetailChange?: (detail: CanvasDetail) => void;
93
+ label: string;
94
+ className?: string;
95
+ }): import("react").JSX.Element;
96
+ export declare function CanvasChrome({ anchor, clamp, className, style, children, }: {
97
+ anchor?: "top left" | "bottom left";
98
+ clamp?: boolean;
99
+ className?: string;
100
+ style?: React.CSSProperties;
101
+ children: ReactNode;
102
+ }): import("react").JSX.Element;
103
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{jsx as m,jsxs as M,Fragment as J}from"react/jsx-runtime";import{useEffect as $,useId as Q,useLayoutEffect as tt,useMemo as et,useRef as z,useState as B}from"react";import{CANVAS_BG_VAR as rt,CANVAS_ATTR as nt,CanvasDetailContext as it,DETAIL_IN as ot,CanvasGesturesContext as at,detailAt as ct,NODE_WIDTH_VAR as L,ZOOM_ATTR as st,ZOOM_VAR as P}from"./canvas-gestures";import{cx as T}from"./cx";const lt=.1,K=.9,ut=2,_=48,ht=(e,i,o)=>Math.min(o,Math.max(i,e));function v(e,i,o,c,l=lt,f=ut){const u=ht(e.z*c,Math.min(l,e.z),f);if(u===e.z)return e;const h=u/e.z;return{z:u,tx:i-(i-e.tx)*h,ty:o-(o-e.ty)*h}}function S(e,i=0){const o=i===1?e*16:i===2?e*160:e;return Math.exp(-o*.0015)}function j(e,i,o=_){const c=i.maxX-i.minX,l=i.maxY-i.minY;if(c<=0||l<=0)return{z:1,tx:e.width/2-i.minX,ty:e.height/2-i.minY};const f={width:Math.max(1,e.width-o*2),height:Math.max(1,e.height-o*2)},u=Math.min(f.width/c,f.height/l,1);return{z:u,tx:(e.width-c*u)/2-i.minX*u,ty:(e.height-l*u)/2-i.minY*u}}function dt(e,i,o,c=_){const l=j(e,i,c),f=Math.max(l.z,K),u=o?Math.min((e.width-c*2)/Math.max(1,o.width),(e.height-c*2)/Math.max(1,o.height)):f,h=Math.min(f,Math.max(l.z,ot,Math.min(K,u)));return h===l.z?l:{z:h,tx:c-i.minX*h,ty:c-i.minY*h}}function ft(e,i,o,c=_){const l=(x,g,w)=>{if(g+c*2>w||x<c)return c-x;const d=x+g;return d>w-c?w-c-d:0},f=e.tx+o.x*e.z,u=e.ty+o.y*e.z,h=l(f,o.width*e.z,i.width),b=l(u,o.height*e.z,i.height);return h===0&&b===0?e:{...e,tx:e.tx+h,ty:e.ty+b}}function xt(e,i,o){if(!o||o.length===0){const f=(e.x+i.x)/2;return`M ${e.x},${e.y} C ${f},${e.y} ${f},${i.y} ${i.x},${i.y}`}const c=[e,...o,i];let l=`M ${e.x},${e.y}`;for(let f=1;f<c.length;f+=1){const u=c[f-1],h=c[f];if(!u||!h)continue;const b=Math.abs(h.x-u.x)>=Math.abs(h.y-u.y),x=b?{x:(u.x+h.x)/2,y:u.y}:{x:u.x,y:(u.y+h.y)/2},g=b?{x:(u.x+h.x)/2,y:h.y}:{x:h.x,y:(u.y+h.y)/2};l+=` C ${x.x},${x.y} ${g.x},${g.y} ${h.x},${h.y}`}return l}function mt(e,i,o){if(o&&o.length>=2){const c=o[0],l=o[o.length-1];if(c&&l)return{x:(c.x+l.x)/2,y:(c.y+l.y)/2}}return{x:(e.x+i.x)/2,y:(e.y+i.y)/2}}const O=60;function zt({nodes:e,edges:i,bbox:o,fitKey:c,reveal:l,onViewportChange:f,onDetailChange:u,label:h,className:b}){const x=z(null),g=z(null),w=z(null),[d,p]=B(null),[C,Z]=B("near"),y=z(!1),A=z(null),F=Q(),E=(t="fit")=>{const r=x.current;if(!r)return;const n=r.getBoundingClientRect();if(n.width===0||n.height===0)return;const a={width:n.width,height:n.height},s=e.find(N=>!N.behind&&N.width>1)??null;p(t==="open"?dt(a,o,s&&{width:s.width,height:s.height}):j(a,o))},k=z(E);k.current=E,tt(()=>{k.current("open")},[]);const I=z(!0);$(()=>{if(I.current){I.current=!1;return}y.current=!1,k.current("open")},[c]),$(()=>{const t=x.current;if(!t||typeof ResizeObserver>"u")return;const r=new ResizeObserver(()=>{y.current||k.current("open")});return r.observe(t),()=>r.disconnect()},[]),$(()=>{u?.(C)},[C]),$(()=>{if(!l)return;const t=e.find(a=>a.key===l.key),r=x.current;if(!t||!r)return;const n=r.getBoundingClientRect();p(a=>a&&ft(a,{width:n.width,height:n.height},t))},[l?.tick]),$(()=>{d&&(f?.(d),Z(t=>{const r=ct(d.z,t);if(r==="far"&&t==="near"){const n=x.current,a=n?.ownerDocument.activeElement;n&&a instanceof HTMLElement&&n.contains(a)&&a!==n&&n.focus()}return r}))},[d]),$(()=>{const t=x.current;if(!t)return;const r=n=>{if(!n.ctrlKey&&!n.metaKey){const s=n.target;if(s instanceof Element&&s.closest("textarea, input, select, [contenteditable='true']"))return}n.preventDefault(),y.current=!0;const a=t.getBoundingClientRect();p(s=>s&&(n.ctrlKey||n.metaKey?v(s,n.clientX-a.left,n.clientY-a.top,S(n.deltaY,n.deltaMode)):{...s,tx:s.tx-n.deltaX,ty:s.ty-n.deltaY}))};return t.addEventListener("wheel",r,{passive:!1}),()=>t.removeEventListener("wheel",r)},[]);const R=t=>{y.current=!0;const r=x.current;if(!r)return;const n=r.getBoundingClientRect();p(a=>a&&v(a,n.width/2,n.height/2,t))},W=t=>{y.current=!0;const r=x.current;if(!r)return;const n=r.getBoundingClientRect();p(a=>a&&v(a,n.width/2,n.height/2,t/a.z))},H=t=>{if(t.button===0&&!(t.target!==x.current&&t.target!==g.current&&t.target!==w.current)){A.current={id:t.pointerId,x:t.clientX,y:t.clientY,moved:!1};try{t.currentTarget.setPointerCapture(t.pointerId)}catch{}}},G=t=>{const r=A.current;if(!r||r.id!==t.pointerId)return;if(t.buttons===0){A.current=null;return}const n=t.clientX-r.x,a=t.clientY-r.y;!r.moved&&Math.hypot(n,a)<4||(r.moved=!0,y.current=!0,r.x=t.clientX,r.y=t.clientY,p(s=>s&&{...s,tx:s.tx+n,ty:s.ty+a}))},X=t=>{A.current?.id===t.pointerId&&(A.current=null)},U=et(()=>({zoomAtClient:(t,r,n,a)=>{const s=x.current;if(!s)return;y.current=!0;const N=s.getBoundingClientRect();p(D=>D&&v(D,t-N.left,r-N.top,S(n,a)))}}),[]),q=t=>{if(t.target!==x.current)return;const r=(n,a)=>{y.current=!0,p(s=>s&&{...s,tx:s.tx+n,ty:s.ty+a})};switch(t.key){case"ArrowLeft":r(O,0);break;case"ArrowRight":r(-O,0);break;case"ArrowUp":r(0,O);break;case"ArrowDown":r(0,-O);break;case"+":case"=":R(1.2);break;case"-":R(1/1.2);break;case"0":y.current=!1,k.current();break;default:return}t.preventDefault()},Y=d?.z??1,V=`wb-arrow-${F}`;return m(at.Provider,{value:U,children:m(it.Provider,{value:C,children:m("div",{ref:x,role:"region","aria-label":h,"aria-roledescription":"lienzo",[nt]:"",tabIndex:0,onPointerDown:H,onPointerMove:G,onPointerUp:X,onPointerCancel:X,onKeyDown:q,className:T("relative h-full w-full cursor-grab touch-none select-none [overflow:clip] active:cursor-grabbing","focus-visible:outline-2 focus-visible:outline-sky-400",b),children:d&&M(J,{children:[m("div",{ref:g,"aria-hidden":!0,className:"absolute inset-0 text-zinc-950/[0.12] transition-opacity duration-200 dark:text-white/10",style:{backgroundImage:"radial-gradient(currentColor 1px, transparent 1px)",backgroundSize:"32px 32px",backgroundPosition:`${d.tx}px ${d.ty}px`,opacity:d.z<.5?0:1}}),M("div",{ref:w,[st]:String(d.z),className:"absolute left-0 top-0 will-change-transform",style:{transform:`translate(${d.tx}px, ${d.ty}px) scale(${d.z})`,transformOrigin:"0 0",[P]:String(d.z)},children:[e.filter(t=>t.behind).map(t=>m("div",{"aria-hidden":!0,className:"pointer-events-none absolute",style:{left:t.x,top:t.y,width:t.width,height:t.height},children:t.element},t.key)),M("svg",{"aria-hidden":!0,width:"1",height:"1",className:"pointer-events-none absolute left-0 top-0 text-zinc-400 dark:text-zinc-500",style:{overflow:"visible"},children:[m("defs",{children:m("marker",{id:V,viewBox:"0 0 10 10",refX:"8.5",refY:"5",markerWidth:"7",markerHeight:"7",orient:"auto-start-reverse",children:m("path",{d:"M 0 0 L 10 5 L 0 10 z",fill:"currentColor"})})}),i.map(t=>{const r=mt(t.from,t.to,t.waypoints);return M("g",{children:[m("path",{d:xt(t.from,t.to,t.waypoints),fill:"none",stroke:"currentColor",strokeWidth:1.75/d.z,strokeDasharray:t.variant==="dashed"?`${8/d.z} ${6/d.z}`:void 0,markerEnd:`url(#${V})`}),t.label&&C==="near"&&m("text",{transform:`translate(${r.x} ${r.y}) scale(${1/d.z})`,textAnchor:"middle",dominantBaseline:"middle",fontSize:12,stroke:`var(${rt}, #fafafa)`,strokeWidth:5,style:{paintOrder:"stroke"},className:"fill-zinc-600 font-medium dark:fill-zinc-300",children:t.label})]},t.id)})]}),e.filter(t=>!t.behind).map(t=>m("div",{className:"absolute select-text",style:{left:t.x,top:t.y,width:t.width,...t.alwaysRender?{}:{contentVisibility:"auto",containIntrinsicSize:`${t.width}px ${t.height}px`}},onContextMenu:t.onContextMenu?r=>{r.preventDefault(),t.onContextMenu?.({x:r.clientX,y:r.clientY})}:void 0,children:t.element},t.key)),e.filter(t=>t.chrome).map(t=>m("div",{className:"pointer-events-none absolute",role:"group","aria-label":t.chromeLabel,style:{left:t.x,top:t.y,width:t.width,height:t.height,[L]:String(t.width)},children:t.chrome},`chrome:${t.key}`))]}),M("div",{className:"absolute bottom-3 left-3 z-10 flex items-center gap-1 rounded-md border border-zinc-950/10 bg-white/70 p-1 text-zinc-600 shadow-sm backdrop-blur transition-opacity duration-200 hover:bg-white/95 dark:border-white/10 dark:bg-zinc-900/70 dark:text-zinc-300 dark:hover:bg-zinc-900/95",children:[C==="far"&&m("span",{role:"status",className:"px-1 text-[11px] text-zinc-500 dark:text-zinc-400",children:"Vista de mapa \xB7 ac\xE9rcate para ver las p\xE1ginas y editarlas"}),m("button",{type:"button","aria-label":"Ajustar el recorrido a la vista",onClick:()=>{y.current=!1,k.current()},className:"rounded px-2 py-1 text-[11px] font-medium hover:bg-zinc-950/5 dark:hover:bg-white/10",children:"Ajustar"}),m("button",{type:"button","aria-label":"Alejar",onClick:()=>R(1/1.2),className:"rounded px-2 py-1 text-[13px] leading-none hover:bg-zinc-950/5 dark:hover:bg-white/10",children:"\u2212"}),M("button",{type:"button","aria-label":`${Math.round(Y*100)}% \u2014 volver al 100%`,onClick:()=>W(1),className:"rounded px-1.5 py-1 font-mono text-[11px] tabular-nums hover:bg-zinc-950/5 dark:hover:bg-white/10",children:[Math.round(Y*100),"%"]}),m("button",{type:"button","aria-label":"Acercar",onClick:()=>R(1.2),className:"rounded px-2 py-1 text-[13px] leading-none hover:bg-zinc-950/5 dark:hover:bg-white/10",children:"+"})]})]})})})})}function wt({anchor:e="top left",clamp:i=!1,className:o,style:c,children:l}){return m("div",{className:T("pointer-events-auto absolute w-max select-text",o),style:{transform:`scale(calc(1 / var(${P}, 1)))`,transformOrigin:e,...i?{maxWidth:`max(9rem, calc(var(${L}, 9999) * var(${P}, 1) * 1px))`}:{},...c},children:l})}export{wt as CanvasChrome,_ as FIT_PAD,zt as FlowCanvas,ut as MAX_ZOOM,lt as MIN_ZOOM,K as OPEN_ZOOM,mt as edgeLabelPoint,xt as edgePath,j as fitViewport,dt as openViewport,ft as revealViewport,S as wheelZoomFactor,v as zoomAt};
@@ -0,0 +1,61 @@
1
+ import { type Lane } from "./flow-layout";
2
+ import { type JourneyAct } from "./journey-acts";
3
+ import type { Flow } from "./manifest.types";
4
+ import type { FlowBody, FlowStepInput } from "./pin";
5
+ export type LaneRef = {
6
+ kind: "trunk";
7
+ } | {
8
+ kind: "branch";
9
+ index: number;
10
+ id: string;
11
+ };
12
+ export declare function laneRefOf(lane: Lane): LaneRef;
13
+ export declare function refKey(ref: LaneRef): string;
14
+ export interface ComposerTarget {
15
+ lane: LaneRef;
16
+ after: number;
17
+ anchorRoute: string;
18
+ fork: boolean;
19
+ flat: number;
20
+ }
21
+ export declare function bodyOf(flow: Flow, defaultViewport?: string): FlowBody;
22
+ export declare function appendStep(body: FlowBody, lane: LaneRef, step: FlowStepInput): FlowBody;
23
+ export declare function forkFrom(body: FlowBody, fromRoute: string, branchLabel: string, step: FlowStepInput): FlowBody;
24
+ export declare function removeLast(body: FlowBody, lane: LaneRef): FlowBody;
25
+ export declare function stepCount(flow: Flow): number;
26
+ export declare function atCap(flow: Flow, maxFlowSteps: number): boolean;
27
+ export declare function isForkPoint(flow: Flow, position: number): boolean;
28
+ export declare function forkResolvesTo(flow: Flow, route: string): number;
29
+ export type ConnectAction = JourneyAct;
30
+ export interface PickedStep {
31
+ lane: "trunk" | "branch";
32
+ position: number;
33
+ isLast: boolean;
34
+ }
35
+ export interface ConnectIntent {
36
+ actions: ConnectAction[];
37
+ note: string | null;
38
+ existing: {
39
+ number: number;
40
+ label: string;
41
+ } | null;
42
+ }
43
+ export declare function journeyMoves(at: {
44
+ lane: "trunk" | "branch";
45
+ isLast: boolean;
46
+ }, atCap?: boolean): {
47
+ append: boolean;
48
+ fork: boolean;
49
+ why: string | null;
50
+ };
51
+ export declare function connectIntent(flow: Flow, at: PickedStep, route: string | null, atCap?: boolean): ConnectIntent;
52
+ export declare function connectGestures(intent: ConnectIntent): string;
53
+ export declare function actionFor(intent: ConnectIntent, wanted: ConnectAction["kind"]): ConnectAction | null;
54
+ export declare function routeOfHref(href: string | null, origin: string): string | null;
55
+ export declare function hasStepAt(flow: Flow, at: ComposerTarget, route: string): boolean;
56
+ export declare function stepInputOf(draft: {
57
+ route: string;
58
+ label: string;
59
+ spec: string;
60
+ via: string;
61
+ }, viewport: string): FlowStepInput;
@@ -0,0 +1 @@
1
+ import{laneKey as l}from"./flow-layout";import{APPEND as d,AT_CAP as k,CLICK as m,FORK as x,NOWHERE as h,ONLY_AT_THE_END as p,ONLY_FROM_TRUNK as c,RIGHT_CLICK as v}from"./journey-acts";function _(e){return e.kind==="trunk"?{kind:"trunk"}:{kind:"branch",index:e.index,id:e.id}}function y(e){return l(e.kind==="trunk"?null:e.id)}const i=e=>e??"";function b(e,t){return{route:e.route,label:e.label,via:i(e.via),viewport:e.viewport===t?"":e.viewport,note:i(e.note),spec:i(e.spec)}}function K(e,t=""){const n=r=>b(r,t);return{title:e.title,description:i(e.description),intent:"",acceptance:"",steps:e.steps.map(n),branches:e.branches.map(r=>({id:r.id,label:r.label,from:e.steps[r.from]?.route??"",steps:r.steps.map(n)}))}}function N(e,t,n){return t.kind==="trunk"?{...e,steps:[...e.steps,n]}:{...e,branches:e.branches.map((r,s)=>s===t.index?{...r,steps:[...r.steps,n]}:r)}}function T(e,t,n,r){return{...e,branches:[...e.branches,{label:n,from:t,steps:[r]}]}}function $(e,t){if(t.kind==="trunk")return{...e,steps:e.steps.slice(0,-1)};const n=e.branches.map((r,s)=>s===t.index?{...r,steps:r.steps.slice(0,-1)}:r);return{...e,branches:n.filter(r=>r.steps.length>0)}}function O(e){return e.steps.length+e.branches.reduce((t,n)=>t+n.steps.length,0)}function A(e,t){return O(e)>=t}function E(e,t){return e.branches.some(n=>n.from===t)}function L(e,t){return e.steps.findIndex(n=>n.route===t)}function R(e,t=!1){return t?{append:!1,fork:!1,why:k}:e.lane==="trunk"?e.isLast?{append:!0,fork:!0,why:null}:{append:!1,fork:!0,why:p}:e.isLast?{append:!0,fork:!1,why:c}:{append:!1,fork:!1,why:`${p} ${c}`}}function F(e,t,n,r=!1){const s=R(t,r),o=[];s.append&&o.push(d),s.fork&&o.push(x);const u=n===null?-1:e.steps.findIndex(a=>a.route===n),f=u===-1?null:{number:u+1,label:e.steps[u]?.label??n??""};return{actions:o,note:s.why,existing:f}}function g(e){const t=e.actions[0];if(!t)return h;const n=[`${m} ${t.verb}`],r=e.actions.find(s=>s.kind==="fork");return r&&r!==t&&n.push(`${v} ${r.verb}`),n.join(" \xB7 ")}function H(e,t){return e.actions.find(n=>n.kind===t)??e.actions[0]??null}function P(e,t){if(!e)return null;try{const n=new URL(e,t);return n.origin===t?n.pathname:null}catch{return null}}function j(e,t,n){if(t.fork){const r=L(e,e.steps[t.after]?.route??"");return e.branches.some(s=>s.from===r&&s.steps[0]?.route===n)}return t.lane.kind==="trunk"?e.steps[t.after+1]?.route===n:e.branches[t.lane.index]?.steps[t.after+1]?.route===n}function D(e,t){const n={route:e.route.trim()};t&&(n.viewport=t);const r=e.label.trim();r&&(n.label=r);const s=e.via.trim();s&&(n.via=s);const o=e.spec.trim();return o&&(n.spec=o),n}export{H as actionFor,N as appendStep,A as atCap,K as bodyOf,g as connectGestures,F as connectIntent,T as forkFrom,L as forkResolvesTo,j as hasStepAt,E as isForkPoint,R as journeyMoves,_ as laneRefOf,y as refKey,$ as removeLast,P as routeOfHref,O as stepCount,D as stepInputOf};
@@ -0,0 +1,25 @@
1
+ import type { StepNode } from "./flow-layout";
2
+ import type { Sketch } from "./screen-sketch";
3
+ export type StepFrame = {
4
+ kind: "queued";
5
+ } | {
6
+ kind: "capturing";
7
+ ticket: number;
8
+ } | {
9
+ kind: "mirrored";
10
+ srcdoc: string;
11
+ sketch: Sketch | null;
12
+ capturedAt: number;
13
+ } | {
14
+ kind: "restless";
15
+ } | {
16
+ kind: "unbuilt";
17
+ };
18
+ export declare const QUEUED: StepFrame;
19
+ export declare const UNBUILT: StepFrame;
20
+ export type Frames = ReadonlyMap<string, StepFrame>;
21
+ export declare const NO_FRAMES: Frames;
22
+ export declare function frameOf(frames: Frames, node: StepNode): StepFrame;
23
+ export declare function nextCapture(frames: Frames, nodes: readonly StepNode[]): string | null;
24
+ export declare function withFrame(frames: Frames, key: string, frame: StepFrame): Frames;
25
+ export declare function prune(frames: Frames, nodes: readonly StepNode[]): Frames;
@@ -0,0 +1 @@
1
+ const p={kind:"queued"},f={kind:"unbuilt"},s=new Map;function u(e,t){return t.step.exists?e.get(t.key)??p:f}function c(e,t){for(const n of t)if(u(e,n).kind==="capturing")return null;for(const n of t)if(u(e,n).kind==="queued")return n.key;return null}function x(e,t,n){const o=new Map(e);return o.set(t,n),o}function k(e,t){const n=new Set(t.filter(r=>r.step.exists).map(r=>r.key));if([...e.keys()].every(r=>n.has(r)))return e;const o=new Map;for(const[r,i]of e)n.has(r)&&o.set(r,i);return o}export{s as NO_FRAMES,p as QUEUED,f as UNBUILT,u as frameOf,c as nextCapture,k as prune,x as withFrame};
@@ -0,0 +1,109 @@
1
+ import { type Lane, type StepNode } from "./flow-layout";
2
+ export interface Box {
3
+ width: number;
4
+ height: number;
5
+ }
6
+ export type GraphNode = {
7
+ kind: "step";
8
+ key: string;
9
+ node: StepNode;
10
+ box: Box;
11
+ anchorY: number;
12
+ } | {
13
+ kind: "card";
14
+ key: string;
15
+ laneKey: string;
16
+ fork: boolean;
17
+ box: Box;
18
+ anchorY: number;
19
+ };
20
+ export interface GraphEdge {
21
+ id: string;
22
+ from: string;
23
+ to: string;
24
+ label: string | null;
25
+ kind: "step" | "fork" | "tail";
26
+ }
27
+ export interface GraphBand {
28
+ laneKey: string;
29
+ label: string | null;
30
+ forkKey: string | null;
31
+ orphan: boolean;
32
+ nodeKeys: string[];
33
+ }
34
+ export interface FlowGraph {
35
+ nodes: GraphNode[];
36
+ edges: GraphEdge[];
37
+ bands: GraphBand[];
38
+ }
39
+ export declare const NODE_GAP_X = 140;
40
+ export declare const NODE_GAP_Y = 120;
41
+ export declare const SECTION_LIFT = 120;
42
+ export declare const SECTION_PAD = 28;
43
+ export declare const TARGET_ASPECT = 1.6;
44
+ export declare const FORK_CARD_BAND = "card:fork";
45
+ export declare function tailKeyOf(laneKey: string): string;
46
+ export declare function forkCardKey(flat: number): string;
47
+ export declare function labelGap(label: string | null): number;
48
+ export declare function cardBoxOf(viewport: {
49
+ width: number;
50
+ height: number;
51
+ }): Box;
52
+ export interface TailSpec {
53
+ tail: ReadonlyMap<string, Box>;
54
+ forkCard: {
55
+ fork: number;
56
+ width: number;
57
+ height: number;
58
+ label: string;
59
+ } | null;
60
+ }
61
+ export declare function graphOf(lanes: readonly Lane[], tails?: TailSpec): FlowGraph;
62
+ export interface BandPlacement {
63
+ laneKey: string;
64
+ label: string | null;
65
+ row: number;
66
+ x: number;
67
+ y: number;
68
+ width: number;
69
+ height: number;
70
+ section: Box & {
71
+ x: number;
72
+ y: number;
73
+ };
74
+ }
75
+ export declare function layoutGraph(graph: FlowGraph): {
76
+ positions: ReadonlyMap<string, {
77
+ x: number;
78
+ y: number;
79
+ }>;
80
+ bands: BandPlacement[];
81
+ bbox: {
82
+ minX: number;
83
+ minY: number;
84
+ maxX: number;
85
+ maxY: number;
86
+ };
87
+ };
88
+ export interface ProjectedEdge {
89
+ id: string;
90
+ from: {
91
+ x: number;
92
+ y: number;
93
+ };
94
+ to: {
95
+ x: number;
96
+ y: number;
97
+ };
98
+ label: string | null;
99
+ variant: "solid" | "dashed";
100
+ waypoints?: Array<{
101
+ x: number;
102
+ y: number;
103
+ }>;
104
+ }
105
+ export declare function gutterBetween(sourceBottom: number, targetTop: number): number;
106
+ export declare function projectEdges(graph: FlowGraph, positions: ReadonlyMap<string, {
107
+ x: number;
108
+ y: number;
109
+ }>): ProjectedEdge[];
@@ -0,0 +1 @@
1
+ import{laneKey as R}from"./flow-layout";import{FRAME_CHROME as T}from"./screen-frame";const $=140,X=120,H=15,j=120,A=28,S=1.6,D="card:fork";function F(o){return`card:${o}`}function z(o){return`card:fork:${o}`}function C(o){return o?Math.max($,o.length*H+40):$}function W(o){return{width:o.width+T,height:o.height+T}}function q(o){const h=W(o.viewport);return{box:h,anchorY:h.height/2}}function U(o,h){const d=[],c=[],r=[],x=o.find(n=>n.kind==="trunk");for(const n of o){const a=n.kind==="trunk"?R(null):R(n.id),i=[];for(const[e,t]of n.nodes.entries()){const{box:u,anchorY:b}=q(t);d.push({kind:"step",key:t.key,node:t,box:u,anchorY:b}),i.push(t.key);const m=n.nodes[e-1];m&&c.push({id:`${m.key}\u2192${t.key}`,from:m.key,to:t.key,label:t.step.via?`\xAB${t.step.via}\xBB`:null,kind:"step"})}let l=null,p=!1;if(n.kind==="branch"){const e=n.fork===null?void 0:x?.nodes[n.fork],t=n.nodes[0];e&&t?(l=e.key,c.push({id:`${e.key}\u2192${t.key}`,from:e.key,to:t.key,label:n.label,kind:"fork"})):p=n.fork===null}const k=h?.tail.get(a),g=i[i.length-1];if(k){const e=F(a);d.push({kind:"card",key:e,laneKey:a,fork:!1,box:k,anchorY:k.height/2}),i.push(e),g&&c.push({id:`${g}\u2192${e}`,from:g,to:e,label:null,kind:"tail"})}r.push({laneKey:a,label:n.kind==="trunk"?null:`${n.label} \xB7 desde el paso ${n.from+1}`+(n.nodes[0]?.step.via?` \xB7 v\xEDa \xAB${n.nodes[0].step.via}\xBB`:"")+(n.fork===null?" \u2014 ese paso no est\xE1 en el tronco":""),forkKey:l,orphan:p,nodeKeys:i})}const s=h?.forkCard;if(s){const n=x?.nodes.find(l=>l.flat===s.fork),a=z(s.fork),i={width:s.width,height:s.height};d.push({kind:"card",key:a,laneKey:D,fork:!0,box:i,anchorY:i.height/2}),n&&c.push({id:`${n.key}\u2192${a}`,from:n.key,to:a,label:s.label,kind:"fork"}),r.push({laneKey:D,label:`${s.label} \xB7 desde el paso ${s.fork+1}`,forkKey:n?.key??null,orphan:n===void 0,nodeKeys:[a]})}return{nodes:d,edges:c,bands:r}}function V(o){const h=new Map(o.nodes.map(e=>[e.key,e])),d=new Map,c=new Map;for(const e of o.edges)(e.kind==="step"||e.kind==="fork")&&c.set(e.to,e.label);const r=e=>{let t=0,u=0;for(const[b,m]of e.nodeKeys.entries()){const y=h.get(m);if(!y)continue;t+=y.box.width,u=Math.max(u,y.box.height);const O=e.nodeKeys[b+1];O&&(t+=C(c.get(O)??null))}return{width:t,height:u}},x=new Map;for(const e of o.bands)for(const t of e.nodeKeys)x.set(t,e.laneKey);const s=[],n=new Map,a=[];let i=0;for(const e of o.bands){const t=r(e);let u=0,b=null,m=0;if(e.forkKey){const w=d.get(e.forkKey),K=h.get(e.forkKey),M=e.nodeKeys[0];w&&K&&(u=w.x+K.box.width+C(M?c.get(M)??null:null)),b=x.get(e.forkKey)??null,m=(b!==null?n.get(b):void 0)??0}const y=s[s.length-1],O=y!==void 0&&!(b!==null&&y.lanes.has(b))&&!e.orphan&&s.length-1>=m;let I=O;if(O&&y){const w=s.reduce((_,E)=>Math.max(_,E.right),0),K=Math.max(w,Math.max(u,y.right+$)+t.width),M=Math.max(i,y.y+Math.max(y.height,t.height)),Y=Math.max(w,u+t.width),B=i+X+t.height,L=(_,E)=>Math.abs(_/Math.max(1,E)-S);I=L(K,M)<=L(Y,B)}const f=I&&y?y:{y:s.length===0?0:i+X,right:0,height:0,lanes:new Set};f!==y&&s.push(f);const v=s.length-1,N=f.lanes.size===0?u:Math.max(u,f.right+$);let G=N;for(const[w,K]of e.nodeKeys.entries()){const M=h.get(K);if(!M)continue;d.set(K,{x:G,y:f.y});const Y=e.nodeKeys[w+1];G+=M.box.width+(Y?C(c.get(Y)??null):0)}f.right=Math.max(f.right,N+t.width),f.height=Math.max(f.height,t.height),f.lanes.add(e.laneKey),n.set(e.laneKey,v),i=Math.max(i,f.y+f.height),a.push({laneKey:e.laneKey,label:e.label,row:v,x:N,y:f.y,width:t.width,height:t.height,section:{x:N-A,y:f.y-j,width:t.width+A*2,height:t.height+j+A}})}if(d.size===0)return{positions:d,bands:a,bbox:{minX:0,minY:0,maxX:0,maxY:0}};let l=1/0,p=1/0,k=-1/0,g=-1/0;for(const e of o.nodes){const t=d.get(e.key);t&&(l=Math.min(l,t.x),p=Math.min(p,t.y),k=Math.max(k,t.x+e.box.width),g=Math.max(g,t.y+e.box.height))}for(const e of a)l=Math.min(l,e.section.x),p=Math.min(p,e.section.y),k=Math.max(k,e.section.x+e.section.width),g=Math.max(g,e.section.y+e.section.height);return{positions:d,bands:a,bbox:{minX:l,minY:p,maxX:k,maxY:g}}}function P(o,h){return o+(h-o)/2}function Z(o,h){const d=new Map(o.nodes.map(r=>[r.key,r])),c=[];for(const r of o.edges){const x=h.get(r.from),s=d.get(r.from),n=h.get(r.to),a=d.get(r.to);if(!x||!s||!n||!a)continue;const i={x:x.x+s.box.width,y:x.y+s.anchorY},l={x:n.x,y:n.y+a.anchorY},k=n.y>x.y+s.box.height&&l.x>i.x+$;c.push({id:r.id,label:r.label,variant:r.kind==="tail"?"dashed":"solid",from:i,to:l,...k?{waypoints:[{x:i.x+$/2,y:P(x.y+s.box.height,n.y)},{x:l.x-$/2,y:P(x.y+s.box.height,n.y)}]}:{}})}return c}export{D as FORK_CARD_BAND,$ as NODE_GAP_X,X as NODE_GAP_Y,j as SECTION_LIFT,A as SECTION_PAD,S as TARGET_ASPECT,W as cardBoxOf,z as forkCardKey,U as graphOf,P as gutterBetween,C as labelGap,V as layoutGraph,Z as projectEdges,F as tailKeyOf};
@@ -0,0 +1,30 @@
1
+ import type { Flow, FlowStep, Viewport } from "./manifest.types";
2
+ export interface StepNode {
3
+ key: string;
4
+ flat: number;
5
+ number: number;
6
+ step: FlowStep;
7
+ viewport: Viewport;
8
+ }
9
+ export declare function laneKey(branchId: string | null): string;
10
+ export type Lane = {
11
+ kind: "trunk";
12
+ nodes: StepNode[];
13
+ } | {
14
+ kind: "branch";
15
+ id: string;
16
+ index: number;
17
+ label: string;
18
+ from: number;
19
+ fork: number | null;
20
+ nodes: StepNode[];
21
+ };
22
+ export declare function layOut(flow: Flow, viewportOf: (id: string) => Viewport): {
23
+ lanes: Lane[];
24
+ nodes: StepNode[];
25
+ };
26
+ export declare function locateNode(lanes: readonly Lane[], key: string): {
27
+ lane: Lane;
28
+ position: number;
29
+ isLast: boolean;
30
+ } | null;
@@ -0,0 +1 @@
1
+ function f(e){return e===null?"trunk":`branch:${e}`}function d(e,l){const o=[],t=(u,n,s)=>u.map((c,a)=>({key:`${s}:${a}:${c.route}`,flat:o.length+a,number:n+a,step:c,viewport:l(c.viewport)})),r=t(e.steps,1,f(null));o.push(...r);const i=[{kind:"trunk",nodes:r}];for(const[u,n]of e.branches.entries()){const s=t(n.steps,n.from+2,f(n.id));o.push(...s),i.push({kind:"branch",id:n.id,index:u,label:n.label,from:n.from,fork:r[n.from]?.flat??null,nodes:s})}return{lanes:i,nodes:o}}function p(e,l){for(const o of e){const t=o.nodes.findIndex(r=>r.key===l);if(t!==-1)return{lane:o,position:t,isLast:t===o.nodes.length-1}}return null}export{f as laneKey,d as layOut,p as locateNode};
@@ -0,0 +1,9 @@
1
+ import type { SaveFlowInput, SaveFlowResult } from "./pin";
2
+ export declare function FlowStart({ routes, waiting, onSave, onCreated, onCancel, onReset, }: {
3
+ routes: string[];
4
+ waiting: string | null;
5
+ onSave: (input: SaveFlowInput) => Promise<SaveFlowResult>;
6
+ onCreated: (flowId: string) => void;
7
+ onCancel?: () => void;
8
+ onReset: () => void;
9
+ }): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as t,Fragment as x,jsxs as r}from"react/jsx-runtime";import{useState as a}from"react";const b="mt-1 w-full rounded-md border border-zinc-300 px-2 py-1.5 text-[13px] dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100",h="text-[11px] font-medium uppercase tracking-wider text-zinc-400",f="workbench-flow-start-routes";function F({routes:k,waiting:o,onSave:z,onCreated:N,onCancel:l,onReset:y}){const[n,g]=a(""),[c,v]=a("/"),[i,s]=a(!1),[m,d]=a(null),[u,p]=a(null),w=async()=>{if(!(i||!n.trim()||!c.trim())){s(!0),d(null);try{const e=await z({id:null,title:n.trim(),description:"",intent:"",acceptance:"",steps:[{route:c.trim()}],branches:[]});e.ok&&e.flowId?(p(e.note??null),N(e.flowId)):(d(e.error??"Fall\xF3 la escritura."),s(!1))}catch(e){d(e instanceof Error?e.message:"Fall\xF3 la escritura."),s(!1)}}};return t("div",{className:"flex h-full items-center justify-center p-10",children:r("div",{className:"w-80",children:[t("h2",{className:"text-sm font-semibold text-zinc-900 dark:text-zinc-100",children:"A\xF1adir recorrido"}),o?r("div",{className:"mt-2 space-y-3",children:[t("p",{role:"status",className:"text-[12px] text-zinc-600 dark:text-zinc-300",children:u?r(x,{children:["Recorrido \xAB",o,"\xBB creado. ",u]}):r(x,{children:["Recorrido \xAB",o,"\xBB creado \u2014 cargando el mapa\u2026"]})}),u&&t("button",{type:"button",onClick:()=>{p(null),s(!1),y()},className:"rounded-md border border-zinc-300 px-3 py-1.5 text-[12px] text-zinc-700 dark:border-zinc-700 dark:text-zinc-200",children:"Volver"})]}):r("form",{className:"mt-3 space-y-3",onSubmit:e=>{e.preventDefault(),w()},onKeyDown:e=>{e.key==="Escape"&&l&&(e.stopPropagation(),l())},children:[t("p",{className:"text-[11px] text-zinc-500 dark:text-zinc-400",children:"Un t\xEDtulo y su primera pantalla; el resto del recorrido se compone en el tablero, pantalla a pantalla."}),r("label",{className:"block",children:[t("span",{className:h,children:"T\xEDtulo"}),t("input",{value:n,onChange:e=>g(e.target.value),placeholder:"Checkout por m\xE9todo",autoFocus:!0,className:b})]}),r("label",{className:"block",children:[t("span",{className:h,children:"Primera pantalla"}),t("input",{value:c,onChange:e=>v(e.target.value),list:f,placeholder:"/",className:`${b} font-mono`}),t("datalist",{id:f,children:k.map(e=>t("option",{value:e},e))})]}),m&&t("p",{role:"alert",className:"text-[11px] text-red-700 dark:text-red-300",children:m}),r("div",{className:"flex items-center gap-2",children:[t("button",{type:"submit",disabled:i||!n.trim()||!c.trim(),className:"rounded-md bg-zinc-900 px-3 py-1.5 text-[12px] font-medium text-white disabled:opacity-40 dark:bg-zinc-100 dark:text-zinc-900",children:i?"Creando\u2026":"Crear"}),l&&t("button",{type:"button",onClick:l,className:"rounded-md border border-zinc-300 px-3 py-1.5 text-[12px] text-zinc-700 dark:border-zinc-700 dark:text-zinc-200",children:"Cancelar"})]})]})]})})}export{F as FlowStart};
@@ -1 +1 @@
1
- "use client";import{jsx as e,jsxs as a}from"react/jsx-runtime";import{useCallback as M,useMemo as j,useRef as H,useState as p}from"react";import{ComponentPreview as I,ScenarioPreview as O}from"./component-preview";import{ManifestProvider as V}from"./config-context";import{useInspect as _}from"./edit-mode";import{EditSessionOverlay as A,useEditSession as B}from"./edit-session";import{InlineSurface as x,RouteFrame as T}from"./frame";import{buildComponentIndex as U}from"./hover-inspect";import{viewportById as Z,workbenchUrl as q}from"./manifest";import{PickBox as K}from"./pick-box";import{indexRegistry as k}from"./registry";import{ScreenFrame as y,useScale as D,FRAME_CHROME as G}from"./screen-frame";import{AUTO as N,ScreenToolbar as J}from"./screen-toolbar";function le({manifest:r,registry:f,componentId:h,route:n,viewport:S,theme:c,scenario:s}){const g=r.config,[d,E]=p(S??N),[m,R]=p("fit"),[u,P]=p(c==="dark"||c==="split"?c:"light"),i=B({bareKeys:!0}),w=H(null),$=j(()=>U(r.components),[r.components]),o=r.components.find(F=>F.id===h),t=d===N?null:Z(g,d),{ref:z,scale:v}=D(m,t?.width??0,G),l=u==="dark"?"dark":"light";_(i.editing,M(()=>w.current,[]),$,{onPin:i.addPin,onHover:i.setHovering},`${h??n}:${d}:${m}:${l}`);const b=o?s?e(O,{entry:o,registry:k(f).get(o.id),scenario:s}):e(I,{entry:o,registry:k(f).get(o.id)}):a("p",{className:"p-6 text-sm text-zinc-500",children:["Nada que mostrar. Falta ",e("code",{children:"?component="})," o ",e("code",{children:"?route="}),"."]}),C=n?t?e(y,{viewport:t,scale:v,label:n,editing:i.editing,children:e(T,{src:n,title:n,width:t.width,height:t.height})}):e(K,{enabled:i.editing,className:"h-full",children:e("iframe",{src:n,title:n,className:"h-full w-full border-0"})}):t?e(y,{viewport:t,scale:v,label:o?.name,editing:i.editing,children:e(x,{theme:l,editing:!1,children:b})}):e(x,{theme:l,editing:i.editing,children:b});return a(V,{manifest:r,children:[e("div",{className:l==="dark"?"dark":void 0,children:a("div",{className:"flex h-screen flex-col bg-white dark:bg-zinc-900",children:[e(J,{viewport:d,onViewport:E,zoom:m,onZoom:R,theme:u,onTheme:P,zoomEnabled:t!==null,identity:n??(s?`${o?.name} \xB7 ${s}`:o?.name),backHref:q(g,""),editing:i.editing,onToggleEdit:i.toggleEditing,pinCount:i.pins.length}),e("div",{ref:z,className:"min-h-0 flex-1 overflow-auto",children:e("div",{ref:w,className:"h-full p-4",children:C})})]})}),e(A,{session:i})]})}export{le as FrameView};
1
+ "use client";import{jsx as e,jsxs as a}from"react/jsx-runtime";import{useCallback as F,useMemo as I,useRef as H,useState as p}from"react";import{ComponentPreview as M,ScenarioPreview as V}from"./component-preview";import{ManifestProvider as B}from"./config-context";import{useInspect as O}from"./edit-mode";import{EditSessionOverlay as T,useEditSession as U}from"./edit-session";import{InlineSurface as b,RouteFrame as Z}from"./frame";import{buildComponentIndex as _}from"./hover-inspect";import{viewportById as q,workbenchUrl as A}from"./manifest";import{PickBox as K}from"./pick-box";import{indexRegistry as k}from"./registry";import{rowExtent as D,ScreenFrame as y,useScale as G}from"./screen-frame";import{AUTO as N,ScreenToolbar as J}from"./screen-toolbar";function le({manifest:r,registry:f,componentId:h,route:t,viewport:S,theme:c,scenario:s}){const g=r.config,[d,E]=p(S??N),[m,P]=p("fit"),[u,$]=p(c==="dark"||c==="split"?c:"light"),i=U({bareKeys:!0}),w=H(null),z=I(()=>_(r.components),[r.components]),n=r.components.find(C=>C.id===h),o=d===N?null:q(g,d),{ref:R,scale:v}=G(m,[D([o?.width??0])]),l=u==="dark"?"dark":"light";O(i.editing,F(()=>w.current,[]),z,{onPin:i.addPin,onHover:i.setHovering},`${h??t}:${d}:${m}:${l}`);const x=n?s?e(V,{entry:n,registry:k(f).get(n.id),scenario:s}):e(M,{entry:n,registry:k(f).get(n.id)}):a("p",{className:"p-6 text-sm text-zinc-500",children:["Nada que mostrar. Falta ",e("code",{children:"?component="})," o ",e("code",{children:"?route="}),"."]}),j=t?o?e(y,{viewport:o,scale:v,label:t,editing:i.editing,children:e(Z,{src:t,title:t,width:o.width,height:o.height})}):e(K,{enabled:i.editing,className:"h-full",children:e("iframe",{src:t,title:t,className:"h-full w-full border-0"})}):o?e(y,{viewport:o,scale:v,label:n?.name,editing:i.editing,children:e(b,{theme:l,editing:!1,children:x})}):e(b,{theme:l,editing:i.editing,children:x});return a(B,{manifest:r,children:[e("div",{className:l==="dark"?"dark":void 0,children:a("div",{className:"flex h-screen flex-col bg-white dark:bg-zinc-900",children:[e(J,{viewport:d,onViewport:E,zoom:m,onZoom:P,theme:u,onTheme:$,zoomEnabled:o!==null,identity:t??(s?`${n?.name} \xB7 ${s}`:n?.name),backHref:A(g,""),editing:i.editing,onToggleEdit:i.toggleEditing,pinCount:i.pins.length}),e("div",{ref:R,className:"min-h-0 flex-1 overflow-auto",children:e("div",{ref:w,className:"h-full p-4",children:j})})]})}),e(T,{session:i})]})}export{le as FrameView};
package/dist/frame.d.ts CHANGED
@@ -20,10 +20,30 @@ export declare function RouteFrame({ src, title, width, height, theme, frameRef,
20
20
  frameRef?: (frame: HTMLIFrameElement | null) => void;
21
21
  onLoad?: (frame: HTMLIFrameElement) => void;
22
22
  }): import("react").JSX.Element;
23
- export declare function MirrorFrame({ srcdoc, title, width, height, theme, }: {
23
+ export interface MirrorPick {
24
+ wants: "append" | "fork";
25
+ text: string;
26
+ href: string | null;
27
+ rect: {
28
+ top: number;
29
+ left: number;
30
+ width: number;
31
+ height: number;
32
+ };
33
+ }
34
+ export declare function MirrorFrame({ srcdoc, title, width, height, theme, connect, onMenu, }: {
24
35
  srcdoc: string;
25
36
  title: string;
26
37
  width: number;
27
38
  height: number;
28
39
  theme: "light" | "dark";
40
+ connect?: {
41
+ busy?: boolean;
42
+ onPick: (pick: MirrorPick | null) => void;
43
+ onHover: (pick: MirrorPick | null) => void;
44
+ };
45
+ onMenu?: (at: {
46
+ x: number;
47
+ y: number;
48
+ }) => void;
29
49
  }): import("react").JSX.Element;