@ai-matrx/summon 0.1.17 → 0.1.18

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/dist/lite.cjs CHANGED
@@ -109,20 +109,7 @@ function aDismissableLayerIsOpen() {
109
109
  return document.querySelector("[data-radix-popper-content-wrapper]") !== null;
110
110
  }
111
111
  function clampRect(rect, vw, vh) {
112
- const width = Math.min(rect.width, Math.max(240, vw - VIEWPORT_PADDING * 2));
113
- const height = Math.min(
114
- rect.height,
115
- Math.max(180, vh - VIEWPORT_PADDING * 2)
116
- );
117
- return {
118
- width,
119
- height,
120
- x: Math.min(Math.max(rect.x, VIEWPORT_PADDING), vw - width - VIEWPORT_PADDING),
121
- y: Math.min(
122
- Math.max(rect.y, VIEWPORT_PADDING),
123
- vh - height - VIEWPORT_PADDING
124
- )
125
- };
112
+ return (0, import_design_system.clampRectToViewport)(rect, { width: vw, height: vh }, { margin: VIEWPORT_PADDING, fit: { minWidth: 240, minHeight: 180 } });
126
113
  }
127
114
  function LiteWindow({
128
115
  id,
@@ -265,64 +252,17 @@ function LiteWindow({
265
252
  openStack().push(id);
266
253
  }
267
254
  }, [dispatch, id]);
268
- const startDrag = (0, import_react.useCallback)(
269
- (e) => {
270
- if (!draggable || isNarrow) return;
271
- if (e.button !== 0) return;
272
- const target = e.target;
273
- if (target.closest("button,a,input,[role='button']")) return;
274
- e.preventDefault();
275
- raise();
276
- const startX = e.clientX;
277
- const startY = e.clientY;
278
- const origin = { ...view };
279
- const onMove = (move) => {
280
- setRect({
281
- ...origin,
282
- x: origin.x + (move.clientX - startX),
283
- y: origin.y + (move.clientY - startY)
284
- });
285
- };
286
- const onUp = () => {
287
- window.removeEventListener("pointermove", onMove);
288
- window.removeEventListener("pointerup", onUp);
289
- };
290
- window.addEventListener("pointermove", onMove);
291
- window.addEventListener("pointerup", onUp);
292
- },
293
- [draggable, isNarrow, raise, view]
294
- );
295
- const startResize = (0, import_react.useCallback)(
296
- (e, edge) => {
297
- if (!resizable || isNarrow || e.button !== 0) return;
298
- e.preventDefault();
299
- e.stopPropagation();
300
- raise();
301
- const startX = e.clientX;
302
- const startY = e.clientY;
303
- const origin = { ...view };
304
- const onMove = (move) => {
305
- const next = { ...origin };
306
- if (edge === "e" || edge === "se") {
307
- next.width = Math.max(minWidth, origin.width + (move.clientX - startX));
308
- }
309
- if (edge === "s" || edge === "se") {
310
- next.height = Math.max(
311
- minHeight,
312
- origin.height + (move.clientY - startY)
313
- );
314
- }
315
- setRect(next);
316
- };
317
- const onUp = () => {
318
- window.removeEventListener("pointermove", onMove);
319
- window.removeEventListener("pointerup", onUp);
320
- };
321
- window.addEventListener("pointermove", onMove);
322
- window.addEventListener("pointerup", onUp);
323
- },
324
- [isNarrow, minHeight, minWidth, raise, resizable, view]
325
- );
255
+ const drag = (0, import_design_system.useDragMove)({
256
+ element: frameEl,
257
+ enabled: !isNarrow,
258
+ getRect: () => view,
259
+ limits: { minWidth, minHeight },
260
+ constrain: (next) => clampRect(next, viewport.w, viewport.h),
261
+ onCommit: (next, { gesture }) => (
262
+ // A move keeps the size the person chose (the render-time clamp may be squeezing it).
263
+ setRect((current) => gesture === "move" ? { ...current, x: next.x, y: next.y } : next)
264
+ )
265
+ });
326
266
  const onHandleKeyDown = (0, import_react.useCallback)(
327
267
  (e) => {
328
268
  if (isNarrow || !draggable) return;
@@ -371,7 +311,7 @@ function LiteWindow({
371
311
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
372
312
  "div",
373
313
  {
374
- onPointerDown: startDrag,
314
+ onPointerDown: draggable ? drag.onPointerDown : void 0,
375
315
  className: (0, import_cn.cn)(
376
316
  "flex shrink-0 items-center gap-2 border-b border-border bg-muted/40 px-3 py-2",
377
317
  !isNarrow && draggable && "cursor-grab active:cursor-grabbing"
@@ -432,21 +372,21 @@ function LiteWindow({
432
372
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
433
373
  "span",
434
374
  {
435
- onPointerDown: (e) => startResize(e, "e"),
375
+ onPointerDown: drag.resizeHandle("e"),
436
376
  className: "absolute right-0 top-8 h-[calc(100%-3rem)] w-1.5 cursor-ew-resize"
437
377
  }
438
378
  ),
439
379
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
440
380
  "span",
441
381
  {
442
- onPointerDown: (e) => startResize(e, "s"),
382
+ onPointerDown: drag.resizeHandle("s"),
443
383
  className: "absolute bottom-0 left-4 h-1.5 w-[calc(100%-2rem)] cursor-ns-resize"
444
384
  }
445
385
  ),
446
386
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
447
387
  "span",
448
388
  {
449
- onPointerDown: (e) => startResize(e, "se"),
389
+ onPointerDown: drag.resizeHandle("se"),
450
390
  "aria-hidden": true,
451
391
  className: "absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize"
452
392
  }
package/dist/lite.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lite.ts","../src/lite/LiteWindow.tsx","../src/lite/focusOnOpen.ts"],"sourcesContent":["// @ai-matrx/summon/lite — the light floating window: drag, edge resize, Escape when topmost,\n// focus in/out, z-order shared with WindowPanel (the windowManager transientZ lane), a bottom\n// drawer on phones. No minimize, tray, persistence, pop-out or links. See\n// SUMMON-PACKAGE-DESIGN.md (common-docs) for its contract.\nexport { LiteWindow, type LiteWindowProps, type FloatingFrameSize, type FloatingCloseReason } from \"./lite/LiteWindow\";\nexport { KEEP_FOCUS_ATTRIBUTE, isEditableField, mayTakeFocusOnOpen } from \"./lite/focusOnOpen\";\n","\"use client\";\n\n/**\n * LiteWindow (ported from matrx-frontend `MatrxFloatingFrame`, the FLOATING presentation of\n * `MatrxDynamicPanelHost`) — the light floating window.\n *\n * This is the \"lightweight window\" Arman asked for on 2026-09-18: *\"a slightly\n * more lightweight version of the window panel … doesn't minimize and stuff\n * like that but carries the basics of dragging\"*. It exists so a picker or a\n * form never has to live in a blocking modal\n * (`common-docs/policies/no-dead-ends.md` §4).\n *\n * It is NOT a second floating-surface family. It is one more presentation of\n * the panel host this repo already has in 59 files; the host routes to it on\n * `presentation=\"floating\"` and keeps every other call site on the docked\n * branch untouched.\n *\n * CARRIES: drag by header, edge/corner resize, Escape when topmost, focus in\n * on open and back to the opener on close, z-order shared with `WindowPanel`\n * (same `nextZIndex` counter — see `windowManagerSlice.transientZ`), a bottom\n * drawer below 768, and a nested portal container so a `CreatablePicker`\n * popover mounts INSIDE the window instead of behind it.\n *\n * NEVER GETS: minimize, the tray, persistence/restore, pop-out, URL sync,\n * sidebars, a focus trap, `aria-modal`, `inert` on the page, or a body scroll\n * lock. Each one is either the modal disease or a lost-work trap.\n *\n * 🚨 THE NEVER-LOSE-INPUT CONTRACT (the law's §5, and finding C1 of\n * `common-docs/projects/every-picker-takes-new-input/recon/plan-attack-1.md`,\n * where crossing 768 with the agent-variable editor open destroyed the editor\n * and everything typed into it). The floating frame and the drawer are THE\n * SAME DOM NODE and the same React subtree: crossing the breakpoint changes\n * `className` and `style` on one `<div>` and nothing else. There is no\n * `isNarrow ? <Drawer>{children}</Drawer> : <Floating>{children}</Floating>`\n * anywhere in this file, and there must never be — that ternary is exactly\n * what unmounts the body and eats the person's work. `children` is rendered\n * in one place, once.\n */\n\nimport {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useState,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { PortalContainerProvider } from \"@ai-matrx/design-system\";\nimport { cn } from \"@ai-matrx/design-system/cn\";\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\nimport { useIsMobile } from \"@ai-matrx/kit/media-query\";\nimport { raiseTransientWindow, releaseTransientWindow, selectTransientZIndex } from \"@ai-matrx/summon\";\nimport { useOptionalOverlayInstance, useSummonDispatch, useSummonPorts, useSummonSelector } from \"@ai-matrx/summon/react\";\nimport { mayTakeFocusOnOpen } from \"./focusOnOpen\";\n\nfunction X(props: { className?: string }) {\n return (\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden className={props.className}>\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n );\n}\n\nexport type FloatingFrameSize = \"sm\" | \"md\" | \"lg\";\n\nconst SIZE_PX: Record<FloatingFrameSize, { width: number; height: number }> = {\n sm: { width: 380, height: 420 },\n md: { width: 560, height: 560 },\n lg: { width: 780, height: 680 },\n};\n\nconst VIEWPORT_PADDING = 8;\nconst DEFAULT_MIN_WIDTH = 320;\nconst DEFAULT_MIN_HEIGHT = 200;\n/** Keyboard drag step; a modifier drops it to 1px. */\nconst KEY_DRAG_STEP = 20;\n\ninterface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\ntype ResizeEdge = \"e\" | \"s\" | \"se\";\n\nexport type FloatingCloseReason = \"escape\" | \"close-button\" | \"programmatic\";\n\nexport interface LiteWindowProps {\n /** Stable id — the key this window holds in the shared z-order lane. */\n id: string;\n title: ReactNode;\n description?: ReactNode;\n children: ReactNode;\n /**\n * Close request. Omit inside an SummonHost-rendered overlay: the window then closes its own\n * instance. A page-local window must pass it.\n */\n onClose?: ((reason: FloatingCloseReason) => void) | undefined;\n size?: FloatingFrameSize;\n width?: number;\n height?: number;\n minWidth?: number;\n minHeight?: number;\n draggable?: boolean;\n resizable?: boolean;\n /** Blocks Escape and the header close button while true. */\n dismissDisabled?: boolean;\n headerActions?: ReactNode;\n footer?: ReactNode;\n className?: string;\n contentClassName?: string;\n /** Move focus into the body on open, and back to the opener on close. */\n initialFocus?: boolean;\n /**\n * CONTAINER-BOUNDED mode. When set, the window lives INSIDE this element\n * instead of over the whole viewport: it portals into it (position\n * absolute — the element must be positioned), centres in it on open, and\n * is dragged and clamped within its box (it follows the box when it\n * resizes). Used by the canvas workspace's floating chat, which belongs to\n * the canvas region, not the window. `null` = the element is not mounted\n * yet; the frame waits for it. Omit for the viewport-wide default.\n */\n container?: HTMLElement | null;\n}\n\nconst FIELD_SELECTOR = [\n \"[data-panel-initial-focus]\",\n \"input:not([disabled]):not([type='hidden'])\",\n \"textarea:not([disabled])\",\n \"select:not([disabled])\",\n \"[contenteditable='true']\",\n].join(\",\");\n\nconst FALLBACK_SELECTOR = [\n \"button:not([disabled])\",\n \"a[href]\",\n \"[tabindex]:not([tabindex='-1'])\",\n].join(\",\");\n\nfunction firstVisibleMatch(root: HTMLElement, selector: string) {\n return (\n Array.from(root.querySelectorAll<HTMLElement>(selector)).find(\n (el) =>\n el.getAttribute(\"aria-hidden\") !== \"true\" &&\n el.getClientRects().length > 0,\n ) ?? null\n );\n}\n\n/**\n * The open lite windows, newest last. Escape belongs to the TOPMOST one only —\n * two open windows must not both close on one key. Module scope, because a\n * per-component ref cannot see its siblings (the same mistake that opened two\n * Google consent windows for one intent, `check:google-auth-gate`).\n */\nconst openStack = (): string[] => globalSlot<string[]>(\"ai-matrx.summon.lite.openStack\", () => []);\n\n/**\n * True when a Radix popper (a `CreatablePicker` list, a Select, a Popover) is\n * open anywhere. That layer owns the Escape; we must not close the window out\n * from under a person who was only dismissing a dropdown.\n */\nfunction aDismissableLayerIsOpen(): boolean {\n return document.querySelector(\"[data-radix-popper-content-wrapper]\") !== null;\n}\n\nfunction clampRect(rect: Rect, vw: number, vh: number): Rect {\n const width = Math.min(rect.width, Math.max(240, vw - VIEWPORT_PADDING * 2));\n const height = Math.min(\n rect.height,\n Math.max(180, vh - VIEWPORT_PADDING * 2),\n );\n return {\n width,\n height,\n x: Math.min(Math.max(rect.x, VIEWPORT_PADDING), vw - width - VIEWPORT_PADDING),\n y: Math.min(\n Math.max(rect.y, VIEWPORT_PADDING),\n vh - height - VIEWPORT_PADDING,\n ),\n };\n}\n\nexport function LiteWindow({\n id,\n title,\n description,\n children,\n onClose: onCloseProp,\n size = \"md\",\n width,\n height,\n minWidth = DEFAULT_MIN_WIDTH,\n minHeight = DEFAULT_MIN_HEIGHT,\n draggable = true,\n resizable = true,\n dismissDisabled = false,\n headerActions,\n footer,\n className,\n contentClassName = \"px-4 py-3\",\n initialFocus = true,\n container,\n}: LiteWindowProps) {\n // Close: the explicit prop, else the enclosing SummonHost instance; neither is reported.\n const instance = useOptionalOverlayInstance();\n const ports = useSummonPorts();\n const onClose = useCallback(\n (reason: FloatingCloseReason) => {\n if (onCloseProp) onCloseProp(reason);\n else if (instance) instance.close();\n else ports.errors.capture(new Error(\"[summon] LiteWindow has no onClose and renders outside an overlay instance\"), { id, reason });\n },\n [id, instance, onCloseProp, ports],\n );\n const bounded = container !== undefined;\n const dispatch = useSummonDispatch();\n const zIndex = useSummonSelector(selectTransientZIndex(id));\n const isNarrow = useIsMobile();\n /**\n * THE OPENER — captured in the very first render, before this window's body\n * has mounted. It cannot be read later: a form whose first field carries\n * `autoFocus` (the real Add-a-scope-type form does) has already taken focus\n * by the time an effect runs, and the \"opener\" we would return focus to is\n * then a field inside the window that is about to be destroyed. That is\n * exactly how the first live pass closed with focus on nothing.\n */\n const [opener] = useState<HTMLElement | null>(() =>\n typeof document !== \"undefined\" &&\n document.activeElement instanceof HTMLElement &&\n document.activeElement !== document.body\n ? document.activeElement\n : null,\n );\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\n const [frameEl, setFrameEl] = useState<HTMLDivElement | null>(null);\n const titleId = useId();\n const descriptionId = useId();\n\n const [rect, setRect] = useState<Rect>(() => {\n const base = SIZE_PX[size];\n return {\n width: width ?? base.width,\n height: height ?? base.height,\n x: 0,\n y: 0,\n };\n });\n const [placed, setPlaced] = useState(false);\n const [viewport, setViewport] = useState(() => ({\n w: typeof window === \"undefined\" ? 1280 : window.innerWidth,\n h: typeof window === \"undefined\" ? 800 : window.innerHeight,\n }));\n\n /** What actually gets drawn: the person's rect, kept on screen. */\n const view = clampRect(rect, viewport.w, viewport.h);\n\n useEffect(() => {\n if (bounded) {\n setPortalTarget(container ?? null);\n return;\n }\n setPortalTarget(document.getElementById(\"glass-layer\") ?? document.body);\n }, [bounded, container]);\n\n // Join the shared z-order on mount, leave it on unmount.\n useEffect(() => {\n dispatch(raiseTransientWindow(id));\n openStack().push(id);\n return () => {\n dispatch(releaseTransientWindow(id));\n const at = openStack().lastIndexOf(id);\n if (at !== -1) openStack().splice(at, 1);\n };\n }, [dispatch, id]);\n\n /**\n * Centre on first paint, and track the viewport.\n *\n * `rect` is the size and place the PERSON chose; it is never shrunk by a\n * viewport change. The clamp happens at render time only, so a window\n * squeezed at 375 comes back at its own size when there is room again —\n * a window that returned from a phone width permanently 359px wide was\n * the first thing this cost us.\n */\n useLayoutEffect(() => {\n if (typeof window === \"undefined\") return undefined;\n if (bounded) {\n // Bounded: the \"viewport\" is the container's box.\n if (!container) return undefined;\n const measure = () => ({ w: container.clientWidth, h: container.clientHeight });\n const box = measure();\n setViewport(box);\n if (!placed) {\n setRect((current) => ({\n ...current,\n x: Math.round((box.w - current.width) / 2),\n y: Math.round(Math.max(VIEWPORT_PADDING, (box.h - current.height) / 2.4)),\n }));\n setPlaced(true);\n }\n const observer = new ResizeObserver(() => setViewport(measure()));\n observer.observe(container);\n return () => observer.disconnect();\n }\n if (!placed) {\n setRect((current) => ({\n ...current,\n x: Math.round((window.innerWidth - current.width) / 2),\n y: Math.round(\n Math.max(56, (window.innerHeight - current.height) / 2.4),\n ),\n }));\n setPlaced(true);\n }\n const onResize = () =>\n setViewport({ w: window.innerWidth, h: window.innerHeight });\n window.addEventListener(\"resize\", onResize);\n return () => window.removeEventListener(\"resize\", onResize);\n }, [placed, bounded, container]);\n\n // Escape closes the TOPMOST lite window, and only when no inner dismissable\n // layer is holding the key.\n useEffect(() => {\n if (dismissDisabled) return undefined;\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\" || e.defaultPrevented) return;\n if (openStack()[openStack().length - 1] !== id) return;\n if (aDismissableLayerIsOpen()) return;\n e.stopPropagation();\n onClose(\"escape\");\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [dismissDisabled, id, onClose]);\n\n // Focus in on open; back to the opener on close.\n useEffect(() => {\n if (!initialFocus || !frameEl) return undefined;\n const frame = requestAnimationFrame(() => {\n // Never out of a field she is typing in outside this window\n // (focusOnOpen.ts — the same rule as the docked host).\n if (!mayTakeFocusOnOpen(frameEl)) return;\n const content =\n frameEl.querySelector<HTMLElement>(\"[data-panel-content]\") ?? frameEl;\n const target =\n firstVisibleMatch(content, FIELD_SELECTOR) ??\n firstVisibleMatch(content, FALLBACK_SELECTOR) ??\n frameEl;\n target.focus({ preventScroll: true });\n });\n return () => {\n cancelAnimationFrame(frame);\n if (opener?.isConnected && !frameEl.contains(opener)) {\n requestAnimationFrame(() => opener.focus({ preventScroll: true }));\n }\n };\n }, [initialFocus, frameEl, opener]);\n\n const raise = useCallback(() => {\n dispatch(raiseTransientWindow(id));\n const at = openStack().lastIndexOf(id);\n if (at !== -1 && at !== openStack().length - 1) {\n openStack().splice(at, 1);\n openStack().push(id);\n }\n }, [dispatch, id]);\n\n const startDrag = useCallback(\n (e: ReactPointerEvent<HTMLElement>) => {\n if (!draggable || isNarrow) return;\n if (e.button !== 0) return;\n const target = e.target as HTMLElement;\n // A control in the header bar is a control, not a drag handle.\n if (target.closest(\"button,a,input,[role='button']\")) return;\n e.preventDefault();\n raise();\n const startX = e.clientX;\n const startY = e.clientY;\n const origin = { ...view };\n const onMove = (move: PointerEvent) => {\n setRect({\n ...origin,\n x: origin.x + (move.clientX - startX),\n y: origin.y + (move.clientY - startY),\n });\n };\n const onUp = () => {\n window.removeEventListener(\"pointermove\", onMove);\n window.removeEventListener(\"pointerup\", onUp);\n };\n window.addEventListener(\"pointermove\", onMove);\n window.addEventListener(\"pointerup\", onUp);\n },\n [draggable, isNarrow, raise, view],\n );\n\n const startResize = useCallback(\n (e: ReactPointerEvent<HTMLElement>, edge: ResizeEdge) => {\n if (!resizable || isNarrow || e.button !== 0) return;\n e.preventDefault();\n e.stopPropagation();\n raise();\n const startX = e.clientX;\n const startY = e.clientY;\n const origin = { ...view };\n const onMove = (move: PointerEvent) => {\n const next = { ...origin };\n if (edge === \"e\" || edge === \"se\") {\n next.width = Math.max(minWidth, origin.width + (move.clientX - startX));\n }\n if (edge === \"s\" || edge === \"se\") {\n next.height = Math.max(\n minHeight,\n origin.height + (move.clientY - startY),\n );\n }\n setRect(next);\n };\n const onUp = () => {\n window.removeEventListener(\"pointermove\", onMove);\n window.removeEventListener(\"pointerup\", onUp);\n };\n window.addEventListener(\"pointermove\", onMove);\n window.addEventListener(\"pointerup\", onUp);\n },\n [isNarrow, minHeight, minWidth, raise, resizable, view],\n );\n\n /** Drag is never mouse-only: the handle is focusable and arrow keys move it. */\n const onHandleKeyDown = useCallback(\n (e: ReactKeyboardEvent<HTMLElement>) => {\n if (isNarrow || !draggable) return;\n const step = e.shiftKey || e.altKey ? 1 : KEY_DRAG_STEP;\n const delta =\n e.key === \"ArrowLeft\"\n ? { x: -step, y: 0 }\n : e.key === \"ArrowRight\"\n ? { x: step, y: 0 }\n : e.key === \"ArrowUp\"\n ? { x: 0, y: -step }\n : e.key === \"ArrowDown\"\n ? { x: 0, y: step }\n : null;\n if (!delta) return;\n e.preventDefault();\n setRect((current) => ({\n ...current,\n x: current.x + delta.x,\n y: current.y + delta.y,\n }));\n },\n [draggable, isNarrow],\n );\n\n if (!portalTarget) return null;\n\n // ── THE ONE NODE ──────────────────────────────────────────────────────────\n // Floating and drawer differ ONLY in these two values. Same element, same\n // subtree, same children — so crossing 768 is a re-render, never an unmount.\n const style: CSSProperties = isNarrow\n ? { zIndex }\n : {\n zIndex,\n left: view.x,\n top: view.y,\n width: view.width,\n height: view.height,\n };\n\n const frameClass = isNarrow\n ? \"fixed inset-x-0 bottom-0 max-h-[88dvh] rounded-t-xl border-t\"\n : bounded\n ? \"absolute rounded-xl border\"\n : \"fixed rounded-xl border\";\n\n const canDismiss = !dismissDisabled;\n\n const panel = (\n <div\n ref={setFrameEl}\n role=\"dialog\"\n aria-modal=\"false\"\n aria-labelledby={titleId}\n aria-describedby={description ? descriptionId : undefined}\n tabIndex={-1}\n data-matrx-lite-window={id}\n data-presentation={isNarrow ? \"drawer\" : \"floating\"}\n onPointerDownCapture={raise}\n style={style}\n className={cn(\n \"flex flex-col overflow-hidden border-border bg-card text-card-foreground shadow-2xl outline-none\",\n \"transition-[border-radius] duration-150\",\n frameClass,\n className,\n )}\n >\n <div\n onPointerDown={startDrag}\n className={cn(\n \"flex shrink-0 items-center gap-2 border-b border-border bg-muted/40 px-3 py-2\",\n !isNarrow && draggable && \"cursor-grab active:cursor-grabbing\",\n )}\n >\n {!isNarrow && draggable ? (\n <span\n role=\"button\"\n tabIndex={0}\n aria-label={`Move ${typeof title === \"string\" ? title : \"window\"} (arrow keys)`}\n onKeyDown={onHandleKeyDown}\n className=\"h-4 w-1.5 shrink-0 rounded-full bg-border outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n ) : null}\n <div className=\"min-w-0 flex-1\">\n <h2\n id={titleId}\n className=\"truncate text-sm font-semibold text-foreground\"\n >\n {title}\n </h2>\n {description ? (\n <p\n id={descriptionId}\n className=\"mt-0.5 line-clamp-2 text-xs text-muted-foreground\"\n >\n {description}\n </p>\n ) : null}\n </div>\n {headerActions ? (\n <div className=\"flex shrink-0 items-center gap-1\">{headerActions}</div>\n ) : null}\n {canDismiss ? (\n <button\n type=\"button\"\n onClick={() => onClose(\"close-button\")}\n aria-label=\"Close\"\n className=\"shrink-0 rounded-md p-1 text-muted-foreground outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n ) : null}\n </div>\n\n <div\n data-panel-content\n className={cn(\"min-h-0 flex-1 overflow-auto\", contentClassName)}\n >\n {/*\n The nested portal container: a CreatablePicker popover, a Select or a\n Tooltip opened in here mounts INSIDE this window instead of at\n document.body, where it would land behind the window that opened it.\n */}\n <PortalContainerProvider container={frameEl}>\n {children}\n </PortalContainerProvider>\n </div>\n\n {footer ? (\n <div className=\"shrink-0 border-t border-border bg-muted/30 px-4 py-2\">\n {footer}\n </div>\n ) : null}\n\n {resizable && !isNarrow ? (\n <>\n <span\n onPointerDown={(e) => startResize(e, \"e\")}\n className=\"absolute right-0 top-8 h-[calc(100%-3rem)] w-1.5 cursor-ew-resize\"\n />\n <span\n onPointerDown={(e) => startResize(e, \"s\")}\n className=\"absolute bottom-0 left-4 h-1.5 w-[calc(100%-2rem)] cursor-ns-resize\"\n />\n <span\n onPointerDown={(e) => startResize(e, \"se\")}\n aria-hidden\n className=\"absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize\"\n />\n </>\n ) : null}\n </div>\n );\n\n return createPortal(panel, portalTarget);\n}\n","// components/matrx/resizable/focusOnOpen.ts\n//\n// 🚨 AN OPENING WINDOW NEVER TAKES FOCUS OUT OF A FIELD SHE IS TYPING IN\n// (Masterwork cold walk 23, 2026-09-30).\n//\n// `/masterwork/<id>?interview=1&rename=1` opened the Rulebook title's inline\n// rename AND the interview's docked panel on the same load. The rename field\n// took focus first; ~60ms later the panel's focus-on-open moved focus into\n// the panel, the rename field blurred, and EditableLabel commits-and-closes\n// on blur — so the rename the duplicate-name notice sent her to was gone\n// before she could type. Measured headless on localhost: focus() from the\n// rename field at t=2856ms, then from MatrxDynamicPanelHost's\n// focusPreferredTarget at 2921/2930/3043ms.\n//\n// The rule, shared by every window primitive in this folder (the docked host\n// and the floating frame): a window that opens takes focus, EXCEPT out of an\n// editable field that sits inside an element carrying KEEP_FOCUS_ATTRIBUTE —\n// an inline edit the page itself opened (the Rulebook title's rename on\n// `?rename=1`). A panel she opens with Enter or a hotkey from any other field\n// still takes focus: that is the panel she asked to type into.\n//\n// Why a mark and not `navigator.userActivation`: measured headless on\n// localhost, a cold load of the deep link reported `isActive === true` when\n// the panel opened, and a client navigation right after a click (Start → the\n// new Rulebook) is inside the click's activation window too — the gesture\n// cannot tell \"she opened this panel\" from \"she clicked something earlier\".\n// A <select> is never a field she is typing in.\n\n/** Put on an inline-edit container: a field inside it keeps focus when a window opens. */\nexport const KEEP_FOCUS_ATTRIBUTE = \"data-keep-focus-on-open\";\n\nconst EDITABLE_INPUT_TYPES = new Set([\n \"\",\n \"text\",\n \"search\",\n \"email\",\n \"url\",\n \"tel\",\n \"password\",\n \"number\",\n \"date\",\n \"datetime-local\",\n \"month\",\n \"time\",\n \"week\",\n]);\n\n/** Is `el` a field a person types into? */\nexport function isEditableField(el: Element | null): el is HTMLElement {\n if (!(el instanceof HTMLElement)) return false;\n if (el instanceof HTMLTextAreaElement) return !el.disabled && !el.readOnly;\n if (el instanceof HTMLInputElement) {\n return (\n !el.disabled &&\n !el.readOnly &&\n EDITABLE_INPUT_TYPES.has((el.getAttribute(\"type\") ?? \"\").toLowerCase())\n );\n }\n return el.isContentEditable === true;\n}\n\n/**\n * May a window that is opening (`container`) move focus into itself right\n * now? False only while focus is in an editable field outside it that sits in\n * a KEEP_FOCUS_ATTRIBUTE container.\n */\nexport function mayTakeFocusOnOpen(container: HTMLElement | null): boolean {\n const active = typeof document === \"undefined\" ? null : document.activeElement;\n if (!isEditableField(active)) return true;\n if (container != null && container.contains(active)) return true;\n return active.closest(`[${KEEP_FOCUS_ATTRIBUTE}]`) == null;\n}\n"],"mappings":"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","names":["import_react"]}
1
+ {"version":3,"sources":["../src/lite.ts","../src/lite/LiteWindow.tsx","../src/lite/focusOnOpen.ts"],"sourcesContent":["// @ai-matrx/summon/lite — the light floating window: drag, edge resize, Escape when topmost,\n// focus in/out, z-order shared with WindowPanel (the windowManager transientZ lane), a bottom\n// drawer on phones. No minimize, tray, persistence, pop-out or links. See\n// SUMMON-PACKAGE-DESIGN.md (common-docs) for its contract.\nexport { LiteWindow, type LiteWindowProps, type FloatingFrameSize, type FloatingCloseReason } from \"./lite/LiteWindow\";\nexport { KEEP_FOCUS_ATTRIBUTE, isEditableField, mayTakeFocusOnOpen } from \"./lite/focusOnOpen\";\n","\"use client\";\n\n/**\n * LiteWindow (ported from matrx-frontend `MatrxFloatingFrame`, the FLOATING presentation of\n * `MatrxDynamicPanelHost`) — the light floating window.\n *\n * This is the \"lightweight window\" Arman asked for on 2026-09-18: *\"a slightly\n * more lightweight version of the window panel … doesn't minimize and stuff\n * like that but carries the basics of dragging\"*. It exists so a picker or a\n * form never has to live in a blocking modal\n * (`common-docs/policies/no-dead-ends.md` §4).\n *\n * It is NOT a second floating-surface family. It is one more presentation of\n * the panel host this repo already has in 59 files; the host routes to it on\n * `presentation=\"floating\"` and keeps every other call site on the docked\n * branch untouched.\n *\n * CARRIES: drag by header, edge/corner resize, Escape when topmost, focus in\n * on open and back to the opener on close, z-order shared with `WindowPanel`\n * (same `nextZIndex` counter — see `windowManagerSlice.transientZ`), a bottom\n * drawer below 768, and a nested portal container so a `CreatablePicker`\n * popover mounts INSIDE the window instead of behind it.\n *\n * NEVER GETS: minimize, the tray, persistence/restore, pop-out, URL sync,\n * sidebars, a focus trap, `aria-modal`, `inert` on the page, or a body scroll\n * lock. Each one is either the modal disease or a lost-work trap.\n *\n * 🚨 THE NEVER-LOSE-INPUT CONTRACT (the law's §5, and finding C1 of\n * `common-docs/projects/every-picker-takes-new-input/recon/plan-attack-1.md`,\n * where crossing 768 with the agent-variable editor open destroyed the editor\n * and everything typed into it). The floating frame and the drawer are THE\n * SAME DOM NODE and the same React subtree: crossing the breakpoint changes\n * `className` and `style` on one `<div>` and nothing else. There is no\n * `isNarrow ? <Drawer>{children}</Drawer> : <Floating>{children}</Floating>`\n * anywhere in this file, and there must never be — that ternary is exactly\n * what unmounts the body and eats the person's work. `children` is rendered\n * in one place, once.\n */\n\nimport {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useState,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { clampRectToViewport, PortalContainerProvider, useDragMove } from \"@ai-matrx/design-system\";\nimport { cn } from \"@ai-matrx/design-system/cn\";\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\nimport { useIsMobile } from \"@ai-matrx/kit/media-query\";\nimport { raiseTransientWindow, releaseTransientWindow, selectTransientZIndex } from \"@ai-matrx/summon\";\nimport { useOptionalOverlayInstance, useSummonDispatch, useSummonPorts, useSummonSelector } from \"@ai-matrx/summon/react\";\nimport { mayTakeFocusOnOpen } from \"./focusOnOpen\";\n\nfunction X(props: { className?: string }) {\n return (\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden className={props.className}>\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n );\n}\n\nexport type FloatingFrameSize = \"sm\" | \"md\" | \"lg\";\n\nconst SIZE_PX: Record<FloatingFrameSize, { width: number; height: number }> = {\n sm: { width: 380, height: 420 },\n md: { width: 560, height: 560 },\n lg: { width: 780, height: 680 },\n};\n\nconst VIEWPORT_PADDING = 8;\nconst DEFAULT_MIN_WIDTH = 320;\nconst DEFAULT_MIN_HEIGHT = 200;\n/** Keyboard drag step; a modifier drops it to 1px. */\nconst KEY_DRAG_STEP = 20;\n\ninterface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\ntype ResizeEdge = \"e\" | \"s\" | \"se\";\n\nexport type FloatingCloseReason = \"escape\" | \"close-button\" | \"programmatic\";\n\nexport interface LiteWindowProps {\n /** Stable id — the key this window holds in the shared z-order lane. */\n id: string;\n title: ReactNode;\n description?: ReactNode;\n children: ReactNode;\n /**\n * Close request. Omit inside an SummonHost-rendered overlay: the window then closes its own\n * instance. A page-local window must pass it.\n */\n onClose?: ((reason: FloatingCloseReason) => void) | undefined;\n size?: FloatingFrameSize;\n width?: number;\n height?: number;\n minWidth?: number;\n minHeight?: number;\n draggable?: boolean;\n resizable?: boolean;\n /** Blocks Escape and the header close button while true. */\n dismissDisabled?: boolean;\n headerActions?: ReactNode;\n footer?: ReactNode;\n className?: string;\n contentClassName?: string;\n /** Move focus into the body on open, and back to the opener on close. */\n initialFocus?: boolean;\n /**\n * CONTAINER-BOUNDED mode. When set, the window lives INSIDE this element\n * instead of over the whole viewport: it portals into it (position\n * absolute — the element must be positioned), centres in it on open, and\n * is dragged and clamped within its box (it follows the box when it\n * resizes). Used by the canvas workspace's floating chat, which belongs to\n * the canvas region, not the window. `null` = the element is not mounted\n * yet; the frame waits for it. Omit for the viewport-wide default.\n */\n container?: HTMLElement | null;\n}\n\nconst FIELD_SELECTOR = [\n \"[data-panel-initial-focus]\",\n \"input:not([disabled]):not([type='hidden'])\",\n \"textarea:not([disabled])\",\n \"select:not([disabled])\",\n \"[contenteditable='true']\",\n].join(\",\");\n\nconst FALLBACK_SELECTOR = [\n \"button:not([disabled])\",\n \"a[href]\",\n \"[tabindex]:not([tabindex='-1'])\",\n].join(\",\");\n\nfunction firstVisibleMatch(root: HTMLElement, selector: string) {\n return (\n Array.from(root.querySelectorAll<HTMLElement>(selector)).find(\n (el) =>\n el.getAttribute(\"aria-hidden\") !== \"true\" &&\n el.getClientRects().length > 0,\n ) ?? null\n );\n}\n\n/**\n * The open lite windows, newest last. Escape belongs to the TOPMOST one only —\n * two open windows must not both close on one key. Module scope, because a\n * per-component ref cannot see its siblings (the same mistake that opened two\n * Google consent windows for one intent, `check:google-auth-gate`).\n */\nconst openStack = (): string[] => globalSlot<string[]>(\"ai-matrx.summon.lite.openStack\", () => []);\n\n/**\n * True when a Radix popper (a `CreatablePicker` list, a Select, a Popover) is\n * open anywhere. That layer owns the Escape; we must not close the window out\n * from under a person who was only dismissing a dropdown.\n */\nfunction aDismissableLayerIsOpen(): boolean {\n return document.querySelector(\"[data-radix-popper-content-wrapper]\") !== null;\n}\n\n/** The window kept on screen: shrunk to the room (never below 240×180) and placed inside the padding. */\nfunction clampRect(rect: Rect, vw: number, vh: number): Rect {\n return clampRectToViewport(rect, { width: vw, height: vh }, { margin: VIEWPORT_PADDING, fit: { minWidth: 240, minHeight: 180 } });\n}\n\nexport function LiteWindow({\n id,\n title,\n description,\n children,\n onClose: onCloseProp,\n size = \"md\",\n width,\n height,\n minWidth = DEFAULT_MIN_WIDTH,\n minHeight = DEFAULT_MIN_HEIGHT,\n draggable = true,\n resizable = true,\n dismissDisabled = false,\n headerActions,\n footer,\n className,\n contentClassName = \"px-4 py-3\",\n initialFocus = true,\n container,\n}: LiteWindowProps) {\n // Close: the explicit prop, else the enclosing SummonHost instance; neither is reported.\n const instance = useOptionalOverlayInstance();\n const ports = useSummonPorts();\n const onClose = useCallback(\n (reason: FloatingCloseReason) => {\n if (onCloseProp) onCloseProp(reason);\n else if (instance) instance.close();\n else ports.errors.capture(new Error(\"[summon] LiteWindow has no onClose and renders outside an overlay instance\"), { id, reason });\n },\n [id, instance, onCloseProp, ports],\n );\n const bounded = container !== undefined;\n const dispatch = useSummonDispatch();\n const zIndex = useSummonSelector(selectTransientZIndex(id));\n const isNarrow = useIsMobile();\n /**\n * THE OPENER — captured in the very first render, before this window's body\n * has mounted. It cannot be read later: a form whose first field carries\n * `autoFocus` (the real Add-a-scope-type form does) has already taken focus\n * by the time an effect runs, and the \"opener\" we would return focus to is\n * then a field inside the window that is about to be destroyed. That is\n * exactly how the first live pass closed with focus on nothing.\n */\n const [opener] = useState<HTMLElement | null>(() =>\n typeof document !== \"undefined\" &&\n document.activeElement instanceof HTMLElement &&\n document.activeElement !== document.body\n ? document.activeElement\n : null,\n );\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\n const [frameEl, setFrameEl] = useState<HTMLDivElement | null>(null);\n const titleId = useId();\n const descriptionId = useId();\n\n const [rect, setRect] = useState<Rect>(() => {\n const base = SIZE_PX[size];\n return {\n width: width ?? base.width,\n height: height ?? base.height,\n x: 0,\n y: 0,\n };\n });\n const [placed, setPlaced] = useState(false);\n const [viewport, setViewport] = useState(() => ({\n w: typeof window === \"undefined\" ? 1280 : window.innerWidth,\n h: typeof window === \"undefined\" ? 800 : window.innerHeight,\n }));\n\n /** What actually gets drawn: the person's rect, kept on screen. */\n const view = clampRect(rect, viewport.w, viewport.h);\n\n useEffect(() => {\n if (bounded) {\n setPortalTarget(container ?? null);\n return;\n }\n setPortalTarget(document.getElementById(\"glass-layer\") ?? document.body);\n }, [bounded, container]);\n\n // Join the shared z-order on mount, leave it on unmount.\n useEffect(() => {\n dispatch(raiseTransientWindow(id));\n openStack().push(id);\n return () => {\n dispatch(releaseTransientWindow(id));\n const at = openStack().lastIndexOf(id);\n if (at !== -1) openStack().splice(at, 1);\n };\n }, [dispatch, id]);\n\n /**\n * Centre on first paint, and track the viewport.\n *\n * `rect` is the size and place the PERSON chose; it is never shrunk by a\n * viewport change. The clamp happens at render time only, so a window\n * squeezed at 375 comes back at its own size when there is room again —\n * a window that returned from a phone width permanently 359px wide was\n * the first thing this cost us.\n */\n useLayoutEffect(() => {\n if (typeof window === \"undefined\") return undefined;\n if (bounded) {\n // Bounded: the \"viewport\" is the container's box.\n if (!container) return undefined;\n const measure = () => ({ w: container.clientWidth, h: container.clientHeight });\n const box = measure();\n setViewport(box);\n if (!placed) {\n setRect((current) => ({\n ...current,\n x: Math.round((box.w - current.width) / 2),\n y: Math.round(Math.max(VIEWPORT_PADDING, (box.h - current.height) / 2.4)),\n }));\n setPlaced(true);\n }\n const observer = new ResizeObserver(() => setViewport(measure()));\n observer.observe(container);\n return () => observer.disconnect();\n }\n if (!placed) {\n setRect((current) => ({\n ...current,\n x: Math.round((window.innerWidth - current.width) / 2),\n y: Math.round(\n Math.max(56, (window.innerHeight - current.height) / 2.4),\n ),\n }));\n setPlaced(true);\n }\n const onResize = () =>\n setViewport({ w: window.innerWidth, h: window.innerHeight });\n window.addEventListener(\"resize\", onResize);\n return () => window.removeEventListener(\"resize\", onResize);\n }, [placed, bounded, container]);\n\n // Escape closes the TOPMOST lite window, and only when no inner dismissable\n // layer is holding the key.\n useEffect(() => {\n if (dismissDisabled) return undefined;\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\" || e.defaultPrevented) return;\n if (openStack()[openStack().length - 1] !== id) return;\n if (aDismissableLayerIsOpen()) return;\n e.stopPropagation();\n onClose(\"escape\");\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [dismissDisabled, id, onClose]);\n\n // Focus in on open; back to the opener on close.\n useEffect(() => {\n if (!initialFocus || !frameEl) return undefined;\n const frame = requestAnimationFrame(() => {\n // Never out of a field she is typing in outside this window\n // (focusOnOpen.ts — the same rule as the docked host).\n if (!mayTakeFocusOnOpen(frameEl)) return;\n const content =\n frameEl.querySelector<HTMLElement>(\"[data-panel-content]\") ?? frameEl;\n const target =\n firstVisibleMatch(content, FIELD_SELECTOR) ??\n firstVisibleMatch(content, FALLBACK_SELECTOR) ??\n frameEl;\n target.focus({ preventScroll: true });\n });\n return () => {\n cancelAnimationFrame(frame);\n if (opener?.isConnected && !frameEl.contains(opener)) {\n requestAnimationFrame(() => opener.focus({ preventScroll: true }));\n }\n };\n }, [initialFocus, frameEl, opener]);\n\n const raise = useCallback(() => {\n dispatch(raiseTransientWindow(id));\n const at = openStack().lastIndexOf(id);\n if (at !== -1 && at !== openStack().length - 1) {\n openStack().splice(at, 1);\n openStack().push(id);\n }\n }, [dispatch, id]);\n\n // THE WINDOW DRAG (design-system window-drag.ts): header drag and e/s/se resize paint per\n // frame and commit once on release, kept on screen by the same clamp the render uses.\n const drag = useDragMove({\n element: frameEl,\n enabled: !isNarrow,\n getRect: () => view,\n limits: { minWidth, minHeight },\n constrain: (next) => clampRect(next, viewport.w, viewport.h),\n onCommit: (next, { gesture }) =>\n // A move keeps the size the person chose (the render-time clamp may be squeezing it).\n setRect((current) => (gesture === \"move\" ? { ...current, x: next.x, y: next.y } : next)),\n });\n\n /** Drag is never mouse-only: the handle is focusable and arrow keys move it. */\n const onHandleKeyDown = useCallback(\n (e: ReactKeyboardEvent<HTMLElement>) => {\n if (isNarrow || !draggable) return;\n const step = e.shiftKey || e.altKey ? 1 : KEY_DRAG_STEP;\n const delta =\n e.key === \"ArrowLeft\"\n ? { x: -step, y: 0 }\n : e.key === \"ArrowRight\"\n ? { x: step, y: 0 }\n : e.key === \"ArrowUp\"\n ? { x: 0, y: -step }\n : e.key === \"ArrowDown\"\n ? { x: 0, y: step }\n : null;\n if (!delta) return;\n e.preventDefault();\n setRect((current) => ({\n ...current,\n x: current.x + delta.x,\n y: current.y + delta.y,\n }));\n },\n [draggable, isNarrow],\n );\n\n if (!portalTarget) return null;\n\n // ── THE ONE NODE ──────────────────────────────────────────────────────────\n // Floating and drawer differ ONLY in these two values. Same element, same\n // subtree, same children — so crossing 768 is a re-render, never an unmount.\n const style: CSSProperties = isNarrow\n ? { zIndex }\n : {\n zIndex,\n left: view.x,\n top: view.y,\n width: view.width,\n height: view.height,\n };\n\n const frameClass = isNarrow\n ? \"fixed inset-x-0 bottom-0 max-h-[88dvh] rounded-t-xl border-t\"\n : bounded\n ? \"absolute rounded-xl border\"\n : \"fixed rounded-xl border\";\n\n const canDismiss = !dismissDisabled;\n\n const panel = (\n <div\n ref={setFrameEl}\n role=\"dialog\"\n aria-modal=\"false\"\n aria-labelledby={titleId}\n aria-describedby={description ? descriptionId : undefined}\n tabIndex={-1}\n data-matrx-lite-window={id}\n data-presentation={isNarrow ? \"drawer\" : \"floating\"}\n onPointerDownCapture={raise}\n style={style}\n className={cn(\n \"flex flex-col overflow-hidden border-border bg-card text-card-foreground shadow-2xl outline-none\",\n \"transition-[border-radius] duration-150\",\n frameClass,\n className,\n )}\n >\n <div\n onPointerDown={draggable ? drag.onPointerDown : undefined}\n className={cn(\n \"flex shrink-0 items-center gap-2 border-b border-border bg-muted/40 px-3 py-2\",\n !isNarrow && draggable && \"cursor-grab active:cursor-grabbing\",\n )}\n >\n {!isNarrow && draggable ? (\n <span\n role=\"button\"\n tabIndex={0}\n aria-label={`Move ${typeof title === \"string\" ? title : \"window\"} (arrow keys)`}\n onKeyDown={onHandleKeyDown}\n className=\"h-4 w-1.5 shrink-0 rounded-full bg-border outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n ) : null}\n <div className=\"min-w-0 flex-1\">\n <h2\n id={titleId}\n className=\"truncate text-sm font-semibold text-foreground\"\n >\n {title}\n </h2>\n {description ? (\n <p\n id={descriptionId}\n className=\"mt-0.5 line-clamp-2 text-xs text-muted-foreground\"\n >\n {description}\n </p>\n ) : null}\n </div>\n {headerActions ? (\n <div className=\"flex shrink-0 items-center gap-1\">{headerActions}</div>\n ) : null}\n {canDismiss ? (\n <button\n type=\"button\"\n onClick={() => onClose(\"close-button\")}\n aria-label=\"Close\"\n className=\"shrink-0 rounded-md p-1 text-muted-foreground outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n ) : null}\n </div>\n\n <div\n data-panel-content\n className={cn(\"min-h-0 flex-1 overflow-auto\", contentClassName)}\n >\n {/*\n The nested portal container: a CreatablePicker popover, a Select or a\n Tooltip opened in here mounts INSIDE this window instead of at\n document.body, where it would land behind the window that opened it.\n */}\n <PortalContainerProvider container={frameEl}>\n {children}\n </PortalContainerProvider>\n </div>\n\n {footer ? (\n <div className=\"shrink-0 border-t border-border bg-muted/30 px-4 py-2\">\n {footer}\n </div>\n ) : null}\n\n {resizable && !isNarrow ? (\n <>\n <span\n onPointerDown={drag.resizeHandle(\"e\")}\n className=\"absolute right-0 top-8 h-[calc(100%-3rem)] w-1.5 cursor-ew-resize\"\n />\n <span\n onPointerDown={drag.resizeHandle(\"s\")}\n className=\"absolute bottom-0 left-4 h-1.5 w-[calc(100%-2rem)] cursor-ns-resize\"\n />\n <span\n onPointerDown={drag.resizeHandle(\"se\")}\n aria-hidden\n className=\"absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize\"\n />\n </>\n ) : null}\n </div>\n );\n\n return createPortal(panel, portalTarget);\n}\n","// components/matrx/resizable/focusOnOpen.ts\n//\n// 🚨 AN OPENING WINDOW NEVER TAKES FOCUS OUT OF A FIELD SHE IS TYPING IN\n// (Masterwork cold walk 23, 2026-09-30).\n//\n// `/masterwork/<id>?interview=1&rename=1` opened the Rulebook title's inline\n// rename AND the interview's docked panel on the same load. The rename field\n// took focus first; ~60ms later the panel's focus-on-open moved focus into\n// the panel, the rename field blurred, and EditableLabel commits-and-closes\n// on blur — so the rename the duplicate-name notice sent her to was gone\n// before she could type. Measured headless on localhost: focus() from the\n// rename field at t=2856ms, then from MatrxDynamicPanelHost's\n// focusPreferredTarget at 2921/2930/3043ms.\n//\n// The rule, shared by every window primitive in this folder (the docked host\n// and the floating frame): a window that opens takes focus, EXCEPT out of an\n// editable field that sits inside an element carrying KEEP_FOCUS_ATTRIBUTE —\n// an inline edit the page itself opened (the Rulebook title's rename on\n// `?rename=1`). A panel she opens with Enter or a hotkey from any other field\n// still takes focus: that is the panel she asked to type into.\n//\n// Why a mark and not `navigator.userActivation`: measured headless on\n// localhost, a cold load of the deep link reported `isActive === true` when\n// the panel opened, and a client navigation right after a click (Start → the\n// new Rulebook) is inside the click's activation window too — the gesture\n// cannot tell \"she opened this panel\" from \"she clicked something earlier\".\n// A <select> is never a field she is typing in.\n\n/** Put on an inline-edit container: a field inside it keeps focus when a window opens. */\nexport const KEEP_FOCUS_ATTRIBUTE = \"data-keep-focus-on-open\";\n\nconst EDITABLE_INPUT_TYPES = new Set([\n \"\",\n \"text\",\n \"search\",\n \"email\",\n \"url\",\n \"tel\",\n \"password\",\n \"number\",\n \"date\",\n \"datetime-local\",\n \"month\",\n \"time\",\n \"week\",\n]);\n\n/** Is `el` a field a person types into? */\nexport function isEditableField(el: Element | null): el is HTMLElement {\n if (!(el instanceof HTMLElement)) return false;\n if (el instanceof HTMLTextAreaElement) return !el.disabled && !el.readOnly;\n if (el instanceof HTMLInputElement) {\n return (\n !el.disabled &&\n !el.readOnly &&\n EDITABLE_INPUT_TYPES.has((el.getAttribute(\"type\") ?? \"\").toLowerCase())\n );\n }\n return el.isContentEditable === true;\n}\n\n/**\n * May a window that is opening (`container`) move focus into itself right\n * now? False only while focus is in an editable field outside it that sits in\n * a KEEP_FOCUS_ATTRIBUTE container.\n */\nexport function mayTakeFocusOnOpen(container: HTMLElement | null): boolean {\n const active = typeof document === \"undefined\" ? null : document.activeElement;\n if (!isEditableField(active)) return true;\n if (container != null && container.contains(active)) return true;\n return active.closest(`[${KEEP_FOCUS_ATTRIBUTE}]`) == null;\n}\n"],"mappings":"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","names":["import_react"]}
package/dist/lite.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  useState
10
10
  } from "react";
11
11
  import { createPortal } from "react-dom";
12
- import { PortalContainerProvider } from "@ai-matrx/design-system";
12
+ import { clampRectToViewport, PortalContainerProvider, useDragMove } from "@ai-matrx/design-system";
13
13
  import { cn } from "@ai-matrx/design-system/cn";
14
14
  import { globalSlot } from "@ai-matrx/kit/global-slot";
15
15
  import { useIsMobile } from "@ai-matrx/kit/media-query";
@@ -87,20 +87,7 @@ function aDismissableLayerIsOpen() {
87
87
  return document.querySelector("[data-radix-popper-content-wrapper]") !== null;
88
88
  }
89
89
  function clampRect(rect, vw, vh) {
90
- const width = Math.min(rect.width, Math.max(240, vw - VIEWPORT_PADDING * 2));
91
- const height = Math.min(
92
- rect.height,
93
- Math.max(180, vh - VIEWPORT_PADDING * 2)
94
- );
95
- return {
96
- width,
97
- height,
98
- x: Math.min(Math.max(rect.x, VIEWPORT_PADDING), vw - width - VIEWPORT_PADDING),
99
- y: Math.min(
100
- Math.max(rect.y, VIEWPORT_PADDING),
101
- vh - height - VIEWPORT_PADDING
102
- )
103
- };
90
+ return clampRectToViewport(rect, { width: vw, height: vh }, { margin: VIEWPORT_PADDING, fit: { minWidth: 240, minHeight: 180 } });
104
91
  }
105
92
  function LiteWindow({
106
93
  id,
@@ -243,64 +230,17 @@ function LiteWindow({
243
230
  openStack().push(id);
244
231
  }
245
232
  }, [dispatch, id]);
246
- const startDrag = useCallback(
247
- (e) => {
248
- if (!draggable || isNarrow) return;
249
- if (e.button !== 0) return;
250
- const target = e.target;
251
- if (target.closest("button,a,input,[role='button']")) return;
252
- e.preventDefault();
253
- raise();
254
- const startX = e.clientX;
255
- const startY = e.clientY;
256
- const origin = { ...view };
257
- const onMove = (move) => {
258
- setRect({
259
- ...origin,
260
- x: origin.x + (move.clientX - startX),
261
- y: origin.y + (move.clientY - startY)
262
- });
263
- };
264
- const onUp = () => {
265
- window.removeEventListener("pointermove", onMove);
266
- window.removeEventListener("pointerup", onUp);
267
- };
268
- window.addEventListener("pointermove", onMove);
269
- window.addEventListener("pointerup", onUp);
270
- },
271
- [draggable, isNarrow, raise, view]
272
- );
273
- const startResize = useCallback(
274
- (e, edge) => {
275
- if (!resizable || isNarrow || e.button !== 0) return;
276
- e.preventDefault();
277
- e.stopPropagation();
278
- raise();
279
- const startX = e.clientX;
280
- const startY = e.clientY;
281
- const origin = { ...view };
282
- const onMove = (move) => {
283
- const next = { ...origin };
284
- if (edge === "e" || edge === "se") {
285
- next.width = Math.max(minWidth, origin.width + (move.clientX - startX));
286
- }
287
- if (edge === "s" || edge === "se") {
288
- next.height = Math.max(
289
- minHeight,
290
- origin.height + (move.clientY - startY)
291
- );
292
- }
293
- setRect(next);
294
- };
295
- const onUp = () => {
296
- window.removeEventListener("pointermove", onMove);
297
- window.removeEventListener("pointerup", onUp);
298
- };
299
- window.addEventListener("pointermove", onMove);
300
- window.addEventListener("pointerup", onUp);
301
- },
302
- [isNarrow, minHeight, minWidth, raise, resizable, view]
303
- );
233
+ const drag = useDragMove({
234
+ element: frameEl,
235
+ enabled: !isNarrow,
236
+ getRect: () => view,
237
+ limits: { minWidth, minHeight },
238
+ constrain: (next) => clampRect(next, viewport.w, viewport.h),
239
+ onCommit: (next, { gesture }) => (
240
+ // A move keeps the size the person chose (the render-time clamp may be squeezing it).
241
+ setRect((current) => gesture === "move" ? { ...current, x: next.x, y: next.y } : next)
242
+ )
243
+ });
304
244
  const onHandleKeyDown = useCallback(
305
245
  (e) => {
306
246
  if (isNarrow || !draggable) return;
@@ -349,7 +289,7 @@ function LiteWindow({
349
289
  /* @__PURE__ */ jsxs(
350
290
  "div",
351
291
  {
352
- onPointerDown: startDrag,
292
+ onPointerDown: draggable ? drag.onPointerDown : void 0,
353
293
  className: cn(
354
294
  "flex shrink-0 items-center gap-2 border-b border-border bg-muted/40 px-3 py-2",
355
295
  !isNarrow && draggable && "cursor-grab active:cursor-grabbing"
@@ -410,21 +350,21 @@ function LiteWindow({
410
350
  /* @__PURE__ */ jsx(
411
351
  "span",
412
352
  {
413
- onPointerDown: (e) => startResize(e, "e"),
353
+ onPointerDown: drag.resizeHandle("e"),
414
354
  className: "absolute right-0 top-8 h-[calc(100%-3rem)] w-1.5 cursor-ew-resize"
415
355
  }
416
356
  ),
417
357
  /* @__PURE__ */ jsx(
418
358
  "span",
419
359
  {
420
- onPointerDown: (e) => startResize(e, "s"),
360
+ onPointerDown: drag.resizeHandle("s"),
421
361
  className: "absolute bottom-0 left-4 h-1.5 w-[calc(100%-2rem)] cursor-ns-resize"
422
362
  }
423
363
  ),
424
364
  /* @__PURE__ */ jsx(
425
365
  "span",
426
366
  {
427
- onPointerDown: (e) => startResize(e, "se"),
367
+ onPointerDown: drag.resizeHandle("se"),
428
368
  "aria-hidden": true,
429
369
  className: "absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize"
430
370
  }
package/dist/lite.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lite/LiteWindow.tsx","../src/lite/focusOnOpen.ts"],"sourcesContent":["\"use client\";\n\n/**\n * LiteWindow (ported from matrx-frontend `MatrxFloatingFrame`, the FLOATING presentation of\n * `MatrxDynamicPanelHost`) — the light floating window.\n *\n * This is the \"lightweight window\" Arman asked for on 2026-09-18: *\"a slightly\n * more lightweight version of the window panel … doesn't minimize and stuff\n * like that but carries the basics of dragging\"*. It exists so a picker or a\n * form never has to live in a blocking modal\n * (`common-docs/policies/no-dead-ends.md` §4).\n *\n * It is NOT a second floating-surface family. It is one more presentation of\n * the panel host this repo already has in 59 files; the host routes to it on\n * `presentation=\"floating\"` and keeps every other call site on the docked\n * branch untouched.\n *\n * CARRIES: drag by header, edge/corner resize, Escape when topmost, focus in\n * on open and back to the opener on close, z-order shared with `WindowPanel`\n * (same `nextZIndex` counter — see `windowManagerSlice.transientZ`), a bottom\n * drawer below 768, and a nested portal container so a `CreatablePicker`\n * popover mounts INSIDE the window instead of behind it.\n *\n * NEVER GETS: minimize, the tray, persistence/restore, pop-out, URL sync,\n * sidebars, a focus trap, `aria-modal`, `inert` on the page, or a body scroll\n * lock. Each one is either the modal disease or a lost-work trap.\n *\n * 🚨 THE NEVER-LOSE-INPUT CONTRACT (the law's §5, and finding C1 of\n * `common-docs/projects/every-picker-takes-new-input/recon/plan-attack-1.md`,\n * where crossing 768 with the agent-variable editor open destroyed the editor\n * and everything typed into it). The floating frame and the drawer are THE\n * SAME DOM NODE and the same React subtree: crossing the breakpoint changes\n * `className` and `style` on one `<div>` and nothing else. There is no\n * `isNarrow ? <Drawer>{children}</Drawer> : <Floating>{children}</Floating>`\n * anywhere in this file, and there must never be — that ternary is exactly\n * what unmounts the body and eats the person's work. `children` is rendered\n * in one place, once.\n */\n\nimport {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useState,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { PortalContainerProvider } from \"@ai-matrx/design-system\";\nimport { cn } from \"@ai-matrx/design-system/cn\";\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\nimport { useIsMobile } from \"@ai-matrx/kit/media-query\";\nimport { raiseTransientWindow, releaseTransientWindow, selectTransientZIndex } from \"@ai-matrx/summon\";\nimport { useOptionalOverlayInstance, useSummonDispatch, useSummonPorts, useSummonSelector } from \"@ai-matrx/summon/react\";\nimport { mayTakeFocusOnOpen } from \"./focusOnOpen\";\n\nfunction X(props: { className?: string }) {\n return (\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden className={props.className}>\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n );\n}\n\nexport type FloatingFrameSize = \"sm\" | \"md\" | \"lg\";\n\nconst SIZE_PX: Record<FloatingFrameSize, { width: number; height: number }> = {\n sm: { width: 380, height: 420 },\n md: { width: 560, height: 560 },\n lg: { width: 780, height: 680 },\n};\n\nconst VIEWPORT_PADDING = 8;\nconst DEFAULT_MIN_WIDTH = 320;\nconst DEFAULT_MIN_HEIGHT = 200;\n/** Keyboard drag step; a modifier drops it to 1px. */\nconst KEY_DRAG_STEP = 20;\n\ninterface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\ntype ResizeEdge = \"e\" | \"s\" | \"se\";\n\nexport type FloatingCloseReason = \"escape\" | \"close-button\" | \"programmatic\";\n\nexport interface LiteWindowProps {\n /** Stable id — the key this window holds in the shared z-order lane. */\n id: string;\n title: ReactNode;\n description?: ReactNode;\n children: ReactNode;\n /**\n * Close request. Omit inside an SummonHost-rendered overlay: the window then closes its own\n * instance. A page-local window must pass it.\n */\n onClose?: ((reason: FloatingCloseReason) => void) | undefined;\n size?: FloatingFrameSize;\n width?: number;\n height?: number;\n minWidth?: number;\n minHeight?: number;\n draggable?: boolean;\n resizable?: boolean;\n /** Blocks Escape and the header close button while true. */\n dismissDisabled?: boolean;\n headerActions?: ReactNode;\n footer?: ReactNode;\n className?: string;\n contentClassName?: string;\n /** Move focus into the body on open, and back to the opener on close. */\n initialFocus?: boolean;\n /**\n * CONTAINER-BOUNDED mode. When set, the window lives INSIDE this element\n * instead of over the whole viewport: it portals into it (position\n * absolute — the element must be positioned), centres in it on open, and\n * is dragged and clamped within its box (it follows the box when it\n * resizes). Used by the canvas workspace's floating chat, which belongs to\n * the canvas region, not the window. `null` = the element is not mounted\n * yet; the frame waits for it. Omit for the viewport-wide default.\n */\n container?: HTMLElement | null;\n}\n\nconst FIELD_SELECTOR = [\n \"[data-panel-initial-focus]\",\n \"input:not([disabled]):not([type='hidden'])\",\n \"textarea:not([disabled])\",\n \"select:not([disabled])\",\n \"[contenteditable='true']\",\n].join(\",\");\n\nconst FALLBACK_SELECTOR = [\n \"button:not([disabled])\",\n \"a[href]\",\n \"[tabindex]:not([tabindex='-1'])\",\n].join(\",\");\n\nfunction firstVisibleMatch(root: HTMLElement, selector: string) {\n return (\n Array.from(root.querySelectorAll<HTMLElement>(selector)).find(\n (el) =>\n el.getAttribute(\"aria-hidden\") !== \"true\" &&\n el.getClientRects().length > 0,\n ) ?? null\n );\n}\n\n/**\n * The open lite windows, newest last. Escape belongs to the TOPMOST one only —\n * two open windows must not both close on one key. Module scope, because a\n * per-component ref cannot see its siblings (the same mistake that opened two\n * Google consent windows for one intent, `check:google-auth-gate`).\n */\nconst openStack = (): string[] => globalSlot<string[]>(\"ai-matrx.summon.lite.openStack\", () => []);\n\n/**\n * True when a Radix popper (a `CreatablePicker` list, a Select, a Popover) is\n * open anywhere. That layer owns the Escape; we must not close the window out\n * from under a person who was only dismissing a dropdown.\n */\nfunction aDismissableLayerIsOpen(): boolean {\n return document.querySelector(\"[data-radix-popper-content-wrapper]\") !== null;\n}\n\nfunction clampRect(rect: Rect, vw: number, vh: number): Rect {\n const width = Math.min(rect.width, Math.max(240, vw - VIEWPORT_PADDING * 2));\n const height = Math.min(\n rect.height,\n Math.max(180, vh - VIEWPORT_PADDING * 2),\n );\n return {\n width,\n height,\n x: Math.min(Math.max(rect.x, VIEWPORT_PADDING), vw - width - VIEWPORT_PADDING),\n y: Math.min(\n Math.max(rect.y, VIEWPORT_PADDING),\n vh - height - VIEWPORT_PADDING,\n ),\n };\n}\n\nexport function LiteWindow({\n id,\n title,\n description,\n children,\n onClose: onCloseProp,\n size = \"md\",\n width,\n height,\n minWidth = DEFAULT_MIN_WIDTH,\n minHeight = DEFAULT_MIN_HEIGHT,\n draggable = true,\n resizable = true,\n dismissDisabled = false,\n headerActions,\n footer,\n className,\n contentClassName = \"px-4 py-3\",\n initialFocus = true,\n container,\n}: LiteWindowProps) {\n // Close: the explicit prop, else the enclosing SummonHost instance; neither is reported.\n const instance = useOptionalOverlayInstance();\n const ports = useSummonPorts();\n const onClose = useCallback(\n (reason: FloatingCloseReason) => {\n if (onCloseProp) onCloseProp(reason);\n else if (instance) instance.close();\n else ports.errors.capture(new Error(\"[summon] LiteWindow has no onClose and renders outside an overlay instance\"), { id, reason });\n },\n [id, instance, onCloseProp, ports],\n );\n const bounded = container !== undefined;\n const dispatch = useSummonDispatch();\n const zIndex = useSummonSelector(selectTransientZIndex(id));\n const isNarrow = useIsMobile();\n /**\n * THE OPENER — captured in the very first render, before this window's body\n * has mounted. It cannot be read later: a form whose first field carries\n * `autoFocus` (the real Add-a-scope-type form does) has already taken focus\n * by the time an effect runs, and the \"opener\" we would return focus to is\n * then a field inside the window that is about to be destroyed. That is\n * exactly how the first live pass closed with focus on nothing.\n */\n const [opener] = useState<HTMLElement | null>(() =>\n typeof document !== \"undefined\" &&\n document.activeElement instanceof HTMLElement &&\n document.activeElement !== document.body\n ? document.activeElement\n : null,\n );\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\n const [frameEl, setFrameEl] = useState<HTMLDivElement | null>(null);\n const titleId = useId();\n const descriptionId = useId();\n\n const [rect, setRect] = useState<Rect>(() => {\n const base = SIZE_PX[size];\n return {\n width: width ?? base.width,\n height: height ?? base.height,\n x: 0,\n y: 0,\n };\n });\n const [placed, setPlaced] = useState(false);\n const [viewport, setViewport] = useState(() => ({\n w: typeof window === \"undefined\" ? 1280 : window.innerWidth,\n h: typeof window === \"undefined\" ? 800 : window.innerHeight,\n }));\n\n /** What actually gets drawn: the person's rect, kept on screen. */\n const view = clampRect(rect, viewport.w, viewport.h);\n\n useEffect(() => {\n if (bounded) {\n setPortalTarget(container ?? null);\n return;\n }\n setPortalTarget(document.getElementById(\"glass-layer\") ?? document.body);\n }, [bounded, container]);\n\n // Join the shared z-order on mount, leave it on unmount.\n useEffect(() => {\n dispatch(raiseTransientWindow(id));\n openStack().push(id);\n return () => {\n dispatch(releaseTransientWindow(id));\n const at = openStack().lastIndexOf(id);\n if (at !== -1) openStack().splice(at, 1);\n };\n }, [dispatch, id]);\n\n /**\n * Centre on first paint, and track the viewport.\n *\n * `rect` is the size and place the PERSON chose; it is never shrunk by a\n * viewport change. The clamp happens at render time only, so a window\n * squeezed at 375 comes back at its own size when there is room again —\n * a window that returned from a phone width permanently 359px wide was\n * the first thing this cost us.\n */\n useLayoutEffect(() => {\n if (typeof window === \"undefined\") return undefined;\n if (bounded) {\n // Bounded: the \"viewport\" is the container's box.\n if (!container) return undefined;\n const measure = () => ({ w: container.clientWidth, h: container.clientHeight });\n const box = measure();\n setViewport(box);\n if (!placed) {\n setRect((current) => ({\n ...current,\n x: Math.round((box.w - current.width) / 2),\n y: Math.round(Math.max(VIEWPORT_PADDING, (box.h - current.height) / 2.4)),\n }));\n setPlaced(true);\n }\n const observer = new ResizeObserver(() => setViewport(measure()));\n observer.observe(container);\n return () => observer.disconnect();\n }\n if (!placed) {\n setRect((current) => ({\n ...current,\n x: Math.round((window.innerWidth - current.width) / 2),\n y: Math.round(\n Math.max(56, (window.innerHeight - current.height) / 2.4),\n ),\n }));\n setPlaced(true);\n }\n const onResize = () =>\n setViewport({ w: window.innerWidth, h: window.innerHeight });\n window.addEventListener(\"resize\", onResize);\n return () => window.removeEventListener(\"resize\", onResize);\n }, [placed, bounded, container]);\n\n // Escape closes the TOPMOST lite window, and only when no inner dismissable\n // layer is holding the key.\n useEffect(() => {\n if (dismissDisabled) return undefined;\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\" || e.defaultPrevented) return;\n if (openStack()[openStack().length - 1] !== id) return;\n if (aDismissableLayerIsOpen()) return;\n e.stopPropagation();\n onClose(\"escape\");\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [dismissDisabled, id, onClose]);\n\n // Focus in on open; back to the opener on close.\n useEffect(() => {\n if (!initialFocus || !frameEl) return undefined;\n const frame = requestAnimationFrame(() => {\n // Never out of a field she is typing in outside this window\n // (focusOnOpen.ts — the same rule as the docked host).\n if (!mayTakeFocusOnOpen(frameEl)) return;\n const content =\n frameEl.querySelector<HTMLElement>(\"[data-panel-content]\") ?? frameEl;\n const target =\n firstVisibleMatch(content, FIELD_SELECTOR) ??\n firstVisibleMatch(content, FALLBACK_SELECTOR) ??\n frameEl;\n target.focus({ preventScroll: true });\n });\n return () => {\n cancelAnimationFrame(frame);\n if (opener?.isConnected && !frameEl.contains(opener)) {\n requestAnimationFrame(() => opener.focus({ preventScroll: true }));\n }\n };\n }, [initialFocus, frameEl, opener]);\n\n const raise = useCallback(() => {\n dispatch(raiseTransientWindow(id));\n const at = openStack().lastIndexOf(id);\n if (at !== -1 && at !== openStack().length - 1) {\n openStack().splice(at, 1);\n openStack().push(id);\n }\n }, [dispatch, id]);\n\n const startDrag = useCallback(\n (e: ReactPointerEvent<HTMLElement>) => {\n if (!draggable || isNarrow) return;\n if (e.button !== 0) return;\n const target = e.target as HTMLElement;\n // A control in the header bar is a control, not a drag handle.\n if (target.closest(\"button,a,input,[role='button']\")) return;\n e.preventDefault();\n raise();\n const startX = e.clientX;\n const startY = e.clientY;\n const origin = { ...view };\n const onMove = (move: PointerEvent) => {\n setRect({\n ...origin,\n x: origin.x + (move.clientX - startX),\n y: origin.y + (move.clientY - startY),\n });\n };\n const onUp = () => {\n window.removeEventListener(\"pointermove\", onMove);\n window.removeEventListener(\"pointerup\", onUp);\n };\n window.addEventListener(\"pointermove\", onMove);\n window.addEventListener(\"pointerup\", onUp);\n },\n [draggable, isNarrow, raise, view],\n );\n\n const startResize = useCallback(\n (e: ReactPointerEvent<HTMLElement>, edge: ResizeEdge) => {\n if (!resizable || isNarrow || e.button !== 0) return;\n e.preventDefault();\n e.stopPropagation();\n raise();\n const startX = e.clientX;\n const startY = e.clientY;\n const origin = { ...view };\n const onMove = (move: PointerEvent) => {\n const next = { ...origin };\n if (edge === \"e\" || edge === \"se\") {\n next.width = Math.max(minWidth, origin.width + (move.clientX - startX));\n }\n if (edge === \"s\" || edge === \"se\") {\n next.height = Math.max(\n minHeight,\n origin.height + (move.clientY - startY),\n );\n }\n setRect(next);\n };\n const onUp = () => {\n window.removeEventListener(\"pointermove\", onMove);\n window.removeEventListener(\"pointerup\", onUp);\n };\n window.addEventListener(\"pointermove\", onMove);\n window.addEventListener(\"pointerup\", onUp);\n },\n [isNarrow, minHeight, minWidth, raise, resizable, view],\n );\n\n /** Drag is never mouse-only: the handle is focusable and arrow keys move it. */\n const onHandleKeyDown = useCallback(\n (e: ReactKeyboardEvent<HTMLElement>) => {\n if (isNarrow || !draggable) return;\n const step = e.shiftKey || e.altKey ? 1 : KEY_DRAG_STEP;\n const delta =\n e.key === \"ArrowLeft\"\n ? { x: -step, y: 0 }\n : e.key === \"ArrowRight\"\n ? { x: step, y: 0 }\n : e.key === \"ArrowUp\"\n ? { x: 0, y: -step }\n : e.key === \"ArrowDown\"\n ? { x: 0, y: step }\n : null;\n if (!delta) return;\n e.preventDefault();\n setRect((current) => ({\n ...current,\n x: current.x + delta.x,\n y: current.y + delta.y,\n }));\n },\n [draggable, isNarrow],\n );\n\n if (!portalTarget) return null;\n\n // ── THE ONE NODE ──────────────────────────────────────────────────────────\n // Floating and drawer differ ONLY in these two values. Same element, same\n // subtree, same children — so crossing 768 is a re-render, never an unmount.\n const style: CSSProperties = isNarrow\n ? { zIndex }\n : {\n zIndex,\n left: view.x,\n top: view.y,\n width: view.width,\n height: view.height,\n };\n\n const frameClass = isNarrow\n ? \"fixed inset-x-0 bottom-0 max-h-[88dvh] rounded-t-xl border-t\"\n : bounded\n ? \"absolute rounded-xl border\"\n : \"fixed rounded-xl border\";\n\n const canDismiss = !dismissDisabled;\n\n const panel = (\n <div\n ref={setFrameEl}\n role=\"dialog\"\n aria-modal=\"false\"\n aria-labelledby={titleId}\n aria-describedby={description ? descriptionId : undefined}\n tabIndex={-1}\n data-matrx-lite-window={id}\n data-presentation={isNarrow ? \"drawer\" : \"floating\"}\n onPointerDownCapture={raise}\n style={style}\n className={cn(\n \"flex flex-col overflow-hidden border-border bg-card text-card-foreground shadow-2xl outline-none\",\n \"transition-[border-radius] duration-150\",\n frameClass,\n className,\n )}\n >\n <div\n onPointerDown={startDrag}\n className={cn(\n \"flex shrink-0 items-center gap-2 border-b border-border bg-muted/40 px-3 py-2\",\n !isNarrow && draggable && \"cursor-grab active:cursor-grabbing\",\n )}\n >\n {!isNarrow && draggable ? (\n <span\n role=\"button\"\n tabIndex={0}\n aria-label={`Move ${typeof title === \"string\" ? title : \"window\"} (arrow keys)`}\n onKeyDown={onHandleKeyDown}\n className=\"h-4 w-1.5 shrink-0 rounded-full bg-border outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n ) : null}\n <div className=\"min-w-0 flex-1\">\n <h2\n id={titleId}\n className=\"truncate text-sm font-semibold text-foreground\"\n >\n {title}\n </h2>\n {description ? (\n <p\n id={descriptionId}\n className=\"mt-0.5 line-clamp-2 text-xs text-muted-foreground\"\n >\n {description}\n </p>\n ) : null}\n </div>\n {headerActions ? (\n <div className=\"flex shrink-0 items-center gap-1\">{headerActions}</div>\n ) : null}\n {canDismiss ? (\n <button\n type=\"button\"\n onClick={() => onClose(\"close-button\")}\n aria-label=\"Close\"\n className=\"shrink-0 rounded-md p-1 text-muted-foreground outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n ) : null}\n </div>\n\n <div\n data-panel-content\n className={cn(\"min-h-0 flex-1 overflow-auto\", contentClassName)}\n >\n {/*\n The nested portal container: a CreatablePicker popover, a Select or a\n Tooltip opened in here mounts INSIDE this window instead of at\n document.body, where it would land behind the window that opened it.\n */}\n <PortalContainerProvider container={frameEl}>\n {children}\n </PortalContainerProvider>\n </div>\n\n {footer ? (\n <div className=\"shrink-0 border-t border-border bg-muted/30 px-4 py-2\">\n {footer}\n </div>\n ) : null}\n\n {resizable && !isNarrow ? (\n <>\n <span\n onPointerDown={(e) => startResize(e, \"e\")}\n className=\"absolute right-0 top-8 h-[calc(100%-3rem)] w-1.5 cursor-ew-resize\"\n />\n <span\n onPointerDown={(e) => startResize(e, \"s\")}\n className=\"absolute bottom-0 left-4 h-1.5 w-[calc(100%-2rem)] cursor-ns-resize\"\n />\n <span\n onPointerDown={(e) => startResize(e, \"se\")}\n aria-hidden\n className=\"absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize\"\n />\n </>\n ) : null}\n </div>\n );\n\n return createPortal(panel, portalTarget);\n}\n","// components/matrx/resizable/focusOnOpen.ts\n//\n// 🚨 AN OPENING WINDOW NEVER TAKES FOCUS OUT OF A FIELD SHE IS TYPING IN\n// (Masterwork cold walk 23, 2026-09-30).\n//\n// `/masterwork/<id>?interview=1&rename=1` opened the Rulebook title's inline\n// rename AND the interview's docked panel on the same load. The rename field\n// took focus first; ~60ms later the panel's focus-on-open moved focus into\n// the panel, the rename field blurred, and EditableLabel commits-and-closes\n// on blur — so the rename the duplicate-name notice sent her to was gone\n// before she could type. Measured headless on localhost: focus() from the\n// rename field at t=2856ms, then from MatrxDynamicPanelHost's\n// focusPreferredTarget at 2921/2930/3043ms.\n//\n// The rule, shared by every window primitive in this folder (the docked host\n// and the floating frame): a window that opens takes focus, EXCEPT out of an\n// editable field that sits inside an element carrying KEEP_FOCUS_ATTRIBUTE —\n// an inline edit the page itself opened (the Rulebook title's rename on\n// `?rename=1`). A panel she opens with Enter or a hotkey from any other field\n// still takes focus: that is the panel she asked to type into.\n//\n// Why a mark and not `navigator.userActivation`: measured headless on\n// localhost, a cold load of the deep link reported `isActive === true` when\n// the panel opened, and a client navigation right after a click (Start → the\n// new Rulebook) is inside the click's activation window too — the gesture\n// cannot tell \"she opened this panel\" from \"she clicked something earlier\".\n// A <select> is never a field she is typing in.\n\n/** Put on an inline-edit container: a field inside it keeps focus when a window opens. */\nexport const KEEP_FOCUS_ATTRIBUTE = \"data-keep-focus-on-open\";\n\nconst EDITABLE_INPUT_TYPES = new Set([\n \"\",\n \"text\",\n \"search\",\n \"email\",\n \"url\",\n \"tel\",\n \"password\",\n \"number\",\n \"date\",\n \"datetime-local\",\n \"month\",\n \"time\",\n \"week\",\n]);\n\n/** Is `el` a field a person types into? */\nexport function isEditableField(el: Element | null): el is HTMLElement {\n if (!(el instanceof HTMLElement)) return false;\n if (el instanceof HTMLTextAreaElement) return !el.disabled && !el.readOnly;\n if (el instanceof HTMLInputElement) {\n return (\n !el.disabled &&\n !el.readOnly &&\n EDITABLE_INPUT_TYPES.has((el.getAttribute(\"type\") ?? \"\").toLowerCase())\n );\n }\n return el.isContentEditable === true;\n}\n\n/**\n * May a window that is opening (`container`) move focus into itself right\n * now? False only while focus is in an editable field outside it that sits in\n * a KEEP_FOCUS_ATTRIBUTE container.\n */\nexport function mayTakeFocusOnOpen(container: HTMLElement | null): boolean {\n const active = typeof document === \"undefined\" ? null : document.activeElement;\n if (!isEditableField(active)) return true;\n if (container != null && container.contains(active)) return true;\n return active.closest(`[${KEEP_FOCUS_ATTRIBUTE}]`) == null;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/lite/LiteWindow.tsx","../src/lite/focusOnOpen.ts"],"sourcesContent":["\"use client\";\n\n/**\n * LiteWindow (ported from matrx-frontend `MatrxFloatingFrame`, the FLOATING presentation of\n * `MatrxDynamicPanelHost`) — the light floating window.\n *\n * This is the \"lightweight window\" Arman asked for on 2026-09-18: *\"a slightly\n * more lightweight version of the window panel … doesn't minimize and stuff\n * like that but carries the basics of dragging\"*. It exists so a picker or a\n * form never has to live in a blocking modal\n * (`common-docs/policies/no-dead-ends.md` §4).\n *\n * It is NOT a second floating-surface family. It is one more presentation of\n * the panel host this repo already has in 59 files; the host routes to it on\n * `presentation=\"floating\"` and keeps every other call site on the docked\n * branch untouched.\n *\n * CARRIES: drag by header, edge/corner resize, Escape when topmost, focus in\n * on open and back to the opener on close, z-order shared with `WindowPanel`\n * (same `nextZIndex` counter — see `windowManagerSlice.transientZ`), a bottom\n * drawer below 768, and a nested portal container so a `CreatablePicker`\n * popover mounts INSIDE the window instead of behind it.\n *\n * NEVER GETS: minimize, the tray, persistence/restore, pop-out, URL sync,\n * sidebars, a focus trap, `aria-modal`, `inert` on the page, or a body scroll\n * lock. Each one is either the modal disease or a lost-work trap.\n *\n * 🚨 THE NEVER-LOSE-INPUT CONTRACT (the law's §5, and finding C1 of\n * `common-docs/projects/every-picker-takes-new-input/recon/plan-attack-1.md`,\n * where crossing 768 with the agent-variable editor open destroyed the editor\n * and everything typed into it). The floating frame and the drawer are THE\n * SAME DOM NODE and the same React subtree: crossing the breakpoint changes\n * `className` and `style` on one `<div>` and nothing else. There is no\n * `isNarrow ? <Drawer>{children}</Drawer> : <Floating>{children}</Floating>`\n * anywhere in this file, and there must never be — that ternary is exactly\n * what unmounts the body and eats the person's work. `children` is rendered\n * in one place, once.\n */\n\nimport {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useState,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { clampRectToViewport, PortalContainerProvider, useDragMove } from \"@ai-matrx/design-system\";\nimport { cn } from \"@ai-matrx/design-system/cn\";\nimport { globalSlot } from \"@ai-matrx/kit/global-slot\";\nimport { useIsMobile } from \"@ai-matrx/kit/media-query\";\nimport { raiseTransientWindow, releaseTransientWindow, selectTransientZIndex } from \"@ai-matrx/summon\";\nimport { useOptionalOverlayInstance, useSummonDispatch, useSummonPorts, useSummonSelector } from \"@ai-matrx/summon/react\";\nimport { mayTakeFocusOnOpen } from \"./focusOnOpen\";\n\nfunction X(props: { className?: string }) {\n return (\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden className={props.className}>\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n );\n}\n\nexport type FloatingFrameSize = \"sm\" | \"md\" | \"lg\";\n\nconst SIZE_PX: Record<FloatingFrameSize, { width: number; height: number }> = {\n sm: { width: 380, height: 420 },\n md: { width: 560, height: 560 },\n lg: { width: 780, height: 680 },\n};\n\nconst VIEWPORT_PADDING = 8;\nconst DEFAULT_MIN_WIDTH = 320;\nconst DEFAULT_MIN_HEIGHT = 200;\n/** Keyboard drag step; a modifier drops it to 1px. */\nconst KEY_DRAG_STEP = 20;\n\ninterface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\ntype ResizeEdge = \"e\" | \"s\" | \"se\";\n\nexport type FloatingCloseReason = \"escape\" | \"close-button\" | \"programmatic\";\n\nexport interface LiteWindowProps {\n /** Stable id — the key this window holds in the shared z-order lane. */\n id: string;\n title: ReactNode;\n description?: ReactNode;\n children: ReactNode;\n /**\n * Close request. Omit inside an SummonHost-rendered overlay: the window then closes its own\n * instance. A page-local window must pass it.\n */\n onClose?: ((reason: FloatingCloseReason) => void) | undefined;\n size?: FloatingFrameSize;\n width?: number;\n height?: number;\n minWidth?: number;\n minHeight?: number;\n draggable?: boolean;\n resizable?: boolean;\n /** Blocks Escape and the header close button while true. */\n dismissDisabled?: boolean;\n headerActions?: ReactNode;\n footer?: ReactNode;\n className?: string;\n contentClassName?: string;\n /** Move focus into the body on open, and back to the opener on close. */\n initialFocus?: boolean;\n /**\n * CONTAINER-BOUNDED mode. When set, the window lives INSIDE this element\n * instead of over the whole viewport: it portals into it (position\n * absolute — the element must be positioned), centres in it on open, and\n * is dragged and clamped within its box (it follows the box when it\n * resizes). Used by the canvas workspace's floating chat, which belongs to\n * the canvas region, not the window. `null` = the element is not mounted\n * yet; the frame waits for it. Omit for the viewport-wide default.\n */\n container?: HTMLElement | null;\n}\n\nconst FIELD_SELECTOR = [\n \"[data-panel-initial-focus]\",\n \"input:not([disabled]):not([type='hidden'])\",\n \"textarea:not([disabled])\",\n \"select:not([disabled])\",\n \"[contenteditable='true']\",\n].join(\",\");\n\nconst FALLBACK_SELECTOR = [\n \"button:not([disabled])\",\n \"a[href]\",\n \"[tabindex]:not([tabindex='-1'])\",\n].join(\",\");\n\nfunction firstVisibleMatch(root: HTMLElement, selector: string) {\n return (\n Array.from(root.querySelectorAll<HTMLElement>(selector)).find(\n (el) =>\n el.getAttribute(\"aria-hidden\") !== \"true\" &&\n el.getClientRects().length > 0,\n ) ?? null\n );\n}\n\n/**\n * The open lite windows, newest last. Escape belongs to the TOPMOST one only —\n * two open windows must not both close on one key. Module scope, because a\n * per-component ref cannot see its siblings (the same mistake that opened two\n * Google consent windows for one intent, `check:google-auth-gate`).\n */\nconst openStack = (): string[] => globalSlot<string[]>(\"ai-matrx.summon.lite.openStack\", () => []);\n\n/**\n * True when a Radix popper (a `CreatablePicker` list, a Select, a Popover) is\n * open anywhere. That layer owns the Escape; we must not close the window out\n * from under a person who was only dismissing a dropdown.\n */\nfunction aDismissableLayerIsOpen(): boolean {\n return document.querySelector(\"[data-radix-popper-content-wrapper]\") !== null;\n}\n\n/** The window kept on screen: shrunk to the room (never below 240×180) and placed inside the padding. */\nfunction clampRect(rect: Rect, vw: number, vh: number): Rect {\n return clampRectToViewport(rect, { width: vw, height: vh }, { margin: VIEWPORT_PADDING, fit: { minWidth: 240, minHeight: 180 } });\n}\n\nexport function LiteWindow({\n id,\n title,\n description,\n children,\n onClose: onCloseProp,\n size = \"md\",\n width,\n height,\n minWidth = DEFAULT_MIN_WIDTH,\n minHeight = DEFAULT_MIN_HEIGHT,\n draggable = true,\n resizable = true,\n dismissDisabled = false,\n headerActions,\n footer,\n className,\n contentClassName = \"px-4 py-3\",\n initialFocus = true,\n container,\n}: LiteWindowProps) {\n // Close: the explicit prop, else the enclosing SummonHost instance; neither is reported.\n const instance = useOptionalOverlayInstance();\n const ports = useSummonPorts();\n const onClose = useCallback(\n (reason: FloatingCloseReason) => {\n if (onCloseProp) onCloseProp(reason);\n else if (instance) instance.close();\n else ports.errors.capture(new Error(\"[summon] LiteWindow has no onClose and renders outside an overlay instance\"), { id, reason });\n },\n [id, instance, onCloseProp, ports],\n );\n const bounded = container !== undefined;\n const dispatch = useSummonDispatch();\n const zIndex = useSummonSelector(selectTransientZIndex(id));\n const isNarrow = useIsMobile();\n /**\n * THE OPENER — captured in the very first render, before this window's body\n * has mounted. It cannot be read later: a form whose first field carries\n * `autoFocus` (the real Add-a-scope-type form does) has already taken focus\n * by the time an effect runs, and the \"opener\" we would return focus to is\n * then a field inside the window that is about to be destroyed. That is\n * exactly how the first live pass closed with focus on nothing.\n */\n const [opener] = useState<HTMLElement | null>(() =>\n typeof document !== \"undefined\" &&\n document.activeElement instanceof HTMLElement &&\n document.activeElement !== document.body\n ? document.activeElement\n : null,\n );\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\n const [frameEl, setFrameEl] = useState<HTMLDivElement | null>(null);\n const titleId = useId();\n const descriptionId = useId();\n\n const [rect, setRect] = useState<Rect>(() => {\n const base = SIZE_PX[size];\n return {\n width: width ?? base.width,\n height: height ?? base.height,\n x: 0,\n y: 0,\n };\n });\n const [placed, setPlaced] = useState(false);\n const [viewport, setViewport] = useState(() => ({\n w: typeof window === \"undefined\" ? 1280 : window.innerWidth,\n h: typeof window === \"undefined\" ? 800 : window.innerHeight,\n }));\n\n /** What actually gets drawn: the person's rect, kept on screen. */\n const view = clampRect(rect, viewport.w, viewport.h);\n\n useEffect(() => {\n if (bounded) {\n setPortalTarget(container ?? null);\n return;\n }\n setPortalTarget(document.getElementById(\"glass-layer\") ?? document.body);\n }, [bounded, container]);\n\n // Join the shared z-order on mount, leave it on unmount.\n useEffect(() => {\n dispatch(raiseTransientWindow(id));\n openStack().push(id);\n return () => {\n dispatch(releaseTransientWindow(id));\n const at = openStack().lastIndexOf(id);\n if (at !== -1) openStack().splice(at, 1);\n };\n }, [dispatch, id]);\n\n /**\n * Centre on first paint, and track the viewport.\n *\n * `rect` is the size and place the PERSON chose; it is never shrunk by a\n * viewport change. The clamp happens at render time only, so a window\n * squeezed at 375 comes back at its own size when there is room again —\n * a window that returned from a phone width permanently 359px wide was\n * the first thing this cost us.\n */\n useLayoutEffect(() => {\n if (typeof window === \"undefined\") return undefined;\n if (bounded) {\n // Bounded: the \"viewport\" is the container's box.\n if (!container) return undefined;\n const measure = () => ({ w: container.clientWidth, h: container.clientHeight });\n const box = measure();\n setViewport(box);\n if (!placed) {\n setRect((current) => ({\n ...current,\n x: Math.round((box.w - current.width) / 2),\n y: Math.round(Math.max(VIEWPORT_PADDING, (box.h - current.height) / 2.4)),\n }));\n setPlaced(true);\n }\n const observer = new ResizeObserver(() => setViewport(measure()));\n observer.observe(container);\n return () => observer.disconnect();\n }\n if (!placed) {\n setRect((current) => ({\n ...current,\n x: Math.round((window.innerWidth - current.width) / 2),\n y: Math.round(\n Math.max(56, (window.innerHeight - current.height) / 2.4),\n ),\n }));\n setPlaced(true);\n }\n const onResize = () =>\n setViewport({ w: window.innerWidth, h: window.innerHeight });\n window.addEventListener(\"resize\", onResize);\n return () => window.removeEventListener(\"resize\", onResize);\n }, [placed, bounded, container]);\n\n // Escape closes the TOPMOST lite window, and only when no inner dismissable\n // layer is holding the key.\n useEffect(() => {\n if (dismissDisabled) return undefined;\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\" || e.defaultPrevented) return;\n if (openStack()[openStack().length - 1] !== id) return;\n if (aDismissableLayerIsOpen()) return;\n e.stopPropagation();\n onClose(\"escape\");\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [dismissDisabled, id, onClose]);\n\n // Focus in on open; back to the opener on close.\n useEffect(() => {\n if (!initialFocus || !frameEl) return undefined;\n const frame = requestAnimationFrame(() => {\n // Never out of a field she is typing in outside this window\n // (focusOnOpen.ts — the same rule as the docked host).\n if (!mayTakeFocusOnOpen(frameEl)) return;\n const content =\n frameEl.querySelector<HTMLElement>(\"[data-panel-content]\") ?? frameEl;\n const target =\n firstVisibleMatch(content, FIELD_SELECTOR) ??\n firstVisibleMatch(content, FALLBACK_SELECTOR) ??\n frameEl;\n target.focus({ preventScroll: true });\n });\n return () => {\n cancelAnimationFrame(frame);\n if (opener?.isConnected && !frameEl.contains(opener)) {\n requestAnimationFrame(() => opener.focus({ preventScroll: true }));\n }\n };\n }, [initialFocus, frameEl, opener]);\n\n const raise = useCallback(() => {\n dispatch(raiseTransientWindow(id));\n const at = openStack().lastIndexOf(id);\n if (at !== -1 && at !== openStack().length - 1) {\n openStack().splice(at, 1);\n openStack().push(id);\n }\n }, [dispatch, id]);\n\n // THE WINDOW DRAG (design-system window-drag.ts): header drag and e/s/se resize paint per\n // frame and commit once on release, kept on screen by the same clamp the render uses.\n const drag = useDragMove({\n element: frameEl,\n enabled: !isNarrow,\n getRect: () => view,\n limits: { minWidth, minHeight },\n constrain: (next) => clampRect(next, viewport.w, viewport.h),\n onCommit: (next, { gesture }) =>\n // A move keeps the size the person chose (the render-time clamp may be squeezing it).\n setRect((current) => (gesture === \"move\" ? { ...current, x: next.x, y: next.y } : next)),\n });\n\n /** Drag is never mouse-only: the handle is focusable and arrow keys move it. */\n const onHandleKeyDown = useCallback(\n (e: ReactKeyboardEvent<HTMLElement>) => {\n if (isNarrow || !draggable) return;\n const step = e.shiftKey || e.altKey ? 1 : KEY_DRAG_STEP;\n const delta =\n e.key === \"ArrowLeft\"\n ? { x: -step, y: 0 }\n : e.key === \"ArrowRight\"\n ? { x: step, y: 0 }\n : e.key === \"ArrowUp\"\n ? { x: 0, y: -step }\n : e.key === \"ArrowDown\"\n ? { x: 0, y: step }\n : null;\n if (!delta) return;\n e.preventDefault();\n setRect((current) => ({\n ...current,\n x: current.x + delta.x,\n y: current.y + delta.y,\n }));\n },\n [draggable, isNarrow],\n );\n\n if (!portalTarget) return null;\n\n // ── THE ONE NODE ──────────────────────────────────────────────────────────\n // Floating and drawer differ ONLY in these two values. Same element, same\n // subtree, same children — so crossing 768 is a re-render, never an unmount.\n const style: CSSProperties = isNarrow\n ? { zIndex }\n : {\n zIndex,\n left: view.x,\n top: view.y,\n width: view.width,\n height: view.height,\n };\n\n const frameClass = isNarrow\n ? \"fixed inset-x-0 bottom-0 max-h-[88dvh] rounded-t-xl border-t\"\n : bounded\n ? \"absolute rounded-xl border\"\n : \"fixed rounded-xl border\";\n\n const canDismiss = !dismissDisabled;\n\n const panel = (\n <div\n ref={setFrameEl}\n role=\"dialog\"\n aria-modal=\"false\"\n aria-labelledby={titleId}\n aria-describedby={description ? descriptionId : undefined}\n tabIndex={-1}\n data-matrx-lite-window={id}\n data-presentation={isNarrow ? \"drawer\" : \"floating\"}\n onPointerDownCapture={raise}\n style={style}\n className={cn(\n \"flex flex-col overflow-hidden border-border bg-card text-card-foreground shadow-2xl outline-none\",\n \"transition-[border-radius] duration-150\",\n frameClass,\n className,\n )}\n >\n <div\n onPointerDown={draggable ? drag.onPointerDown : undefined}\n className={cn(\n \"flex shrink-0 items-center gap-2 border-b border-border bg-muted/40 px-3 py-2\",\n !isNarrow && draggable && \"cursor-grab active:cursor-grabbing\",\n )}\n >\n {!isNarrow && draggable ? (\n <span\n role=\"button\"\n tabIndex={0}\n aria-label={`Move ${typeof title === \"string\" ? title : \"window\"} (arrow keys)`}\n onKeyDown={onHandleKeyDown}\n className=\"h-4 w-1.5 shrink-0 rounded-full bg-border outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n ) : null}\n <div className=\"min-w-0 flex-1\">\n <h2\n id={titleId}\n className=\"truncate text-sm font-semibold text-foreground\"\n >\n {title}\n </h2>\n {description ? (\n <p\n id={descriptionId}\n className=\"mt-0.5 line-clamp-2 text-xs text-muted-foreground\"\n >\n {description}\n </p>\n ) : null}\n </div>\n {headerActions ? (\n <div className=\"flex shrink-0 items-center gap-1\">{headerActions}</div>\n ) : null}\n {canDismiss ? (\n <button\n type=\"button\"\n onClick={() => onClose(\"close-button\")}\n aria-label=\"Close\"\n className=\"shrink-0 rounded-md p-1 text-muted-foreground outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n ) : null}\n </div>\n\n <div\n data-panel-content\n className={cn(\"min-h-0 flex-1 overflow-auto\", contentClassName)}\n >\n {/*\n The nested portal container: a CreatablePicker popover, a Select or a\n Tooltip opened in here mounts INSIDE this window instead of at\n document.body, where it would land behind the window that opened it.\n */}\n <PortalContainerProvider container={frameEl}>\n {children}\n </PortalContainerProvider>\n </div>\n\n {footer ? (\n <div className=\"shrink-0 border-t border-border bg-muted/30 px-4 py-2\">\n {footer}\n </div>\n ) : null}\n\n {resizable && !isNarrow ? (\n <>\n <span\n onPointerDown={drag.resizeHandle(\"e\")}\n className=\"absolute right-0 top-8 h-[calc(100%-3rem)] w-1.5 cursor-ew-resize\"\n />\n <span\n onPointerDown={drag.resizeHandle(\"s\")}\n className=\"absolute bottom-0 left-4 h-1.5 w-[calc(100%-2rem)] cursor-ns-resize\"\n />\n <span\n onPointerDown={drag.resizeHandle(\"se\")}\n aria-hidden\n className=\"absolute bottom-0 right-0 h-4 w-4 cursor-nwse-resize\"\n />\n </>\n ) : null}\n </div>\n );\n\n return createPortal(panel, portalTarget);\n}\n","// components/matrx/resizable/focusOnOpen.ts\n//\n// 🚨 AN OPENING WINDOW NEVER TAKES FOCUS OUT OF A FIELD SHE IS TYPING IN\n// (Masterwork cold walk 23, 2026-09-30).\n//\n// `/masterwork/<id>?interview=1&rename=1` opened the Rulebook title's inline\n// rename AND the interview's docked panel on the same load. The rename field\n// took focus first; ~60ms later the panel's focus-on-open moved focus into\n// the panel, the rename field blurred, and EditableLabel commits-and-closes\n// on blur — so the rename the duplicate-name notice sent her to was gone\n// before she could type. Measured headless on localhost: focus() from the\n// rename field at t=2856ms, then from MatrxDynamicPanelHost's\n// focusPreferredTarget at 2921/2930/3043ms.\n//\n// The rule, shared by every window primitive in this folder (the docked host\n// and the floating frame): a window that opens takes focus, EXCEPT out of an\n// editable field that sits inside an element carrying KEEP_FOCUS_ATTRIBUTE —\n// an inline edit the page itself opened (the Rulebook title's rename on\n// `?rename=1`). A panel she opens with Enter or a hotkey from any other field\n// still takes focus: that is the panel she asked to type into.\n//\n// Why a mark and not `navigator.userActivation`: measured headless on\n// localhost, a cold load of the deep link reported `isActive === true` when\n// the panel opened, and a client navigation right after a click (Start → the\n// new Rulebook) is inside the click's activation window too — the gesture\n// cannot tell \"she opened this panel\" from \"she clicked something earlier\".\n// A <select> is never a field she is typing in.\n\n/** Put on an inline-edit container: a field inside it keeps focus when a window opens. */\nexport const KEEP_FOCUS_ATTRIBUTE = \"data-keep-focus-on-open\";\n\nconst EDITABLE_INPUT_TYPES = new Set([\n \"\",\n \"text\",\n \"search\",\n \"email\",\n \"url\",\n \"tel\",\n \"password\",\n \"number\",\n \"date\",\n \"datetime-local\",\n \"month\",\n \"time\",\n \"week\",\n]);\n\n/** Is `el` a field a person types into? */\nexport function isEditableField(el: Element | null): el is HTMLElement {\n if (!(el instanceof HTMLElement)) return false;\n if (el instanceof HTMLTextAreaElement) return !el.disabled && !el.readOnly;\n if (el instanceof HTMLInputElement) {\n return (\n !el.disabled &&\n !el.readOnly &&\n EDITABLE_INPUT_TYPES.has((el.getAttribute(\"type\") ?? \"\").toLowerCase())\n );\n }\n return el.isContentEditable === true;\n}\n\n/**\n * May a window that is opening (`container`) move focus into itself right\n * now? False only while focus is in an editable field outside it that sits in\n * a KEEP_FOCUS_ATTRIBUTE container.\n */\nexport function mayTakeFocusOnOpen(container: HTMLElement | null): boolean {\n const active = typeof document === \"undefined\" ? null : document.activeElement;\n if (!isEditableField(active)) return true;\n if (container != null && container.contains(active)) return true;\n return active.closest(`[${KEEP_FOCUS_ATTRIBUTE}]`) == null;\n}\n"],"mappings":"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","names":[]}