@phreshos/react-ui 0.1.33 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/color.d.ts CHANGED
@@ -14,6 +14,8 @@ export declare function color(value: string): ColorScale;
14
14
  export declare function resolveColorLevel(value: string, level: ColorLevel): string;
15
15
  /** Returns the complete visual treatments derived from one concrete color. */
16
16
  export declare function useColor(value: string): ColorScale;
17
+ /** Returns the readable Appearance content color for one semantic or CSS fill. */
18
+ export declare function useContrastingColor(value: Color): string;
17
19
  /** Resolves a semantic color from an already selected Appearance branch. */
18
20
  export declare function resolveColor(value: Color | undefined, colors: AppearanceColors): string;
19
21
  /** Resolves opaque control paint from an already selected Appearance branch. */
package/dist/color.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { useMemo } from "react";
2
+ import { useResolvedAppearance } from "./appearance-context.js";
2
3
  import ColorSpace from "colorjs.io/src/ColorSpace.js";
3
4
  import { mix } from "colorjs.io/src/interpolation.js";
4
5
  import parse from "colorjs.io/src/parse.js";
@@ -61,6 +62,11 @@ export function resolveColorLevel(value, level) {
61
62
  export function useColor(value) {
62
63
  return useMemo(() => color(value), [value]);
63
64
  }
65
+ /** Returns the readable Appearance content color for one semantic or CSS fill. */
66
+ export function useContrastingColor(value) {
67
+ const { colors } = useResolvedAppearance();
68
+ return useMemo(() => contrastingColor(value, colors), [value, colors]);
69
+ }
64
70
  /** Resolves a semantic color from an already selected Appearance branch. */
65
71
  export function resolveColor(value, colors) {
66
72
  const resolved = value ?? defaultColor;
package/dist/main.d.ts CHANGED
@@ -13,7 +13,7 @@ export { Grid, type GridProps } from "./grid.js";
13
13
  export { Surface, type MaterialMode, type MaterialOptions, type ShadowOptions, type SurfaceComponent, type SurfaceHost, type SurfaceHostProps, type SurfaceOwnProps, type SurfaceProps } from "./surface.js";
14
14
  export { defaultColor, type Color, type ColorLevel, type ColorScale } from "./color.js";
15
15
  export { Panel, type PanelProps, type PanelHeaderProps, type PanelContentProps } from "./panel.js";
16
- export { Window, type WindowProps, type WindowContentProps, type WindowHeaderProps, type WindowIdentityProps, type WindowCenterProps, type WindowActionsProps, type WindowActionProps, type WindowControlProps, type WindowMaximizeProps, type WindowCloseProps } from "./window.js";
16
+ export { Window, type WindowProps, type WindowContentProps, type WindowHeaderProps, type WindowIdentityProps, type WindowCenterProps, type WindowActionsProps, type WindowActionProps, type WindowControlProps, type WindowMaximizeProps, type WindowCloseProps, type WindowMoveCaptureProps } from "./window.js";
17
17
  export { default as useWindowMoveHandle, type WindowMoveHandle } from "./use-window-move-handle.js";
18
18
  export { Button, type ButtonColor, type ButtonProps } from "./button.js";
19
19
  export { Input, type InputProps } from "./input.js";
@@ -36,6 +36,8 @@ export { Table, type TableProps, type TableNoSelectionProps, type TableSingleSel
36
36
  export { Tree, type TreeProps, type TreeNoSelectionProps, type TreeSingleSelectionProps, type TreeMultipleSelectionProps, type TreeMultipleValue, type TreeItemProps, type TreeContentProps, type TreeCollectionProps } from "./tree.js";
37
37
  export { Slider, type SliderProps } from "./slider.js";
38
38
  export { ProgressBar, type ProgressBarProps } from "./progress-bar.js";
39
+ export { Spinner, type SpinnerProps } from "./spinner.js";
40
+ export { Readiness, useReadiness, useRequirement, type ReadinessFallback, type ReadinessProps, type ReadinessRequirement, type ReadinessRequirementProps } from "./readiness.js";
39
41
  export { ScrollArea, type ScrollAreaAxis, type ScrollAreaProps } from "./scroll-area.js";
40
42
  export { Toolbar, type ToolbarProps, type ToolbarGroupProps, type ToolbarSeparatorProps } from "./toolbar.js";
41
43
  export { Disclosure, type DisclosureProps, type DisclosureTriggerProps, type DisclosureContentProps } from "./disclosure.js";
@@ -51,5 +53,5 @@ export type { ControlColor } from "./control.js";
51
53
  export type { LayoutAlignment, LayoutGap, LayoutJustification } from "./layout.js";
52
54
  export { resolveRadius, type Radius, type RadiusProps } from "./radius.js";
53
55
  export { resolveSpacing, type Spacing } from "./spacing.js";
54
- export { useColor } from "./color.js";
56
+ export { useColor, useContrastingColor } from "./color.js";
55
57
  export { useScale, type NumericScale, type ScaleLevel } from "./scale.js";
package/dist/main.js CHANGED
@@ -34,6 +34,8 @@ export { Table } from "./table.js";
34
34
  export { Tree } from "./tree.js";
35
35
  export { Slider } from "./slider.js";
36
36
  export { ProgressBar } from "./progress-bar.js";
37
+ export { Spinner } from "./spinner.js";
38
+ export { Readiness, useReadiness, useRequirement } from "./readiness.js";
37
39
  export { ScrollArea } from "./scroll-area.js";
38
40
  export { Toolbar } from "./toolbar.js";
39
41
  export { Disclosure } from "./disclosure.js";
@@ -47,5 +49,5 @@ export { AlertDialog } from "./alert-dialog.js";
47
49
  export { Tooltip } from "./tooltip.js";
48
50
  export { resolveRadius } from "./radius.js";
49
51
  export { resolveSpacing } from "./spacing.js";
50
- export { useColor } from "./color.js";
52
+ export { useColor, useContrastingColor } from "./color.js";
51
53
  export { useScale } from "./scale.js";
@@ -0,0 +1,37 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ /** One requirement observed during the boundary's initial readiness cycle. */
3
+ export interface ReadinessRequirement {
4
+ readonly message: string;
5
+ readonly createdAt: Date;
6
+ readonly readyAt: Date | null;
7
+ }
8
+ export interface ReadinessProps {
9
+ /** Interface prepared behind this initial readiness boundary. */
10
+ readonly children: ReactNode;
11
+ /** Optional presentation rendered while initial requirements remain pending. */
12
+ readonly fallback?: (requirements: readonly ReadinessRequirement[]) => ReactNode;
13
+ /** Message used when the oldest pending requirement has none of its own. */
14
+ readonly message?: string;
15
+ }
16
+ /** Prevents interaction with one React subtree until its initial requirements are ready. */
17
+ export declare function ReadinessRoot({ children, fallback, message }: ReadinessProps): import("react").JSX.Element;
18
+ export interface ReadinessRequirementProps {
19
+ /** Message exposed while this mounted requirement remains pending. */
20
+ readonly message?: string;
21
+ }
22
+ /** One declarative requirement whose pending lifetime matches this component's mount. */
23
+ export declare function ReadinessRequirement({ message }: ReadinessRequirementProps): null;
24
+ /** Registers one requirement and returns its stable, idempotent completion. */
25
+ export declare function useRequirement(message?: string): () => void;
26
+ /** Reads the nearest boundary's current initial-readiness state. */
27
+ export declare function useReadiness(): Readonly<{
28
+ ready: boolean;
29
+ requirements: readonly ReadinessRequirement[];
30
+ }>;
31
+ interface ReadinessComponent {
32
+ (properties: ReadinessProps): ReactElement | null;
33
+ readonly Requirement: typeof ReadinessRequirement;
34
+ }
35
+ export declare const Readiness: ReadinessComponent;
36
+ export type ReadinessFallback = NonNullable<ReadinessProps["fallback"]>;
37
+ export {};
@@ -0,0 +1,134 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
3
+ const ReadinessContext = createContext(null);
4
+ /** Prevents interaction with one React subtree until its initial requirements are ready. */
5
+ export function ReadinessRoot({ children, fallback, message = "Loading…" }) {
6
+ const parent = useContext(ReadinessContext);
7
+ const requirements = useRef(new Map());
8
+ const revealedRef = useRef(false);
9
+ const [revealed, setRevealed] = useState(false);
10
+ const [revision, invalidate] = useReducer(value => value + 1, 0);
11
+ const registry = useMemo(() => ({
12
+ register(identity, nextMessage) {
13
+ if (revealedRef.current)
14
+ return;
15
+ const current = requirements.current.get(identity);
16
+ if (current != null) {
17
+ if (current.readyAt == null)
18
+ return;
19
+ // React Strict Mode restores mounted effects before the initial tree settles.
20
+ requirements.current.set(identity, {
21
+ ...current,
22
+ message: nextMessage,
23
+ readyAt: null
24
+ });
25
+ invalidate();
26
+ return;
27
+ }
28
+ requirements.current.set(identity, {
29
+ message: nextMessage,
30
+ createdAt: new Date(),
31
+ readyAt: null
32
+ });
33
+ invalidate();
34
+ },
35
+ update(identity, nextMessage) {
36
+ if (revealedRef.current)
37
+ return;
38
+ const current = requirements.current.get(identity);
39
+ if (current == null || current.message === nextMessage)
40
+ return;
41
+ requirements.current.set(identity, { ...current, message: nextMessage });
42
+ invalidate();
43
+ },
44
+ complete(identity) {
45
+ if (revealedRef.current)
46
+ return;
47
+ const current = requirements.current.get(identity);
48
+ if (current == null || current.readyAt != null)
49
+ return;
50
+ requirements.current.set(identity, { ...current, readyAt: new Date() });
51
+ invalidate();
52
+ },
53
+ release(identity) {
54
+ if (revealedRef.current)
55
+ return;
56
+ const current = requirements.current.get(identity);
57
+ if (current == null || current.readyAt != null)
58
+ return;
59
+ requirements.current.set(identity, { ...current, readyAt: new Date() });
60
+ invalidate();
61
+ }
62
+ }), []);
63
+ const currentRequirements = useMemo(() => {
64
+ return [...requirements.current.values()].map(requirement => ({
65
+ message: requirement.message ?? message,
66
+ createdAt: new Date(requirement.createdAt),
67
+ readyAt: requirement.readyAt == null ? null : new Date(requirement.readyAt)
68
+ }));
69
+ }, [message, revision]);
70
+ const currentMessage = currentRequirements.find(requirement => requirement.readyAt == null)?.message ?? message;
71
+ const completeParent = useManagedRequirement(parent?.registry ?? null, currentMessage);
72
+ useLayoutEffect(() => {
73
+ if (revealedRef.current
74
+ || [...requirements.current.values()].some(requirement => requirement.readyAt == null))
75
+ return;
76
+ // Descendant layout effects register first, so an empty set here represents a settled commit.
77
+ revealedRef.current = true;
78
+ setRevealed(true);
79
+ }, [revision]);
80
+ useLayoutEffect(() => {
81
+ if (revealed)
82
+ completeParent();
83
+ }, [completeParent, revealed]);
84
+ const context = useMemo(() => ({
85
+ ready: revealed,
86
+ requirements: currentRequirements,
87
+ registry
88
+ }), [currentRequirements, registry, revealed]);
89
+ return _jsxs(ReadinessContext.Provider, { value: context, children: [_jsx("div", { "data-readiness": "", "data-ready": revealed ? "true" : "false", inert: revealed ? undefined : true, style: { display: "contents" }, children: children }), !revealed && fallback?.(currentRequirements)] });
90
+ }
91
+ /** One declarative requirement whose pending lifetime matches this component's mount. */
92
+ export function ReadinessRequirement({ message }) {
93
+ useRequirement(message);
94
+ return null;
95
+ }
96
+ /** Registers one requirement and returns its stable, idempotent completion. */
97
+ export function useRequirement(message) {
98
+ const context = useContext(ReadinessContext);
99
+ if (context == null)
100
+ throw new Error("useRequirement must be used inside Readiness");
101
+ return useManagedRequirement(context.registry, message);
102
+ }
103
+ /** Reads the nearest boundary's current initial-readiness state. */
104
+ export function useReadiness() {
105
+ const context = useContext(ReadinessContext);
106
+ if (context == null)
107
+ throw new Error("useReadiness must be used inside Readiness");
108
+ return useMemo(() => ({
109
+ ready: context.ready,
110
+ requirements: context.requirements
111
+ }), [context.ready, context.requirements]);
112
+ }
113
+ function useManagedRequirement(registry, message) {
114
+ const identity = useRef({});
115
+ const completed = useRef(false);
116
+ useLayoutEffect(() => {
117
+ if (!completed.current)
118
+ registry?.register(identity.current, message);
119
+ return () => registry?.release(identity.current);
120
+ }, [registry]);
121
+ useLayoutEffect(() => {
122
+ if (!completed.current)
123
+ registry?.update(identity.current, message);
124
+ }, [message, registry]);
125
+ return useCallback(() => {
126
+ if (completed.current)
127
+ return;
128
+ completed.current = true;
129
+ registry?.complete(identity.current);
130
+ }, [registry]);
131
+ }
132
+ export const Readiness = Object.assign(ReadinessRoot, {
133
+ Requirement: ReadinessRequirement
134
+ });
@@ -0,0 +1,29 @@
1
+ import type { CSSProperties } from "react";
2
+ import type { ProgressBarProps as AriaProgressBarProps } from "react-aria-components";
3
+ import type { ControlColor } from "./control.js";
4
+ import { type ScaleLevel } from "./scale.js";
5
+ type NativeSpinnerProps = Omit<AriaProgressBarProps, "aria-hidden" | "aria-label" | "aria-labelledby" | "children" | "className" | "formatOptions" | "isIndeterminate" | "maxValue" | "minValue" | "slot" | "style" | "value" | "valueLabel">;
6
+ interface SpinnerVisualProps extends NativeSpinnerProps {
7
+ /** Native class name applied without replacing the component contract. */
8
+ readonly className?: string;
9
+ /** Color of the moving indicator. */
10
+ readonly color?: ControlColor;
11
+ /** Derives the indicator diameter from Appearance. */
12
+ readonly size?: ScaleLevel;
13
+ /** Native styles applied after Spinner defaults. */
14
+ readonly style?: CSSProperties;
15
+ }
16
+ type NamedSpinnerProps = Readonly<{
17
+ /** Accessible operation name announced with indeterminate progress semantics. */
18
+ label: string;
19
+ decorative?: false;
20
+ }>;
21
+ type DecorativeSpinnerProps = Readonly<{
22
+ /** Hides the Spinner when surrounding content already communicates progress. */
23
+ decorative: true;
24
+ label?: never;
25
+ }>;
26
+ export type SpinnerProps = SpinnerVisualProps & (NamedSpinnerProps | DecorativeSpinnerProps);
27
+ /** Compact indeterminate progress, either named or explicitly decorative. */
28
+ export declare const Spinner: import("react").ForwardRefExoticComponent<SpinnerProps & import("react").RefAttributes<HTMLDivElement>>;
29
+ export {};
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { ProgressBar as AriaProgressBar } from "react-aria-components";
4
+ import { motion } from "motion/react";
5
+ import { useAppearance } from "./appearance-context.js";
6
+ import { colorOpacity } from "./color.js";
7
+ import { useControlTheme } from "./control.js";
8
+ import { scale } from "./scale.js";
9
+ /** Compact indeterminate progress, either named or explicitly decorative. */
10
+ export const Spinner = forwardRef(function Spinner({ className, color = "default:base", decorative = false, label, size = "medium", style, ...properties }, ref) {
11
+ const appearance = useAppearance();
12
+ const theme = useControlTheme({ color, size });
13
+ // Spinner is a standalone status, so its scale must remain visibly distinct
14
+ // instead of inheriting the compact indicator size used inside controls.
15
+ const diameter = appearance.spacing + scale(appearance.spacing, size);
16
+ const rootStyle = {
17
+ display: "inline-grid",
18
+ placeItems: "center",
19
+ boxSizing: "border-box",
20
+ flexShrink: 0,
21
+ width: diameter,
22
+ height: diameter,
23
+ verticalAlign: "middle",
24
+ ...style
25
+ };
26
+ const indicator = _jsx(SpinnerIndicator, { contextualColor: color === "currentColor", theme: theme });
27
+ if (decorative)
28
+ return _jsx("div", { ...properties, ref: ref, "aria-hidden": "true", className: className, style: rootStyle, children: indicator });
29
+ return _jsx(AriaProgressBar, { ...properties, ref: ref, "aria-label": label, className: className, isIndeterminate: true, style: rootStyle, children: indicator });
30
+ });
31
+ function SpinnerIndicator({ contextualColor, theme }) {
32
+ const duration = typeof theme.motionTransition.duration === "number"
33
+ ? Math.max(0.6, theme.motionTransition.duration * 4)
34
+ : 0.8;
35
+ // currentColor belongs to the surrounding surface, whose canvas is not the
36
+ // Appearance canvas. Alpha preserves contrast against that actual owner.
37
+ const track = contextualColor
38
+ ? colorOpacity("currentColor", 0.25)
39
+ : theme.paints.subtle.rest.background;
40
+ const fill = contextualColor ? "currentColor" : theme.paints.palette.rest.background;
41
+ return _jsxs(motion.svg, { "data-spinner-indicator": "", "aria-hidden": "true", focusable: "false", viewBox: "0 0 24 24", width: "100%", height: "100%", fill: "none", initial: false, animate: { rotate: theme.animations ? [0, 360] : 0 }, transition: theme.animations ? { duration, repeat: Infinity, ease: "linear" } : { duration: 0 }, style: { display: "block", transformOrigin: "center" }, children: [_jsx("circle", { "data-spinner-track": "", cx: "12", cy: "12", r: "9", stroke: track, strokeWidth: "3" }), _jsx("circle", { "data-spinner-fill": "", cx: "12", cy: "12", r: "9", pathLength: "100", stroke: fill, strokeWidth: "3", strokeLinecap: "round", strokeDasharray: "68 32" })] });
42
+ }
@@ -35,6 +35,10 @@ export function ToggleIndicator({ kind, direction = "ltr", selected, indetermina
35
35
  }
36
36
  export function toggleStyle(theme, disabled, readOnly) {
37
37
  return {
38
+ // React Aria's focusable native input is visually hidden with absolute
39
+ // positioning. Anchor it here so focus cannot scroll an ancestor viewport
40
+ // toward a position outside the visible toggle.
41
+ position: "relative",
38
42
  display: "flex",
39
43
  alignItems: "center",
40
44
  gap: theme.gap,
package/dist/window.d.ts CHANGED
@@ -14,6 +14,10 @@ export interface WindowHeaderProps extends Omit<HTMLAttributes<HTMLDivElement>,
14
14
  readonly color?: Color;
15
15
  /** Hands an intentional header drag to its host after the pointer threshold. */
16
16
  readonly beginMoveGesture?: BeginWindowMoveGesture;
17
+ /** Whether the represented window is currently maximized. */
18
+ readonly maximized?: boolean;
19
+ /** Toggles the represented window between maximized and restored states. */
20
+ readonly onMaximize?: () => void;
17
21
  /** Receives a synchronous or asynchronous move handoff failure. */
18
22
  readonly onMoveError?: (error: unknown) => void;
19
23
  }
@@ -29,6 +33,7 @@ export interface WindowMaximizeProps extends WindowControlProps {
29
33
  readonly maximized?: boolean;
30
34
  }
31
35
  export type WindowCloseProps = WindowControlProps;
36
+ export type WindowMoveCaptureProps = HTMLAttributes<HTMLDivElement>;
32
37
  /** One Surface with a ready-to-use header-and-content composition. */
33
38
  export declare const Window: import("react").ForwardRefExoticComponent<Omit<WindowProps, "ref"> & import("react").RefAttributes<HTMLDivElement>> & {
34
39
  Header: import("react").ForwardRefExoticComponent<WindowHeaderProps & import("react").RefAttributes<HTMLDivElement>> & {
@@ -41,4 +46,5 @@ export declare const Window: import("react").ForwardRefExoticComponent<Omit<Wind
41
46
  Close: import("react").ForwardRefExoticComponent<WindowControlProps & import("react").RefAttributes<HTMLButtonElement>>;
42
47
  };
43
48
  Content: import("react").ForwardRefExoticComponent<WindowContentProps & import("react").RefAttributes<HTMLDivElement>>;
49
+ MoveCapture: import("react").ForwardRefExoticComponent<WindowMoveCaptureProps & import("react").RefAttributes<HTMLDivElement>>;
44
50
  };
package/dist/window.js CHANGED
@@ -33,7 +33,7 @@ const WindowContent = forwardRef(function WindowContent({ style, ...properties }
33
33
  } });
34
34
  });
35
35
  /** The composable top region of a window; its enclosing Surface owns the material. */
36
- const HeaderRoot = forwardRef(function HeaderRoot({ active = true, beginMoveGesture, children, color, onLostPointerCapture, onMoveError, onPointerCancel, onPointerDown, onPointerMove, onPointerUp, style, ...properties }, ref) {
36
+ const HeaderRoot = forwardRef(function HeaderRoot({ active = true, beginMoveGesture, children, color, maximized = false, onDoubleClick, onLostPointerCapture, onMaximize, onMoveError, onPointerCancel, onPointerDown, onPointerMove, onPointerUp, style, ...properties }, ref) {
37
37
  const resolved = useResolvedAppearance();
38
38
  const spacing = resolved.appearance.spacing;
39
39
  const foreground = style?.color ?? resolveColor(color ?? "foreground:base", resolved.colors);
@@ -41,6 +41,8 @@ const HeaderRoot = forwardRef(function HeaderRoot({ active = true, beginMoveGest
41
41
  active,
42
42
  foreground,
43
43
  iconRadius: scale(resolved.appearance.radius, "xsmall"),
44
+ maximized,
45
+ onMaximize,
44
46
  spacing,
45
47
  transition: transitionTiming(resolved.transaction, resolved.preferences.animations)
46
48
  };
@@ -49,7 +51,11 @@ const HeaderRoot = forwardRef(function HeaderRoot({ active = true, beginMoveGest
49
51
  onPointerDown?.(event);
50
52
  if (!event.defaultPrevented)
51
53
  move.onPointerDown(event);
52
- }, onPointerMove: event => { onPointerMove?.(event); move.onPointerMove(event); }, onPointerUp: event => { onPointerUp?.(event); move.onPointerUp(event); }, onPointerCancel: event => { onPointerCancel?.(event); move.onPointerCancel(event); }, onLostPointerCapture: event => { onLostPointerCapture?.(event); move.onLostPointerCapture(event); }, style: {
54
+ }, onPointerMove: event => { onPointerMove?.(event); move.onPointerMove(event); }, onPointerUp: event => { onPointerUp?.(event); move.onPointerUp(event); }, onPointerCancel: event => { onPointerCancel?.(event); move.onPointerCancel(event); }, onLostPointerCapture: event => { onLostPointerCapture?.(event); move.onLostPointerCapture(event); }, onDoubleClick: event => {
55
+ onDoubleClick?.(event);
56
+ if (!event.defaultPrevented)
57
+ onMaximize?.();
58
+ }, style: {
53
59
  boxSizing: "border-box",
54
60
  display: "flex",
55
61
  alignItems: "center",
@@ -118,8 +124,10 @@ const WindowAction = forwardRef(function WindowAction({ ...properties }, ref) {
118
124
  const WindowMinimize = forwardRef(function WindowMinimize({ "aria-label": label = "Minimize", preventFocusOnPress = true, ...properties }, ref) {
119
125
  return _jsx(WindowAction, { ...properties, ref: ref, "aria-label": label, preventFocusOnPress: preventFocusOnPress, children: _jsx(ControlIcon, { children: _jsx("path", { d: "M1.5 7.5h7" }) }) });
120
126
  });
121
- const WindowMaximize = forwardRef(function WindowMaximize({ "aria-label": label, maximized = false, ...properties }, ref) {
122
- return _jsx(WindowAction, { ...properties, ref: ref, "aria-label": label ?? (maximized ? "Restore" : "Maximize"), children: _jsx(ControlIcon, { children: maximized
127
+ const WindowMaximize = forwardRef(function WindowMaximize({ "aria-label": label, maximized, onPress, ...properties }, ref) {
128
+ const header = useHeader();
129
+ const selected = maximized ?? header.maximized;
130
+ return _jsx(WindowAction, { ...properties, ref: ref, onPress: onPress ?? header.onMaximize, "aria-label": label ?? (selected ? "Restore" : "Maximize"), children: _jsx(ControlIcon, { children: selected
123
131
  ? _jsx("path", { d: "M1 4h5v5H1zM4 1h5v5H6.5", strokeWidth: "1.3", strokeLinejoin: "round" })
124
132
  : _jsx("path", { d: "M1.5 1.5h7v7h-7z", strokeWidth: "1.3", strokeLinejoin: "round" }) }) });
125
133
  });
@@ -138,8 +146,20 @@ const Header = Object.assign(HeaderRoot, {
138
146
  Maximize: WindowMaximize,
139
147
  Close: WindowClose
140
148
  });
149
+ /** The active full-viewport side of a Window move handoff. */
150
+ const WindowMoveCapture = forwardRef(function WindowMoveCapture({ style, ...properties }, ref) {
151
+ return _jsx("div", { ...properties, ref: ref, style: {
152
+ position: "fixed",
153
+ inset: 0,
154
+ touchAction: "none",
155
+ userSelect: "none",
156
+ cursor: "grab",
157
+ ...style
158
+ } });
159
+ });
141
160
  /** One Surface with a ready-to-use header-and-content composition. */
142
161
  export const Window = Object.assign(WindowRoot, {
143
162
  Header,
144
- Content: WindowContent
163
+ Content: WindowContent,
164
+ MoveCapture: WindowMoveCapture
145
165
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/react-ui",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "description": "React components for coherent PhreshOS Program interfaces.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -57,7 +57,7 @@
57
57
  "dependencies": {
58
58
  "@base-ui/react": "^1.7.0",
59
59
  "@internationalized/date": "^3.12.3",
60
- "@phreshos/core": "^0.1.59",
60
+ "@phreshos/core": "^0.1.60",
61
61
  "colorjs.io": "^0.7.1",
62
62
  "motion": "^13.2.0",
63
63
  "react-aria": "^3.51.0",