@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 +2 -0
- package/dist/color.js +6 -0
- package/dist/main.d.ts +4 -2
- package/dist/main.js +3 -1
- package/dist/readiness.d.ts +37 -0
- package/dist/readiness.js +134 -0
- package/dist/spinner.d.ts +29 -0
- package/dist/spinner.js +42 -0
- package/dist/toggle-indicator.js +4 -0
- package/dist/window.d.ts +6 -0
- package/dist/window.js +25 -5
- package/package.json +2 -2
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 {};
|
package/dist/spinner.js
ADDED
|
@@ -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
|
+
}
|
package/dist/toggle-indicator.js
CHANGED
|
@@ -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); },
|
|
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
|
|
122
|
-
|
|
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.
|
|
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.
|
|
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",
|