@ai-matrx/alchemy 0.8.23 → 0.8.25
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.
- package/CHANGELOG.md +16 -0
- package/dist/actions.cjs +1 -1
- package/dist/actions.cjs.map +1 -1
- package/dist/actions.d.cts +10 -0
- package/dist/actions.d.ts +10 -0
- package/dist/actions.js +1 -1
- package/dist/actions.js.map +1 -1
- package/dist/menu.cjs +1 -1
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.d.cts +10 -0
- package/dist/menu.d.ts +10 -0
- package/dist/menu.js +1 -1
- package/dist/menu.js.map +1 -1
- package/dist/react-bar.cjs +1 -1
- package/dist/react-bar.cjs.map +1 -1
- package/dist/react-bar.js +1 -1
- package/dist/react-bar.js.map +1 -1
- package/dist/react-host.cjs +1 -1
- package/dist/react-host.cjs.map +1 -1
- package/dist/react-host.d.cts +10 -0
- package/dist/react-host.d.ts +10 -0
- package/dist/react-host.js +1 -1
- package/dist/react-host.js.map +1 -1
- package/dist/react-menu.cjs +2 -2
- package/dist/react-menu.cjs.map +1 -1
- package/dist/react-menu.js +2 -2
- package/dist/react-menu.js.map +1 -1
- package/dist/react-overflow.cjs +1 -1
- package/dist/react-overflow.cjs.map +1 -1
- package/dist/react-overflow.d.cts +10 -0
- package/dist/react-overflow.d.ts +10 -0
- package/dist/react-overflow.js +1 -1
- package/dist/react-overflow.js.map +1 -1
- package/dist/react-palette.cjs +1 -1
- package/dist/react-palette.cjs.map +1 -1
- package/dist/react-palette.js +1 -1
- package/dist/react-palette.js.map +1 -1
- package/dist/react-selection.cjs +1 -1
- package/dist/react-selection.cjs.map +1 -1
- package/dist/react-selection.js +1 -1
- package/dist/react-selection.js.map +1 -1
- package/dist/react-sheet.cjs +1 -1
- package/dist/react-sheet.cjs.map +1 -1
- package/dist/react-sheet.js +1 -1
- package/dist/react-sheet.js.map +1 -1
- package/package.json +3 -3
package/dist/react-host.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import*as React2 from"react";var UNIVERSAL_VERBS=["copy","cut","paste","undo","redo","find"];var DuplicateActionError=class extends Error{actionId;providers;constructor(actionId,first,second){super(first===second?`Action "${actionId}" is yielded twice by provider "${first}". Ids are unique; the second was refused.`:`Action "${actionId}" is registered by provider "${first}" and again by "${second}". Ids are unique; "${second}"'s copy was refused.`),this.name="DuplicateActionError",this.actionId=actionId,this.providers=[first,second]}},DuplicateProviderError=class extends Error{providerId;constructor(providerId){super(`Action provider "${providerId}" is already registered. Unregister it first; nothing was overwritten.`),this.name="DuplicateProviderError",this.providerId=providerId}},VERBS=new Set(UNIVERSAL_VERBS);function capture(ports,error,detail){try{ports.diagnostics.capture(error,{area:"actions",detail})}catch{}}function computeEligibility(action,target,options){let restrict=options.restrict;if(target.excludedActionIds.has(action.id))return{status:"absent"};if(restrict?.exclude?.includes(action.id))return{status:"absent"};if(restrict?.categories&&!restrict.categories.includes(action.category))return{status:"absent"};if(action.writes&&action.writes.length>0){if(target.readOnly)return{status:"absent"};let writable=new Set(target.writable.map(w=>w.name));if(!action.writes.some(name=>writable.has(name)))return{status:"absent"}}if(action.requiresAuth&&!target.auth.authenticated&&!options.authResumable)return{status:"absent"};let own=action.eligible(target);return own.status!=="unavailable-verb"?own:action.category!=="clipboard"||!VERBS.has(own.verb)?{status:"contract-break",reason:`Action "${action.id}" answered unavailable-verb but only the six universal verbs (category "clipboard") may grey; it is absent instead.`}:own.sentence.trim()?own:{status:"contract-break",reason:`Universal verb "${action.id}" greyed without a sentence; a greyed verb must say why.`}}function createActionRegistry(options){let ports=options.ports,entries=new Map,listeners=new Set,notify=()=>{for(let l of[...listeners])l()};return{register(provider){if(entries.has(provider.id))throw new DuplicateProviderError(provider.id);let declared=provider.declaredIds?.()??[],own=new Set;for(let id of declared){if(own.has(id))throw new DuplicateActionError(id,provider.id,provider.id);own.add(id);for(let[otherId,other]of entries)if(other.declared.includes(id))throw new DuplicateActionError(id,otherId,provider.id)}let entry={provider,declared};return entries.set(provider.id,entry),notify(),()=>{entries.get(provider.id)===entry&&(entries.delete(provider.id),notify())}},async resolve(target,restrict){let authResumable=!!ports.auth,seen=new Map,out=[];for(let[providerId,{provider}]of[...entries]){let actions;try{actions=await provider.actions(target)}catch(error){capture(ports,error,{providerId,stage:"actions"});continue}for(let raw of actions){let first=seen.get(raw.id);if(first!==void 0){capture(ports,new DuplicateActionError(raw.id,first,providerId),{actionId:raw.id});continue}seen.set(raw.id,providerId);let action=raw.provider===providerId?raw:{...raw,provider:providerId},eligibility;try{eligibility=computeEligibility(action,target,{restrict:restrict??{},authResumable})}catch(error){capture(ports,error,{actionId:action.id,stage:"eligible"});continue}if(eligibility.status==="contract-break"){capture(ports,new Error(eligibility.reason),{actionId:action.id,stage:"eligible"});continue}eligibility.status!=="absent"&&out.push({action,eligibility})}}return out},providers(){return[...entries.keys()]},subscribe(listener){return listeners.add(listener),()=>{listeners.delete(listener)}}}}import*as React from"react";var KEY=Symbol.for("ai-matrx.alchemy.actions-context"),AlchemyActionsContext=globalThis[KEY]??=React.createContext(null);function useAlchemyActions(){let value=React.useContext(AlchemyActionsContext);if(!value)throw new Error("Alchemy actions: render inside <AlchemyActionsProvider> (@ai-matrx/alchemy/react/host).");return value}import{Fragment,jsx,jsxs}from"react/jsx-runtime";function AlchemyActionsProvider({ports,registry,children}){let own=React2.useMemo(()=>registry??createActionRegistry({ports}),[registry,ports]),value=React2.useMemo(()=>({registry:own,ports}),[own,ports]);return jsx(AlchemyActionsContext.Provider,{value,children})}function useRegisterActionProvider(provider){let{registry}=useAlchemyActions();React2.useEffect(()=>provider?registry.register(provider):void 0,[registry,provider])}var LONG_PRESS_MS=480;function isEditable(el){return el?el instanceof HTMLTextAreaElement?!0:el instanceof HTMLInputElement?!["checkbox","radio","button","submit","range","color","file"].includes(el.type):el.isContentEditable===!0:!1}function mobileViewport(){return typeof window>"u"?!1:window.matchMedia?.("(max-width: 767px)").matches??!1}function ActionMenuTrigger({resolveTarget,children,arrangement,density,mobile,editable=!1,loadMenu=()=>import("@ai-matrx/alchemy/react/menu"),loadSheet=()=>import("@ai-matrx/alchemy/react/sheet"),...options}){let[state,setState]=React2.useState(null),[open,setOpen]=React2.useState(!1),[Menu,setMenu]=React2.useState(null),[Sheet,setSheet]=React2.useState(null),press=React2.useRef(null),openAt=React2.useCallback((element,x,y,touch)=>{let editable2=isEditable(element)||isEditable(document.activeElement),selectionText=window.getSelection?.()?.toString()??"",target=resolveTarget({element,selectionText,editable:editable2});if(!target)return!1;let sheet=mobile??(touch||mobileViewport());return setState({target,point:{x,y},sheet}),setOpen(!0),sheet&&!Sheet&&loadSheet().then(m=>setSheet(()=>m.ActionSheet)),!sheet&&!Menu&&loadMenu().then(m=>setMenu(()=>m.ContextMenuPanel)),!0},[resolveTarget,mobile,Sheet,Menu,loadSheet,loadMenu]),child=React2.Children.only(children),props=child.props,compose=(own,theirs)=>e=>{typeof theirs=="function"&&theirs(e),own(e)},trigger=React2.cloneElement(child,{"data-alchemy-trigger":"context",onContextMenu:compose(e=>{if(e.defaultPrevented)return;let el=e.target;isEditable(el)&&!editable||openAt(el,e.clientX,e.clientY,!1)&&e.preventDefault()},props.onContextMenu),onTouchStart:compose(e=>{let t=e.touches[0];if(!t)return;let el=e.target;if(isEditable(el)&&!editable)return;let timer=setTimeout(()=>{press.current=null,openAt(el,t.clientX,t.clientY,!0)},LONG_PRESS_MS);press.current={timer,x:t.clientX,y:t.clientY},e.stopPropagation()},props.onTouchStart),onTouchMove:compose(e=>{let t=e.touches[0],p=press.current;p&&t&&Math.hypot(t.clientX-p.x,t.clientY-p.y)>8&&(clearTimeout(p.timer),press.current=null)},props.onTouchMove),onTouchEnd:compose(()=>{press.current&&clearTimeout(press.current.timer),press.current=null},props.onTouchEnd)});return jsxs(Fragment,{children:[trigger,state&&!state.sheet&&Menu?jsx(Menu,{...options,target:state.target,point:state.point,open,onOpenChange:setOpen,...arrangement?{arrangement}:{},...density?{density}:{}}):null,state&&state.sheet&&Sheet?jsx(Sheet,{...options,target:state.target,open,onOpenChange:setOpen}):null]})}export{ActionMenuTrigger,AlchemyActionsProvider,useAlchemyActions,useRegisterActionProvider};
|
|
2
|
+
import*as React2 from"react";var UNIVERSAL_VERBS=["copy","cut","paste","undo","redo","find"];var DuplicateActionError=class extends Error{actionId;providers;constructor(actionId,first,second){super(first===second?`Action "${actionId}" is yielded twice by provider "${first}". Ids are unique; the second was refused.`:`Action "${actionId}" is registered by provider "${first}" and again by "${second}". Ids are unique; "${second}"'s copy was refused.`),this.name="DuplicateActionError",this.actionId=actionId,this.providers=[first,second]}},DuplicateProviderError=class extends Error{providerId;constructor(providerId){super(`Action provider "${providerId}" is already registered. Unregister it first; nothing was overwritten.`),this.name="DuplicateProviderError",this.providerId=providerId}},VERBS=new Set(UNIVERSAL_VERBS);function capture(ports,error,detail){try{ports.diagnostics.capture(error,{area:"actions",detail})}catch{}}function computeEligibility(action,target,options){let restrict=options.restrict;if(target.excludedActionIds.has(action.id))return{status:"absent"};if(restrict?.exclude?.includes(action.id))return{status:"absent"};if(restrict?.categories&&!restrict.categories.includes(action.category))return{status:"absent"};if(action.writes&&action.writes.length>0){if(target.readOnly)return{status:"absent"};let writable=new Set(target.writable.map(w=>w.name));if(!action.writes.some(name=>writable.has(name)))return{status:"absent"}}if(action.requiresAuth&&!target.auth.authenticated&&!options.authResumable)return{status:"absent"};let own=action.eligible(target);return own.status==="unavailable"?own.sentence.trim()?own:{status:"absent"}:own.status!=="unavailable-verb"?own:action.category!=="clipboard"||!VERBS.has(own.verb)?{status:"contract-break",reason:`Action "${action.id}" answered unavailable-verb but only the six universal verbs (category "clipboard") may grey; it is absent instead.`}:own.sentence.trim()?own:{status:"contract-break",reason:`Universal verb "${action.id}" greyed without a sentence; a greyed verb must say why.`}}function createActionRegistry(options){let ports=options.ports,entries=new Map,listeners=new Set,notify=()=>{for(let l of[...listeners])l()};return{register(provider){if(entries.has(provider.id))throw new DuplicateProviderError(provider.id);let declared=provider.declaredIds?.()??[],own=new Set;for(let id of declared){if(own.has(id))throw new DuplicateActionError(id,provider.id,provider.id);own.add(id);for(let[otherId,other]of entries)if(other.declared.includes(id))throw new DuplicateActionError(id,otherId,provider.id)}let entry={provider,declared};return entries.set(provider.id,entry),notify(),()=>{entries.get(provider.id)===entry&&(entries.delete(provider.id),notify())}},async resolve(target,restrict){let authResumable=!!ports.auth,seen=new Map,out=[];for(let[providerId,{provider}]of[...entries]){let actions;try{actions=await provider.actions(target)}catch(error){capture(ports,error,{providerId,stage:"actions"});continue}for(let raw of actions){let first=seen.get(raw.id);if(first!==void 0){capture(ports,new DuplicateActionError(raw.id,first,providerId),{actionId:raw.id});continue}seen.set(raw.id,providerId);let action=raw.provider===providerId?raw:{...raw,provider:providerId},eligibility;try{eligibility=computeEligibility(action,target,{restrict:restrict??{},authResumable})}catch(error){capture(ports,error,{actionId:action.id,stage:"eligible"});continue}if(eligibility.status==="contract-break"){capture(ports,new Error(eligibility.reason),{actionId:action.id,stage:"eligible"});continue}eligibility.status!=="absent"&&out.push({action,eligibility})}}return out},providers(){return[...entries.keys()]},subscribe(listener){return listeners.add(listener),()=>{listeners.delete(listener)}}}}import*as React from"react";var KEY=Symbol.for("ai-matrx.alchemy.actions-context"),AlchemyActionsContext=globalThis[KEY]??=React.createContext(null);function useAlchemyActions(){let value=React.useContext(AlchemyActionsContext);if(!value)throw new Error("Alchemy actions: render inside <AlchemyActionsProvider> (@ai-matrx/alchemy/react/host).");return value}import{Fragment,jsx,jsxs}from"react/jsx-runtime";function AlchemyActionsProvider({ports,registry,children}){let own=React2.useMemo(()=>registry??createActionRegistry({ports}),[registry,ports]),value=React2.useMemo(()=>({registry:own,ports}),[own,ports]);return jsx(AlchemyActionsContext.Provider,{value,children})}function useRegisterActionProvider(provider){let{registry}=useAlchemyActions();React2.useEffect(()=>provider?registry.register(provider):void 0,[registry,provider])}var LONG_PRESS_MS=480;function isEditable(el){return el?el instanceof HTMLTextAreaElement?!0:el instanceof HTMLInputElement?!["checkbox","radio","button","submit","range","color","file"].includes(el.type):el.isContentEditable===!0:!1}function mobileViewport(){return typeof window>"u"?!1:window.matchMedia?.("(max-width: 767px)").matches??!1}function ActionMenuTrigger({resolveTarget,children,arrangement,density,mobile,editable=!1,loadMenu=()=>import("@ai-matrx/alchemy/react/menu"),loadSheet=()=>import("@ai-matrx/alchemy/react/sheet"),...options}){let[state,setState]=React2.useState(null),[open,setOpen]=React2.useState(!1),[Menu,setMenu]=React2.useState(null),[Sheet,setSheet]=React2.useState(null),press=React2.useRef(null),openAt=React2.useCallback((element,x,y,touch)=>{let editable2=isEditable(element)||isEditable(document.activeElement),selectionText=window.getSelection?.()?.toString()??"",target=resolveTarget({element,selectionText,editable:editable2});if(!target)return!1;let sheet=mobile??(touch||mobileViewport());return setState({target,point:{x,y},sheet}),setOpen(!0),sheet&&!Sheet&&loadSheet().then(m=>setSheet(()=>m.ActionSheet)),!sheet&&!Menu&&loadMenu().then(m=>setMenu(()=>m.ContextMenuPanel)),!0},[resolveTarget,mobile,Sheet,Menu,loadSheet,loadMenu]),child=React2.Children.only(children),props=child.props,compose=(own,theirs)=>e=>{typeof theirs=="function"&&theirs(e),own(e)},trigger=React2.cloneElement(child,{"data-alchemy-trigger":"context",onContextMenu:compose(e=>{if(e.defaultPrevented)return;let el=e.target;isEditable(el)&&!editable||openAt(el,e.clientX,e.clientY,!1)&&e.preventDefault()},props.onContextMenu),onTouchStart:compose(e=>{let t=e.touches[0];if(!t)return;let el=e.target;if(isEditable(el)&&!editable)return;let timer=setTimeout(()=>{press.current=null,openAt(el,t.clientX,t.clientY,!0)},LONG_PRESS_MS);press.current={timer,x:t.clientX,y:t.clientY},e.stopPropagation()},props.onTouchStart),onTouchMove:compose(e=>{let t=e.touches[0],p=press.current;p&&t&&Math.hypot(t.clientX-p.x,t.clientY-p.y)>8&&(clearTimeout(p.timer),press.current=null)},props.onTouchMove),onTouchEnd:compose(()=>{press.current&&clearTimeout(press.current.timer),press.current=null},props.onTouchEnd)});return jsxs(Fragment,{children:[trigger,state&&!state.sheet&&Menu?jsx(Menu,{...options,target:state.target,point:state.point,open,onOpenChange:setOpen,...arrangement?{arrangement}:{},...density?{density}:{}}):null,state&&state.sheet&&Sheet?jsx(Sheet,{...options,target:state.target,open,onOpenChange:setOpen}):null]})}export{ActionMenuTrigger,AlchemyActionsProvider,useAlchemyActions,useRegisterActionProvider};
|
|
3
3
|
//# sourceMappingURL=react-host.js.map
|
package/dist/react-host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react/host.tsx","../src/actions/click-target.ts","../src/actions/registry.ts","../src/react/context.ts"],"sourcesContent":["\"use client\";\n/**\n * `@ai-matrx/alchemy/react/host` — T0, the shell every mount pays for.\n *\n * Provider (registry + ports), provider registration, and the gesture shell\n * (right-click on desktop, long-press on touch). It imports NO model, NO\n * layout, NO renderer: the context menu (`/react/menu`, T1) and the mobile\n * sheet (`/react/sheet`, T1m) load by dynamic import on first open only.\n */\nimport * as React from \"react\";\nimport { createActionRegistry } from \"../actions/registry\";\nimport type { ActionProvider, ActionRegistry, ClickTarget } from \"../actions/types\";\nimport type { AlchemyHostPorts } from \"../ports\";\nimport { AlchemyActionsContext, useAlchemyActions, type AlchemyActionsValue } from \"./context\";\nimport type { MenuEngineOptions } from \"./engine\";\n\nexport { useAlchemyActions, type AlchemyActionsValue };\n\nexport interface AlchemyActionsProviderProps {\n ports: AlchemyHostPorts;\n /** Share one registry across providers; default: one per provider mount. */\n registry?: ActionRegistry;\n children: React.ReactNode;\n}\n\nexport function AlchemyActionsProvider({ ports, registry, children }: AlchemyActionsProviderProps): React.ReactElement {\n const own = React.useMemo(() => registry ?? createActionRegistry({ ports }), [registry, ports]);\n const value = React.useMemo(() => ({ registry: own, ports }), [own, ports]);\n return <AlchemyActionsContext.Provider value={value}>{children}</AlchemyActionsContext.Provider>;\n}\n\n/** Register a provider for the lifetime of the calling component. Duplicate ids throw (refused). */\nexport function useRegisterActionProvider(provider: ActionProvider | null): void {\n const { registry } = useAlchemyActions();\n React.useEffect(() => (provider ? registry.register(provider) : undefined), [registry, provider]);\n}\n\ntype MenuModule = typeof import(\"./menu\");\ntype SheetModule = typeof import(\"./sheet\");\n\nexport interface ActionMenuTriggerProps extends MenuEngineOptions {\n /** Build the target at gesture time (selection captured then). */\n resolveTarget(gesture: { element: Element; selectionText: string; editable: boolean }): ClickTarget | null;\n children: React.ReactElement;\n arrangement?: \"classic\" | \"tiered\" | \"command\";\n density?: \"comfortable\" | \"compact\";\n /** Force a renderer; default: sheet below 768px or on coarse pointers. */\n mobile?: boolean;\n /**\n * The wrapped surface is an editor that wires text mutation into the menu,\n * so the menu takes its right-click. Otherwise a live text field keeps its\n * native menu (Paste, spellcheck, autofill).\n */\n editable?: boolean;\n /** Override the lazy loaders (tests, custom bundlers). */\n loadMenu?: () => Promise<Pick<MenuModule, \"ContextMenuPanel\">>;\n loadSheet?: () => Promise<Pick<SheetModule, \"ActionSheet\">>;\n}\n\nconst LONG_PRESS_MS = 480;\n\nfunction isEditable(el: Element | null): boolean {\n if (!el) return false;\n if (el instanceof HTMLTextAreaElement) return true;\n if (el instanceof HTMLInputElement) return ![\"checkbox\", \"radio\", \"button\", \"submit\", \"range\", \"color\", \"file\"].includes(el.type);\n return (el as HTMLElement).isContentEditable === true;\n}\n\nfunction mobileViewport(): boolean {\n if (typeof window === \"undefined\") return false;\n return window.matchMedia?.(\"(max-width: 767px)\").matches ?? false;\n}\n\nexport function ActionMenuTrigger({\n resolveTarget,\n children,\n arrangement,\n density,\n mobile,\n editable = false,\n loadMenu = () => import(\"@ai-matrx/alchemy/react/menu\"),\n loadSheet = () => import(\"@ai-matrx/alchemy/react/sheet\"),\n ...options\n}: ActionMenuTriggerProps): React.ReactElement {\n const [state, setState] = React.useState<{ target: ClickTarget; point: { x: number; y: number }; sheet: boolean } | null>(null);\n const [open, setOpen] = React.useState(false);\n const [Menu, setMenu] = React.useState<MenuModule[\"ContextMenuPanel\"] | null>(null);\n const [Sheet, setSheet] = React.useState<SheetModule[\"ActionSheet\"] | null>(null);\n const press = React.useRef<{ timer: ReturnType<typeof setTimeout>; x: number; y: number } | null>(null);\n\n const openAt = React.useCallback(\n (element: Element, x: number, y: number, touch: boolean) => {\n const editable = isEditable(element) || isEditable(document.activeElement);\n const selectionText = window.getSelection?.()?.toString() ?? \"\";\n const target = resolveTarget({ element, selectionText, editable });\n if (!target) return false;\n const sheet = mobile ?? (touch || mobileViewport());\n setState({ target, point: { x, y }, sheet });\n setOpen(true);\n if (sheet && !Sheet) void loadSheet().then((m) => setSheet(() => m.ActionSheet));\n if (!sheet && !Menu) void loadMenu().then((m) => setMenu(() => m.ContextMenuPanel));\n return true;\n },\n [resolveTarget, mobile, Sheet, Menu, loadSheet, loadMenu],\n );\n\n const child = React.Children.only(children) as React.ReactElement<Record<string, unknown>>;\n const props = child.props;\n const compose =\n <E,>(own: (e: E) => void, theirs: unknown) =>\n (e: E) => {\n if (typeof theirs === \"function\") (theirs as (e: E) => void)(e);\n own(e);\n };\n\n const trigger = React.cloneElement(child, {\n \"data-alchemy-trigger\": \"context\",\n onContextMenu: compose((e: React.MouseEvent) => {\n if (e.defaultPrevented) return; // an inner menu owns the gesture\n const el = e.target as Element;\n if (isEditable(el) && !editable) return; // never steal a live text field's native menu\n if (openAt(el, e.clientX, e.clientY, false)) e.preventDefault();\n }, props.onContextMenu),\n onTouchStart: compose((e: React.TouchEvent) => {\n const t = e.touches[0];\n if (!t) return;\n const el = e.target as Element;\n if (isEditable(el) && !editable) return;\n const timer = setTimeout(() => {\n press.current = null;\n openAt(el, t.clientX, t.clientY, true);\n }, LONG_PRESS_MS);\n press.current = { timer, x: t.clientX, y: t.clientY };\n e.stopPropagation();\n }, props.onTouchStart),\n onTouchMove: compose((e: React.TouchEvent) => {\n const t = e.touches[0];\n const p = press.current;\n if (p && t && Math.hypot(t.clientX - p.x, t.clientY - p.y) > 8) {\n clearTimeout(p.timer);\n press.current = null;\n }\n }, props.onTouchMove),\n onTouchEnd: compose(() => {\n if (press.current) clearTimeout(press.current.timer);\n press.current = null;\n }, props.onTouchEnd),\n });\n\n return (\n <>\n {trigger}\n {state && !state.sheet && Menu ? (\n <Menu\n {...options}\n target={state.target}\n point={state.point}\n open={open}\n onOpenChange={setOpen}\n {...(arrangement ? { arrangement } : {})}\n {...(density ? { density } : {})}\n />\n ) : null}\n {state && state.sheet && Sheet ? <Sheet {...options} target={state.target} open={open} onOpenChange={setOpen} /> : null}\n </>\n );\n}\n","/**\n * The click target (CONTRACT §2.4) in its own module: `/ports` references it,\n * and `/ports` must stay free of every other package (types only).\n */\nimport type { QualifiedValueKey, ResolvedItem, ResolvedSurfaceDeclaration, WriteTarget } from \"../declare/types\";\n\n/** The six universal verbs — the ONLY rows that may grey (R1 c). */\nexport const UNIVERSAL_VERBS = [\"copy\", \"cut\", \"paste\", \"undo\", \"redo\", \"find\"] as const;\nexport type UniversalVerb = (typeof UNIVERSAL_VERBS)[number];\n\n/** The transfer Payload's kinds (`actions/types.ts` asserts equality at compile time). */\nexport type PayloadKind = \"text\" | \"markdown\" | \"json\" | \"rows\" | \"registered\";\n\n/** What eligibility is computed from: everything declared at the click target. */\nexport interface ClickTarget {\n surface: { instanceId: string; surfaceName: string } | null;\n /** The resolved declaration (inheritance applied). Eligibility decides by KEY existence over it. */\n declaration: ResolvedSurfaceDeclaration | null;\n /** Declared value keys with a read path here (screen names + qualified item keys). Key existence, not population. */\n valueKeys: ReadonlySet<QualifiedValueKey>;\n /** Scope chain, most general first: [\"global\", \"<domain>\", \"<surfaceName>\"]. */\n scopeChain: readonly string[];\n /** The surface's explicit exclusions (the one override). */\n excludedActionIds: ReadonlySet<string>;\n item: ResolvedItem | null;\n level: string | null;\n /** Offsets + the live range handle, editable or not. */\n selection: {\n text: string;\n type: \"editable\" | \"non-editable\";\n start: number;\n end: number;\n /** Opaque host handle (DOM Range / element) used to restore or stream into the selection. */\n handle?: unknown;\n } | null;\n /** Payload kinds a capture of this target would produce (from declared value types/kinds). */\n payloadKinds: readonly PayloadKind[];\n /** Declared targets writable on this target right now. */\n writable: readonly WriteTarget[];\n readOnly: boolean;\n /** True when any declared value here is secret/credential or non-exportable. */\n restricted: boolean;\n organizationId: string | null;\n auth: { authenticated: boolean; admin: boolean; creator: boolean };\n /**\n * Host-owned context a provider reads to act (the rich-document source, the\n * editable field's callbacks). Opaque to the package; never crosses into the\n * model. Providers that need it narrow it themselves.\n */\n host?: unknown;\n}\n\n","/**\n * The one action registry (CONTRACT §2.4).\n *\n * - Duplicate ids are REFUSED, never overwritten (rich-document's registry\n * silently overwrote — R12). A duplicate known at registration throws; one\n * that only appears when a provider yields it is dropped and captured.\n * - Eligibility is one pure pass over the resolved click target, under\n * \"restrict, never relax\": a host may remove ids or categories, never add.\n * - R1: an action that does not apply is ABSENT. Only the six universal verbs\n * (category \"clipboard\") may answer `unavailable-verb`, with a sentence; any\n * other action answering it is a contract break → absent + diagnostics.\n */\nimport type { AlchemyHostPorts, Json } from \"../ports\";\nimport {\n UNIVERSAL_VERBS,\n type Action,\n type ActionProvider,\n type ActionRegistry,\n type ClickTarget,\n type Eligibility,\n type ResolveRestriction,\n type ResolvedAction,\n} from \"./types\";\n\nexport class DuplicateActionError extends Error {\n readonly actionId: string;\n readonly providers: readonly [string, string];\n constructor(actionId: string, first: string, second: string) {\n super(\n first === second\n ? `Action \"${actionId}\" is yielded twice by provider \"${first}\". Ids are unique; the second was refused.`\n : `Action \"${actionId}\" is registered by provider \"${first}\" and again by \"${second}\". Ids are unique; \"${second}\"'s copy was refused.`,\n );\n this.name = \"DuplicateActionError\";\n this.actionId = actionId;\n this.providers = [first, second];\n }\n}\n\nexport class DuplicateProviderError extends Error {\n readonly providerId: string;\n constructor(providerId: string) {\n super(`Action provider \"${providerId}\" is already registered. Unregister it first; nothing was overwritten.`);\n this.name = \"DuplicateProviderError\";\n this.providerId = providerId;\n }\n}\n\nexport interface ActionRegistryOptions {\n /** `diagnostics` is required (nothing fails silently); `auth` decides whether requiresAuth actions reach a signed-out person. */\n ports: Pick<AlchemyHostPorts, \"diagnostics\" | \"auth\">;\n}\n\nconst VERBS = new Set<string>(UNIVERSAL_VERBS);\n\nfunction capture(ports: ActionRegistryOptions[\"ports\"], error: unknown, detail: Json): void {\n try {\n ports.diagnostics.capture(error, { area: \"actions\", detail });\n } catch {\n // A broken diagnostics port must never take the menu down with it.\n }\n}\n\n/**\n * Eligibility of one action at one target — the ONE pure pass (CONTRACT §1.6).\n * Exported for providers' tests; hosts call `registry.resolve`.\n */\nexport function computeEligibility(\n action: Action,\n target: ClickTarget,\n options: { restrict?: ResolveRestriction; authResumable: boolean },\n): Eligibility | { status: \"contract-break\"; reason: string } {\n const restrict = options.restrict;\n if (target.excludedActionIds.has(action.id)) return { status: \"absent\" };\n if (restrict?.exclude?.includes(action.id)) return { status: \"absent\" };\n if (restrict?.categories && !restrict.categories.includes(action.category)) return { status: \"absent\" };\n if (action.writes && action.writes.length > 0) {\n // A read-only target offers nothing that would change the record.\n if (target.readOnly) return { status: \"absent\" };\n const writable = new Set(target.writable.map((w) => w.name));\n if (!action.writes.some((name) => writable.has(name))) return { status: \"absent\" };\n }\n if (action.requiresAuth && !target.auth.authenticated && !options.authResumable) {\n // Sign-in-then-resume is the capability; without the auth port it is absent (N8).\n return { status: \"absent\" };\n }\n const own = action.eligible(target);\n if (own.status !== \"unavailable-verb\") return own;\n if (action.category !== \"clipboard\" || !VERBS.has(own.verb)) {\n return {\n status: \"contract-break\",\n reason: `Action \"${action.id}\" answered unavailable-verb but only the six universal verbs (category \"clipboard\") may grey; it is absent instead.`,\n };\n }\n if (!own.sentence.trim()) {\n return {\n status: \"contract-break\",\n reason: `Universal verb \"${action.id}\" greyed without a sentence; a greyed verb must say why.`,\n };\n }\n return own;\n}\n\ninterface Entry {\n provider: ActionProvider;\n declared: readonly string[];\n}\n\nexport function createActionRegistry(options: ActionRegistryOptions): ActionRegistry {\n const ports = options.ports;\n const entries = new Map<string, Entry>();\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const l of [...listeners]) l();\n };\n\n return {\n register(provider) {\n if (entries.has(provider.id)) throw new DuplicateProviderError(provider.id);\n const declared = provider.declaredIds?.() ?? [];\n const own = new Set<string>();\n for (const id of declared) {\n if (own.has(id)) throw new DuplicateActionError(id, provider.id, provider.id);\n own.add(id);\n for (const [otherId, other] of entries) {\n if (other.declared.includes(id)) throw new DuplicateActionError(id, otherId, provider.id);\n }\n }\n const entry: Entry = { provider, declared };\n entries.set(provider.id, entry);\n notify();\n return () => {\n if (entries.get(provider.id) === entry) {\n entries.delete(provider.id);\n notify();\n }\n };\n },\n\n async resolve(target, restrict) {\n const authResumable = Boolean(ports.auth);\n const seen = new Map<string, string>();\n const out: ResolvedAction[] = [];\n for (const [providerId, { provider }] of [...entries]) {\n let actions: readonly Action[];\n try {\n actions = await provider.actions(target);\n } catch (error) {\n capture(ports, error, { providerId, stage: \"actions\" });\n continue;\n }\n for (const raw of actions) {\n const first = seen.get(raw.id);\n if (first !== undefined) {\n capture(ports, new DuplicateActionError(raw.id, first, providerId), { actionId: raw.id });\n continue;\n }\n seen.set(raw.id, providerId);\n const action: Action = raw.provider === providerId ? raw : { ...raw, provider: providerId };\n let eligibility: ReturnType<typeof computeEligibility>;\n try {\n eligibility = computeEligibility(action, target, { restrict: restrict ?? {}, authResumable });\n } catch (error) {\n capture(ports, error, { actionId: action.id, stage: \"eligible\" });\n continue;\n }\n if (eligibility.status === \"contract-break\") {\n capture(ports, new Error(eligibility.reason), { actionId: action.id, stage: \"eligible\" });\n continue;\n }\n if (eligibility.status === \"absent\") continue;\n out.push({ action, eligibility });\n }\n }\n return out;\n },\n\n providers() {\n return [...entries.keys()];\n },\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n/** A provider over a fixed list; its ids are declared up front so duplicates fail at registration. */\nexport function staticActionProvider(\n id: string,\n actions: readonly Action[],\n tier: ActionProvider[\"tier\"] = \"T0\",\n): ActionProvider {\n const list = actions.map((a) => (a.provider === id ? a : { ...a, provider: id }));\n return {\n id,\n tier,\n declaredIds: () => list.map((a) => a.id),\n actions: () => list,\n };\n}\n\n/** Always-available eligibility, for actions with no rule of their own. */\nexport const alwaysEligible = (): Eligibility => ({ status: \"available\" });\n","/**\n * The shared React context (T0-safe: imports only React and types). Shared\n * through globalThis so every per-layout bundle reads the same provider.\n */\nimport * as React from \"react\";\nimport type { ActionRegistry } from \"../actions/types\";\nimport type { AlchemyHostPorts } from \"../ports\";\n\nexport interface AlchemyActionsValue {\n registry: ActionRegistry;\n ports: AlchemyHostPorts;\n}\n\nconst KEY = Symbol.for(\"ai-matrx.alchemy.actions-context\");\ntype Holder = { [KEY]?: React.Context<AlchemyActionsValue | null> };\nexport const AlchemyActionsContext: React.Context<AlchemyActionsValue | null> =\n ((globalThis as Holder)[KEY] ??= React.createContext<AlchemyActionsValue | null>(null));\n\nexport function useAlchemyActions(): AlchemyActionsValue {\n const value = React.useContext(AlchemyActionsContext);\n if (!value) throw new Error(\"Alchemy actions: render inside <AlchemyActionsProvider> (@ai-matrx/alchemy/react/host).\");\n return value;\n}\n"],"mappings":"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","names":["React","editable"]}
|
|
1
|
+
{"version":3,"sources":["../src/react/host.tsx","../src/actions/click-target.ts","../src/actions/registry.ts","../src/react/context.ts"],"sourcesContent":["\"use client\";\n/**\n * `@ai-matrx/alchemy/react/host` — T0, the shell every mount pays for.\n *\n * Provider (registry + ports), provider registration, and the gesture shell\n * (right-click on desktop, long-press on touch). It imports NO model, NO\n * layout, NO renderer: the context menu (`/react/menu`, T1) and the mobile\n * sheet (`/react/sheet`, T1m) load by dynamic import on first open only.\n */\nimport * as React from \"react\";\nimport { createActionRegistry } from \"../actions/registry\";\nimport type { ActionProvider, ActionRegistry, ClickTarget } from \"../actions/types\";\nimport type { AlchemyHostPorts } from \"../ports\";\nimport { AlchemyActionsContext, useAlchemyActions, type AlchemyActionsValue } from \"./context\";\nimport type { MenuEngineOptions } from \"./engine\";\n\nexport { useAlchemyActions, type AlchemyActionsValue };\n\nexport interface AlchemyActionsProviderProps {\n ports: AlchemyHostPorts;\n /** Share one registry across providers; default: one per provider mount. */\n registry?: ActionRegistry;\n children: React.ReactNode;\n}\n\nexport function AlchemyActionsProvider({ ports, registry, children }: AlchemyActionsProviderProps): React.ReactElement {\n const own = React.useMemo(() => registry ?? createActionRegistry({ ports }), [registry, ports]);\n const value = React.useMemo(() => ({ registry: own, ports }), [own, ports]);\n return <AlchemyActionsContext.Provider value={value}>{children}</AlchemyActionsContext.Provider>;\n}\n\n/** Register a provider for the lifetime of the calling component. Duplicate ids throw (refused). */\nexport function useRegisterActionProvider(provider: ActionProvider | null): void {\n const { registry } = useAlchemyActions();\n React.useEffect(() => (provider ? registry.register(provider) : undefined), [registry, provider]);\n}\n\ntype MenuModule = typeof import(\"./menu\");\ntype SheetModule = typeof import(\"./sheet\");\n\nexport interface ActionMenuTriggerProps extends MenuEngineOptions {\n /** Build the target at gesture time (selection captured then). */\n resolveTarget(gesture: { element: Element; selectionText: string; editable: boolean }): ClickTarget | null;\n children: React.ReactElement;\n arrangement?: \"classic\" | \"tiered\" | \"command\";\n density?: \"comfortable\" | \"compact\";\n /** Force a renderer; default: sheet below 768px or on coarse pointers. */\n mobile?: boolean;\n /**\n * The wrapped surface is an editor that wires text mutation into the menu,\n * so the menu takes its right-click. Otherwise a live text field keeps its\n * native menu (Paste, spellcheck, autofill).\n */\n editable?: boolean;\n /** Override the lazy loaders (tests, custom bundlers). */\n loadMenu?: () => Promise<Pick<MenuModule, \"ContextMenuPanel\">>;\n loadSheet?: () => Promise<Pick<SheetModule, \"ActionSheet\">>;\n}\n\nconst LONG_PRESS_MS = 480;\n\nfunction isEditable(el: Element | null): boolean {\n if (!el) return false;\n if (el instanceof HTMLTextAreaElement) return true;\n if (el instanceof HTMLInputElement) return ![\"checkbox\", \"radio\", \"button\", \"submit\", \"range\", \"color\", \"file\"].includes(el.type);\n return (el as HTMLElement).isContentEditable === true;\n}\n\nfunction mobileViewport(): boolean {\n if (typeof window === \"undefined\") return false;\n return window.matchMedia?.(\"(max-width: 767px)\").matches ?? false;\n}\n\nexport function ActionMenuTrigger({\n resolveTarget,\n children,\n arrangement,\n density,\n mobile,\n editable = false,\n loadMenu = () => import(\"@ai-matrx/alchemy/react/menu\"),\n loadSheet = () => import(\"@ai-matrx/alchemy/react/sheet\"),\n ...options\n}: ActionMenuTriggerProps): React.ReactElement {\n const [state, setState] = React.useState<{ target: ClickTarget; point: { x: number; y: number }; sheet: boolean } | null>(null);\n const [open, setOpen] = React.useState(false);\n const [Menu, setMenu] = React.useState<MenuModule[\"ContextMenuPanel\"] | null>(null);\n const [Sheet, setSheet] = React.useState<SheetModule[\"ActionSheet\"] | null>(null);\n const press = React.useRef<{ timer: ReturnType<typeof setTimeout>; x: number; y: number } | null>(null);\n\n const openAt = React.useCallback(\n (element: Element, x: number, y: number, touch: boolean) => {\n const editable = isEditable(element) || isEditable(document.activeElement);\n const selectionText = window.getSelection?.()?.toString() ?? \"\";\n const target = resolveTarget({ element, selectionText, editable });\n if (!target) return false;\n const sheet = mobile ?? (touch || mobileViewport());\n setState({ target, point: { x, y }, sheet });\n setOpen(true);\n if (sheet && !Sheet) void loadSheet().then((m) => setSheet(() => m.ActionSheet));\n if (!sheet && !Menu) void loadMenu().then((m) => setMenu(() => m.ContextMenuPanel));\n return true;\n },\n [resolveTarget, mobile, Sheet, Menu, loadSheet, loadMenu],\n );\n\n const child = React.Children.only(children) as React.ReactElement<Record<string, unknown>>;\n const props = child.props;\n const compose =\n <E,>(own: (e: E) => void, theirs: unknown) =>\n (e: E) => {\n if (typeof theirs === \"function\") (theirs as (e: E) => void)(e);\n own(e);\n };\n\n const trigger = React.cloneElement(child, {\n \"data-alchemy-trigger\": \"context\",\n onContextMenu: compose((e: React.MouseEvent) => {\n if (e.defaultPrevented) return; // an inner menu owns the gesture\n const el = e.target as Element;\n if (isEditable(el) && !editable) return; // never steal a live text field's native menu\n if (openAt(el, e.clientX, e.clientY, false)) e.preventDefault();\n }, props.onContextMenu),\n onTouchStart: compose((e: React.TouchEvent) => {\n const t = e.touches[0];\n if (!t) return;\n const el = e.target as Element;\n if (isEditable(el) && !editable) return;\n const timer = setTimeout(() => {\n press.current = null;\n openAt(el, t.clientX, t.clientY, true);\n }, LONG_PRESS_MS);\n press.current = { timer, x: t.clientX, y: t.clientY };\n e.stopPropagation();\n }, props.onTouchStart),\n onTouchMove: compose((e: React.TouchEvent) => {\n const t = e.touches[0];\n const p = press.current;\n if (p && t && Math.hypot(t.clientX - p.x, t.clientY - p.y) > 8) {\n clearTimeout(p.timer);\n press.current = null;\n }\n }, props.onTouchMove),\n onTouchEnd: compose(() => {\n if (press.current) clearTimeout(press.current.timer);\n press.current = null;\n }, props.onTouchEnd),\n });\n\n return (\n <>\n {trigger}\n {state && !state.sheet && Menu ? (\n <Menu\n {...options}\n target={state.target}\n point={state.point}\n open={open}\n onOpenChange={setOpen}\n {...(arrangement ? { arrangement } : {})}\n {...(density ? { density } : {})}\n />\n ) : null}\n {state && state.sheet && Sheet ? <Sheet {...options} target={state.target} open={open} onOpenChange={setOpen} /> : null}\n </>\n );\n}\n","/**\n * The click target (CONTRACT §2.4) in its own module: `/ports` references it,\n * and `/ports` must stay free of every other package (types only).\n */\nimport type { QualifiedValueKey, ResolvedItem, ResolvedSurfaceDeclaration, WriteTarget } from \"../declare/types\";\n\n/** The six universal verbs — the ONLY rows that may grey (R1 c). */\nexport const UNIVERSAL_VERBS = [\"copy\", \"cut\", \"paste\", \"undo\", \"redo\", \"find\"] as const;\nexport type UniversalVerb = (typeof UNIVERSAL_VERBS)[number];\n\n/** The transfer Payload's kinds (`actions/types.ts` asserts equality at compile time). */\nexport type PayloadKind = \"text\" | \"markdown\" | \"json\" | \"rows\" | \"registered\";\n\n/** What eligibility is computed from: everything declared at the click target. */\nexport interface ClickTarget {\n surface: { instanceId: string; surfaceName: string } | null;\n /** The resolved declaration (inheritance applied). Eligibility decides by KEY existence over it. */\n declaration: ResolvedSurfaceDeclaration | null;\n /** Declared value keys with a read path here (screen names + qualified item keys). Key existence, not population. */\n valueKeys: ReadonlySet<QualifiedValueKey>;\n /** Scope chain, most general first: [\"global\", \"<domain>\", \"<surfaceName>\"]. */\n scopeChain: readonly string[];\n /** The surface's explicit exclusions (the one override). */\n excludedActionIds: ReadonlySet<string>;\n item: ResolvedItem | null;\n level: string | null;\n /** Offsets + the live range handle, editable or not. */\n selection: {\n text: string;\n type: \"editable\" | \"non-editable\";\n start: number;\n end: number;\n /** Opaque host handle (DOM Range / element) used to restore or stream into the selection. */\n handle?: unknown;\n } | null;\n /** Payload kinds a capture of this target would produce (from declared value types/kinds). */\n payloadKinds: readonly PayloadKind[];\n /** Declared targets writable on this target right now. */\n writable: readonly WriteTarget[];\n readOnly: boolean;\n /** True when any declared value here is secret/credential or non-exportable. */\n restricted: boolean;\n organizationId: string | null;\n auth: { authenticated: boolean; admin: boolean; creator: boolean };\n /**\n * Host-owned context a provider reads to act (the rich-document source, the\n * editable field's callbacks). Opaque to the package; never crosses into the\n * model. Providers that need it narrow it themselves.\n */\n host?: unknown;\n}\n\n","/**\n * The one action registry (CONTRACT §2.4).\n *\n * - Duplicate ids are REFUSED, never overwritten (rich-document's registry\n * silently overwrote — R12). A duplicate known at registration throws; one\n * that only appears when a provider yields it is dropped and captured.\n * - Eligibility is one pure pass over the resolved click target, under\n * \"restrict, never relax\": a host may remove ids or categories, never add.\n * - R1: an action that does not apply is ABSENT. Only the six universal verbs\n * (category \"clipboard\") may answer `unavailable-verb`, with a sentence; any\n * other action answering it is a contract break → absent + diagnostics.\n */\nimport type { AlchemyHostPorts, Json } from \"../ports\";\nimport {\n UNIVERSAL_VERBS,\n type Action,\n type ActionProvider,\n type ActionRegistry,\n type ClickTarget,\n type Eligibility,\n type ResolveRestriction,\n type ResolvedAction,\n} from \"./types\";\n\nexport class DuplicateActionError extends Error {\n readonly actionId: string;\n readonly providers: readonly [string, string];\n constructor(actionId: string, first: string, second: string) {\n super(\n first === second\n ? `Action \"${actionId}\" is yielded twice by provider \"${first}\". Ids are unique; the second was refused.`\n : `Action \"${actionId}\" is registered by provider \"${first}\" and again by \"${second}\". Ids are unique; \"${second}\"'s copy was refused.`,\n );\n this.name = \"DuplicateActionError\";\n this.actionId = actionId;\n this.providers = [first, second];\n }\n}\n\nexport class DuplicateProviderError extends Error {\n readonly providerId: string;\n constructor(providerId: string) {\n super(`Action provider \"${providerId}\" is already registered. Unregister it first; nothing was overwritten.`);\n this.name = \"DuplicateProviderError\";\n this.providerId = providerId;\n }\n}\n\nexport interface ActionRegistryOptions {\n /** `diagnostics` is required (nothing fails silently); `auth` decides whether requiresAuth actions reach a signed-out person. */\n ports: Pick<AlchemyHostPorts, \"diagnostics\" | \"auth\">;\n}\n\nconst VERBS = new Set<string>(UNIVERSAL_VERBS);\n\nfunction capture(ports: ActionRegistryOptions[\"ports\"], error: unknown, detail: Json): void {\n try {\n ports.diagnostics.capture(error, { area: \"actions\", detail });\n } catch {\n // A broken diagnostics port must never take the menu down with it.\n }\n}\n\n/**\n * Eligibility of one action at one target — the ONE pure pass (CONTRACT §1.6).\n * Exported for providers' tests; hosts call `registry.resolve`.\n */\nexport function computeEligibility(\n action: Action,\n target: ClickTarget,\n options: { restrict?: ResolveRestriction; authResumable: boolean },\n): Eligibility | { status: \"contract-break\"; reason: string } {\n const restrict = options.restrict;\n if (target.excludedActionIds.has(action.id)) return { status: \"absent\" };\n if (restrict?.exclude?.includes(action.id)) return { status: \"absent\" };\n if (restrict?.categories && !restrict.categories.includes(action.category)) return { status: \"absent\" };\n if (action.writes && action.writes.length > 0) {\n // A read-only target offers nothing that would change the record.\n if (target.readOnly) return { status: \"absent\" };\n const writable = new Set(target.writable.map((w) => w.name));\n if (!action.writes.some((name) => writable.has(name))) return { status: \"absent\" };\n }\n if (action.requiresAuth && !target.auth.authenticated && !options.authResumable) {\n // Sign-in-then-resume is the capability; without the auth port it is absent (N8).\n return { status: \"absent\" };\n }\n const own = action.eligible(target);\n if (own.status === \"unavailable\") {\n // A greyed row must say why; without a sentence it is absent.\n return own.sentence.trim() ? own : { status: \"absent\" };\n }\n if (own.status !== \"unavailable-verb\") return own;\n if (action.category !== \"clipboard\" || !VERBS.has(own.verb)) {\n return {\n status: \"contract-break\",\n reason: `Action \"${action.id}\" answered unavailable-verb but only the six universal verbs (category \"clipboard\") may grey; it is absent instead.`,\n };\n }\n if (!own.sentence.trim()) {\n return {\n status: \"contract-break\",\n reason: `Universal verb \"${action.id}\" greyed without a sentence; a greyed verb must say why.`,\n };\n }\n return own;\n}\n\ninterface Entry {\n provider: ActionProvider;\n declared: readonly string[];\n}\n\nexport function createActionRegistry(options: ActionRegistryOptions): ActionRegistry {\n const ports = options.ports;\n const entries = new Map<string, Entry>();\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const l of [...listeners]) l();\n };\n\n return {\n register(provider) {\n if (entries.has(provider.id)) throw new DuplicateProviderError(provider.id);\n const declared = provider.declaredIds?.() ?? [];\n const own = new Set<string>();\n for (const id of declared) {\n if (own.has(id)) throw new DuplicateActionError(id, provider.id, provider.id);\n own.add(id);\n for (const [otherId, other] of entries) {\n if (other.declared.includes(id)) throw new DuplicateActionError(id, otherId, provider.id);\n }\n }\n const entry: Entry = { provider, declared };\n entries.set(provider.id, entry);\n notify();\n return () => {\n if (entries.get(provider.id) === entry) {\n entries.delete(provider.id);\n notify();\n }\n };\n },\n\n async resolve(target, restrict) {\n const authResumable = Boolean(ports.auth);\n const seen = new Map<string, string>();\n const out: ResolvedAction[] = [];\n for (const [providerId, { provider }] of [...entries]) {\n let actions: readonly Action[];\n try {\n actions = await provider.actions(target);\n } catch (error) {\n capture(ports, error, { providerId, stage: \"actions\" });\n continue;\n }\n for (const raw of actions) {\n const first = seen.get(raw.id);\n if (first !== undefined) {\n capture(ports, new DuplicateActionError(raw.id, first, providerId), { actionId: raw.id });\n continue;\n }\n seen.set(raw.id, providerId);\n const action: Action = raw.provider === providerId ? raw : { ...raw, provider: providerId };\n let eligibility: ReturnType<typeof computeEligibility>;\n try {\n eligibility = computeEligibility(action, target, { restrict: restrict ?? {}, authResumable });\n } catch (error) {\n capture(ports, error, { actionId: action.id, stage: \"eligible\" });\n continue;\n }\n if (eligibility.status === \"contract-break\") {\n capture(ports, new Error(eligibility.reason), { actionId: action.id, stage: \"eligible\" });\n continue;\n }\n if (eligibility.status === \"absent\") continue;\n out.push({ action, eligibility });\n }\n }\n return out;\n },\n\n providers() {\n return [...entries.keys()];\n },\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n/** A provider over a fixed list; its ids are declared up front so duplicates fail at registration. */\nexport function staticActionProvider(\n id: string,\n actions: readonly Action[],\n tier: ActionProvider[\"tier\"] = \"T0\",\n): ActionProvider {\n const list = actions.map((a) => (a.provider === id ? a : { ...a, provider: id }));\n return {\n id,\n tier,\n declaredIds: () => list.map((a) => a.id),\n actions: () => list,\n };\n}\n\n/** Always-available eligibility, for actions with no rule of their own. */\nexport const alwaysEligible = (): Eligibility => ({ status: \"available\" });\n","/**\n * The shared React context (T0-safe: imports only React and types). Shared\n * through globalThis so every per-layout bundle reads the same provider.\n */\nimport * as React from \"react\";\nimport type { ActionRegistry } from \"../actions/types\";\nimport type { AlchemyHostPorts } from \"../ports\";\n\nexport interface AlchemyActionsValue {\n registry: ActionRegistry;\n ports: AlchemyHostPorts;\n}\n\nconst KEY = Symbol.for(\"ai-matrx.alchemy.actions-context\");\ntype Holder = { [KEY]?: React.Context<AlchemyActionsValue | null> };\nexport const AlchemyActionsContext: React.Context<AlchemyActionsValue | null> =\n ((globalThis as Holder)[KEY] ??= React.createContext<AlchemyActionsValue | null>(null));\n\nexport function useAlchemyActions(): AlchemyActionsValue {\n const value = React.useContext(AlchemyActionsContext);\n if (!value) throw new Error(\"Alchemy actions: render inside <AlchemyActionsProvider> (@ai-matrx/alchemy/react/host).\");\n return value;\n}\n"],"mappings":"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","names":["React","editable"]}
|
package/dist/react-menu.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
"use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var react_menu_exports={};__export(react_menu_exports,{ContextMenuPanel:()=>ContextMenuPanel});module.exports=__toCommonJS(react_menu_exports);var React4=__toESM(require("react"),1),import_react_dom=require("react-dom"),import_design_system2=require("@ai-matrx/design-system");function sectionLabel(section){return section.label??null}function isLeaf(node){return node.kind==="item"||node.kind==="checkbox"||node.kind==="link"}function fold(section){return{kind:"submenu",id:`${section.id}:fold`,label:section.label,...section.icon!==void 0?{icon:section.icon}:{},children:section.nodes}}function rowCount(nodes){return nodes.filter(n=>n.kind!=="separator"&&n.kind!=="label").length}function stripOf(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(isLeaf):[]}function clipboardRest(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(n=>!isLeaf(n)&&n.kind==="submenu"):[]}function classic(model){return{strip:[],sections:model.sections,filterable:!1}}function tiered(model,filterable){let strip=stripOf(model),sections=[],rest=clipboardRest(model);for(let s of model.sections){if(s.group==="clipboard"&&!s.primary){rest.length&§ions.push({...s,nodes:rest});continue}if(s.primary){sections.push(s);continue}if(!s.label||s.group==="surface-info"||rowCount(s.nodes)<=3){sections.push(s);continue}sections.push({id:s.id,group:s.group,nodes:[fold({...s,label:s.label})]})}return{strip,sections,filterable}}function context(model,arrangement){return arrangement==="classic"?classic(model):tiered(model,arrangement==="command")}function palette(model){let leaves=[],walk=(nodes,trail)=>{for(let n of nodes)isLeaf(n)?leaves.push({node:n,breadcrumb:trail}):n.kind==="submenu"&&(n.lazy?leaves.push({node:n,breadcrumb:trail}):walk(n.children,[...trail,n.label]))};for(let s of model.sections)walk(s.nodes,s.label?[s.label]:[]);return{leaves}}function filterPalette(arranged,query){let words=query.toLowerCase().split(/\s+/).filter(Boolean);return words.length?{leaves:arranged.leaves.filter(l=>{let hay=`${l.node.label} ${l.breadcrumb.join(" ")}`.toLowerCase();return words.every(w=>hay.includes(w))})}:arranged}var React2=__toESM(require("react"),1);var DEFAULT_SENSITIVITY=Object.freeze({exportable:!0,classification:"ordinary",includedByDefault:!0});function resolveSensitivity(value){return{exportable:value.sensitivity?.exportable??DEFAULT_SENSITIVITY.exportable,classification:value.sensitivity?.classification??DEFAULT_SENSITIVITY.classification,includedByDefault:value.sensitivity?.includedByDefault??DEFAULT_SENSITIVITY.includedByDefault}}function toJson(value){if(!(value===void 0||typeof value=="function"||typeof value=="symbol")){if(value===null||typeof value=="string"||typeof value=="boolean")return value;if(typeof value=="number")return Number.isFinite(value)?value:null;if(typeof value=="bigint")return value.toString();if(value instanceof Date)return value.toISOString();if(Array.isArray(value))return value.map(v=>toJson(v)??null);if(typeof value=="object"){let out={};for(let[k,v]of Object.entries(value)){let j=toJson(v);j!==void 0&&(out[k]=j)}return out}}}function projectValues(raw,declared,pathPrefix=""){let byName=new Map(declared.map(d=>[d.name,d])),values={},omissions=[];for(let[name,value]of Object.entries(raw)){let path=pathPrefix?`${pathPrefix}.${name}`:name,decl=byName.get(name);if(!decl){omissions.push({path,reason:"filtered"});continue}let sensitivity=resolveSensitivity(decl);if(sensitivity.classification!=="ordinary"){omissions.push({path,reason:"excluded",classification:sensitivity.classification});continue}if(!sensitivity.exportable){omissions.push({path,reason:"excluded"});continue}let json=toJson(value);json!==void 0&&(values[name]=json)}return{values,omissions}}function createProjectedReads(source){return{async values(signal){if(!source.surface||!source.declaration)return{values:{},omissions:[]};let raw=await source.surface.readValues(signal),screenValues=source.declaration.values;return projectValues(raw,screenValues)},async item(signal){let item=source.item;if(!item||!source.declaration)return null;let type=source.declaration.itemTypes?.find(t=>t.name===item.itemType);if(!type)return{values:{},omissions:[{path:item.itemType,reason:"filtered"}]};let raw=await item.readValues(signal);return projectValues(raw,type.values,item.itemType)},async live(signal){return source.surface?.readLive?source.surface.readLive(signal):{}}}}async function runAction(action,target,options){let{ports}=options;if(action.requiresAuth&&!target.auth.authenticated)return ports.auth?(ports.auth.signInThenResume({actionId:action.id,contentFingerprint:options.contentFingerprint??action.id}),{status:"auth-pending"}):(ports.diagnostics.capture(new Error(`requiresAuth action "${action.id}" ran signed out with no auth port`),{area:"actions",detail:{actionId:action.id}}),{status:"failed",error:new Error("sign-in unavailable")});let reads=createProjectedReads(options.source??{surface:null,declaration:target.declaration,item:null}),signal=options.signal??new AbortController().signal;try{return{status:"ran",outcome:await action.run(target,{reads,ports,signal})}}catch(error){ports.diagnostics.capture(error,{area:"actions",detail:{actionId:action.id,stage:"run"}});let label=typeof action.label=="string"?action.label:action.id;return ports.notify?.error(`${label} did not finish.`,"Try again; if it keeps failing, report it from the feedback button."),{status:"failed",error}}}var APPROVED_HEADINGS=new Set(["Save","Copy as","Export","Edit","Creator tools","Admin","App","Compare","History"]),CONTAINER_WORD=/^(more|more…|more\.\.\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i,TARGET_SHAPE=/^(Cell|Row|Column|Table|Range|Selection|Record) · .+$/;function isContainerWord(label){return CONTAINER_WORD.test(label.trim())}function isApprovedHeading(label,kind="group",approved=APPROVED_HEADINGS){let l=label.trim();return!l||isContainerWord(l)?!1:approved.has(l)?!0:kind==="target"||TARGET_SHAPE.test(l)}function defaultHeadingPolicy(){return globalThis.process?.env?.NODE_ENV==="production"?"inline":"throw"}var UnapprovedHeadingError=class extends Error{heading;constructor(heading,where){super(`Menu heading "${heading}" (${where}) is not an approved heading. Remedy: show these rows inline \u2014 declare no section (or kind "target" for the clicked thing's own label) \u2014 or ask Arman to add the name to APPROVED_HEADINGS in @ai-matrx/alchemy/menu. A generic container word ("More\u2026", "Other") is never approved.`),this.name="UnapprovedHeadingError",this.heading=heading}};var CATEGORY_GROUP={clipboard:"clipboard",copy:"transfer",export:"transfer",save:"document",share:"document",edit:"editable",history:"history",ai:"ai",ask:"ai",listen:"tools",study:"tools",app:"tools",feedback:"quick",creator:"admin",admin:"admin","surface-info":"surface-info"},GROUP_ORDER=["clipboard","editable","transfer","document","history","ai","tools","quick","surface","admin","surface-info"],CATEGORY_ORDER=["clipboard","edit","copy","export","save","share","history","ask","ai","listen","study","app","feedback","creator","admin","surface-info"];function translate(i18n,key,fallback){if(!i18n)return fallback;try{return i18n.t(key,fallback)}catch{return fallback}}function actionToNode(resolved,target,options={}){let{action,eligibility}=resolved,rawLabel=typeof action.label=="function"?action.label(target):action.label,label=translate(options.i18n,action.labelKey??action.id,rawLabel),base={id:action.id,actionId:action.id,...action.icon!==void 0?{icon:action.icon}:{},...action.placement!==void 0?{placement:action.placement}:{}};if(action.expand)return{...base,kind:"submenu",label,lazy:!0,loading:!1,...action.pending?.(target)?{pending:!0}:{},children:[],...action.emptyLabel!==void 0?{emptyLabel:action.emptyLabel}:{}};if(action.href!==void 0){let href=typeof action.href=="function"?action.href(target):action.href;return{...base,kind:"link",label,href}}if(action.toggle)return{...base,kind:"checkbox",label,checked:action.pressed?.(target)??!1,...action.description!==void 0?{description:action.description}:{}};let state=action.stateIcon?.(target)??null,pressed=action.pressed?.(target),hint=action.hint!==void 0?translate(options.i18n,action.hintKey??`${action.id}.hint`,action.hint):void 0;return{...base,kind:"item",label,...action.iconTone!==void 0?{iconTone:action.iconTone}:{},...state?{stateIcon:state}:{},...action.description!==void 0?{description:action.description}:{},...hint!==void 0?{hint}:{},...pressed!==void 0?{pressed}:{},...action.destructive?{destructive:!0}:{},...action.preserveSelection?{preserveSelection:!0}:{},...eligibility.status==="unavailable-verb"?{unavailable:{sentence:eligibility.sentence}}:{}}}function buildMenuModel(target,resolved,options={}){let policy=options.headingPolicy??defaultHeadingPolicy();resolved=resolved.map(r=>{let sec=r.action.section;if(r.action.expand&&isContainerWord(typeof r.action.label=="string"?r.action.label:"")&&policy==="throw")throw new UnapprovedHeadingError(r.action.label,`submenu of ${r.action.id}`);if(!sec)return r;let kind=sec.kind??(sec.primary?"target":"group");if(isApprovedHeading(sec.label,kind))return r;if(policy==="throw")throw new UnapprovedHeadingError(sec.label,`section of ${r.action.id}`);let{section:_dropped,...rest}=r.action;return{...r,action:rest}});let ordered=resolved.map((r,index)=>({r,index})).sort((a,b)=>{let ca=CATEGORY_ORDER.indexOf(a.r.action.category),cb=CATEGORY_ORDER.indexOf(b.r.action.category);if(ca!==cb)return ca-cb;let oa=a.r.action.order??0,ob=b.r.action.order??0;return oa!==ob?oa-ob:a.index-b.index}).map(x=>x.r),sections=new Map,roles={},explicit=new Map;resolved.forEach((r,index)=>{let sec=r.action.section;if(!sec)return;let entry=explicit.get(sec.id)??{group:CATEGORY_GROUP[r.action.category],rows:[]};entry.rows.push({r,index}),explicit.set(sec.id,entry)});let placed=new Set;for(let r of ordered){let sec=r.action.section;if(sec&&placed.has(sec.id))continue;let group=sec?explicit.get(sec.id)?.group??CATEGORY_GROUP[r.action.category]:CATEGORY_GROUP[r.action.category],sectionId=sec?`section:${sec.id}`:group,section=sections.get(sectionId);section||(section={id:sectionId,group,nodes:[],...sec?{label:translate(options.i18n,sec.labelKey??`section.${sec.id}`,sec.label)}:{},...sec?.icon!==void 0?{icon:sec.icon}:{},...sec?.primary?{primary:!0}:{},...sec&&(sec.kind??(sec.primary?"target":"group"))==="target"?{target:!0}:{}},sections.set(sectionId,section));let rows=sec?(explicit.get(sec.id)?.rows??[]).sort((x,y)=>(x.r.action.order??0)-(y.r.action.order??0)||x.index-y.index).map(x=>x.r):[r];sec&&placed.add(sec.id);for(let row of rows)section.nodes.push(actionToNode(row,target,options)),row.action.verb&&(roles[row.action.verb]=row.action.id)}let list=[...sections.values()].sort((a,b)=>!!a.primary!=!!b.primary?a.primary?-1:1:GROUP_ORDER.indexOf(a.group)-GROUP_ORDER.indexOf(b.group)),selected=target.selection?.text?.trim(),content=options.content?.trim(),fieldName=options.contentLabel?.trim();return{header:selected?{label:"Selected",text:target.selection?.text??""}:fieldName?{label:fieldName,text:options.content??""}:content?{label:"Content",text:options.content??""}:null,sections:list,roles}}function withExpandedChildren(model,nodeId,children){let visit=nodes=>nodes.map(n=>n.kind!=="submenu"?n:n.id===nodeId?children==="loading"?{...n,loading:!0}:{...n,lazy:!1,loading:!1,pending:!1,children}:{...n,children:visit(n.children)});return{...model,sections:model.sections.map(s=>({...s,nodes:visit(s.nodes)}))}}function findNode(nodes,id){for(let n of nodes){if(n.id===id)return n;if(n.kind==="submenu"){let hit=findNode(n.children,id);if(hit)return hit}}return null}var React=__toESM(require("react"),1),KEY=Symbol.for("ai-matrx.alchemy.actions-context"),AlchemyActionsContext=globalThis[KEY]??=React.createContext(null);function useAlchemyActions(){let value=React.useContext(AlchemyActionsContext);if(!value)throw new Error("Alchemy actions: render inside <AlchemyActionsProvider> (@ai-matrx/alchemy/react/host).");return value}function isLatestExpansion(capturedTargetEpoch,currentTargetEpoch,capturedNodeSequence,currentNodeSequence){return capturedTargetEpoch===currentTargetEpoch&&capturedNodeSequence===currentNodeSequence}var import_jsx_runtime=require("react/jsx-runtime");function useMenuEngine(target,options={}){let{registry,ports}=useAlchemyActions(),[resolved,setResolved]=React2.useState([]),[model,setModel]=React2.useState(null),[tick,setTick]=React2.useState(0),expanded=React2.useRef(new Map),expandSeq=React2.useRef(new Map),targetEpoch=React2.useRef(0),currentTarget=React2.useRef(target);currentTarget.current!==target&&(currentTarget.current=target,targetEpoch.current+=1);let active=options.active??!0,optionsRef=React2.useRef(options);optionsRef.current=options,React2.useEffect(()=>registry.subscribe(()=>setTick(t=>t+1)),[registry]);let expandAction=React2.useCallback(async(action,nodeId)=>{if(!target||!action.expand)return;let epoch=targetEpoch.current,seq=(expandSeq.current.get(nodeId)??0)+1;expandSeq.current.set(nodeId,seq);let latest=()=>isLatestExpansion(epoch,targetEpoch.current,seq,expandSeq.current.get(nodeId));setModel(m=>m&&withExpandedChildren(m,nodeId,"loading"));try{let children=await action.expand(target,new AbortController().signal);if(!latest())return;let nodes=children.map(child=>(expanded.current.set(child.id,child),actionToNode({action:child,eligibility:{status:"available"}},target,ports.i18n?{i18n:ports.i18n}:{})));setModel(m=>m&&withExpandedChildren(m,nodeId,nodes))}catch(error){if(!latest())return;ports.diagnostics.capture(error,{area:"menu",detail:{nodeId,stage:"expand"}}),setModel(m=>m&&withExpandedChildren(m,nodeId,[]))}},[target,ports]);React2.useEffect(()=>{expanded.current.clear()},[target]),React2.useEffect(()=>{if(!target||!active)return;let live=!0;return registry.resolve(target,optionsRef.current.restrict).then(list=>{if(live){setResolved(list),setModel(buildMenuModel(target,list,{...ports.i18n?{i18n:ports.i18n}:{},content:optionsRef.current.content??null,contentLabel:optionsRef.current.contentLabel??null}));for(let{action}of list)expanded.current.get(action.id)===null&&action.expand&&expandAction(action,action.id)}}),()=>{live=!1}},[registry,ports,target,active,tick,options.revision,expandAction]);let resolvedRef=React2.useRef(resolved);resolvedRef.current=resolved;let subscribedIds=resolved.map(r=>r.action.id).join(`
|
|
3
|
-
`);React2.useEffect(()=>{if(!target)return;let subscribing=!0,onChange=()=>{subscribing||setTick(t=>t+1)},offs=resolvedRef.current.map(r=>r.action.subscribe?.(onChange,target)).filter(off=>typeof off=="function");return subscribing=!1,()=>offs.forEach(off=>off())},[subscribedIds,target]);let findAction=React2.useCallback(id=>resolved.find(r=>r.action.id===id)?.action??expanded.current.get(id)??void 0,[resolved]),run=React2.useCallback(async nodeId=>{if(!target||!model)return null;let node=model.sections.map(s=>findNode(s.nodes,nodeId)).find(Boolean)??null,action=findAction(node&&"actionId"in node&&node.actionId?node.actionId:nodeId);if(!action)return null;let o=optionsRef.current,result=await runAction(action,target,{ports,...o.source?{source:o.source}:{},...o.contentFingerprint?{contentFingerprint:o.contentFingerprint}:{}});return o.onRan?.(nodeId,result),result},[target,model,findAction,ports]),expand=React2.useCallback(async nodeId=>{let action=findAction(nodeId);!target||!action?.expand||(expanded.current.set(nodeId,null),await expandAction(action,nodeId))},[findAction,target,expandAction]),icon=React2.useCallback((key,tone)=>{if(!key||!ports.icons)return null;let Resolved=ports.icons.resolve(key);return Resolved?(0,import_jsx_runtime.jsx)(Resolved,{className:`h-4 w-4 shrink-0 ${tone??""}`,"aria-hidden":!0}):null},[ports.icons]),explain=React2.useCallback(sentence=>{ports.notify?ports.notify.info(sentence):ports.diagnostics.capture(new Error(`No notify port to explain: ${sentence}`),{area:"menu",detail:{sentence}})},[ports]);return{model,resolved,run,expand,icon,explain}}var React3=__toESM(require("react"),1),import_design_system=require("@ai-matrx/design-system");var import_jsx_runtime2=require("react/jsx-runtime");function NodeList({nodes,engine}){return(0,import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment,{children:nodes.map(node=>(0,import_jsx_runtime2.jsx)(MenuNodeView,{node,engine},node.id))})}function SectionList({sections,engine}){return(0,import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment,{children:sections.map((section,index)=>(0,import_jsx_runtime2.jsxs)(React3.Fragment,{children:[index>0?(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuSeparator,{}):null,sectionLabel(section)?(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel,{className:"text-xs font-medium text-muted-foreground",children:sectionLabel(section)}):null,(0,import_jsx_runtime2.jsx)(NodeList,{nodes:section.nodes,engine})]},section.id))})}function iconOf(node,engine){if(node.kind!=="item")return"icon"in node?engine.icon(node.icon):null;if(node.stateIcon){let glyph=engine.icon(node.stateIcon.icon);return node.stateIcon.spin?(0,import_jsx_runtime2.jsx)("span",{className:"inline-flex animate-spin",children:glyph}):glyph}if(!("icon"in node))return null;let tone=node.pressed===!1?void 0:node.iconTone;return engine.icon(node.icon,tone)}function MenuNodeView({node,engine}){switch(node.kind){case"separator":return(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuSeparator,{});case"label":return(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel,{className:"text-xs font-medium text-muted-foreground",children:node.label});case"submenu":return(0,import_jsx_runtime2.jsxs)(import_design_system.DropdownMenuSub,{onOpenChange:open=>{open&&node.lazy&&!node.loading&&engine.expand(node.id)},children:[(0,import_jsx_runtime2.jsxs)(import_design_system.DropdownMenuSubTrigger,{"data-alchemy-node":node.id,className:"gap-2","aria-busy":node.pending?!0:void 0,children:[iconOf(node,engine),(0,import_jsx_runtime2.jsx)("span",{className:"truncate",children:node.label}),node.pending?(0,import_jsx_runtime2.jsx)("span",{className:"ml-auto text-xs text-muted-foreground",children:"Loading\u2026"}):null]}),(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuSubContent,{className:"max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto",children:node.loading?(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel,{className:"text-xs text-muted-foreground",children:"Loading\u2026"}):node.children.length===0&&!node.lazy?(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel,{className:"text-xs text-muted-foreground",children:node.emptyLabel??"Nothing here yet"}):(0,import_jsx_runtime2.jsx)(NodeList,{nodes:node.children,engine})})]});case"checkbox":return(0,import_jsx_runtime2.jsxs)(import_design_system.DropdownMenuCheckboxItem,{"data-alchemy-node":node.id,checked:node.checked,onSelect:()=>{engine.run(node.id)},className:"gap-2",children:[iconOf(node,engine),(0,import_jsx_runtime2.jsx)("span",{className:"truncate",children:node.label})]});case"link":return(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuItem,{asChild:!0,"data-alchemy-node":node.id,children:(0,import_jsx_runtime2.jsxs)("a",{href:node.href,target:node.target,className:"gap-2",children:[iconOf(node,engine),(0,import_jsx_runtime2.jsx)("span",{className:"truncate",children:node.label})]})});case"item":return(0,import_jsx_runtime2.jsx)(LeafItem,{node,engine})}}function LeafItem({node,engine}){let row=(0,import_jsx_runtime2.jsxs)(import_design_system.DropdownMenuItem,{"data-alchemy-node":node.id,"aria-disabled":node.unavailable?!0:void 0,"aria-pressed":node.pressed,onSelect:event=>{if(node.unavailable){event.preventDefault();return}engine.run(node.id)},className:`gap-2 ${node.unavailable?"opacity-50":""} ${node.destructive?"text-destructive":""}`,children:[iconOf(node,engine),(0,import_jsx_runtime2.jsxs)("span",{className:"flex min-w-0 flex-1 flex-col",children:[(0,import_jsx_runtime2.jsx)("span",{className:"truncate",children:node.label}),node.description?(0,import_jsx_runtime2.jsx)("span",{className:"truncate text-xs text-muted-foreground",children:node.description}):null]}),node.hint?(0,import_jsx_runtime2.jsx)("span",{className:"ml-auto text-xs text-muted-foreground",children:node.hint}):null]});return node.unavailable?(0,import_jsx_runtime2.jsxs)(import_design_system.Tooltip,{children:[(0,import_jsx_runtime2.jsx)(import_design_system.TooltipTrigger,{asChild:!0,children:row}),(0,import_jsx_runtime2.jsx)(import_design_system.TooltipContent,{children:node.unavailable.sentence})]}):row}function LeafButton({node,engine,showLabel}){if(node.kind!=="item"&&node.kind!=="checkbox"&&node.kind!=="link")return null;let unavailable=node.kind==="item"?node.unavailable:void 0,pressed=node.kind==="item"?node.pressed:node.kind==="checkbox"?node.checked:void 0,glyph=iconOf(node,engine),button=(0,import_jsx_runtime2.jsxs)("button",{type:"button","data-alchemy-node":node.id,"data-matrx-button":"","data-tap-floor":"","data-tap-floor-square":"","aria-label":node.label,"aria-disabled":unavailable?!0:void 0,"aria-pressed":pressed,onMouseDown:e=>{node.kind==="item"&&node.preserveSelection&&e.preventDefault()},onClick:()=>{if(unavailable){engine.explain(unavailable.sentence);return}if(node.kind==="link"){window.location.assign(node.href);return}engine.run(node.id)},className:`inline-flex h-7 min-w-7 pointer-coarse:h-11 pointer-coarse:min-w-11 shrink-0 items-center justify-center gap-1.5 rounded-md px-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground ${unavailable?"cursor-default opacity-50 hover:bg-transparent":""} ${pressed?"text-foreground":""}`,children:[glyph,showLabel||!glyph?(0,import_jsx_runtime2.jsx)("span",{className:"whitespace-nowrap",children:node.label}):null]});return(0,import_jsx_runtime2.jsxs)(import_design_system.Tooltip,{children:[(0,import_jsx_runtime2.jsx)(import_design_system.TooltipTrigger,{asChild:!0,children:button}),(0,import_jsx_runtime2.jsx)(import_design_system.TooltipContent,{children:unavailable?unavailable.sentence:node.label})]})}var NO_LONGER="Nothing here right now.";function kept(node){if(node.kind==="separator"||node.kind==="label")return node;let label="label"in node?node.label:"",icon="icon"in node?node.icon:void 0,sentence=node.kind==="submenu"?node.emptyLabel??NO_LONGER:NO_LONGER;return{kind:"item",id:node.id,label,...icon!==void 0?{icon}:{},unavailable:{sentence}}}var LOADING="__loading_after_open",LATE="__arrived_after_open";function isLoading(node){return node.kind==="submenu"&&!!node.pending}function keepSlotsWhileOpen(shown,current){if(!shown){let loading=[],loadingGroup,sections2=[];for(let section of current){let nodes=section.nodes.filter(node=>isLoading(node)?(loading.push(node),loadingGroup??=section.group,!1):!0);(nodes.length===section.nodes.length||nodes.some(n=>n.kind!=="separator"&&n.kind!=="label"))&§ions2.push({...section,nodes})}return loading.length&§ions2.push({id:LOADING,group:loadingGroup,nodes:loading}),{sections:sections2}}let now=new Map,nowSection=new Map;for(let section of current){nowSection.set(section.id,section);for(let node of section.nodes)now.set(node.id,node)}let drawn=new Set,sections=[],loadingSection;for(let section of shown.sections){if(section.id===LOADING){let nodes2=[];for(let node of section.nodes){drawn.add(node.id);let live=now.get(node.id);live&&nodes2.push(live)}loadingSection={...section,nodes:nodes2};continue}let fresh=nowSection.get(section.id),nodes=section.nodes.map(node=>(drawn.add(node.id),now.get(node.id)??kept(node)));sections.push({...fresh??section,nodes})}let late=[];for(let section of current)for(let node of section.nodes)drawn.has(node.id)||(isLoading(node)?(loadingSection??={id:LOADING,group:section.group,nodes:[]},loadingSection.nodes.push(node)):late.push(node));if(late.length){let lateSection=sections.find(s=>s.id===LATE);lateSection?lateSection.nodes.push(...late):sections.push({id:LATE,group:sections[sections.length-1]?.group??"more",nodes:late})}return loadingSection?.nodes.length&§ions.push(loadingSection),{sections}}var import_jsx_runtime3=require("react/jsx-runtime");function ContextMenuPanel({target,point,open,onOpenChange,arrangement="command",density="comfortable",...options}){let engine=useMenuEngine(target,{...options,onRan:(id,r)=>{options.onRan?.(id,r),onOpenChange(!1)}}),[query,setQuery]=React4.useState(""),arranged=engine.model?context(engine.model,arrangement):null,slots=React4.useRef({at:"",shown:null}),at=`${point.x},${point.y}`;(!open||slots.current.at!==at)&&(slots.current={at,shown:null});let sections=arranged&&open?(slots.current.shown=keepSlotsWhileOpen(slots.current.shown,arranged.sections)).sections:arranged?.sections??[],filtered=engine.model&&query.trim()?filterPalette(palette(engine.model),query):null,anchor=(0,import_jsx_runtime3.jsx)(import_design_system2.DropdownMenuTrigger,{asChild:!0,children:(0,import_jsx_runtime3.jsx)("span",{"aria-hidden":!0,"data-alchemy-anchor":"context",style:{position:"fixed",left:point.x,top:point.y,width:0,height:0,pointerEvents:"none"}})});return(0,import_jsx_runtime3.jsxs)(import_design_system2.DropdownMenu,{open,onOpenChange,modal:!1,children:[typeof document>"u"?anchor:(0,import_react_dom.createPortal)(anchor,document.body),(0,import_jsx_runtime3.jsx)(import_design_system2.DropdownMenuContent,{side:"right",align:"start",sideOffset:2,collisionPadding:8,"data-alchemy-layout":`context-${arrangement}`,onPointerUpCapture:event=>{event.button===2&&event.preventDefault()},className:`w-72 max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto ${density==="compact"?"text-xs":""}`,children:(0,import_jsx_runtime3.jsxs)(import_design_system2.TooltipProvider,{children:[engine.model?.header?(0,import_jsx_runtime3.jsxs)(import_design_system2.DropdownMenuLabel,{className:"truncate text-xs font-normal text-muted-foreground",title:engine.model.header.text,children:[engine.model.header.label,engine.model.header.text.trim()?`: ${engine.model.header.text}`:null]}):null,arranged?(0,import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment,{children:[arranged.strip.length?(0,import_jsx_runtime3.jsx)("div",{role:"toolbar","aria-label":"Clipboard",className:"flex items-center gap-0.5 px-1 py-1",children:arranged.strip.map(node=>(0,import_jsx_runtime3.jsx)(LeafButton,{node,engine,showLabel:!1},node.id))}):null,arranged.filterable?(0,import_jsx_runtime3.jsx)("div",{className:"px-1 pb-1",children:(0,import_jsx_runtime3.jsx)("input",{"aria-label":"Filter actions",placeholder:"Type to filter\u2026",value:query,onChange:e=>setQuery(e.target.value),onKeyDown:e=>{e.stopPropagation(),e.key==="Enter"&&filtered?.leaves[0]&&engine.run(filtered.leaves[0].node.id)},className:"h-7 w-full rounded-md border border-border bg-transparent px-2 text-sm outline-none"})}):null,(arranged.strip.length||arranged.filterable)&§ions.length?(0,import_jsx_runtime3.jsx)(import_design_system2.DropdownMenuSeparator,{}):null,filtered?filtered.leaves.length?filtered.leaves.map(({node,breadcrumb})=>(0,import_jsx_runtime3.jsxs)(import_design_system2.DropdownMenuItem,{"data-alchemy-node":node.id,onSelect:()=>{node.kind==="submenu"?engine.expand(node.id):engine.run(node.id)},className:"flex-col items-start gap-0",children:[(0,import_jsx_runtime3.jsx)("span",{children:node.label}),(0,import_jsx_runtime3.jsx)("span",{className:"text-xs text-muted-foreground",children:breadcrumb.join(" \u203A ")})]},node.id)):(0,import_jsx_runtime3.jsxs)("div",{className:"px-2 py-1.5 text-xs text-muted-foreground",children:["No action matches \u201C",query,"\u201D."]}):(0,import_jsx_runtime3.jsx)(SectionList,{sections,engine})]}):(0,import_jsx_runtime3.jsx)("div",{className:"px-2 py-1.5 text-xs text-muted-foreground",children:"Loading\u2026"})]})})]})}
|
|
2
|
+
"use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var react_menu_exports={};__export(react_menu_exports,{ContextMenuPanel:()=>ContextMenuPanel});module.exports=__toCommonJS(react_menu_exports);var React4=__toESM(require("react"),1),import_react_dom=require("react-dom"),import_design_system2=require("@ai-matrx/design-system");function sectionLabel(section){return section.label??null}function isLeaf(node){return node.kind==="item"||node.kind==="checkbox"||node.kind==="link"}function fold(section){return{kind:"submenu",id:`${section.id}:fold`,label:section.label,...section.icon!==void 0?{icon:section.icon}:{},children:section.nodes}}function rowCount(nodes){return nodes.filter(n=>n.kind!=="separator"&&n.kind!=="label").length}function stripOf(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(isLeaf):[]}function clipboardRest(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(n=>!isLeaf(n)&&n.kind==="submenu"):[]}function classic(model){return{strip:[],sections:model.sections,filterable:!1}}function tiered(model,filterable){let strip=stripOf(model),sections=[],rest=clipboardRest(model);for(let s of model.sections){if(s.group==="clipboard"&&!s.primary){rest.length&§ions.push({...s,nodes:rest});continue}if(s.primary){sections.push(s);continue}if(!s.label||s.group==="surface-info"||rowCount(s.nodes)<=3){sections.push(s);continue}sections.push({id:s.id,group:s.group,nodes:[fold({...s,label:s.label})]})}return{strip,sections,filterable}}function context(model,arrangement){return arrangement==="classic"?classic(model):tiered(model,arrangement==="command")}function palette(model){let leaves=[],walk=(nodes,trail)=>{for(let n of nodes)isLeaf(n)?leaves.push({node:n,breadcrumb:trail}):n.kind==="submenu"&&(n.lazy?leaves.push({node:n,breadcrumb:trail}):walk(n.children,[...trail,n.label]))};for(let s of model.sections)walk(s.nodes,s.label?[s.label]:[]);return{leaves}}function filterPalette(arranged,query){let words=query.toLowerCase().split(/\s+/).filter(Boolean);return words.length?{leaves:arranged.leaves.filter(l=>{let hay=`${l.node.label} ${l.breadcrumb.join(" ")}`.toLowerCase();return words.every(w=>hay.includes(w))})}:arranged}var React2=__toESM(require("react"),1);var DEFAULT_SENSITIVITY=Object.freeze({exportable:!0,classification:"ordinary",includedByDefault:!0});function resolveSensitivity(value){return{exportable:value.sensitivity?.exportable??DEFAULT_SENSITIVITY.exportable,classification:value.sensitivity?.classification??DEFAULT_SENSITIVITY.classification,includedByDefault:value.sensitivity?.includedByDefault??DEFAULT_SENSITIVITY.includedByDefault}}function toJson(value){if(!(value===void 0||typeof value=="function"||typeof value=="symbol")){if(value===null||typeof value=="string"||typeof value=="boolean")return value;if(typeof value=="number")return Number.isFinite(value)?value:null;if(typeof value=="bigint")return value.toString();if(value instanceof Date)return value.toISOString();if(Array.isArray(value))return value.map(v=>toJson(v)??null);if(typeof value=="object"){let out={};for(let[k,v]of Object.entries(value)){let j=toJson(v);j!==void 0&&(out[k]=j)}return out}}}function projectValues(raw,declared,pathPrefix=""){let byName=new Map(declared.map(d=>[d.name,d])),values={},omissions=[];for(let[name,value]of Object.entries(raw)){let path=pathPrefix?`${pathPrefix}.${name}`:name,decl=byName.get(name);if(!decl){omissions.push({path,reason:"filtered"});continue}let sensitivity=resolveSensitivity(decl);if(sensitivity.classification!=="ordinary"){omissions.push({path,reason:"excluded",classification:sensitivity.classification});continue}if(!sensitivity.exportable){omissions.push({path,reason:"excluded"});continue}let json=toJson(value);json!==void 0&&(values[name]=json)}return{values,omissions}}function createProjectedReads(source){return{async values(signal){if(!source.surface||!source.declaration)return{values:{},omissions:[]};let raw=await source.surface.readValues(signal),screenValues=source.declaration.values;return projectValues(raw,screenValues)},async item(signal){let item=source.item;if(!item||!source.declaration)return null;let type=source.declaration.itemTypes?.find(t=>t.name===item.itemType);if(!type)return{values:{},omissions:[{path:item.itemType,reason:"filtered"}]};let raw=await item.readValues(signal);return projectValues(raw,type.values,item.itemType)},async live(signal){return source.surface?.readLive?source.surface.readLive(signal):{}}}}async function runAction(action,target,options){let{ports}=options;if(action.requiresAuth&&!target.auth.authenticated)return ports.auth?(ports.auth.signInThenResume({actionId:action.id,contentFingerprint:options.contentFingerprint??action.id}),{status:"auth-pending"}):(ports.diagnostics.capture(new Error(`requiresAuth action "${action.id}" ran signed out with no auth port`),{area:"actions",detail:{actionId:action.id}}),{status:"failed",error:new Error("sign-in unavailable")});let reads=createProjectedReads(options.source??{surface:null,declaration:target.declaration,item:null}),signal=options.signal??new AbortController().signal;try{return{status:"ran",outcome:await action.run(target,{reads,ports,signal})}}catch(error){ports.diagnostics.capture(error,{area:"actions",detail:{actionId:action.id,stage:"run"}});let label=typeof action.label=="string"?action.label:action.id;return ports.notify?.error(`${label} did not finish.`,"Try again; if it keeps failing, report it from the feedback button."),{status:"failed",error}}}var APPROVED_HEADINGS=new Set(["Save","Copy as","Export","Edit","Creator tools","Admin","App","Compare","History"]),CONTAINER_WORD=/^(more|more…|more\.\.\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i,TARGET_SHAPE=/^(Cell|Row|Column|Table|Range|Selection|Record) · .+$/;function isContainerWord(label){return CONTAINER_WORD.test(label.trim())}function isApprovedHeading(label,kind="group",approved=APPROVED_HEADINGS){let l=label.trim();return!l||isContainerWord(l)?!1:approved.has(l)?!0:kind==="target"||TARGET_SHAPE.test(l)}function defaultHeadingPolicy(){return globalThis.process?.env?.NODE_ENV==="production"?"inline":"throw"}var UnapprovedHeadingError=class extends Error{heading;constructor(heading,where){super(`Menu heading "${heading}" (${where}) is not an approved heading. Remedy: show these rows inline \u2014 declare no section (or kind "target" for the clicked thing's own label) \u2014 or ask Arman to add the name to APPROVED_HEADINGS in @ai-matrx/alchemy/menu. A generic container word ("More\u2026", "Other") is never approved.`),this.name="UnapprovedHeadingError",this.heading=heading}};var CATEGORY_GROUP={clipboard:"clipboard",copy:"transfer",export:"transfer",save:"document",share:"document",edit:"editable",history:"history",ai:"ai",ask:"ai",listen:"tools",study:"tools",app:"tools",feedback:"quick",creator:"admin",admin:"admin","surface-info":"surface-info"},GROUP_ORDER=["clipboard","editable","transfer","document","history","ai","tools","quick","surface","admin","surface-info"],CATEGORY_ORDER=["clipboard","edit","copy","export","save","share","history","ask","ai","listen","study","app","feedback","creator","admin","surface-info"];function translate(i18n,key,fallback){if(!i18n)return fallback;try{return i18n.t(key,fallback)}catch{return fallback}}function actionToNode(resolved,target,options={}){let{action,eligibility}=resolved,rawLabel=typeof action.label=="function"?action.label(target):action.label,label=translate(options.i18n,action.labelKey??action.id,rawLabel),base={id:action.id,actionId:action.id,...action.icon!==void 0?{icon:action.icon}:{},...action.placement!==void 0?{placement:action.placement}:{}};if(action.expand)return{...base,kind:"submenu",label,lazy:!0,loading:!1,...action.pending?.(target)?{pending:!0}:{},children:[],...action.emptyLabel!==void 0?{emptyLabel:action.emptyLabel}:{}};if(action.href!==void 0){let href=typeof action.href=="function"?action.href(target):action.href;return{...base,kind:"link",label,href}}if(action.toggle)return{...base,kind:"checkbox",label,checked:action.pressed?.(target)??!1,...action.description!==void 0?{description:action.description}:{}};let state=action.stateIcon?.(target)??null,pressed=action.pressed?.(target),hint=action.hint!==void 0?translate(options.i18n,action.hintKey??`${action.id}.hint`,action.hint):void 0;return{...base,kind:"item",label,...action.iconTone!==void 0?{iconTone:action.iconTone}:{},...state?{stateIcon:state}:{},...action.description!==void 0?{description:action.description}:{},...hint!==void 0?{hint}:{},...pressed!==void 0?{pressed}:{},...action.destructive?{destructive:!0}:{},...action.preserveSelection?{preserveSelection:!0}:{},...eligibility.status==="unavailable-verb"||eligibility.status==="unavailable"?{unavailable:{sentence:eligibility.sentence}}:{}}}function buildMenuModel(target,resolved,options={}){let policy=options.headingPolicy??defaultHeadingPolicy();resolved=resolved.map(r=>{let sec=r.action.section;if(r.action.expand&&isContainerWord(typeof r.action.label=="string"?r.action.label:"")&&policy==="throw")throw new UnapprovedHeadingError(r.action.label,`submenu of ${r.action.id}`);if(!sec)return r;let kind=sec.kind??(sec.primary?"target":"group");if(isApprovedHeading(sec.label,kind))return r;if(policy==="throw")throw new UnapprovedHeadingError(sec.label,`section of ${r.action.id}`);let{section:_dropped,...rest}=r.action;return{...r,action:rest}});let ordered=resolved.map((r,index)=>({r,index})).sort((a,b)=>{let ca=CATEGORY_ORDER.indexOf(a.r.action.category),cb=CATEGORY_ORDER.indexOf(b.r.action.category);if(ca!==cb)return ca-cb;let oa=a.r.action.order??0,ob=b.r.action.order??0;return oa!==ob?oa-ob:a.index-b.index}).map(x=>x.r),sections=new Map,roles={},explicit=new Map;resolved.forEach((r,index)=>{let sec=r.action.section;if(!sec)return;let entry=explicit.get(sec.id)??{group:CATEGORY_GROUP[r.action.category],rows:[]};entry.rows.push({r,index}),explicit.set(sec.id,entry)});let placed=new Set;for(let r of ordered){let sec=r.action.section;if(sec&&placed.has(sec.id))continue;let group=sec?explicit.get(sec.id)?.group??CATEGORY_GROUP[r.action.category]:CATEGORY_GROUP[r.action.category],sectionId=sec?`section:${sec.id}`:group,section=sections.get(sectionId);section||(section={id:sectionId,group,nodes:[],...sec?{label:translate(options.i18n,sec.labelKey??`section.${sec.id}`,sec.label)}:{},...sec?.icon!==void 0?{icon:sec.icon}:{},...sec?.primary?{primary:!0}:{},...sec&&(sec.kind??(sec.primary?"target":"group"))==="target"?{target:!0}:{}},sections.set(sectionId,section));let rows=sec?(explicit.get(sec.id)?.rows??[]).sort((x,y)=>(x.r.action.order??0)-(y.r.action.order??0)||x.index-y.index).map(x=>x.r):[r];sec&&placed.add(sec.id);for(let row of rows)section.nodes.push(actionToNode(row,target,options)),row.action.verb&&(roles[row.action.verb]=row.action.id)}let list=[...sections.values()].sort((a,b)=>!!a.primary!=!!b.primary?a.primary?-1:1:GROUP_ORDER.indexOf(a.group)-GROUP_ORDER.indexOf(b.group)),selected=target.selection?.text?.trim(),content=options.content?.trim(),fieldName=options.contentLabel?.trim();return{header:selected?{label:"Selected",text:target.selection?.text??""}:fieldName?{label:fieldName,text:options.content??""}:content?{label:"Content",text:options.content??""}:null,sections:list,roles}}function withExpandedChildren(model,nodeId,children){let visit=nodes=>nodes.map(n=>n.kind!=="submenu"?n:n.id===nodeId?children==="loading"?{...n,loading:!0}:{...n,lazy:!1,loading:!1,pending:!1,children}:{...n,children:visit(n.children)});return{...model,sections:model.sections.map(s=>({...s,nodes:visit(s.nodes)}))}}function findNode(nodes,id){for(let n of nodes){if(n.id===id)return n;if(n.kind==="submenu"){let hit=findNode(n.children,id);if(hit)return hit}}return null}var React=__toESM(require("react"),1),KEY=Symbol.for("ai-matrx.alchemy.actions-context"),AlchemyActionsContext=globalThis[KEY]??=React.createContext(null);function useAlchemyActions(){let value=React.useContext(AlchemyActionsContext);if(!value)throw new Error("Alchemy actions: render inside <AlchemyActionsProvider> (@ai-matrx/alchemy/react/host).");return value}function isLatestExpansion(capturedTargetEpoch,currentTargetEpoch,capturedNodeSequence,currentNodeSequence){return capturedTargetEpoch===currentTargetEpoch&&capturedNodeSequence===currentNodeSequence}var import_jsx_runtime=require("react/jsx-runtime");function useMenuEngine(target,options={}){let{registry,ports}=useAlchemyActions(),[resolved,setResolved]=React2.useState([]),[model,setModel]=React2.useState(null),[tick,setTick]=React2.useState(0),expanded=React2.useRef(new Map),expandSeq=React2.useRef(new Map),targetEpoch=React2.useRef(0),currentTarget=React2.useRef(target);currentTarget.current!==target&&(currentTarget.current=target,targetEpoch.current+=1);let active=options.active??!0,optionsRef=React2.useRef(options);optionsRef.current=options,React2.useEffect(()=>registry.subscribe(()=>setTick(t=>t+1)),[registry]);let expandAction=React2.useCallback(async(action,nodeId)=>{if(!target||!action.expand)return;let epoch=targetEpoch.current,seq=(expandSeq.current.get(nodeId)??0)+1;expandSeq.current.set(nodeId,seq);let latest=()=>isLatestExpansion(epoch,targetEpoch.current,seq,expandSeq.current.get(nodeId));setModel(m=>m&&withExpandedChildren(m,nodeId,"loading"));try{let children=await action.expand(target,new AbortController().signal);if(!latest())return;let nodes=children.map(child=>(expanded.current.set(child.id,child),actionToNode({action:child,eligibility:{status:"available"}},target,ports.i18n?{i18n:ports.i18n}:{})));setModel(m=>m&&withExpandedChildren(m,nodeId,nodes))}catch(error){if(!latest())return;ports.diagnostics.capture(error,{area:"menu",detail:{nodeId,stage:"expand"}}),setModel(m=>m&&withExpandedChildren(m,nodeId,[]))}},[target,ports]);React2.useEffect(()=>{expanded.current.clear()},[target]),React2.useEffect(()=>{if(!target||!active)return;let live=!0;return registry.resolve(target,optionsRef.current.restrict).then(list=>{if(live){setResolved(list),setModel(buildMenuModel(target,list,{...ports.i18n?{i18n:ports.i18n}:{},content:optionsRef.current.content??null,contentLabel:optionsRef.current.contentLabel??null}));for(let{action}of list)expanded.current.get(action.id)===null&&action.expand&&expandAction(action,action.id)}}),()=>{live=!1}},[registry,ports,target,active,tick,options.revision,expandAction]);let resolvedRef=React2.useRef(resolved);resolvedRef.current=resolved;let subscribedIds=resolved.map(r=>r.action.id).join(`
|
|
3
|
+
`);React2.useEffect(()=>{if(!target)return;let subscribing=!0,onChange=()=>{subscribing||setTick(t=>t+1)},offs=resolvedRef.current.map(r=>r.action.subscribe?.(onChange,target)).filter(off=>typeof off=="function");return subscribing=!1,()=>offs.forEach(off=>off())},[subscribedIds,target]);let findAction=React2.useCallback(id=>resolved.find(r=>r.action.id===id)?.action??expanded.current.get(id)??void 0,[resolved]),run=React2.useCallback(async nodeId=>{if(!target||!model)return null;let node=model.sections.map(s=>findNode(s.nodes,nodeId)).find(Boolean)??null,action=findAction(node&&"actionId"in node&&node.actionId?node.actionId:nodeId);if(!action)return null;let o=optionsRef.current,result=await runAction(action,target,{ports,...o.source?{source:o.source}:{},...o.contentFingerprint?{contentFingerprint:o.contentFingerprint}:{}});return o.onRan?.(nodeId,result),result},[target,model,findAction,ports]),expand=React2.useCallback(async nodeId=>{let action=findAction(nodeId);!target||!action?.expand||(expanded.current.set(nodeId,null),await expandAction(action,nodeId))},[findAction,target,expandAction]),icon=React2.useCallback((key,tone)=>{if(!key||!ports.icons)return null;let Resolved=ports.icons.resolve(key);return Resolved?(0,import_jsx_runtime.jsx)(Resolved,{className:`h-4 w-4 shrink-0 ${tone??""}`,"aria-hidden":!0}):null},[ports.icons]),explain=React2.useCallback(sentence=>{ports.notify?ports.notify.info(sentence):ports.diagnostics.capture(new Error(`No notify port to explain: ${sentence}`),{area:"menu",detail:{sentence}})},[ports]);return{model,resolved,run,expand,icon,explain}}var React3=__toESM(require("react"),1),import_design_system=require("@ai-matrx/design-system");var import_jsx_runtime2=require("react/jsx-runtime");function NodeList({nodes,engine}){return(0,import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment,{children:nodes.map(node=>(0,import_jsx_runtime2.jsx)(MenuNodeView,{node,engine},node.id))})}function SectionList({sections,engine}){return(0,import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment,{children:sections.map((section,index)=>(0,import_jsx_runtime2.jsxs)(React3.Fragment,{children:[index>0?(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuSeparator,{}):null,sectionLabel(section)?(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel,{className:"text-xs font-medium text-muted-foreground",children:sectionLabel(section)}):null,(0,import_jsx_runtime2.jsx)(NodeList,{nodes:section.nodes,engine})]},section.id))})}function iconOf(node,engine){if(node.kind!=="item")return"icon"in node?engine.icon(node.icon):null;if(node.stateIcon){let glyph=engine.icon(node.stateIcon.icon);return node.stateIcon.spin?(0,import_jsx_runtime2.jsx)("span",{className:"inline-flex animate-spin",children:glyph}):glyph}if(!("icon"in node))return null;let tone=node.pressed===!1?void 0:node.iconTone;return engine.icon(node.icon,tone)}function MenuNodeView({node,engine}){switch(node.kind){case"separator":return(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuSeparator,{});case"label":return(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel,{className:"text-xs font-medium text-muted-foreground",children:node.label});case"submenu":return(0,import_jsx_runtime2.jsxs)(import_design_system.DropdownMenuSub,{onOpenChange:open=>{open&&node.lazy&&!node.loading&&engine.expand(node.id)},children:[(0,import_jsx_runtime2.jsxs)(import_design_system.DropdownMenuSubTrigger,{"data-alchemy-node":node.id,className:"gap-2","aria-busy":node.pending?!0:void 0,children:[iconOf(node,engine),(0,import_jsx_runtime2.jsx)("span",{className:"truncate",children:node.label}),node.pending?(0,import_jsx_runtime2.jsx)("span",{className:"ml-auto text-xs text-muted-foreground",children:"Loading\u2026"}):null]}),(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuSubContent,{className:"max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto",children:node.loading?(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel,{className:"text-xs text-muted-foreground",children:"Loading\u2026"}):node.children.length===0&&!node.lazy?(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel,{className:"text-xs text-muted-foreground",children:node.emptyLabel??"Nothing here yet"}):(0,import_jsx_runtime2.jsx)(NodeList,{nodes:node.children,engine})})]});case"checkbox":return(0,import_jsx_runtime2.jsxs)(import_design_system.DropdownMenuCheckboxItem,{"data-alchemy-node":node.id,checked:node.checked,onSelect:()=>{engine.run(node.id)},className:"gap-2",children:[iconOf(node,engine),(0,import_jsx_runtime2.jsx)("span",{className:"truncate",children:node.label})]});case"link":return(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuItem,{asChild:!0,"data-alchemy-node":node.id,children:(0,import_jsx_runtime2.jsxs)("a",{href:node.href,target:node.target,className:"gap-2",children:[iconOf(node,engine),(0,import_jsx_runtime2.jsx)("span",{className:"truncate",children:node.label})]})});case"item":return(0,import_jsx_runtime2.jsx)(LeafItem,{node,engine})}}function LeafItem({node,engine}){let row=(0,import_jsx_runtime2.jsxs)(import_design_system.DropdownMenuItem,{"data-alchemy-node":node.id,"aria-disabled":node.unavailable?!0:void 0,"aria-pressed":node.pressed,onSelect:event=>{if(node.unavailable){event.preventDefault();return}engine.run(node.id)},className:`gap-2 ${node.unavailable?"opacity-50":""} ${node.destructive?"text-destructive":""}`,children:[iconOf(node,engine),(0,import_jsx_runtime2.jsxs)("span",{className:"flex min-w-0 flex-1 flex-col",children:[(0,import_jsx_runtime2.jsx)("span",{className:"truncate",children:node.label}),node.description?(0,import_jsx_runtime2.jsx)("span",{className:"truncate text-xs text-muted-foreground",children:node.description}):null]}),node.hint?(0,import_jsx_runtime2.jsx)("span",{className:"ml-auto text-xs text-muted-foreground",children:node.hint}):null]});return node.unavailable?(0,import_jsx_runtime2.jsxs)(import_design_system.Tooltip,{children:[(0,import_jsx_runtime2.jsx)(import_design_system.TooltipTrigger,{asChild:!0,children:row}),(0,import_jsx_runtime2.jsx)(import_design_system.TooltipContent,{children:node.unavailable.sentence})]}):row}function LeafButton({node,engine,showLabel}){if(node.kind!=="item"&&node.kind!=="checkbox"&&node.kind!=="link")return null;let unavailable=node.kind==="item"?node.unavailable:void 0,pressed=node.kind==="item"?node.pressed:node.kind==="checkbox"?node.checked:void 0,glyph=iconOf(node,engine),button=(0,import_jsx_runtime2.jsxs)("button",{type:"button","data-alchemy-node":node.id,"data-matrx-button":"","data-tap-floor":"","data-tap-floor-square":"","aria-label":node.label,"aria-disabled":unavailable?!0:void 0,"aria-pressed":pressed,onMouseDown:e=>{node.kind==="item"&&node.preserveSelection&&e.preventDefault()},onClick:()=>{if(unavailable){engine.explain(unavailable.sentence);return}if(node.kind==="link"){window.location.assign(node.href);return}engine.run(node.id)},className:`inline-flex h-7 min-w-7 pointer-coarse:h-11 pointer-coarse:min-w-11 shrink-0 items-center justify-center gap-1.5 rounded-md px-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground ${unavailable?"cursor-default opacity-50 hover:bg-transparent":""} ${pressed?"text-foreground":""}`,children:[glyph,showLabel||!glyph?(0,import_jsx_runtime2.jsx)("span",{className:"whitespace-nowrap",children:node.label}):null]});return(0,import_jsx_runtime2.jsxs)(import_design_system.Tooltip,{children:[(0,import_jsx_runtime2.jsx)(import_design_system.TooltipTrigger,{asChild:!0,children:button}),(0,import_jsx_runtime2.jsx)(import_design_system.TooltipContent,{children:unavailable?unavailable.sentence:node.label})]})}var NO_LONGER="Nothing here right now.";function kept(node){if(node.kind==="separator"||node.kind==="label")return node;let label="label"in node?node.label:"",icon="icon"in node?node.icon:void 0,sentence=node.kind==="submenu"?node.emptyLabel??NO_LONGER:NO_LONGER;return{kind:"item",id:node.id,label,...icon!==void 0?{icon}:{},unavailable:{sentence}}}var LATE="__arrived_after_open";function isLoading(node){return node.kind==="submenu"&&!!node.pending}function keepSlotsWhileOpen(shown,current){if(!shown)return{sections:current.map(s=>({...s,nodes:[...s.nodes]}))};let now=new Map,nowSection=new Map;for(let section of current){nowSection.set(section.id,section);for(let node of section.nodes)now.set(node.id,node)}let drawn=new Set,sections=[];for(let section of shown.sections){let fresh=nowSection.get(section.id),nodes=[];for(let node of section.nodes){drawn.add(node.id);let live=now.get(node.id);live?nodes.push(live):isLoading(node)||nodes.push(kept(node))}nodes.length===0&§ion.nodes.length>0||sections.push({...fresh??section,nodes})}let late=[];for(let section of current)for(let node of section.nodes)drawn.has(node.id)||late.push(node);if(late.length){let lateSection=sections.find(s=>s.id===LATE);lateSection?lateSection.nodes.push(...late):sections.push({id:LATE,group:sections[sections.length-1]?.group??"more",nodes:late})}return{sections}}var import_jsx_runtime3=require("react/jsx-runtime");function ContextMenuPanel({target,point,open,onOpenChange,arrangement="command",density="comfortable",...options}){let engine=useMenuEngine(target,{...options,onRan:(id,r)=>{options.onRan?.(id,r),onOpenChange(!1)}}),[query,setQuery]=React4.useState(""),arranged=engine.model?context(engine.model,arrangement):null,slots=React4.useRef({at:"",shown:null}),at=`${point.x},${point.y}`;(!open||slots.current.at!==at)&&(slots.current={at,shown:null});let sections=arranged&&open?(slots.current.shown=keepSlotsWhileOpen(slots.current.shown,arranged.sections)).sections:arranged?.sections??[],filtered=engine.model&&query.trim()?filterPalette(palette(engine.model),query):null,anchor=(0,import_jsx_runtime3.jsx)(import_design_system2.DropdownMenuTrigger,{asChild:!0,children:(0,import_jsx_runtime3.jsx)("span",{"aria-hidden":!0,"data-alchemy-anchor":"context",style:{position:"fixed",left:point.x,top:point.y,width:0,height:0,pointerEvents:"none"}})});return(0,import_jsx_runtime3.jsxs)(import_design_system2.DropdownMenu,{open,onOpenChange,modal:!1,children:[typeof document>"u"?anchor:(0,import_react_dom.createPortal)(anchor,document.body),(0,import_jsx_runtime3.jsx)(import_design_system2.DropdownMenuContent,{side:"right",align:"start",sideOffset:2,collisionPadding:8,"data-alchemy-layout":`context-${arrangement}`,onPointerUpCapture:event=>{event.button===2&&event.preventDefault()},className:`w-72 max-h-[min(var(--radix-dropdown-menu-content-available-height),32rem)] overflow-y-auto ${density==="compact"?"text-xs":""}`,children:(0,import_jsx_runtime3.jsxs)(import_design_system2.TooltipProvider,{children:[engine.model?.header?(0,import_jsx_runtime3.jsxs)(import_design_system2.DropdownMenuLabel,{className:"truncate text-xs font-normal text-muted-foreground",title:engine.model.header.text,children:[engine.model.header.label,engine.model.header.text.trim()?`: ${engine.model.header.text}`:null]}):null,arranged?(0,import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment,{children:[arranged.strip.length?(0,import_jsx_runtime3.jsx)("div",{role:"toolbar","aria-label":"Clipboard",className:"flex items-center gap-0.5 px-1 py-1",children:arranged.strip.map(node=>(0,import_jsx_runtime3.jsx)(LeafButton,{node,engine,showLabel:!1},node.id))}):null,arranged.filterable?(0,import_jsx_runtime3.jsx)("div",{className:"px-1 pb-1",children:(0,import_jsx_runtime3.jsx)("input",{"aria-label":"Filter actions",placeholder:"Type to filter\u2026",value:query,onChange:e=>setQuery(e.target.value),onKeyDown:e=>{e.stopPropagation(),e.key==="Enter"&&filtered?.leaves[0]&&engine.run(filtered.leaves[0].node.id)},className:"h-7 w-full rounded-md border border-border bg-transparent px-2 text-sm outline-none"})}):null,(arranged.strip.length||arranged.filterable)&§ions.length?(0,import_jsx_runtime3.jsx)(import_design_system2.DropdownMenuSeparator,{}):null,filtered?filtered.leaves.length?filtered.leaves.map(({node,breadcrumb})=>(0,import_jsx_runtime3.jsxs)(import_design_system2.DropdownMenuItem,{"data-alchemy-node":node.id,onSelect:()=>{node.kind==="submenu"?engine.expand(node.id):engine.run(node.id)},className:"flex-col items-start gap-0",children:[(0,import_jsx_runtime3.jsx)("span",{children:node.label}),(0,import_jsx_runtime3.jsx)("span",{className:"text-xs text-muted-foreground",children:breadcrumb.join(" \u203A ")})]},node.id)):(0,import_jsx_runtime3.jsxs)("div",{className:"px-2 py-1.5 text-xs text-muted-foreground",children:["No action matches \u201C",query,"\u201D."]}):(0,import_jsx_runtime3.jsx)(SectionList,{sections,engine})]}):(0,import_jsx_runtime3.jsx)("div",{className:"px-2 py-1.5 text-xs text-muted-foreground",children:"Loading\u2026"})]})})]})}
|
|
4
4
|
//# sourceMappingURL=react-menu.cjs.map
|