@tearleads/windowing 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -6
- package/createRequiredContext.js.map +1 -1
- package/index.js.map +1 -1
- package/menu/Menu.js.map +1 -1
- package/menu/MenuItem.js.map +1 -1
- package/menu/useContextMenuState.js.map +1 -1
- package/menu/useMenuKeyboard.js.map +1 -1
- package/package.json +1 -1
- package/sidebar/SidebarResize.js.map +1 -1
- package/window/CurrentWindowContext.js.map +1 -1
- package/window/Window.d.ts +8 -1
- package/window/Window.js +9 -12
- package/window/Window.js.map +1 -1
- package/window/WindowBody.js.map +1 -1
- package/window/WindowChromeActions.d.ts +7 -0
- package/window/WindowChromeActions.js +3 -1
- package/window/WindowChromeActions.js.map +1 -1
- package/window/WindowCloseButton.js.map +1 -1
- package/window/WindowMaximizeButton.js.map +1 -1
- package/window/WindowMenuBar.js.map +1 -1
- package/window/WindowMenuContext.js.map +1 -1
- package/window/WindowMinimizeButton.js.map +1 -1
- package/window/WindowResizeHandle.css +121 -32
- package/window/WindowResizeHandle.d.ts +8 -4
- package/window/WindowResizeHandle.js +18 -3
- package/window/WindowResizeHandle.js.map +1 -1
- package/window/WindowSidebar.js.map +1 -1
- package/window/WindowSidebarContext.js.map +1 -1
- package/window/WindowStateProvider/WindowStateProvider.js.map +1 -1
- package/window/WindowStateProvider/context.js.map +1 -1
- package/window/WindowStateProvider/index.js.map +1 -1
- package/window/WindowStateProvider/types.js.map +1 -1
- package/window/WindowStateProvider/useWindowActions.js.map +1 -1
- package/window/WindowStateProvider/useWindowStateActions.js.map +1 -1
- package/window/WindowStateProvider/useWindowStateData.js.map +1 -1
- package/window/WindowStateProvider/util/bringWindowToFront.js.map +1 -1
- package/window/WindowStateProvider/util/createWindowEntry.js.map +1 -1
- package/window/WindowStateProvider/util/findAdjacentWindow.js.map +1 -1
- package/window/WindowStateProvider/util/findTopWindow.js.map +1 -1
- package/window/WindowStateProvider/util/getMaxWindowZIndex.js.map +1 -1
- package/window/WindowStateProvider/util/index.js.map +1 -1
- package/window/WindowStateProvider/util/swapWindowZIndexes.js.map +1 -1
- package/window/WindowStateProvider/util/updateWindowFlag.js.map +1 -1
- package/window/WindowStatusBar.js.map +1 -1
- package/window/WindowTitleBar.d.ts +1 -0
- package/window/WindowTitleBar.js.map +1 -1
- package/window/WindowToolBar.css +6 -0
- package/window/WindowToolBar.js.map +1 -1
- package/window/useWindowGeometry.d.ts +2 -2
- package/window/useWindowGeometry.js +5 -5
- package/window/useWindowGeometry.js.map +1 -1
- package/window/useWindowItemRegistration.js.map +1 -1
- package/window/useWindowItemRegistry.js.map +1 -1
- package/window/useWindowKeyboardGeometry.js.map +1 -1
- package/window/useWindowLayout.js.map +1 -1
- package/window/useWindowToolbarReservationRegistry.js.map +1 -1
- package/window/windowGeometry.d.ts +2 -2
- package/window/windowGeometry.js +8 -4
- package/window/windowGeometry.js.map +1 -1
- package/window/windowMenuRegistrations.js.map +1 -1
package/README.md
CHANGED
|
@@ -52,12 +52,16 @@ desktop surface must be a positioned container.
|
|
|
52
52
|
|
|
53
53
|
## Geometry and input
|
|
54
54
|
|
|
55
|
-
Windows move by their title bar and resize by
|
|
56
|
-
events, so mouse, touch, and pen all work.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
Windows move by their title bar and resize by any side or corner with pointer
|
|
56
|
+
events, so mouse, touch, and pen all work. The resize targets reach a few
|
|
57
|
+
pixels past the window's border, so an edge can be taken from just outside the
|
|
58
|
+
window, and a hovered corner lights up over the window's own rounded border.
|
|
59
|
+
|
|
60
|
+
While a gesture runs the geometry stays local to that window; when it ends, the
|
|
61
|
+
window commits its `position` and `size` (surface-relative) to its
|
|
62
|
+
`WindowEntry`. The first layout commits too, so `position` always reflects
|
|
63
|
+
where the window sits. Without `size` a window takes its stylesheet's default
|
|
64
|
+
size.
|
|
61
65
|
|
|
62
66
|
To save a layout, read `position` and `size` from `useWindowStateData()`. To
|
|
63
67
|
restore one, pass them back as `create` options, which take precedence over
|
|
@@ -77,6 +81,28 @@ resizes a window programmatically.
|
|
|
77
81
|
Windows register their own menu items, title-bar actions, a Back action, and a
|
|
78
82
|
sidebar through the `useWindow*` hooks, from inside the window's content.
|
|
79
83
|
|
|
84
|
+
## Toolbar
|
|
85
|
+
|
|
86
|
+
The toolbar row sits between the menu bar and the body. Content registers icon
|
|
87
|
+
buttons in it with `useWindowTitleBarAction`; give an action `pressed` to make
|
|
88
|
+
it a toggle, announced as `aria-pressed` and drawn pressed while true:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
const icon = useMemo(() => <HashIcon aria-hidden size={18} />, []);
|
|
92
|
+
useWindowTitleBarAction(
|
|
93
|
+
useMemo(
|
|
94
|
+
() => ({ icon, id: "ascii", label: "ASCII", onClick: toggle, pressed }),
|
|
95
|
+
[icon, pressed, toggle],
|
|
96
|
+
),
|
|
97
|
+
);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
A window with an `appId` also offers Back through its own route history, so its
|
|
101
|
+
row is reserved from the start. An app that never routes passes
|
|
102
|
+
`historyBack={false}` to `Window`; its window then has no row until the app
|
|
103
|
+
first registers toolbar actions. From then on the row stays, as in any window,
|
|
104
|
+
so the body does not shift when actions come and go.
|
|
105
|
+
|
|
80
106
|
## Styles
|
|
81
107
|
|
|
82
108
|
Each component imports its own stylesheet, so a bundler that handles CSS imports
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createRequiredContext.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"createRequiredContext.js","sourceRoot":"","sources":["../../home/a2f0/github/tearleads/packages/windowing/src/createRequiredContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAWhE;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,YAAoB;IAEpB,MAAM,OAAO,GAAG,aAAa,CAAW,IAAI,CAAC,CAAC;IAE9C,SAAS,WAAW;QAClB,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAED,SAAS,WAAW;QAClB,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC;AAC/C,CAAC","sourcesContent":["import { type Context, createContext, useContext } from \"react\";\n\nexport interface RequiredContext<T> {\n /** The raw context, for rendering `context.Provider` in the owning provider. */\n context: Context<T | null>;\n /** Non-throwing twin for surfaces that may render outside the provider. */\n useOptional: () => T | null;\n /** Reads the context value, throwing `errorMessage` without a provider. */\n useRequired: () => T;\n}\n\n/**\n * A context whose value is mandatory for consumers. Callers export\n * `useRequired`/`useOptional` under their own hook names, so every\n * provider-guard hook shares this one accessor pair instead of restating the\n * `useContext`-then-throw boilerplate.\n */\nexport function createRequiredContext<T>(\n errorMessage: string,\n): RequiredContext<T> {\n const context = createContext<T | null>(null);\n\n function useOptional(): T | null {\n return useContext(context);\n }\n\n function useRequired(): T {\n const value = useContext(context);\n if (value === null) {\n throw new Error(errorMessage);\n }\n return value;\n }\n\n return { context, useOptional, useRequired };\n}\n"]}
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../home/a2f0/github/tearleads/packages/windowing/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAEtB,6EAA6E;AAC7E,sEAAsE;AACtE,OAAO,EACL,qBAAqB,GAEtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,IAAI,EAAqB,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAsB,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAEL,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChF,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,MAAM,EAA8B,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,6BAA6B,EAC7B,uBAAuB,EACvB,wBAAwB,EACxB,2BAA2B,EAC3B,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,gBAAgB,EAChB,0BAA0B,EAC1B,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAQlB,mBAAmB,GACpB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC","sourcesContent":["import \"./tokens.css\";\n\n// The public windowing API: window state, window chrome and its registration\n// hooks, and the menu and sidebar primitives the chrome renders with.\nexport {\n createRequiredContext,\n type RequiredContext,\n} from \"./createRequiredContext\";\nexport { Menu, type MenuPosition } from \"./menu/Menu\";\nexport { MenuItem, type MenuItemProps } from \"./menu/MenuItem\";\nexport {\n type ContextMenuState,\n useContextMenuPositionState,\n useContextMenuState,\n} from \"./menu/useContextMenuState\";\nexport { SidebarResizeHandle, useSidebarResize } from \"./sidebar/SidebarResize\";\nexport {\n CurrentWindowProvider,\n useCurrentWindow,\n useSuppressWindowToolbar,\n} from \"./window/CurrentWindowContext\";\nexport { Window, type WindowContentBoundary } from \"./window/Window\";\nexport { WindowTitleBarActionButtons } from \"./window/WindowChromeActions\";\nexport { WindowCloseButton } from \"./window/WindowCloseButton\";\nexport {\n useWindowBackAction,\n useWindowBackActionValue,\n useWindowFileMenuItem,\n useWindowRefreshMenuItem,\n useWindowRefreshMenuItemValue,\n useWindowTitleBarAction,\n useWindowTitleBarActions,\n useWindowToolbarReservation,\n useWindowViewMenuItem,\n WindowMenuProvider,\n} from \"./window/WindowMenuContext\";\nexport {\n hasWindowSidebar,\n useRegisteredWindowSidebar,\n useWindowSidebar,\n WindowSidebarProvider,\n} from \"./window/WindowSidebarContext\";\nexport {\n findTopWindow,\n useWindowActions,\n useWindowStateData,\n type WindowCreateOptions,\n type WindowEntry,\n type WindowGeometry,\n type WindowPosition,\n type WindowSize,\n type WindowStateActions,\n type WindowStateData,\n WindowStateProvider,\n} from \"./window/WindowStateProvider\";\nexport { WindowToolBar } from \"./window/WindowToolBar\";\n"]}
|
package/menu/Menu.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Menu.js","sourceRoot":"","sources":["../../../../../../../Users/a2f0/github/tearleads5/packages/windowing/src/menu/Menu.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n type CSSProperties,\n type PropsWithChildren,\n type RefObject,\n useEffect,\n useEffectEvent,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport \"./Menu.css\";\nimport { useMenuKeyboard } from \"./useMenuKeyboard\";\n\nconst MENU_VIEWPORT_MARGIN_PX = 8;\nconst MEASUREMENT_MENU_STYLE: CSSProperties = {\n left: MENU_VIEWPORT_MARGIN_PX,\n pointerEvents: \"none\",\n top: MENU_VIEWPORT_MARGIN_PX,\n visibility: \"hidden\",\n};\n\n// Fractional layout or zoom can report a spurious pixel of overflow; treat\n// that as fitting, since a false positive hands touch panning to the page.\nconst SCROLL_OVERFLOW_TOLERANCE_PX = 1;\n\nfunction overflows(element: HTMLElement): boolean {\n return (\n element.scrollHeight > element.clientHeight + SCROLL_OVERFLOW_TOLERANCE_PX\n );\n}\n\n/**\n * Whether touch should be allowed to pan inside the menu: its own box scrolls,\n * or a descendant scroll container (a select menu's option list) does. The\n * menu box is a scroll container by its stylesheet; descendants qualify by\n * their computed overflow so a plain overflowing block does not count.\n */\nfunction hasTouchScrollableContent(menu: HTMLElement): boolean {\n if (overflows(menu)) {\n return true;\n }\n\n for (const element of Array.from(menu.querySelectorAll<HTMLElement>(\"*\"))) {\n if (!overflows(element)) {\n continue;\n }\n const overflowY = getComputedStyle(element).overflowY;\n if (overflowY === \"auto\" || overflowY === \"scroll\") {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Dismiss the menu on a pointer press or a scroll outside it. The closer is\n * read through an effect event: consumers commonly pass an inline one, and\n * the document listeners should subscribe once, not on every render.\n */\nfunction useMenuOutsideDismissal(\n menuRef: RefObject<HTMLDivElement | null>,\n onClose: () => void,\n): void {\n const close = useEffectEvent(onClose);\n\n useEffect(() => {\n function handleClick(e: MouseEvent) {\n const target = e.target;\n if (!(target instanceof Node)) {\n close();\n return;\n }\n\n if (menuRef.current && !menuRef.current.contains(target)) {\n close();\n }\n }\n document.addEventListener(\"mousedown\", handleClick);\n return () => document.removeEventListener(\"mousedown\", handleClick);\n }, [menuRef]);\n\n // The menu is anchored once, from its trigger's position, so a scroll\n // anywhere else would leave it floating where the trigger used to be. Close\n // it instead. Scrolls inside the menu — its own overflow, or a list it\n // hosts — are the menu working as intended. Capture phase, because scroll\n // events do not bubble. The check covers this menu's own subtree only: a\n // nested Menu is portaled to the body too, so scrolling one would close its\n // parent. Nothing nests menus today; revisit here if that changes.\n useEffect(() => {\n function handleScroll(e: Event) {\n const target = e.target;\n if (target instanceof Node && menuRef.current?.contains(target)) {\n return;\n }\n close();\n }\n document.addEventListener(\"scroll\", handleScroll, true);\n return () => document.removeEventListener(\"scroll\", handleScroll, true);\n }, [menuRef]);\n}\n\nexport interface MenuPosition {\n x: number;\n y: number;\n}\n\ninterface MenuPlacement {\n anchorDirection: \"up\" | \"down\";\n anchorX: number;\n anchorY: number;\n left: number;\n maxHeight: number | undefined;\n maxWidth: number | undefined;\n measured: boolean;\n top: number;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\nfunction createInitialMenuPlacement(\n position: MenuPosition,\n direction: \"up\" | \"down\",\n): MenuPlacement {\n return {\n anchorDirection: direction,\n anchorX: position.x,\n anchorY: position.y,\n left: position.x,\n maxHeight: undefined,\n maxWidth: undefined,\n measured: false,\n top: position.y,\n };\n}\n\nfunction placeMenuInViewport(input: {\n direction: \"up\" | \"down\";\n height: number;\n position: MenuPosition;\n viewportHeight: number;\n viewportWidth: number;\n width: number;\n}): MenuPlacement {\n const availableWidth = Math.max(\n 0,\n input.viewportWidth - MENU_VIEWPORT_MARGIN_PX * 2,\n );\n const availableHeight = Math.max(\n 0,\n input.viewportHeight - MENU_VIEWPORT_MARGIN_PX * 2,\n );\n const boundedWidth = Math.min(input.width, availableWidth);\n const boundedHeight = Math.min(input.height, availableHeight);\n const maxLeft = Math.max(\n MENU_VIEWPORT_MARGIN_PX,\n input.viewportWidth - MENU_VIEWPORT_MARGIN_PX - boundedWidth,\n );\n const maxTop = Math.max(\n MENU_VIEWPORT_MARGIN_PX,\n input.viewportHeight - MENU_VIEWPORT_MARGIN_PX - boundedHeight,\n );\n const preferredTop =\n input.direction === \"up\"\n ? input.position.y - boundedHeight\n : input.position.y;\n\n return {\n anchorDirection: input.direction,\n anchorX: input.position.x,\n anchorY: input.position.y,\n left: clamp(input.position.x, MENU_VIEWPORT_MARGIN_PX, maxLeft),\n maxHeight: availableHeight > 0 ? availableHeight : undefined,\n maxWidth: availableWidth > 0 ? availableWidth : undefined,\n measured: true,\n top: clamp(preferredTop, MENU_VIEWPORT_MARGIN_PX, maxTop),\n };\n}\n\nexport function Menu({\n position,\n onClose,\n direction = \"up\",\n keyboardNavigation = true,\n children,\n}: PropsWithChildren<{\n position: MenuPosition;\n onClose: () => void;\n direction?: \"up\" | \"down\";\n keyboardNavigation?: boolean;\n}>) {\n const menuRef = useRef<HTMLDivElement>(null);\n useMenuOutsideDismissal(menuRef, onClose);\n const { x, y } = position;\n const [placement, setPlacement] = useState(() =>\n createInitialMenuPlacement(position, direction),\n );\n // Whether anything in the menu scrolls, so the stylesheet can let touch pan\n // it while consuming swipes on a menu that fits (see Menu.css).\n const [scrollable, setScrollable] = useState(false);\n\n // Re-measure after every commit as well as on resize below: children can be\n // swapped or filtered while the menu is open without its box changing size,\n // which moves scrollHeight but not the ResizeObserver. setState bails out\n // when the answer is unchanged.\n useLayoutEffect(() => {\n const menu = menuRef.current;\n if (menu) {\n setScrollable(hasTouchScrollableContent(menu));\n }\n });\n\n useLayoutEffect(() => {\n const menu = menuRef.current;\n if (!menu) {\n return;\n }\n\n const updatePlacement = () => {\n const rect = menu.getBoundingClientRect();\n setPlacement(\n placeMenuInViewport({\n direction,\n height: rect.height,\n position,\n viewportHeight: window.innerHeight,\n viewportWidth: window.innerWidth,\n width: rect.width,\n }),\n );\n setScrollable(hasTouchScrollableContent(menu));\n };\n updatePlacement();\n const observer = new ResizeObserver(updatePlacement);\n observer.observe(menu);\n window.addEventListener(\"resize\", updatePlacement);\n return () => {\n observer.disconnect();\n window.removeEventListener(\"resize\", updatePlacement);\n };\n }, [x, y, direction]);\n\n const placementMatchesAnchor =\n placement.measured &&\n placement.anchorX === position.x &&\n placement.anchorY === position.y &&\n placement.anchorDirection === direction;\n useMenuKeyboard(\n menuRef,\n placementMatchesAnchor && keyboardNavigation,\n onClose,\n );\n const menuStyle: CSSProperties = placementMatchesAnchor\n ? {\n left: placement.left,\n maxHeight: placement.maxHeight,\n maxWidth: placement.maxWidth,\n top: placement.top,\n }\n : MEASUREMENT_MENU_STYLE;\n\n return createPortal(\n <div\n ref={menuRef}\n className=\"menu\"\n data-scrollable={scrollable || undefined}\n style={menuStyle}\n tabIndex={-1}\n >\n {children}\n </div>,\n document.body,\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"Menu.js","sourceRoot":"","sources":["../../../home/a2f0/github/tearleads/packages/windowing/src/menu/Menu.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n type CSSProperties,\n type PropsWithChildren,\n type RefObject,\n useEffect,\n useEffectEvent,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport \"./Menu.css\";\nimport { useMenuKeyboard } from \"./useMenuKeyboard\";\n\nconst MENU_VIEWPORT_MARGIN_PX = 8;\nconst MEASUREMENT_MENU_STYLE: CSSProperties = {\n left: MENU_VIEWPORT_MARGIN_PX,\n pointerEvents: \"none\",\n top: MENU_VIEWPORT_MARGIN_PX,\n visibility: \"hidden\",\n};\n\n// Fractional layout or zoom can report a spurious pixel of overflow; treat\n// that as fitting, since a false positive hands touch panning to the page.\nconst SCROLL_OVERFLOW_TOLERANCE_PX = 1;\n\nfunction overflows(element: HTMLElement): boolean {\n return (\n element.scrollHeight > element.clientHeight + SCROLL_OVERFLOW_TOLERANCE_PX\n );\n}\n\n/**\n * Whether touch should be allowed to pan inside the menu: its own box scrolls,\n * or a descendant scroll container (a select menu's option list) does. The\n * menu box is a scroll container by its stylesheet; descendants qualify by\n * their computed overflow so a plain overflowing block does not count.\n */\nfunction hasTouchScrollableContent(menu: HTMLElement): boolean {\n if (overflows(menu)) {\n return true;\n }\n\n for (const element of Array.from(menu.querySelectorAll<HTMLElement>(\"*\"))) {\n if (!overflows(element)) {\n continue;\n }\n const overflowY = getComputedStyle(element).overflowY;\n if (overflowY === \"auto\" || overflowY === \"scroll\") {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Dismiss the menu on a pointer press or a scroll outside it. The closer is\n * read through an effect event: consumers commonly pass an inline one, and\n * the document listeners should subscribe once, not on every render.\n */\nfunction useMenuOutsideDismissal(\n menuRef: RefObject<HTMLDivElement | null>,\n onClose: () => void,\n): void {\n const close = useEffectEvent(onClose);\n\n useEffect(() => {\n function handleClick(e: MouseEvent) {\n const target = e.target;\n if (!(target instanceof Node)) {\n close();\n return;\n }\n\n if (menuRef.current && !menuRef.current.contains(target)) {\n close();\n }\n }\n document.addEventListener(\"mousedown\", handleClick);\n return () => document.removeEventListener(\"mousedown\", handleClick);\n }, [menuRef]);\n\n // The menu is anchored once, from its trigger's position, so a scroll\n // anywhere else would leave it floating where the trigger used to be. Close\n // it instead. Scrolls inside the menu — its own overflow, or a list it\n // hosts — are the menu working as intended. Capture phase, because scroll\n // events do not bubble. The check covers this menu's own subtree only: a\n // nested Menu is portaled to the body too, so scrolling one would close its\n // parent. Nothing nests menus today; revisit here if that changes.\n useEffect(() => {\n function handleScroll(e: Event) {\n const target = e.target;\n if (target instanceof Node && menuRef.current?.contains(target)) {\n return;\n }\n close();\n }\n document.addEventListener(\"scroll\", handleScroll, true);\n return () => document.removeEventListener(\"scroll\", handleScroll, true);\n }, [menuRef]);\n}\n\nexport interface MenuPosition {\n x: number;\n y: number;\n}\n\ninterface MenuPlacement {\n anchorDirection: \"up\" | \"down\";\n anchorX: number;\n anchorY: number;\n left: number;\n maxHeight: number | undefined;\n maxWidth: number | undefined;\n measured: boolean;\n top: number;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\nfunction createInitialMenuPlacement(\n position: MenuPosition,\n direction: \"up\" | \"down\",\n): MenuPlacement {\n return {\n anchorDirection: direction,\n anchorX: position.x,\n anchorY: position.y,\n left: position.x,\n maxHeight: undefined,\n maxWidth: undefined,\n measured: false,\n top: position.y,\n };\n}\n\nfunction placeMenuInViewport(input: {\n direction: \"up\" | \"down\";\n height: number;\n position: MenuPosition;\n viewportHeight: number;\n viewportWidth: number;\n width: number;\n}): MenuPlacement {\n const availableWidth = Math.max(\n 0,\n input.viewportWidth - MENU_VIEWPORT_MARGIN_PX * 2,\n );\n const availableHeight = Math.max(\n 0,\n input.viewportHeight - MENU_VIEWPORT_MARGIN_PX * 2,\n );\n const boundedWidth = Math.min(input.width, availableWidth);\n const boundedHeight = Math.min(input.height, availableHeight);\n const maxLeft = Math.max(\n MENU_VIEWPORT_MARGIN_PX,\n input.viewportWidth - MENU_VIEWPORT_MARGIN_PX - boundedWidth,\n );\n const maxTop = Math.max(\n MENU_VIEWPORT_MARGIN_PX,\n input.viewportHeight - MENU_VIEWPORT_MARGIN_PX - boundedHeight,\n );\n const preferredTop =\n input.direction === \"up\"\n ? input.position.y - boundedHeight\n : input.position.y;\n\n return {\n anchorDirection: input.direction,\n anchorX: input.position.x,\n anchorY: input.position.y,\n left: clamp(input.position.x, MENU_VIEWPORT_MARGIN_PX, maxLeft),\n maxHeight: availableHeight > 0 ? availableHeight : undefined,\n maxWidth: availableWidth > 0 ? availableWidth : undefined,\n measured: true,\n top: clamp(preferredTop, MENU_VIEWPORT_MARGIN_PX, maxTop),\n };\n}\n\nexport function Menu({\n position,\n onClose,\n direction = \"up\",\n keyboardNavigation = true,\n children,\n}: PropsWithChildren<{\n position: MenuPosition;\n onClose: () => void;\n direction?: \"up\" | \"down\";\n keyboardNavigation?: boolean;\n}>) {\n const menuRef = useRef<HTMLDivElement>(null);\n useMenuOutsideDismissal(menuRef, onClose);\n const { x, y } = position;\n const [placement, setPlacement] = useState(() =>\n createInitialMenuPlacement(position, direction),\n );\n // Whether anything in the menu scrolls, so the stylesheet can let touch pan\n // it while consuming swipes on a menu that fits (see Menu.css).\n const [scrollable, setScrollable] = useState(false);\n\n // Re-measure after every commit as well as on resize below: children can be\n // swapped or filtered while the menu is open without its box changing size,\n // which moves scrollHeight but not the ResizeObserver. setState bails out\n // when the answer is unchanged.\n useLayoutEffect(() => {\n const menu = menuRef.current;\n if (menu) {\n setScrollable(hasTouchScrollableContent(menu));\n }\n });\n\n useLayoutEffect(() => {\n const menu = menuRef.current;\n if (!menu) {\n return;\n }\n\n const updatePlacement = () => {\n const rect = menu.getBoundingClientRect();\n setPlacement(\n placeMenuInViewport({\n direction,\n height: rect.height,\n position,\n viewportHeight: window.innerHeight,\n viewportWidth: window.innerWidth,\n width: rect.width,\n }),\n );\n setScrollable(hasTouchScrollableContent(menu));\n };\n updatePlacement();\n const observer = new ResizeObserver(updatePlacement);\n observer.observe(menu);\n window.addEventListener(\"resize\", updatePlacement);\n return () => {\n observer.disconnect();\n window.removeEventListener(\"resize\", updatePlacement);\n };\n }, [x, y, direction]);\n\n const placementMatchesAnchor =\n placement.measured &&\n placement.anchorX === position.x &&\n placement.anchorY === position.y &&\n placement.anchorDirection === direction;\n useMenuKeyboard(\n menuRef,\n placementMatchesAnchor && keyboardNavigation,\n onClose,\n );\n const menuStyle: CSSProperties = placementMatchesAnchor\n ? {\n left: placement.left,\n maxHeight: placement.maxHeight,\n maxWidth: placement.maxWidth,\n top: placement.top,\n }\n : MEASUREMENT_MENU_STYLE;\n\n return createPortal(\n <div\n ref={menuRef}\n className=\"menu\"\n data-scrollable={scrollable || undefined}\n style={menuStyle}\n tabIndex={-1}\n >\n {children}\n </div>,\n document.body,\n );\n}\n"]}
|
package/menu/MenuItem.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MenuItem.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"MenuItem.js","sourceRoot":"","sources":["../../../home/a2f0/github/tearleads/packages/windowing/src/menu/MenuItem.tsx"],"names":[],"mappings":";AAUA,MAAM,UAAU,QAAQ,CAAC,EACvB,IAAI,EAAE,aAAa,EACnB,KAAK,EACL,QAAQ,EACR,OAAO,GACO;IACd,OAAO,CACL,kBAAQ,IAAI,EAAC,QAAQ,EAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,aACvD,aAAa,IAAI,CAChB,KAAC,aAAa,mBACA,MAAM,EAClB,SAAS,EAAC,gBAAgB,EAC1B,SAAS,EAAC,OAAO,EACjB,IAAI,EAAE,EAAE,EACR,MAAM,EAAC,SAAS,GAChB,CACH,EACD,eAAM,SAAS,EAAC,iBAAiB,YAAE,KAAK,GAAQ,IACzC,CACV,CAAC;AACJ,CAAC","sourcesContent":["import type { Icon } from \"@phosphor-icons/react\";\nimport type { MouseEventHandler } from \"react\";\n\nexport interface MenuItemProps {\n icon?: Icon;\n label: string;\n disabled?: boolean;\n onClick: MouseEventHandler<HTMLButtonElement>;\n}\n\nexport function MenuItem({\n icon: IconComponent,\n label,\n disabled,\n onClick,\n}: MenuItemProps) {\n return (\n <button type=\"button\" disabled={disabled} onClick={onClick}>\n {IconComponent && (\n <IconComponent\n aria-hidden=\"true\"\n className=\"menu-item-icon\"\n focusable=\"false\"\n size={16}\n weight=\"regular\"\n />\n )}\n <span className=\"menu-item-label\">{label}</span>\n </button>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useContextMenuState.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"useContextMenuState.js","sourceRoot":"","sources":["../../../home/a2f0/github/tearleads/packages/windowing/src/menu/useContextMenuState.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAc/D;;;;;;;;;;GAUG;AACH,SAAS,sBAAsB,CAAC,KAA8B;IAC5D,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IAChD,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,aAAa,CAAC,qBAAqB,EAAE,CAAC;IACzD,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAe,MAEjD;IACC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAC5C,IAAI,CACL,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,CAAC;IAE9B,MAAM,eAAe,GAAG,WAAW,CACjC,CAAC,KAA8B,EAAE,EAAO,EAAE,EAAE;QAC1C,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,KAAK,CAAC,eAAe,EAAE,CAAC;QACxB,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QACb,cAAc,CAAC;YACb,EAAE;YACF,QAAQ,EAAE,sBAAsB,CAAC,KAAK,CAAC;SACxC,CAAC,CAAC;IACL,CAAC,EACD,CAAC,MAAM,CAAC,CACT,CAAC;IAEF,MAAM,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAErE,OAAO;QACL,gBAAgB;QAChB,WAAW;QACX,eAAe;KAChB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,2BAA2B;IACzC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAsB,IAAI,CAAC,CAAC;IAC1E,MAAM,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IACrE,MAAM,iBAAiB,GAAG,WAAW,CACnC,CAAC,QAAsB,EAAE,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,EACpD,EAAE,CACH,CAAC;IAEF,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,iBAAiB,EAAE,CAAC;AAC9D,CAAC","sourcesContent":["import { type MouseEvent, useCallback, useState } from \"react\";\nimport type { MenuPosition } from \"./Menu\";\n\nexport interface ContextMenuState<TId = string> {\n id: TId;\n position: MenuPosition;\n}\n\ninterface ContextMenuStateModel<TId = string> {\n closeContextMenu: () => void;\n contextMenu: ContextMenuState<TId> | null;\n openContextMenu: (event: MouseEvent<HTMLElement>, id: TId) => void;\n}\n\n/**\n * Where to open the menu: the pointer, or — when the event carries no pointer\n * position — the trigger's own box.\n *\n * Keyboard activation (Enter/Space on a kebab, the context-menu key on a row)\n * dispatches with 0,0 client coordinates, which would pin the menu to the\n * viewport's top-left corner, far from the control that opened it. Anchoring\n * under the trigger instead matches what the standalone kebab menus already do\n * (see MiniAppRowActionsButton's callers). A real click at exactly 0,0 lands in\n * the very corner of the window, where none of these triggers can sit.\n */\nfunction getContextMenuPosition(event: MouseEvent<HTMLElement>): MenuPosition {\n if (event.clientX !== 0 || event.clientY !== 0) {\n return { x: event.clientX, y: event.clientY };\n }\n\n const rect = event.currentTarget.getBoundingClientRect();\n return { x: rect.left, y: rect.bottom };\n}\n\nexport function useContextMenuState<TId = string>(params?: {\n onOpen?: (id: TId) => void;\n}): ContextMenuStateModel<TId> {\n const [contextMenu, setContextMenu] = useState<ContextMenuState<TId> | null>(\n null,\n );\n const onOpen = params?.onOpen;\n\n const openContextMenu = useCallback(\n (event: MouseEvent<HTMLElement>, id: TId) => {\n event.preventDefault();\n event.stopPropagation();\n onOpen?.(id);\n setContextMenu({\n id,\n position: getContextMenuPosition(event),\n });\n },\n [onOpen],\n );\n\n const closeContextMenu = useCallback(() => setContextMenu(null), []);\n\n return {\n closeContextMenu,\n contextMenu,\n openContextMenu,\n };\n}\n\nexport function useContextMenuPositionState() {\n const [contextMenu, setContextMenu] = useState<MenuPosition | null>(null);\n const closeContextMenu = useCallback(() => setContextMenu(null), []);\n const openContextMenuAt = useCallback(\n (position: MenuPosition) => setContextMenu(position),\n [],\n );\n\n return { closeContextMenu, contextMenu, openContextMenuAt };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMenuKeyboard.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"useMenuKeyboard.js","sourceRoot":"","sources":["../../../home/a2f0/github/tearleads/packages/windowing/src/menu/useMenuKeyboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { type RefObject, useEffect, useEffectEvent } from \"react\";\n\nfunction nextControl(\n controls: HTMLElement[],\n focused: Element | null,\n key: string,\n) {\n if (controls.length === 0) return undefined;\n const index = focused instanceof HTMLElement ? controls.indexOf(focused) : -1;\n switch (key) {\n case \"ArrowDown\":\n return controls[(index + 1) % controls.length];\n case \"ArrowUp\":\n return controls[\n index < 0\n ? controls.length - 1\n : (index - 1 + controls.length) % controls.length\n ];\n case \"Home\":\n return controls[0];\n case \"End\":\n return controls.at(-1);\n default:\n return undefined;\n }\n}\n\nexport function useMenuKeyboard(\n menuRef: RefObject<HTMLDivElement | null>,\n ready: boolean,\n onClose: () => void,\n) {\n const close = useEffectEvent(onClose);\n\n useEffect(() => {\n const menu = menuRef.current;\n if (!ready || !menu) return;\n\n const trigger = document.activeElement;\n let dismissedByPointer = false;\n const items = () =>\n Array.from(\n menu.querySelectorAll<HTMLElement>(\n \":is(button, input[type=checkbox]):not(:disabled):not([hidden])\",\n ),\n );\n const focusFirst = () =>\n (items()[0] ?? menu).focus({ preventScroll: true });\n focusFirst();\n // Startup and sync can replace an action while its menu is open. Recover\n // focus only when that replacement dropped it onto the document body.\n const observer = new MutationObserver(() => {\n if (!dismissedByPointer && document.activeElement === document.body) {\n focusFirst();\n }\n });\n observer.observe(menu, { childList: true, subtree: true });\n\n function restoreFocus() {\n if (trigger instanceof HTMLElement && trigger.isConnected) {\n trigger.focus({ preventScroll: true });\n return document.activeElement === trigger && trigger !== document.body;\n }\n return false;\n }\n\n function handleKeyDown(event: KeyboardEvent) {\n if (event.altKey || event.ctrlKey || event.metaKey) return;\n if (!menu?.contains(document.activeElement)) return;\n if (event.key === \"Escape\") {\n event.preventDefault();\n event.stopPropagation();\n restoreFocus();\n close();\n return;\n }\n if (event.key === \"Tab\") {\n if (!restoreFocus()) event.preventDefault();\n close();\n return;\n }\n const next = nextControl(items(), document.activeElement, event.key);\n if (next) {\n event.preventDefault();\n event.stopPropagation();\n next.focus();\n }\n }\n\n function handleMouseDown(event: MouseEvent) {\n dismissedByPointer =\n event.target instanceof Node && !menu?.contains(event.target);\n }\n\n document.addEventListener(\"mousedown\", handleMouseDown, true);\n document.addEventListener(\"keydown\", handleKeyDown, true);\n return () => {\n observer.disconnect();\n document.removeEventListener(\"mousedown\", handleMouseDown, true);\n document.removeEventListener(\"keydown\", handleKeyDown, true);\n if (\n !dismissedByPointer &&\n (menu.contains(document.activeElement) ||\n document.activeElement === document.body)\n ) {\n restoreFocus();\n }\n };\n }, [menuRef, ready]);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tearleads/windowing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "App-agnostic window management for React: window state, window chrome, and the menu and sidebar primitives it renders with.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SidebarResize.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"SidebarResize.js","sourceRoot":"","sources":["../../../home/a2f0/github/tearleads/packages/windowing/src/sidebar/SidebarResize.tsx"],"names":[],"mappings":";AAAA,OAAO,EAML,WAAW,EACX,SAAS,EACT,MAAM,EACN,QAAQ,GACT,MAAM,OAAO,CAAC;AACf,OAAO,qBAAqB,CAAC;AAE7B,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAQhC,SAAS,iBAAiB,CAAC,KAAa;IACtC,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,oBAAoB,CAAC,EAC5B,MAAM,EACN,WAAW,EACX,OAAO,EACP,QAAQ,GAMT;IACC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO;QACT,CAAC;QAED,SAAS,iBAAiB,CAAC,KAAmB;YAC5C,MAAM,SAAS,GAAG,WAAW,CAAC,OAAO,CAAC;YACtC,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,SAAS,EAAE,CAAC;gBAC1D,OAAO;YACT,CAAC;YAED,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC;YAC5C,QAAQ,CAAC,iBAAiB,CAAC,SAAS,CAAC,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC;QACzD,CAAC;QAED,SAAS,gBAAgB,CAAC,KAAmB;YAC3C,IAAI,KAAK,CAAC,SAAS,KAAK,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC;gBACvD,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QAED,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;QAC5D,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;QACzD,QAAQ,CAAC,gBAAgB,CAAC,eAAe,EAAE,gBAAgB,CAAC,CAAC;QAC7D,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,mBAAmB,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;YAC/D,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;YAC5D,QAAQ,CAAC,mBAAmB,CAAC,eAAe,EAAE,gBAAgB,CAAC,CAAC;YAChE,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAC/B,YAAY,EACZ,YAAY,GAAG,YAAY,GAI5B;IACC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,YAAY,CAAC,CAAC;IAChE,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAChD,MAAM,WAAW,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAC;IAEnD,MAAM,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE;QACxC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,EAAE,qBAAqB,EAAE,CAAC,KAAK,CAAC;QACxE,OAAO,aAAa,IAAI,aAAa,GAAG,CAAC;YACvC,CAAC,CAAC,iBAAiB,CAAC,aAAa,CAAC;YAClC,CAAC,CAAC,YAAY,CAAC;IACnB,CAAC,EAAE,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC;IAE1B,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/B,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YACzB,OAAO;QACT,CAAC;QAED,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;QAC3B,aAAa,CAAC,KAAK,CAAC,CAAC;QACrB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC;QAChC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,EAAE,CAAC;IACtC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,oBAAoB,CAAC;QACnB,MAAM,EAAE,UAAU;QAClB,WAAW;QACX,OAAO;QACP,QAAQ;KACT,CAAC,CAAC;IAEH,MAAM,iBAAiB,GAAG,WAAW,CACnC,CAAC,KAAuC,EAAE,EAAE;QAC1C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,MAAM,UAAU,GAAG,gBAAgB,EAAE,CAAC;QACtC,WAAW,CAAC,OAAO,GAAG;YACpB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,UAAU;YACV,MAAM,EAAE,KAAK,CAAC,OAAO;SACtB,CAAC;QACF,QAAQ,CAAC,UAAU,CAAC,CAAC;QACrB,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC;QAC1C,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,MAAM,CAAC;IAC1C,CAAC,EACD,CAAC,gBAAgB,CAAC,CACnB,CAAC;IAEF,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,KAAwC,EAAE,EAAE;QAC3C,IAAI,KAAK,CAAC,GAAG,KAAK,MAAM,EAAE,CAAC;YACzB,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,QAAQ,CAAC,SAAS,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;YACxB,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,QAAQ,CAAC,SAAS,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,IAAI,KAAK,CAAC,GAAG,KAAK,WAAW,IAAI,KAAK,CAAC,GAAG,KAAK,YAAY,EAAE,CAAC;YAC5D,OAAO;QACT,CAAC;QAED,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrD,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1C,QAAQ,CAAC,CAAC,YAAY,EAAE,EAAE,CACxB,iBAAiB,CACf,CAAC,YAAY,IAAI,gBAAgB,EAAE,CAAC;YAClC,SAAS,GAAG,oBAAoB,GAAG,UAAU,CAChD,CACF,CAAC;IACJ,CAAC,EACD,CAAC,gBAAgB,CAAC,CACnB,CAAC;IAEF,OAAO;QACL,aAAa;QACb,iBAAiB;QACjB,aAAa,EAAE,gBAAgB,EAAE;QACjC,UAAU;QACV,KAAK;KACN,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,EAClC,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,aAAa,GAOd;IACC,OAAO,CACL,8BACiB,QAAQ,gBACZ,gBAAgB,sBACV,UAAU,mBACZ,SAAS,mBACT,SAAS,mBACT,YAAY,EAC3B,SAAS,EAAE,yBAAyB,SAAS,EAAE,EAC/C,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,aAAa,EAC5B,QAAQ,EAAE,CAAC,GACX,CACH,CAAC;AACJ,CAAC","sourcesContent":["import {\n type Dispatch,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type RefObject,\n type SetStateAction,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport \"./SidebarResize.css\";\n\nconst MIN_WIDTH = 80;\nconst MAX_WIDTH = 400;\nconst KEYBOARD_RESIZE_STEP = 10;\n\ninterface DragState {\n pointerId: number;\n startWidth: number;\n startX: number;\n}\n\nfunction clampSidebarWidth(width: number) {\n return Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, width));\n}\n\nfunction useSidebarDragEffect({\n active,\n draggingRef,\n endDrag,\n setWidth,\n}: {\n active: boolean;\n draggingRef: RefObject<DragState | null>;\n endDrag: () => void;\n setWidth: Dispatch<SetStateAction<number | null>>;\n}) {\n useEffect(() => {\n if (!active) {\n return;\n }\n\n function handlePointerMove(event: PointerEvent) {\n const dragState = draggingRef.current;\n if (!dragState || event.pointerId !== dragState.pointerId) {\n return;\n }\n\n const dx = event.clientX - dragState.startX;\n setWidth(clampSidebarWidth(dragState.startWidth + dx));\n }\n\n function handlePointerEnd(event: PointerEvent) {\n if (event.pointerId === draggingRef.current?.pointerId) {\n endDrag();\n }\n }\n\n document.addEventListener(\"pointermove\", handlePointerMove);\n document.addEventListener(\"pointerup\", handlePointerEnd);\n document.addEventListener(\"pointercancel\", handlePointerEnd);\n return () => {\n document.removeEventListener(\"pointermove\", handlePointerMove);\n document.removeEventListener(\"pointerup\", handlePointerEnd);\n document.removeEventListener(\"pointercancel\", handlePointerEnd);\n endDrag();\n };\n }, [active, draggingRef, endDrag, setWidth]);\n}\n\nexport function useSidebarResize({\n defaultWidth,\n initialWidth = defaultWidth,\n}: {\n defaultWidth: number;\n initialWidth?: number | null;\n}) {\n const [width, setWidth] = useState<number | null>(initialWidth);\n const [isDragging, setIsDragging] = useState(false);\n const sidebarRef = useRef<HTMLDivElement>(null);\n const draggingRef = useRef<DragState | null>(null);\n\n const readCurrentWidth = useCallback(() => {\n if (width !== null) {\n return width;\n }\n\n const measuredWidth = sidebarRef.current?.getBoundingClientRect().width;\n return measuredWidth && measuredWidth > 0\n ? clampSidebarWidth(measuredWidth)\n : defaultWidth;\n }, [defaultWidth, width]);\n\n const endDrag = useCallback(() => {\n if (!draggingRef.current) {\n return;\n }\n\n draggingRef.current = null;\n setIsDragging(false);\n document.body.style.cursor = \"\";\n document.body.style.userSelect = \"\";\n }, []);\n\n useSidebarDragEffect({\n active: isDragging,\n draggingRef,\n endDrag,\n setWidth,\n });\n\n const handlePointerDown = useCallback(\n (event: ReactPointerEvent<HTMLHRElement>) => {\n if (event.button !== 0 || !event.isPrimary) {\n return;\n }\n\n event.preventDefault();\n const startWidth = readCurrentWidth();\n draggingRef.current = {\n pointerId: event.pointerId,\n startWidth,\n startX: event.clientX,\n };\n setWidth(startWidth);\n setIsDragging(true);\n document.body.style.cursor = \"col-resize\";\n document.body.style.userSelect = \"none\";\n },\n [readCurrentWidth],\n );\n\n const handleKeyDown = useCallback(\n (event: ReactKeyboardEvent<HTMLHRElement>) => {\n if (event.key === \"Home\") {\n event.preventDefault();\n setWidth(MIN_WIDTH);\n return;\n }\n\n if (event.key === \"End\") {\n event.preventDefault();\n setWidth(MAX_WIDTH);\n return;\n }\n\n if (event.key !== \"ArrowLeft\" && event.key !== \"ArrowRight\") {\n return;\n }\n\n event.preventDefault();\n const direction = event.key === \"ArrowLeft\" ? -1 : 1;\n const multiplier = event.shiftKey ? 5 : 1;\n setWidth((currentWidth) =>\n clampSidebarWidth(\n (currentWidth ?? readCurrentWidth()) +\n direction * KEYBOARD_RESIZE_STEP * multiplier,\n ),\n );\n },\n [readCurrentWidth],\n );\n\n return {\n handleKeyDown,\n handlePointerDown,\n resolvedWidth: readCurrentWidth(),\n sidebarRef,\n width,\n };\n}\n\nexport function SidebarResizeHandle({\n className,\n controls,\n currentWidth,\n onKeyDown,\n onPointerDown,\n}: {\n className: string;\n controls?: string | undefined;\n currentWidth: number;\n onKeyDown: (event: ReactKeyboardEvent<HTMLHRElement>) => void;\n onPointerDown: (event: ReactPointerEvent<HTMLHRElement>) => void;\n}) {\n return (\n <hr\n aria-controls={controls}\n aria-label=\"Resize sidebar\"\n aria-orientation=\"vertical\"\n aria-valuemax={MAX_WIDTH}\n aria-valuemin={MIN_WIDTH}\n aria-valuenow={currentWidth}\n className={`sidebar-resize-handle ${className}`}\n onKeyDown={onKeyDown}\n onPointerDown={onPointerDown}\n tabIndex={0}\n />\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CurrentWindowContext.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"CurrentWindowContext.js","sourceRoot":"","sources":["../../../home/a2f0/github/tearleads/packages/windowing/src/window/CurrentWindowContext.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,aAAa,EAEb,UAAU,EACV,eAAe,EACf,OAAO,GACR,MAAM,OAAO,CAAC;AAcf,MAAM,oBAAoB,GAAG,aAAa,CACxC,IAAI,CACL,CAAC;AAEF,MAAM,UAAU,qBAAqB,CAAC,EACpC,QAAQ,EACR,KAAK,EACL,EAAE,EACF,WAAW,EACX,iBAAiB,EACjB,eAAe,GAC8B;IAC7C,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,iBAAiB,EAAE,eAAe,EAAE,CAAC,EACtE,CAAC,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,iBAAiB,EAAE,eAAe,CAAC,CAC7D,CAAC;IAEF,OAAO,CACL,KAAC,oBAAoB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACxC,QAAQ,GACqB,CACjC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,UAAU,CAAC,oBAAoB,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAe;IACtD,MAAM,eAAe,GAAG,gBAAgB,EAAE,EAAE,eAAe,CAAC;IAE5D,eAAe,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;YAChC,OAAO;QACT,CAAC;QACD,OAAO,eAAe,EAAE,CAAC;IAC3B,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;AAChC,CAAC","sourcesContent":["import {\n createContext,\n type PropsWithChildren,\n useContext,\n useLayoutEffect,\n useMemo,\n} from \"react\";\n\ninterface CurrentWindowContextValue {\n close: () => void;\n id: string;\n overlayHost: HTMLElement | null;\n showStatusMessage: (message: string) => void;\n /**\n * Hide the window's toolbar row until the returned release is called. Refcounted,\n * so overlapping suppressions restore the row only once the last one releases.\n */\n suppressToolbar: () => () => void;\n}\n\nconst CurrentWindowContext = createContext<CurrentWindowContextValue | null>(\n null,\n);\n\nexport function CurrentWindowProvider({\n children,\n close,\n id,\n overlayHost,\n showStatusMessage,\n suppressToolbar,\n}: PropsWithChildren<CurrentWindowContextValue>) {\n const value = useMemo(\n () => ({ close, id, overlayHost, showStatusMessage, suppressToolbar }),\n [close, id, overlayHost, showStatusMessage, suppressToolbar],\n );\n\n return (\n <CurrentWindowContext.Provider value={value}>\n {children}\n </CurrentWindowContext.Provider>\n );\n}\n\nexport function useCurrentWindow() {\n return useContext(CurrentWindowContext);\n}\n\n/**\n * Drop the host window's toolbar row while `active`.\n *\n * A full-pane overlay (the image viewer) carries its own toolbar, and the\n * window's row would otherwise stack directly above it — two toolbars, one of\n * them driving chrome the overlay covers. Outside a window this is inert, since\n * the routed shell's overlays cover the whole viewport already.\n *\n * Suppressing in a layout effect keeps the row from being painted alongside the\n * overlay's own toolbar for a frame and then resizing the pane out from under it.\n */\nexport function useSuppressWindowToolbar(active: boolean) {\n const suppressToolbar = useCurrentWindow()?.suppressToolbar;\n\n useLayoutEffect(() => {\n if (!active || !suppressToolbar) {\n return;\n }\n return suppressToolbar();\n }, [active, suppressToolbar]);\n}\n"]}
|
package/window/Window.d.ts
CHANGED
|
@@ -5,7 +5,14 @@ export type WindowContentBoundary = ComponentType<PropsWithChildren<{
|
|
|
5
5
|
}>>;
|
|
6
6
|
interface WindowProps {
|
|
7
7
|
ContentBoundary?: WindowContentBoundary | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Whether the toolbar offers Back through the window's own route history,
|
|
10
|
+
* reserving its row from the start (see `WindowToolBar`). Defaults to whether
|
|
11
|
+
* the window has an `appId`. Pass false for an app that never routes, so its
|
|
12
|
+
* window has no toolbar row until the app first registers toolbar actions.
|
|
13
|
+
*/
|
|
14
|
+
historyBack?: boolean | undefined;
|
|
8
15
|
windowId: string;
|
|
9
16
|
}
|
|
10
|
-
export declare function Window({ ContentBoundary, windowId }: WindowProps): import("react").JSX.Element | null;
|
|
17
|
+
export declare function Window({ ContentBoundary, historyBack, windowId, }: WindowProps): import("react").JSX.Element | null;
|
|
11
18
|
export {};
|
package/window/Window.js
CHANGED
|
@@ -7,21 +7,21 @@ import { useFocusWindowOnShow, useWindowGeometryMenuItems, } from "./useWindowKe
|
|
|
7
7
|
import { WindowBody } from "./WindowBody.js";
|
|
8
8
|
import { WindowMenuBar } from "./WindowMenuBar.js";
|
|
9
9
|
import { useWindowFileMenuItems, useWindowViewMenuItems, WindowMenuProvider, } from "./WindowMenuContext.js";
|
|
10
|
-
import {
|
|
10
|
+
import { WindowResizeHandles } from "./WindowResizeHandle.js";
|
|
11
11
|
import { hasWindowSidebar, useWindowSidebar, WindowSidebarProvider, } from "./WindowSidebarContext.js";
|
|
12
12
|
import { findTopWindow, useWindowActions as useWindowStateActions, useWindowStateData, } from "./WindowStateProvider/index.js";
|
|
13
13
|
import { WindowStatusBar } from "./WindowStatusBar.js";
|
|
14
14
|
import { WindowTitleBar } from "./WindowTitleBar.js";
|
|
15
15
|
import { WindowToolBar } from "./WindowToolBar.js";
|
|
16
16
|
const WINDOW_STATUS_MESSAGE_DURATION_MS = 2500;
|
|
17
|
-
export function Window({ ContentBoundary, windowId }) {
|
|
17
|
+
export function Window({ ContentBoundary, historyBack, windowId, }) {
|
|
18
18
|
const { windowMap, windows } = useWindowStateData();
|
|
19
19
|
const entry = windowMap.get(windowId);
|
|
20
20
|
const isTop = findTopWindow(windows, (candidate) => !candidate.minimized)?.id ===
|
|
21
21
|
windowId;
|
|
22
22
|
if (!entry)
|
|
23
23
|
return null;
|
|
24
|
-
return (_jsx(WindowInner, { ContentBoundary: ContentBoundary, entry: entry, isTop: isTop }));
|
|
24
|
+
return (_jsx(WindowInner, { ContentBoundary: ContentBoundary, entry: entry, historyBack: historyBack ?? entry.appId !== undefined, isTop: isTop }));
|
|
25
25
|
}
|
|
26
26
|
function useWindowActions(entry, fileMenuItems, viewMenuItems, geometryMenuItems, hasSidebar) {
|
|
27
27
|
const { close, minimize, moveBackward, moveForward, toggleMaximize } = useWindowStateActions();
|
|
@@ -137,16 +137,13 @@ function useWindowToolbarSuppression() {
|
|
|
137
137
|
}, []);
|
|
138
138
|
return { suppressToolbar, toolbarSuppressed: suppressionCount > 0 };
|
|
139
139
|
}
|
|
140
|
-
function
|
|
141
|
-
return (
|
|
142
|
-
}
|
|
143
|
-
function WindowInner({ ContentBoundary, entry, isTop }) {
|
|
144
|
-
return (_jsx(WindowMenuProvider, { children: _jsx(WindowSidebarProvider, { children: _jsx(WindowInnerContent, { ContentBoundary: ContentBoundary, entry: entry, isTop: isTop }) }) }));
|
|
140
|
+
function WindowInner({ ContentBoundary, entry, historyBack, isTop, }) {
|
|
141
|
+
return (_jsx(WindowMenuProvider, { children: _jsx(WindowSidebarProvider, { children: _jsx(WindowInnerContent, { ContentBoundary: ContentBoundary, entry: entry, historyBack: historyBack, isTop: isTop }) }) }));
|
|
145
142
|
}
|
|
146
143
|
// The bars above the body: title, menus, and the toolbar row a full-pane overlay
|
|
147
144
|
// can stand down (see `useWindowToolbarSuppression`).
|
|
148
|
-
function WindowChrome({ actions, entry, onGoBack, onPointerDown, titleId, toolbarSuppressed, }) {
|
|
149
|
-
return (_jsxs(_Fragment, { children: [_jsx(WindowTitleBar, { title: entry.title, titleId: titleId, onPointerDown: onPointerDown, onMinimize: actions.handleMinimize, onMaximize: actions.handleMaximize, onClose: actions.handleClose, onMoveForward: actions.handleMoveForward, onMoveBackward: actions.handleMoveBackward }), _jsx(WindowMenuBar, { menus: actions.menus }), !toolbarSuppressed && (_jsx(WindowToolBar, { canGoBack: (entry.routeHistory?.length ?? 0) > 0, showHistoryBack:
|
|
145
|
+
function WindowChrome({ actions, entry, historyBack, onGoBack, onPointerDown, titleId, toolbarSuppressed, }) {
|
|
146
|
+
return (_jsxs(_Fragment, { children: [_jsx(WindowTitleBar, { title: entry.title, titleId: titleId, onPointerDown: onPointerDown, onMinimize: actions.handleMinimize, onMaximize: actions.handleMaximize, onClose: actions.handleClose, onMoveForward: actions.handleMoveForward, onMoveBackward: actions.handleMoveBackward }), _jsx(WindowMenuBar, { menus: actions.menus }), !toolbarSuppressed && (_jsx(WindowToolBar, { canGoBack: (entry.routeHistory?.length ?? 0) > 0, showHistoryBack: historyBack, onGoBack: onGoBack }))] }));
|
|
150
147
|
}
|
|
151
148
|
// Handlers on the window root: its Back caret, raising it on any press inside,
|
|
152
149
|
// and keeping background context menus from opening under window-local ones.
|
|
@@ -169,7 +166,7 @@ function useWindowRootHandlers(windowId) {
|
|
|
169
166
|
};
|
|
170
167
|
return { handleGoBack, handleWindowPointerDown, windowContextMenuTrapProps };
|
|
171
168
|
}
|
|
172
|
-
function WindowInnerContent({ ContentBoundary, entry, isTop, }) {
|
|
169
|
+
function WindowInnerContent({ ContentBoundary, entry, historyBack, isTop, }) {
|
|
173
170
|
const { maximized, minimized, zIndex, component: Component } = entry;
|
|
174
171
|
const windowRef = useRef(null);
|
|
175
172
|
const titleId = useId();
|
|
@@ -196,7 +193,7 @@ function WindowInnerContent({ ContentBoundary, entry, isTop, }) {
|
|
|
196
193
|
// A section labelled by its title is a region landmark. Windows are
|
|
197
194
|
// non-modal and freely arranged, so they are not dialogs; hosts keep that
|
|
198
195
|
// role for the modals they open inside a window.
|
|
199
|
-
return (_jsxs("section", { ref: windowRef, "aria-labelledby": titleId, className: maximized ? "window window--maximized" : "window", tabIndex: -1, ...windowContextMenuTrapProps, onPointerDownCapture: handleWindowPointerDown, style: style, children: [_jsx(WindowChrome, { actions: actions, entry: entry, onGoBack: handleGoBack, onPointerDown: handlePointerDown, titleId: titleId, toolbarSuppressed: toolbarSuppressed }), _jsx(CurrentWindowProvider, { close: actions.handleClose, id: entry.id, overlayHost: overlayHost, showStatusMessage: showStatusMessage, suppressToolbar: suppressToolbar, children: _jsx(WindowBodyWithSidebar, { overlayHostRef: setOverlayHost, showSidebar: actions.showSidebar, children: ContentBoundary ? (_jsx(ContentBoundary, { entry: entry, children: Component && _jsx(Component, {}) })) : (Component && _jsx(Component, {})) }) }), _jsx(WindowStatusBar, { text: statusText, visible: actions.showStatusBar }), !maximized && (_jsx(WindowResizeHandles, { handleResizePointerDown: handleResizePointerDown }))] }));
|
|
196
|
+
return (_jsxs("section", { ref: windowRef, "aria-labelledby": titleId, className: maximized ? "window window--maximized" : "window", tabIndex: -1, ...windowContextMenuTrapProps, onPointerDownCapture: handleWindowPointerDown, style: style, children: [_jsx(WindowChrome, { actions: actions, entry: entry, historyBack: historyBack, onGoBack: handleGoBack, onPointerDown: handlePointerDown, titleId: titleId, toolbarSuppressed: toolbarSuppressed }), _jsx(CurrentWindowProvider, { close: actions.handleClose, id: entry.id, overlayHost: overlayHost, showStatusMessage: showStatusMessage, suppressToolbar: suppressToolbar, children: _jsx(WindowBodyWithSidebar, { overlayHostRef: setOverlayHost, showSidebar: actions.showSidebar, children: ContentBoundary ? (_jsx(ContentBoundary, { entry: entry, children: Component && _jsx(Component, {}) })) : (Component && _jsx(Component, {})) }) }), _jsx(WindowStatusBar, { text: statusText, visible: actions.showStatusBar }), !maximized && (_jsx(WindowResizeHandles, { handleResizePointerDown: handleResizePointerDown }))] }));
|
|
200
197
|
}
|
|
201
198
|
function WindowBodyWithSidebar({ showSidebar, overlayHostRef, children, }) {
|
|
202
199
|
const { sidebar } = useWindowSidebar();
|
package/window/Window.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Window.js","sourceRoot":"","sources":["../../../../../../../Users/a2f0/github/tearleads5/packages/windowing/src/window/Window.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n type ComponentType,\n type CSSProperties,\n type HTMLAttributes,\n type PropsWithChildren,\n type MouseEvent as ReactMouseEvent,\n type PointerEvent as ReactPointerEvent,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport \"./Window.css\";\nimport { CurrentWindowProvider } from \"./CurrentWindowContext\";\nimport { useWindowGeometry } from \"./useWindowGeometry\";\nimport {\n useFocusWindowOnShow,\n useWindowGeometryMenuItems,\n} from \"./useWindowKeyboardGeometry\";\nimport { WindowBody } from \"./WindowBody\";\nimport { WindowMenuBar, type WindowMenuItem } from \"./WindowMenuBar\";\nimport {\n useWindowFileMenuItems,\n useWindowViewMenuItems,\n WindowMenuProvider,\n} from \"./WindowMenuContext\";\nimport type { ResizeCorner } from \"./WindowResizeHandle\";\nimport { WindowResizeHandle } from \"./WindowResizeHandle\";\nimport {\n hasWindowSidebar,\n useWindowSidebar,\n WindowSidebarProvider,\n} from \"./WindowSidebarContext\";\nimport {\n findTopWindow,\n useWindowActions as useWindowStateActions,\n useWindowStateData,\n type WindowEntry,\n type WindowPosition,\n type WindowSize,\n} from \"./WindowStateProvider\";\nimport { WindowStatusBar } from \"./WindowStatusBar\";\nimport { WindowTitleBar } from \"./WindowTitleBar\";\nimport { WindowToolBar } from \"./WindowToolBar\";\n\n// Wraps a window's content so the host can give it app-specific context (a\n// route, an error boundary) without the window layer knowing about apps.\nexport type WindowContentBoundary = ComponentType<\n PropsWithChildren<{ entry: WindowEntry }>\n>;\n\ninterface WindowProps {\n ContentBoundary?: WindowContentBoundary | undefined;\n windowId: string;\n}\n\nconst WINDOW_STATUS_MESSAGE_DURATION_MS = 2500;\n\nexport function Window({ ContentBoundary, windowId }: WindowProps) {\n const { windowMap, windows } = useWindowStateData();\n const entry = windowMap.get(windowId);\n const isTop =\n findTopWindow(windows, (candidate) => !candidate.minimized)?.id ===\n windowId;\n\n if (!entry) return null;\n\n return (\n <WindowInner\n ContentBoundary={ContentBoundary}\n entry={entry}\n isTop={isTop}\n />\n );\n}\n\nfunction useWindowActions(\n entry: WindowEntry,\n fileMenuItems: WindowMenuItem[],\n viewMenuItems: WindowMenuItem[],\n geometryMenuItems: WindowMenuItem[],\n hasSidebar: boolean,\n) {\n const { close, minimize, moveBackward, moveForward, toggleMaximize } =\n useWindowStateActions();\n const [showStatusBar, setShowStatusBar] = useState(true);\n const [showSidebar, setShowSidebar] = useState(\n entry.initialShowSidebar ?? true,\n );\n const handleClose = useCallback(() => close(entry.id), [close, entry.id]);\n const handleMinimize = useCallback(\n () => minimize(entry.id),\n [entry.id, minimize],\n );\n const handleMoveForward = useCallback(\n () => moveForward(entry.id),\n [entry.id, moveForward],\n );\n const handleMoveBackward = useCallback(\n () => moveBackward(entry.id),\n [entry.id, moveBackward],\n );\n const handleMaximize = useCallback(\n () => toggleMaximize(entry.id),\n [entry.id, toggleMaximize],\n );\n const toggleStatusBar = useCallback(\n () => setShowStatusBar((previous) => !previous),\n [],\n );\n const toggleSidebar = useCallback(\n () => setShowSidebar((previous) => !previous),\n [],\n );\n const menus = useMemo(\n () => [\n {\n label: \"File\",\n items: [\n ...fileMenuItems,\n { id: \"close\", label: \"Close\", onClick: handleClose },\n ],\n },\n {\n label: \"View\",\n items: [\n ...viewMenuItems,\n ...geometryMenuItems,\n {\n id: \"toggle-status-bar\",\n label: `${showStatusBar ? \"Hide\" : \"Show\"} Status Bar`,\n onClick: toggleStatusBar,\n },\n ...(hasSidebar\n ? [\n {\n id: \"toggle-sidebar\",\n label: `${showSidebar ? \"Hide\" : \"Show\"} Sidebar`,\n onClick: toggleSidebar,\n },\n ]\n : []),\n ],\n },\n ],\n [\n geometryMenuItems,\n hasSidebar,\n handleClose,\n fileMenuItems,\n showSidebar,\n showStatusBar,\n toggleSidebar,\n toggleStatusBar,\n viewMenuItems,\n ],\n );\n\n return {\n handleClose,\n handleMaximize,\n handleMinimize,\n handleMoveBackward,\n handleMoveForward,\n menus,\n showSidebar,\n showStatusBar,\n };\n}\n\nfunction getWindowStyle(\n maximized: boolean,\n position: WindowPosition | null,\n size: WindowSize | null,\n zIndex: number,\n): CSSProperties | undefined {\n if (maximized) {\n return { top: 0, left: 0, width: \"100%\", height: \"100%\", zIndex };\n }\n if (!position) {\n // Hidden until laid out; a requested size applies already so the first\n // clamp measures the window at the size it will show.\n return {\n visibility: \"hidden\",\n zIndex,\n ...(size ? { width: size.width, height: size.height } : {}),\n };\n }\n\n return {\n left: position.x,\n top: position.y,\n zIndex,\n ...(size ? { width: size.width, height: size.height } : {}),\n };\n}\n\nfunction useWindowStatusMessage() {\n const [statusText, setStatusText] = useState(\"\");\n const statusTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const showStatusMessage = useCallback((message: string) => {\n if (statusTimeoutRef.current) {\n clearTimeout(statusTimeoutRef.current);\n }\n setStatusText(message);\n statusTimeoutRef.current = setTimeout(() => {\n setStatusText(\"\");\n statusTimeoutRef.current = null;\n }, WINDOW_STATUS_MESSAGE_DURATION_MS);\n }, []);\n\n useEffect(() => {\n return () => {\n if (statusTimeoutRef.current) {\n clearTimeout(statusTimeoutRef.current);\n }\n };\n }, []);\n\n return { showStatusMessage, statusText };\n}\n\n// Refcounted so overlapping overlays (one closing as another opens) never leave\n// the row hidden, and never flash it back for a frame between the two.\nfunction useWindowToolbarSuppression() {\n const [suppressionCount, setSuppressionCount] = useState(0);\n const suppressToolbar = useCallback(() => {\n setSuppressionCount((count) => count + 1);\n return () => setSuppressionCount((count) => Math.max(0, count - 1));\n }, []);\n\n return { suppressToolbar, toolbarSuppressed: suppressionCount > 0 };\n}\n\nfunction WindowResizeHandles({\n handleResizePointerDown,\n}: {\n handleResizePointerDown: (\n event: ReactPointerEvent,\n corner: ResizeCorner,\n ) => void;\n}) {\n return (\n <>\n <WindowResizeHandle corner=\"se\" onPointerDown={handleResizePointerDown} />\n <WindowResizeHandle corner=\"sw\" onPointerDown={handleResizePointerDown} />\n <WindowResizeHandle corner=\"ne\" onPointerDown={handleResizePointerDown} />\n <WindowResizeHandle corner=\"nw\" onPointerDown={handleResizePointerDown} />\n </>\n );\n}\n\ninterface WindowInnerProps {\n ContentBoundary?: WindowContentBoundary | undefined;\n entry: WindowEntry;\n // Whether this is the foremost visible window, the one that takes focus.\n isTop: boolean;\n}\n\nfunction WindowInner({ ContentBoundary, entry, isTop }: WindowInnerProps) {\n return (\n <WindowMenuProvider>\n <WindowSidebarProvider>\n <WindowInnerContent\n ContentBoundary={ContentBoundary}\n entry={entry}\n isTop={isTop}\n />\n </WindowSidebarProvider>\n </WindowMenuProvider>\n );\n}\n\n// The bars above the body: title, menus, and the toolbar row a full-pane overlay\n// can stand down (see `useWindowToolbarSuppression`).\nfunction WindowChrome({\n actions,\n entry,\n onGoBack,\n onPointerDown,\n titleId,\n toolbarSuppressed,\n}: {\n actions: ReturnType<typeof useWindowActions>;\n entry: WindowEntry;\n onGoBack: () => void;\n onPointerDown: (event: ReactPointerEvent) => void;\n titleId: string;\n toolbarSuppressed: boolean;\n}) {\n return (\n <>\n <WindowTitleBar\n title={entry.title}\n titleId={titleId}\n onPointerDown={onPointerDown}\n onMinimize={actions.handleMinimize}\n onMaximize={actions.handleMaximize}\n onClose={actions.handleClose}\n onMoveForward={actions.handleMoveForward}\n onMoveBackward={actions.handleMoveBackward}\n />\n <WindowMenuBar menus={actions.menus} />\n {!toolbarSuppressed && (\n <WindowToolBar\n canGoBack={(entry.routeHistory?.length ?? 0) > 0}\n showHistoryBack={entry.appId !== undefined}\n onGoBack={onGoBack}\n />\n )}\n </>\n );\n}\n\n// Handlers on the window root: its Back caret, raising it on any press inside,\n// and keeping background context menus from opening under window-local ones.\nfunction useWindowRootHandlers(windowId: string) {\n // The toolbar renders above the route boundary, so the window's own Back stack\n // is threaded in from here rather than read from context.\n const { bringToFront, goBackRoute } = useWindowStateActions();\n const handleGoBack = useCallback(() => {\n goBackRoute(windowId);\n }, [windowId, goBackRoute]);\n const handleWindowPointerDown = useCallback(() => {\n bringToFront(windowId);\n }, [bringToFront, windowId]);\n const handleWindowContextMenu = useCallback((event: ReactMouseEvent) => {\n // Keep background pane context menus from opening underneath window-local menus.\n event.stopPropagation();\n }, []);\n const windowContextMenuTrapProps: Pick<\n HTMLAttributes<HTMLElement>,\n \"onContextMenu\"\n > = {\n onContextMenu: handleWindowContextMenu,\n };\n\n return { handleGoBack, handleWindowPointerDown, windowContextMenuTrapProps };\n}\n\nfunction WindowInnerContent({\n ContentBoundary,\n entry,\n isTop,\n}: WindowInnerProps) {\n const { maximized, minimized, zIndex, component: Component } = entry;\n const windowRef = useRef<HTMLElement>(null);\n const titleId = useId();\n const [overlayHost, setOverlayHost] = useState<HTMLElement | null>(null);\n const fileMenuItems = useWindowFileMenuItems();\n const viewMenuItems = useWindowViewMenuItems();\n const { sidebar } = useWindowSidebar();\n const hasSidebar = hasWindowSidebar(sidebar);\n const {\n handlePointerDown,\n handleResizePointerDown,\n position,\n size,\n ...stepped\n } = useWindowGeometry(entry, maximized, windowRef);\n const { showStatusMessage, statusText } = useWindowStatusMessage();\n const geometryMenuItems = useWindowGeometryMenuItems(\n stepped,\n { maximized, minimized, windowRef },\n showStatusMessage,\n );\n const actions = useWindowActions(\n entry,\n fileMenuItems,\n viewMenuItems,\n geometryMenuItems,\n hasSidebar,\n );\n // A maximized window fills its surface without a laid-out position.\n useFocusWindowOnShow(windowRef, {\n isTop,\n shown: !minimized && (maximized || position !== null),\n });\n const { suppressToolbar, toolbarSuppressed } = useWindowToolbarSuppression();\n const { handleGoBack, handleWindowPointerDown, windowContextMenuTrapProps } =\n useWindowRootHandlers(entry.id);\n const style = getWindowStyle(maximized, position, size, zIndex);\n\n if (minimized) {\n return null;\n }\n\n // A section labelled by its title is a region landmark. Windows are\n // non-modal and freely arranged, so they are not dialogs; hosts keep that\n // role for the modals they open inside a window.\n return (\n <section\n ref={windowRef}\n aria-labelledby={titleId}\n className={maximized ? \"window window--maximized\" : \"window\"}\n tabIndex={-1}\n {...windowContextMenuTrapProps}\n onPointerDownCapture={handleWindowPointerDown}\n style={style}\n >\n <WindowChrome\n actions={actions}\n entry={entry}\n onGoBack={handleGoBack}\n onPointerDown={handlePointerDown}\n titleId={titleId}\n toolbarSuppressed={toolbarSuppressed}\n />\n <CurrentWindowProvider\n close={actions.handleClose}\n id={entry.id}\n overlayHost={overlayHost}\n showStatusMessage={showStatusMessage}\n suppressToolbar={suppressToolbar}\n >\n <WindowBodyWithSidebar\n overlayHostRef={setOverlayHost}\n showSidebar={actions.showSidebar}\n >\n {ContentBoundary ? (\n <ContentBoundary entry={entry}>\n {Component && <Component />}\n </ContentBoundary>\n ) : (\n Component && <Component />\n )}\n </WindowBodyWithSidebar>\n </CurrentWindowProvider>\n <WindowStatusBar text={statusText} visible={actions.showStatusBar} />\n {!maximized && (\n <WindowResizeHandles\n handleResizePointerDown={handleResizePointerDown}\n />\n )}\n </section>\n );\n}\n\nfunction WindowBodyWithSidebar({\n showSidebar,\n overlayHostRef,\n children,\n}: PropsWithChildren<{\n overlayHostRef: (element: HTMLDivElement | null) => void;\n showSidebar: boolean;\n}>) {\n const { sidebar } = useWindowSidebar();\n const hasSidebar = hasWindowSidebar(sidebar);\n\n return (\n <WindowBody\n contentRef={overlayHostRef}\n showSidebar={showSidebar && hasSidebar}\n sidebar={sidebar}\n >\n {children}\n </WindowBody>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"Window.js","sourceRoot":"","sources":["../../../home/a2f0/github/tearleads/packages/windowing/src/window/Window.tsx"],"names":[],"mappings":";AAAA,OAAO,EAOL,WAAW,EACX,SAAS,EACT,KAAK,EACL,OAAO,EACP,MAAM,EACN,QAAQ,GACT,MAAM,OAAO,CAAC;AACf,OAAO,cAAc,CAAC;AACtB,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EACL,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAuB,MAAM,iBAAiB,CAAC;AACrE,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,aAAa,EACb,gBAAgB,IAAI,qBAAqB,EACzC,kBAAkB,GAInB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAoBhD,MAAM,iCAAiC,GAAG,IAAI,CAAC;AAE/C,MAAM,UAAU,MAAM,CAAC,EACrB,eAAe,EACf,WAAW,EACX,QAAQ,GACI;IACZ,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,kBAAkB,EAAE,CAAC;IACpD,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,KAAK,GACT,aAAa,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,EAAE;QAC/D,QAAQ,CAAC;IAEX,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,OAAO,CACL,KAAC,WAAW,IACV,eAAe,EAAE,eAAe,EAChC,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,WAAW,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EACrD,KAAK,EAAE,KAAK,GACZ,CACH,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CACvB,KAAkB,EAClB,aAA+B,EAC/B,aAA+B,EAC/B,iBAAmC,EACnC,UAAmB;IAEnB,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,GAClE,qBAAqB,EAAE,CAAC;IAC1B,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAC5C,KAAK,CAAC,kBAAkB,IAAI,IAAI,CACjC,CAAC;IACF,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1E,MAAM,cAAc,GAAG,WAAW,CAChC,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EACxB,CAAC,KAAK,CAAC,EAAE,EAAE,QAAQ,CAAC,CACrB,CAAC;IACF,MAAM,iBAAiB,GAAG,WAAW,CACnC,GAAG,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,EAC3B,CAAC,KAAK,CAAC,EAAE,EAAE,WAAW,CAAC,CACxB,CAAC;IACF,MAAM,kBAAkB,GAAG,WAAW,CACpC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,EAC5B,CAAC,KAAK,CAAC,EAAE,EAAE,YAAY,CAAC,CACzB,CAAC;IACF,MAAM,cAAc,GAAG,WAAW,CAChC,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,EAC9B,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAC3B,CAAC;IACF,MAAM,eAAe,GAAG,WAAW,CACjC,GAAG,EAAE,CAAC,gBAAgB,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAC/C,EAAE,CACH,CAAC;IACF,MAAM,aAAa,GAAG,WAAW,CAC/B,GAAG,EAAE,CAAC,cAAc,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAC7C,EAAE,CACH,CAAC;IACF,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC;QACJ;YACE,KAAK,EAAE,MAAM;YACb,KAAK,EAAE;gBACL,GAAG,aAAa;gBAChB,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE;aACtD;SACF;QACD;YACE,KAAK,EAAE,MAAM;YACb,KAAK,EAAE;gBACL,GAAG,aAAa;gBAChB,GAAG,iBAAiB;gBACpB;oBACE,EAAE,EAAE,mBAAmB;oBACvB,KAAK,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,aAAa;oBACtD,OAAO,EAAE,eAAe;iBACzB;gBACD,GAAG,CAAC,UAAU;oBACZ,CAAC,CAAC;wBACE;4BACE,EAAE,EAAE,gBAAgB;4BACpB,KAAK,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,UAAU;4BACjD,OAAO,EAAE,aAAa;yBACvB;qBACF;oBACH,CAAC,CAAC,EAAE,CAAC;aACR;SACF;KACF,EACD;QACE,iBAAiB;QACjB,UAAU;QACV,WAAW;QACX,aAAa;QACb,WAAW;QACX,aAAa;QACb,aAAa;QACb,eAAe;QACf,aAAa;KACd,CACF,CAAC;IAEF,OAAO;QACL,WAAW;QACX,cAAc;QACd,cAAc;QACd,kBAAkB;QAClB,iBAAiB;QACjB,KAAK;QACL,WAAW;QACX,aAAa;KACd,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CACrB,SAAkB,EAClB,QAA+B,EAC/B,IAAuB,EACvB,MAAc;IAEd,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACpE,CAAC;IACD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,uEAAuE;QACvE,sDAAsD;QACtD,OAAO;YACL,UAAU,EAAE,QAAQ;YACpB,MAAM;YACN,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5D,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,CAAC;QAChB,GAAG,EAAE,QAAQ,CAAC,CAAC;QACf,MAAM;QACN,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5D,CAAC;AACJ,CAAC;AAED,SAAS,sBAAsB;IAC7B,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACjD,MAAM,gBAAgB,GAAG,MAAM,CAAuC,IAAI,CAAC,CAAC;IAC5E,MAAM,iBAAiB,GAAG,WAAW,CAAC,CAAC,OAAe,EAAE,EAAE;QACxD,IAAI,gBAAgB,CAAC,OAAO,EAAE,CAAC;YAC7B,YAAY,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACzC,CAAC;QACD,aAAa,CAAC,OAAO,CAAC,CAAC;QACvB,gBAAgB,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YACzC,aAAa,CAAC,EAAE,CAAC,CAAC;YAClB,gBAAgB,CAAC,OAAO,GAAG,IAAI,CAAC;QAClC,CAAC,EAAE,iCAAiC,CAAC,CAAC;IACxC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,IAAI,gBAAgB,CAAC,OAAO,EAAE,CAAC;gBAC7B,YAAY,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;YACzC,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,CAAC;AAC3C,CAAC;AAED,gFAAgF;AAChF,uEAAuE;AACvE,SAAS,2BAA2B;IAClC,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC5D,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;QACvC,mBAAmB,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QAC1C,OAAO,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IACtE,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,GAAG,CAAC,EAAE,CAAC;AACtE,CAAC;AAUD,SAAS,WAAW,CAAC,EACnB,eAAe,EACf,KAAK,EACL,WAAW,EACX,KAAK,GACY;IACjB,OAAO,CACL,KAAC,kBAAkB,cACjB,KAAC,qBAAqB,cACpB,KAAC,kBAAkB,IACjB,eAAe,EAAE,eAAe,EAChC,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,KAAK,GACZ,GACoB,GACL,CACtB,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,sDAAsD;AACtD,SAAS,YAAY,CAAC,EACpB,OAAO,EACP,KAAK,EACL,WAAW,EACX,QAAQ,EACR,aAAa,EACb,OAAO,EACP,iBAAiB,GASlB;IACC,OAAO,CACL,8BACE,KAAC,cAAc,IACb,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,aAAa,EAC5B,UAAU,EAAE,OAAO,CAAC,cAAc,EAClC,UAAU,EAAE,OAAO,CAAC,cAAc,EAClC,OAAO,EAAE,OAAO,CAAC,WAAW,EAC5B,aAAa,EAAE,OAAO,CAAC,iBAAiB,EACxC,cAAc,EAAE,OAAO,CAAC,kBAAkB,GAC1C,EACF,KAAC,aAAa,IAAC,KAAK,EAAE,OAAO,CAAC,KAAK,GAAI,EACtC,CAAC,iBAAiB,IAAI,CACrB,KAAC,aAAa,IACZ,SAAS,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,EAChD,eAAe,EAAE,WAAW,EAC5B,QAAQ,EAAE,QAAQ,GAClB,CACH,IACA,CACJ,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,6EAA6E;AAC7E,SAAS,qBAAqB,CAAC,QAAgB;IAC7C,+EAA+E;IAC/E,0DAA0D;IAC1D,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,GAAG,qBAAqB,EAAE,CAAC;IAC9D,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;QACpC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;IAC5B,MAAM,uBAAuB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/C,YAAY,CAAC,QAAQ,CAAC,CAAC;IACzB,CAAC,EAAE,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC7B,MAAM,uBAAuB,GAAG,WAAW,CAAC,CAAC,KAAsB,EAAE,EAAE;QACrE,iFAAiF;QACjF,KAAK,CAAC,eAAe,EAAE,CAAC;IAC1B,CAAC,EAAE,EAAE,CAAC,CAAC;IACP,MAAM,0BAA0B,GAG5B;QACF,aAAa,EAAE,uBAAuB;KACvC,CAAC;IAEF,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,CAAC;AAC/E,CAAC;AAED,SAAS,kBAAkB,CAAC,EAC1B,eAAe,EACf,KAAK,EACL,WAAW,EACX,KAAK,GACY;IACjB,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IACrE,MAAM,SAAS,GAAG,MAAM,CAAc,IAAI,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC;IACxB,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAqB,IAAI,CAAC,CAAC;IACzE,MAAM,aAAa,GAAG,sBAAsB,EAAE,CAAC;IAC/C,MAAM,aAAa,GAAG,sBAAsB,EAAE,CAAC;IAC/C,MAAM,EAAE,OAAO,EAAE,GAAG,gBAAgB,EAAE,CAAC;IACvC,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,EACJ,iBAAiB,EACjB,uBAAuB,EACvB,QAAQ,EACR,IAAI,EACJ,GAAG,OAAO,EACX,GAAG,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IACnD,MAAM,EAAE,iBAAiB,EAAE,UAAU,EAAE,GAAG,sBAAsB,EAAE,CAAC;IACnE,MAAM,iBAAiB,GAAG,0BAA0B,CAClD,OAAO,EACP,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,EACnC,iBAAiB,CAClB,CAAC;IACF,MAAM,OAAO,GAAG,gBAAgB,CAC9B,KAAK,EACL,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,UAAU,CACX,CAAC;IACF,oEAAoE;IACpE,oBAAoB,CAAC,SAAS,EAAE;QAC9B,KAAK;QACL,KAAK,EAAE,CAAC,SAAS,IAAI,CAAC,SAAS,IAAI,QAAQ,KAAK,IAAI,CAAC;KACtD,CAAC,CAAC;IACH,MAAM,EAAE,eAAe,EAAE,iBAAiB,EAAE,GAAG,2BAA2B,EAAE,CAAC;IAC7E,MAAM,EAAE,YAAY,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,GACzE,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,cAAc,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAEhE,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;IAED,oEAAoE;IACpE,0EAA0E;IAC1E,iDAAiD;IACjD,OAAO,CACL,mBACE,GAAG,EAAE,SAAS,qBACG,OAAO,EACxB,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,QAAQ,EAC5D,QAAQ,EAAE,CAAC,CAAC,KACR,0BAA0B,EAC9B,oBAAoB,EAAE,uBAAuB,EAC7C,KAAK,EAAE,KAAK,aAEZ,KAAC,YAAY,IACX,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,YAAY,EACtB,aAAa,EAAE,iBAAiB,EAChC,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,iBAAiB,GACpC,EACF,KAAC,qBAAqB,IACpB,KAAK,EAAE,OAAO,CAAC,WAAW,EAC1B,EAAE,EAAE,KAAK,CAAC,EAAE,EACZ,WAAW,EAAE,WAAW,EACxB,iBAAiB,EAAE,iBAAiB,EACpC,eAAe,EAAE,eAAe,YAEhC,KAAC,qBAAqB,IACpB,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,OAAO,CAAC,WAAW,YAE/B,eAAe,CAAC,CAAC,CAAC,CACjB,KAAC,eAAe,IAAC,KAAK,EAAE,KAAK,YAC1B,SAAS,IAAI,KAAC,SAAS,KAAG,GACX,CACnB,CAAC,CAAC,CAAC,CACF,SAAS,IAAI,KAAC,SAAS,KAAG,CAC3B,GACqB,GACF,EACxB,KAAC,eAAe,IAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,GAAI,EACpE,CAAC,SAAS,IAAI,CACb,KAAC,mBAAmB,IAClB,uBAAuB,EAAE,uBAAuB,GAChD,CACH,IACO,CACX,CAAC;AACJ,CAAC;AAED,SAAS,qBAAqB,CAAC,EAC7B,WAAW,EACX,cAAc,EACd,QAAQ,GAIR;IACA,MAAM,EAAE,OAAO,EAAE,GAAG,gBAAgB,EAAE,CAAC;IACvC,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAE7C,OAAO,CACL,KAAC,UAAU,IACT,UAAU,EAAE,cAAc,EAC1B,WAAW,EAAE,WAAW,IAAI,UAAU,EACtC,OAAO,EAAE,OAAO,YAEf,QAAQ,GACE,CACd,CAAC;AACJ,CAAC","sourcesContent":["import {\n type ComponentType,\n type CSSProperties,\n type HTMLAttributes,\n type PropsWithChildren,\n type MouseEvent as ReactMouseEvent,\n type PointerEvent as ReactPointerEvent,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport \"./Window.css\";\nimport { CurrentWindowProvider } from \"./CurrentWindowContext\";\nimport { useWindowGeometry } from \"./useWindowGeometry\";\nimport {\n useFocusWindowOnShow,\n useWindowGeometryMenuItems,\n} from \"./useWindowKeyboardGeometry\";\nimport { WindowBody } from \"./WindowBody\";\nimport { WindowMenuBar, type WindowMenuItem } from \"./WindowMenuBar\";\nimport {\n useWindowFileMenuItems,\n useWindowViewMenuItems,\n WindowMenuProvider,\n} from \"./WindowMenuContext\";\nimport { WindowResizeHandles } from \"./WindowResizeHandle\";\nimport {\n hasWindowSidebar,\n useWindowSidebar,\n WindowSidebarProvider,\n} from \"./WindowSidebarContext\";\nimport {\n findTopWindow,\n useWindowActions as useWindowStateActions,\n useWindowStateData,\n type WindowEntry,\n type WindowPosition,\n type WindowSize,\n} from \"./WindowStateProvider\";\nimport { WindowStatusBar } from \"./WindowStatusBar\";\nimport { WindowTitleBar } from \"./WindowTitleBar\";\nimport { WindowToolBar } from \"./WindowToolBar\";\n\n// Wraps a window's content so the host can give it app-specific context (a\n// route, an error boundary) without the window layer knowing about apps.\nexport type WindowContentBoundary = ComponentType<\n PropsWithChildren<{ entry: WindowEntry }>\n>;\n\ninterface WindowProps {\n ContentBoundary?: WindowContentBoundary | undefined;\n /**\n * Whether the toolbar offers Back through the window's own route history,\n * reserving its row from the start (see `WindowToolBar`). Defaults to whether\n * the window has an `appId`. Pass false for an app that never routes, so its\n * window has no toolbar row until the app first registers toolbar actions.\n */\n historyBack?: boolean | undefined;\n windowId: string;\n}\n\nconst WINDOW_STATUS_MESSAGE_DURATION_MS = 2500;\n\nexport function Window({\n ContentBoundary,\n historyBack,\n windowId,\n}: WindowProps) {\n const { windowMap, windows } = useWindowStateData();\n const entry = windowMap.get(windowId);\n const isTop =\n findTopWindow(windows, (candidate) => !candidate.minimized)?.id ===\n windowId;\n\n if (!entry) return null;\n\n return (\n <WindowInner\n ContentBoundary={ContentBoundary}\n entry={entry}\n historyBack={historyBack ?? entry.appId !== undefined}\n isTop={isTop}\n />\n );\n}\n\nfunction useWindowActions(\n entry: WindowEntry,\n fileMenuItems: WindowMenuItem[],\n viewMenuItems: WindowMenuItem[],\n geometryMenuItems: WindowMenuItem[],\n hasSidebar: boolean,\n) {\n const { close, minimize, moveBackward, moveForward, toggleMaximize } =\n useWindowStateActions();\n const [showStatusBar, setShowStatusBar] = useState(true);\n const [showSidebar, setShowSidebar] = useState(\n entry.initialShowSidebar ?? true,\n );\n const handleClose = useCallback(() => close(entry.id), [close, entry.id]);\n const handleMinimize = useCallback(\n () => minimize(entry.id),\n [entry.id, minimize],\n );\n const handleMoveForward = useCallback(\n () => moveForward(entry.id),\n [entry.id, moveForward],\n );\n const handleMoveBackward = useCallback(\n () => moveBackward(entry.id),\n [entry.id, moveBackward],\n );\n const handleMaximize = useCallback(\n () => toggleMaximize(entry.id),\n [entry.id, toggleMaximize],\n );\n const toggleStatusBar = useCallback(\n () => setShowStatusBar((previous) => !previous),\n [],\n );\n const toggleSidebar = useCallback(\n () => setShowSidebar((previous) => !previous),\n [],\n );\n const menus = useMemo(\n () => [\n {\n label: \"File\",\n items: [\n ...fileMenuItems,\n { id: \"close\", label: \"Close\", onClick: handleClose },\n ],\n },\n {\n label: \"View\",\n items: [\n ...viewMenuItems,\n ...geometryMenuItems,\n {\n id: \"toggle-status-bar\",\n label: `${showStatusBar ? \"Hide\" : \"Show\"} Status Bar`,\n onClick: toggleStatusBar,\n },\n ...(hasSidebar\n ? [\n {\n id: \"toggle-sidebar\",\n label: `${showSidebar ? \"Hide\" : \"Show\"} Sidebar`,\n onClick: toggleSidebar,\n },\n ]\n : []),\n ],\n },\n ],\n [\n geometryMenuItems,\n hasSidebar,\n handleClose,\n fileMenuItems,\n showSidebar,\n showStatusBar,\n toggleSidebar,\n toggleStatusBar,\n viewMenuItems,\n ],\n );\n\n return {\n handleClose,\n handleMaximize,\n handleMinimize,\n handleMoveBackward,\n handleMoveForward,\n menus,\n showSidebar,\n showStatusBar,\n };\n}\n\nfunction getWindowStyle(\n maximized: boolean,\n position: WindowPosition | null,\n size: WindowSize | null,\n zIndex: number,\n): CSSProperties | undefined {\n if (maximized) {\n return { top: 0, left: 0, width: \"100%\", height: \"100%\", zIndex };\n }\n if (!position) {\n // Hidden until laid out; a requested size applies already so the first\n // clamp measures the window at the size it will show.\n return {\n visibility: \"hidden\",\n zIndex,\n ...(size ? { width: size.width, height: size.height } : {}),\n };\n }\n\n return {\n left: position.x,\n top: position.y,\n zIndex,\n ...(size ? { width: size.width, height: size.height } : {}),\n };\n}\n\nfunction useWindowStatusMessage() {\n const [statusText, setStatusText] = useState(\"\");\n const statusTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const showStatusMessage = useCallback((message: string) => {\n if (statusTimeoutRef.current) {\n clearTimeout(statusTimeoutRef.current);\n }\n setStatusText(message);\n statusTimeoutRef.current = setTimeout(() => {\n setStatusText(\"\");\n statusTimeoutRef.current = null;\n }, WINDOW_STATUS_MESSAGE_DURATION_MS);\n }, []);\n\n useEffect(() => {\n return () => {\n if (statusTimeoutRef.current) {\n clearTimeout(statusTimeoutRef.current);\n }\n };\n }, []);\n\n return { showStatusMessage, statusText };\n}\n\n// Refcounted so overlapping overlays (one closing as another opens) never leave\n// the row hidden, and never flash it back for a frame between the two.\nfunction useWindowToolbarSuppression() {\n const [suppressionCount, setSuppressionCount] = useState(0);\n const suppressToolbar = useCallback(() => {\n setSuppressionCount((count) => count + 1);\n return () => setSuppressionCount((count) => Math.max(0, count - 1));\n }, []);\n\n return { suppressToolbar, toolbarSuppressed: suppressionCount > 0 };\n}\n\ninterface WindowInnerProps {\n ContentBoundary?: WindowContentBoundary | undefined;\n entry: WindowEntry;\n historyBack: boolean;\n // Whether this is the foremost visible window, the one that takes focus.\n isTop: boolean;\n}\n\nfunction WindowInner({\n ContentBoundary,\n entry,\n historyBack,\n isTop,\n}: WindowInnerProps) {\n return (\n <WindowMenuProvider>\n <WindowSidebarProvider>\n <WindowInnerContent\n ContentBoundary={ContentBoundary}\n entry={entry}\n historyBack={historyBack}\n isTop={isTop}\n />\n </WindowSidebarProvider>\n </WindowMenuProvider>\n );\n}\n\n// The bars above the body: title, menus, and the toolbar row a full-pane overlay\n// can stand down (see `useWindowToolbarSuppression`).\nfunction WindowChrome({\n actions,\n entry,\n historyBack,\n onGoBack,\n onPointerDown,\n titleId,\n toolbarSuppressed,\n}: {\n actions: ReturnType<typeof useWindowActions>;\n entry: WindowEntry;\n historyBack: boolean;\n onGoBack: () => void;\n onPointerDown: (event: ReactPointerEvent) => void;\n titleId: string;\n toolbarSuppressed: boolean;\n}) {\n return (\n <>\n <WindowTitleBar\n title={entry.title}\n titleId={titleId}\n onPointerDown={onPointerDown}\n onMinimize={actions.handleMinimize}\n onMaximize={actions.handleMaximize}\n onClose={actions.handleClose}\n onMoveForward={actions.handleMoveForward}\n onMoveBackward={actions.handleMoveBackward}\n />\n <WindowMenuBar menus={actions.menus} />\n {!toolbarSuppressed && (\n <WindowToolBar\n canGoBack={(entry.routeHistory?.length ?? 0) > 0}\n showHistoryBack={historyBack}\n onGoBack={onGoBack}\n />\n )}\n </>\n );\n}\n\n// Handlers on the window root: its Back caret, raising it on any press inside,\n// and keeping background context menus from opening under window-local ones.\nfunction useWindowRootHandlers(windowId: string) {\n // The toolbar renders above the route boundary, so the window's own Back stack\n // is threaded in from here rather than read from context.\n const { bringToFront, goBackRoute } = useWindowStateActions();\n const handleGoBack = useCallback(() => {\n goBackRoute(windowId);\n }, [windowId, goBackRoute]);\n const handleWindowPointerDown = useCallback(() => {\n bringToFront(windowId);\n }, [bringToFront, windowId]);\n const handleWindowContextMenu = useCallback((event: ReactMouseEvent) => {\n // Keep background pane context menus from opening underneath window-local menus.\n event.stopPropagation();\n }, []);\n const windowContextMenuTrapProps: Pick<\n HTMLAttributes<HTMLElement>,\n \"onContextMenu\"\n > = {\n onContextMenu: handleWindowContextMenu,\n };\n\n return { handleGoBack, handleWindowPointerDown, windowContextMenuTrapProps };\n}\n\nfunction WindowInnerContent({\n ContentBoundary,\n entry,\n historyBack,\n isTop,\n}: WindowInnerProps) {\n const { maximized, minimized, zIndex, component: Component } = entry;\n const windowRef = useRef<HTMLElement>(null);\n const titleId = useId();\n const [overlayHost, setOverlayHost] = useState<HTMLElement | null>(null);\n const fileMenuItems = useWindowFileMenuItems();\n const viewMenuItems = useWindowViewMenuItems();\n const { sidebar } = useWindowSidebar();\n const hasSidebar = hasWindowSidebar(sidebar);\n const {\n handlePointerDown,\n handleResizePointerDown,\n position,\n size,\n ...stepped\n } = useWindowGeometry(entry, maximized, windowRef);\n const { showStatusMessage, statusText } = useWindowStatusMessage();\n const geometryMenuItems = useWindowGeometryMenuItems(\n stepped,\n { maximized, minimized, windowRef },\n showStatusMessage,\n );\n const actions = useWindowActions(\n entry,\n fileMenuItems,\n viewMenuItems,\n geometryMenuItems,\n hasSidebar,\n );\n // A maximized window fills its surface without a laid-out position.\n useFocusWindowOnShow(windowRef, {\n isTop,\n shown: !minimized && (maximized || position !== null),\n });\n const { suppressToolbar, toolbarSuppressed } = useWindowToolbarSuppression();\n const { handleGoBack, handleWindowPointerDown, windowContextMenuTrapProps } =\n useWindowRootHandlers(entry.id);\n const style = getWindowStyle(maximized, position, size, zIndex);\n\n if (minimized) {\n return null;\n }\n\n // A section labelled by its title is a region landmark. Windows are\n // non-modal and freely arranged, so they are not dialogs; hosts keep that\n // role for the modals they open inside a window.\n return (\n <section\n ref={windowRef}\n aria-labelledby={titleId}\n className={maximized ? \"window window--maximized\" : \"window\"}\n tabIndex={-1}\n {...windowContextMenuTrapProps}\n onPointerDownCapture={handleWindowPointerDown}\n style={style}\n >\n <WindowChrome\n actions={actions}\n entry={entry}\n historyBack={historyBack}\n onGoBack={handleGoBack}\n onPointerDown={handlePointerDown}\n titleId={titleId}\n toolbarSuppressed={toolbarSuppressed}\n />\n <CurrentWindowProvider\n close={actions.handleClose}\n id={entry.id}\n overlayHost={overlayHost}\n showStatusMessage={showStatusMessage}\n suppressToolbar={suppressToolbar}\n >\n <WindowBodyWithSidebar\n overlayHostRef={setOverlayHost}\n showSidebar={actions.showSidebar}\n >\n {ContentBoundary ? (\n <ContentBoundary entry={entry}>\n {Component && <Component />}\n </ContentBoundary>\n ) : (\n Component && <Component />\n )}\n </WindowBodyWithSidebar>\n </CurrentWindowProvider>\n <WindowStatusBar text={statusText} visible={actions.showStatusBar} />\n {!maximized && (\n <WindowResizeHandles\n handleResizePointerDown={handleResizePointerDown}\n />\n )}\n </section>\n );\n}\n\nfunction WindowBodyWithSidebar({\n showSidebar,\n overlayHostRef,\n children,\n}: PropsWithChildren<{\n overlayHostRef: (element: HTMLDivElement | null) => void;\n showSidebar: boolean;\n}>) {\n const { sidebar } = useWindowSidebar();\n const hasSidebar = hasWindowSidebar(sidebar);\n\n return (\n <WindowBody\n contentRef={overlayHostRef}\n showSidebar={showSidebar && hasSidebar}\n sidebar={sidebar}\n >\n {children}\n </WindowBody>\n );\n}\n"]}
|
package/window/WindowBody.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WindowBody.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"WindowBody.js","sourceRoot":"","sources":["../../../home/a2f0/github/tearleads/packages/windowing/src/window/WindowBody.tsx"],"names":[],"mappings":";AACA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAQhD,+EAA+E;AAC/E,+EAA+E;AAC/E,yDAAyD;AACzD,MAAM,UAAU,UAAU,CAAC,EACzB,UAAU,EACV,WAAW,GAAG,KAAK,EACnB,OAAO,EACP,QAAQ,GAC2B;IACnC,OAAO,CACL,cAAK,SAAS,EAAC,aAAa,YACzB,WAAW,CAAC,CAAC,CAAC,CACb,KAAC,aAAa,IAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,YACpD,QAAQ,GACK,CACjB,CAAC,CAAC,CAAC,CACF,cAAK,SAAS,EAAC,qBAAqB,EAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC,YAChE,cAAK,SAAS,EAAC,4BAA4B,YAAE,QAAQ,GAAO,GACxD,CACP,GACG,CACP,CAAC;AACJ,CAAC","sourcesContent":["import type { PropsWithChildren, Ref } from \"react\";\nimport \"./WindowBody.css\";\nimport { WindowSidebar } from \"./WindowSidebar\";\n\ninterface WindowBodyProps {\n contentRef?: Ref<HTMLDivElement> | undefined;\n showSidebar?: boolean;\n sidebar?: React.ReactNode;\n}\n\n// The content pane is programmatically focusable (`tabIndex={-1}`, so never in\n// the tab order): an overlay that covered it lands focus here when the control\n// that opened the overlay is gone by the time it closes.\nexport function WindowBody({\n contentRef,\n showSidebar = false,\n sidebar,\n children,\n}: PropsWithChildren<WindowBodyProps>) {\n return (\n <div className=\"window-body\">\n {showSidebar ? (\n <WindowSidebar contentRef={contentRef} sidebar={sidebar}>\n {children}\n </WindowSidebar>\n ) : (\n <div className=\"window-body-content\" ref={contentRef} tabIndex={-1}>\n <div className=\"window-body-content-scroll\">{children}</div>\n </div>\n )}\n </div>\n );\n}\n"]}
|
|
@@ -12,6 +12,12 @@ export interface WindowTitleBarActionInput {
|
|
|
12
12
|
* action id providing a deterministic tie-break. A sole action stays sole.
|
|
13
13
|
*/
|
|
14
14
|
placement?: "penultimate";
|
|
15
|
+
/**
|
|
16
|
+
* Makes the action a toggle: the button announces this value as
|
|
17
|
+
* `aria-pressed` and the toolbar draws it pressed while true. Leave it unset
|
|
18
|
+
* for an action that is not a toggle.
|
|
19
|
+
*/
|
|
20
|
+
pressed?: boolean;
|
|
15
21
|
priority?: number;
|
|
16
22
|
}
|
|
17
23
|
export interface WindowBackActionInput {
|
|
@@ -27,6 +33,7 @@ export interface RegisteredWindowTitleBarAction {
|
|
|
27
33
|
label: string;
|
|
28
34
|
onClick: () => unknown;
|
|
29
35
|
placement: "penultimate" | null;
|
|
36
|
+
pressed: boolean | null;
|
|
30
37
|
priority: number;
|
|
31
38
|
}
|
|
32
39
|
export interface RegisteredWindowBackAction {
|
|
@@ -5,7 +5,7 @@ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
|
5
5
|
* their button class.
|
|
6
6
|
*/
|
|
7
7
|
export function WindowTitleBarActionButtons({ actions, className, }) {
|
|
8
|
-
return (_jsx(_Fragment, { children: actions.map((action) => (_jsx("button", { "aria-label": action.label, className: className, disabled: action.disabled, title: action.label, type: "button", onClick: action.onClick, children: action.icon }, action.id))) }));
|
|
8
|
+
return (_jsx(_Fragment, { children: actions.map((action) => (_jsx("button", { "aria-label": action.label, className: className, "aria-pressed": action.pressed, disabled: action.disabled, title: action.label, type: "button", onClick: action.onClick, children: action.icon }, action.id))) }));
|
|
9
9
|
}
|
|
10
10
|
export function createTitleBarActions(items) {
|
|
11
11
|
const ordered = Array.from(items.values()).sort((left, right) => right.priority - left.priority ||
|
|
@@ -25,6 +25,7 @@ export function createTitleBarActions(items) {
|
|
|
25
25
|
onClick: () => {
|
|
26
26
|
void item.onClick();
|
|
27
27
|
},
|
|
28
|
+
...(item.pressed === null ? {} : { pressed: item.pressed }),
|
|
28
29
|
}));
|
|
29
30
|
}
|
|
30
31
|
export function createRegisteredTitleBarAction(item, onClick) {
|
|
@@ -35,6 +36,7 @@ export function createRegisteredTitleBarAction(item, onClick) {
|
|
|
35
36
|
label: item.label,
|
|
36
37
|
onClick,
|
|
37
38
|
placement: item.placement ?? null,
|
|
39
|
+
pressed: item.pressed ?? null,
|
|
38
40
|
priority: item.priority ?? 0,
|
|
39
41
|
};
|
|
40
42
|
}
|