@phreshos/react-ui 0.1.18 → 0.1.19

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 CHANGED
@@ -92,9 +92,9 @@ wrapper. The host retains ownership of its own behavior and layout properties;
92
92
  Surface retains ownership of material, edge, radius, and required geometry.
93
93
 
94
94
  `MaterialOptions` defines `opacity`, `backdrop`, `grain`, `grainAmount`,
95
- `distortion`, and `saturation`. Surface accepts these properties directly because
96
- it is the material-owning element; it does not accept a nested `material` prop.
97
- Color remains a separate Surface property. Omitted material values follow
95
+ `distortion`, and `saturation`. Every material-bearing component, including
96
+ Surface, exposes these values through its `material` prop. Color remains a
97
+ separate property. Omitted material values follow
98
98
  `appearance.material`. Effect options accept a scale level or a direct number;
99
99
  opacity affects Surface paint only, never its content. The material edge is part
100
100
  of the same Surface rather than a second public entity.
@@ -139,11 +139,11 @@ Theme names never imply particular colors. No component requires a Client or
139
139
  Server SDK.
140
140
 
141
141
  Fields distinguish hover, pointer focus, keyboard focus, and invalid state.
142
- Shared CSS transitions use a fixed 120ms ease-out timing for colors and corner
142
+ Shared CSS transitions use `appearance.transaction` for colors and corner
143
143
  radius. The material fill and opacity values transition on the painted layers, not on the
144
144
  Surface host. Select menus combine a small placement-aware slide with an overlay
145
145
  opacity fade for entry and exit, using React Aria's animation lifecycle. Reduced-motion preferences make
146
- these changes immediate. No Appearance configuration is added for motion yet.
146
+ these changes immediate without changing the Appearance value.
147
147
  Blur, distortion, geometry, and Surface host opacity are not transitioned; gradients
148
148
  and structurally removed effects change directly rather than adding extra
149
149
  layers or keeping disabled effects alive.
@@ -216,6 +216,12 @@ bun run verify
216
216
  `verify` checks the contracts, tests the components, builds the package, and
217
217
  validates its published shape.
218
218
 
219
+ `check` performs static checks, `build` creates distributable output, and `test`
220
+ runs Vitest assertions from `tests/`. Run `build` before testing built artifacts.
221
+ `verify` runs `check`, `build`, and `test` in order. Operational tooling belongs
222
+ in `scripts/`; tests and their fixtures belong in `tests/`. Verification uses
223
+ the committed dependency graph without local package substitutions.
224
+
219
225
  ## Related repositories
220
226
 
221
227
  - [`@phreshos/core`](https://github.com/PhreshOS/core) owns Appearance, Theme,
@@ -0,0 +1,18 @@
1
+ import { type Appearance, type Theme, type ThemedValue } from "@phreshos/core";
2
+ export declare const AppearanceContext: import("react").Context<Readonly<{
3
+ colors: ThemedValue<import("@phreshos/core").AppearanceColors>;
4
+ spacing: number;
5
+ radius: number;
6
+ shadow: ThemedValue<import("@phreshos/core").AppearanceShadow>;
7
+ material: ThemedValue<import("@phreshos/core").AppearanceMaterial>;
8
+ transaction: import("@phreshos/core").AppearanceTransaction;
9
+ signInWallpaper: ThemedValue<string | null>;
10
+ desktopWallpaper: ThemedValue<string | null>;
11
+ }>>;
12
+ export declare const ThemeContext: import("react").Context<Theme | null>;
13
+ /** Returns the nearest unresolved Appearance, or Core's complete default. */
14
+ export declare function useAppearance(): Appearance;
15
+ /** Returns the nearest explicit Theme, or reactively follows the browser. */
16
+ export declare function useTheme(): Theme;
17
+ /** Selects the active branch of one complete ThemedValue. */
18
+ export declare function useThemedValue<Value>(value: ThemedValue<Value>): Value;
@@ -0,0 +1,37 @@
1
+ import { createContext, useContext, useSyncExternalStore } from "react";
2
+ import { defaultAppearance } from "@phreshos/core";
3
+ export const AppearanceContext = createContext(defaultAppearance);
4
+ export const ThemeContext = createContext(null);
5
+ /** Returns the nearest unresolved Appearance, or Core's complete default. */
6
+ export function useAppearance() {
7
+ return useContext(AppearanceContext);
8
+ }
9
+ /** Returns the nearest explicit Theme, or reactively follows the browser. */
10
+ export function useTheme() {
11
+ const theme = useContext(ThemeContext);
12
+ const browserTheme = useBrowserTheme();
13
+ return theme ?? browserTheme;
14
+ }
15
+ /** Selects the active branch of one complete ThemedValue. */
16
+ export function useThemedValue(value) {
17
+ return value[useTheme()];
18
+ }
19
+ function useBrowserTheme() {
20
+ return useSyncExternalStore(subscribeBrowserTheme, browserTheme, serverTheme);
21
+ }
22
+ function subscribeBrowserTheme(change) {
23
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function")
24
+ return () => undefined;
25
+ const preference = window.matchMedia(darkThemeQuery);
26
+ preference.addEventListener("change", change);
27
+ return () => preference.removeEventListener("change", change);
28
+ }
29
+ function browserTheme() {
30
+ return typeof window !== "undefined"
31
+ && typeof window.matchMedia === "function"
32
+ && window.matchMedia(darkThemeQuery).matches
33
+ ? "dark"
34
+ : "light";
35
+ }
36
+ function serverTheme() { return "light"; }
37
+ const darkThemeQuery = "(prefers-color-scheme: dark)";
@@ -1,13 +1,8 @@
1
1
  import type { ReactNode } from "react";
2
- import { type Appearance, type Theme, type ThemedValue } from "@phreshos/core";
2
+ import { type Appearance, type Theme } from "@phreshos/core";
3
+ export { useAppearance, useTheme, useThemedValue } from "./appearance-context.js";
3
4
  /** Optionally overrides unresolved Appearance and effective Theme for a React subtree. */
4
5
  export declare function AppearanceProvider({ appearance, children, theme }: AppearanceProviderProps): import("react").JSX.Element;
5
- /** Returns the nearest unresolved Appearance, or Core's complete default. */
6
- export declare function useAppearance(): Appearance;
7
- /** Returns the nearest explicit Theme, or reactively follows the browser. */
8
- export declare function useTheme(): Theme;
9
- /** Resolves one themed value only where it is consumed. */
10
- export declare function useResolveTheme<Value, DarkValue extends Value = never>(value: ThemedValue<Value, DarkValue>): Exclude<Value, undefined>;
11
6
  export interface AppearanceProviderProps {
12
7
  readonly appearance?: Appearance;
13
8
  readonly children: ReactNode;
@@ -1,10 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { createContext, useContext, useSyncExternalStore } from "react";
3
- import { defaultAppearance } from "@phreshos/core";
2
+ import {} from "@phreshos/core";
4
3
  import DocumentScrollbars from "./document-scrollbars.js";
5
4
  import MotionStyle from "./motion-style.js";
6
- const AppearanceContext = createContext(defaultAppearance);
7
- const ThemeContext = createContext(null);
5
+ import { AppearanceContext, ThemeContext, useAppearance, useTheme } from "./appearance-context.js";
6
+ export { useAppearance, useTheme, useThemedValue } from "./appearance-context.js";
8
7
  /** Optionally overrides unresolved Appearance and effective Theme for a React subtree. */
9
8
  export function AppearanceProvider({ appearance, children, theme }) {
10
9
  const inheritedAppearance = useAppearance();
@@ -13,37 +12,3 @@ export function AppearanceProvider({ appearance, children, theme }) {
13
12
  const resolvedTheme = theme ?? inheritedTheme;
14
13
  return _jsx(AppearanceContext.Provider, { value: resolvedAppearance, children: _jsxs(ThemeContext.Provider, { value: resolvedTheme, children: [_jsx(MotionStyle, {}), _jsx(DocumentScrollbars, { appearance: resolvedAppearance, theme: resolvedTheme }), children] }) });
15
14
  }
16
- /** Returns the nearest unresolved Appearance, or Core's complete default. */
17
- export function useAppearance() {
18
- return useContext(AppearanceContext);
19
- }
20
- /** Returns the nearest explicit Theme, or reactively follows the browser. */
21
- export function useTheme() {
22
- const theme = useContext(ThemeContext);
23
- const browserTheme = useBrowserTheme();
24
- return theme ?? browserTheme;
25
- }
26
- /** Resolves one themed value only where it is consumed. */
27
- export function useResolveTheme(value) {
28
- const theme = useTheme();
29
- return (theme === "dark" && "dark" in value ? value.dark : value.light);
30
- }
31
- function useBrowserTheme() {
32
- return useSyncExternalStore(subscribeBrowserTheme, browserTheme, serverTheme);
33
- }
34
- function subscribeBrowserTheme(change) {
35
- if (typeof window === "undefined" || typeof window.matchMedia !== "function")
36
- return () => undefined;
37
- const preference = window.matchMedia(darkThemeQuery);
38
- preference.addEventListener("change", change);
39
- return () => preference.removeEventListener("change", change);
40
- }
41
- function browserTheme() {
42
- return typeof window !== "undefined"
43
- && typeof window.matchMedia === "function"
44
- && window.matchMedia(darkThemeQuery).matches
45
- ? "dark"
46
- : "light";
47
- }
48
- function serverTheme() { return "light"; }
49
- const darkThemeQuery = "(prefers-color-scheme: dark)";
package/dist/button.js CHANGED
@@ -3,7 +3,6 @@ import { forwardRef } from "react";
3
3
  import { Button as AriaButton } from "react-aria-components";
4
4
  import { controlFontSizes, useControlTheme } from "./control.js";
5
5
  import { SurfaceButton } from "./control-surface.js";
6
- import { visualTransition } from "./motion-style.js";
7
6
  /** An Appearance-aware action with normalized pointer and keyboard behavior. */
8
7
  export const Button = forwardRef(function Button({ children, color, disabled = false, pending = false, onPress, radius = "medium", size = "medium", style, material, type = "button", ...properties }, ref) {
9
8
  const theme = useControlTheme({ color, radius, size });
@@ -25,7 +24,7 @@ function buttonStyle({ theme, disabled, isFocusVisible, isHovered, isPressed, pe
25
24
  const interactive = !disabled && !pending;
26
25
  const paint = buttonPaint(theme, interactive, isHovered, isPressed);
27
26
  return {
28
- ...visualTransition,
27
+ ...theme.transition,
29
28
  appearance: "none",
30
29
  boxSizing: "border-box",
31
30
  display: "inline-grid",
package/dist/color.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useMemo } from "react";
2
2
  import { ColorSpace, mix, parse, serialize, to, toGamut, contrastWCAG21, sRGB, sRGB_Linear, HSL, HWB, Lab, LCH, OKLab, OKLCH, P3, A98RGB, ProPhoto, REC_2020, XYZ_D50, XYZ_D65 } from "colorjs.io/fn";
3
- import { useAppearance, useResolveTheme } from "./appearance-provider.js";
3
+ import { useAppearance, useThemedValue } from "./appearance-provider.js";
4
4
  // Register the CSS color spaces, without bundling unrelated color-model APIs.
5
5
  for (const space of [sRGB, sRGB_Linear, HSL, HWB, Lab, LCH, OKLab, OKLCH, P3, A98RGB, ProPhoto, REC_2020, XYZ_D50, XYZ_D65])
6
6
  ColorSpace.register(space);
@@ -44,14 +44,14 @@ export function useColor(value) {
44
44
  export function useResolveColor(value = defaultColor) {
45
45
  const appearance = useAppearance();
46
46
  const semantic = parseSemanticColor(value);
47
- const source = useResolveTheme(appearance.colors[semantic?.name ?? "background"]);
47
+ const source = useThemedValue(appearance.colors)[semantic?.name ?? "background"];
48
48
  return semantic ? color(source)[semantic.level] : value;
49
49
  }
50
50
  /** Resolves a color to concrete opaque paint for solid interactive states. */
51
51
  export function useResolveSolidColor(value) {
52
52
  const appearance = useAppearance();
53
53
  const semantic = parseSemanticColor(value);
54
- const source = useResolveTheme(appearance.colors[semantic?.name ?? "background"]);
54
+ const source = useThemedValue(appearance.colors)[semantic?.name ?? "background"];
55
55
  return semantic ? resolveColorLevel(source, semantic.level) : opaqueColor(value);
56
56
  }
57
57
  /** Applies material opacity without restricting the source CSS color syntax. */
@@ -3,9 +3,9 @@ import { Surface } from "./surface.js";
3
3
  /** A native button uses Surface without changing its native contract. */
4
4
  export function SurfaceButton({ native, paint, material: options }) {
5
5
  const { ref, style, children, ...properties } = native;
6
- return _jsx(Surface, { ...properties, ...options, as: "button", color: paint.background, ref: ref, style: { ...style, color: paint.color }, children: children });
6
+ return _jsx(Surface, { ...properties, as: "button", color: paint.background, material: options, ref: ref, style: { ...style, color: paint.color }, children: children });
7
7
  }
8
8
  /** Void text controls use a material host while retaining native sizing. */
9
9
  export function SurfaceField({ paint, radius, children, material: options }) {
10
- return _jsx(Surface, { ...options, as: "span", color: paint.background, radius: radius, style: { color: paint.color, display: "grid", minWidth: 0 }, children: children });
10
+ return _jsx(Surface, { as: "span", color: paint.background, material: options, radius: radius, style: { color: paint.color, display: "grid", minWidth: 0 }, children: children });
11
11
  }
package/dist/control.d.ts CHANGED
@@ -25,6 +25,7 @@ export type ControlOverrides = keyof ControlProps | keyof FieldProps | "children
25
25
  export declare const controlFontSizes: Readonly<Record<ScaleLevel, number>>;
26
26
  type SolidColors = ReturnType<typeof solidColors>;
27
27
  export interface ControlTheme {
28
+ readonly transition: CSSProperties;
28
29
  readonly spacing: number;
29
30
  readonly foreground: string;
30
31
  readonly background: string;
package/dist/control.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo } from "react";
3
3
  import { FieldError, Label, Text } from "react-aria-components";
4
- import { useAppearance, useResolveTheme } from "./appearance-provider.js";
4
+ import { useAppearance, useThemedValue } from "./appearance-provider.js";
5
5
  import { resolveRadius } from "./radius.js";
6
6
  import { scale } from "./scale.js";
7
7
  import { solidColors } from "./color.js";
8
8
  import { useResolveSolidColor } from "./color.js";
9
- import { visualTransition } from "./motion-style.js";
9
+ import { useVisualTransition } from "./motion-style.js";
10
10
  export const controlFontSizes = Object.freeze({
11
11
  xsmall: 11,
12
12
  small: 12,
@@ -16,9 +16,11 @@ export const controlFontSizes = Object.freeze({
16
16
  });
17
17
  export function useControlTheme({ size = "medium", color, radius = "medium" }) {
18
18
  const appearance = useAppearance();
19
- const spacing = scale(useResolveTheme(appearance.spacing), size);
20
- const foreground = useResolveTheme(appearance.colors.foreground);
21
- const background = useResolveTheme(appearance.colors.background);
19
+ const transition = useVisualTransition();
20
+ const spacing = scale(appearance.spacing, size);
21
+ const colors = useThemedValue(appearance.colors);
22
+ const foreground = colors.foreground;
23
+ const background = colors.background;
22
24
  const tint = useResolveSolidColor(color ?? "primary:base");
23
25
  const danger = useResolveSolidColor("danger:base");
24
26
  const paints = useMemo(() => ({
@@ -27,6 +29,7 @@ export function useControlTheme({ size = "medium", color, radius = "medium" }) {
27
29
  danger: solidColors(danger, background, foreground)
28
30
  }), [tint, background, foreground, danger]);
29
31
  return {
32
+ transition,
30
33
  spacing,
31
34
  foreground,
32
35
  background,
@@ -43,7 +46,7 @@ export function useControlTheme({ size = "medium", color, radius = "medium" }) {
43
46
  export function fieldStyle(theme, disabled = false, style) {
44
47
  return {
45
48
  ...style,
46
- ...visualTransition,
49
+ ...theme.transition,
47
50
  display: "grid",
48
51
  gap: theme.gap,
49
52
  minWidth: 0,
@@ -61,7 +64,7 @@ export function controlPaint(theme, focused, invalid, hovered = false) {
61
64
  export function controlStyle(theme, focused, invalid, hovered = false, focusVisible = focused) {
62
65
  const paint = controlPaint(theme, focused, invalid, hovered);
63
66
  return {
64
- ...visualTransition,
67
+ ...theme.transition,
65
68
  appearance: "none",
66
69
  boxSizing: "border-box",
67
70
  width: "100%",
@@ -86,7 +86,7 @@ const documents = new WeakMap();
86
86
  /** Applies one Appearance to the complete owning document without rendering. */
87
87
  export default function DocumentScrollbars({ appearance, theme }) {
88
88
  const identity = useRef(Symbol("AppearanceProvider")).current;
89
- const foreground = theme === "dark" ? appearance.colors.foreground.dark : appearance.colors.foreground.light;
89
+ const foreground = appearance.colors[theme].foreground;
90
90
  const padding = 5;
91
91
  const thumbSize = 6;
92
92
  const size = thumbSize + padding * 2;
@@ -95,7 +95,7 @@ export default function DocumentScrollbars({ appearance, theme }) {
95
95
  thumbHover: colorOpacity(foreground, 0.2),
96
96
  size: `${size}px`,
97
97
  padding: `${padding}px`,
98
- radius: `${Math.min(appearance.radius.light, padding + thumbSize / 2)}px`
98
+ radius: `${Math.min(appearance.radius, padding + thumbSize / 2)}px`
99
99
  };
100
100
  useInsertionEffect(function () {
101
101
  if (typeof document === "undefined")
package/dist/main.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * behavior contract has been established by the package's tests.
6
6
  */
7
7
  export { defaultAppearance } from "@phreshos/core";
8
- export { AppearanceProvider, useAppearance, useResolveTheme, useTheme, type AppearanceProviderProps } from "./appearance-provider.js";
8
+ export { AppearanceProvider, useAppearance, useThemedValue, useTheme, type AppearanceProviderProps } from "./appearance-provider.js";
9
9
  export { Flex, type FlexProps } from "./flex.js";
10
10
  export { Grid, type GridProps } from "./grid.js";
11
11
  export { Surface, type MaterialOptions, type SurfaceComponent, type SurfaceHost, type SurfaceHostProps, type SurfaceOwnProps, type SurfaceProps } from "./surface.js";
package/dist/main.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * behavior contract has been established by the package's tests.
6
6
  */
7
7
  export { defaultAppearance } from "@phreshos/core";
8
- export { AppearanceProvider, useAppearance, useResolveTheme, useTheme } from "./appearance-provider.js";
8
+ export { AppearanceProvider, useAppearance, useThemedValue, useTheme } from "./appearance-provider.js";
9
9
  export { Flex } from "./flex.js";
10
10
  export { Grid } from "./grid.js";
11
11
  export { Surface } from "./surface.js";
@@ -1,10 +1,10 @@
1
1
  import { appearanceLimits } from "@phreshos/core";
2
- import { useAppearance, useResolveTheme } from "./appearance-provider.js";
2
+ import { useAppearance, useThemedValue } from "./appearance-provider.js";
3
3
  import { isScaleLevel, scale, scaleMultiplier } from "./scale.js";
4
4
  /** Resolves material values from the current Appearance without knowing any host geometry. */
5
5
  export function useMaterialOptions(values = {}) {
6
6
  const appearance = useAppearance();
7
- const defaults = useResolveTheme(appearance.material);
7
+ const defaults = useThemedValue(appearance.material);
8
8
  const limits = appearanceLimits.material;
9
9
  return {
10
10
  opacity: resolve(values.opacity, defaults.opacity, limits.opacity),
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo } from "react";
3
- import { paintTransition } from "./motion-style.js";
3
+ import { usePaintTransition } from "./motion-style.js";
4
4
  /** The fill, refraction definition, and grain of one Surface. */
5
5
  export function MaterialPaint({ color, distortion, grain, grainAmount, identity, opacity }) {
6
+ const paintTransition = usePaintTransition();
6
7
  const seed = useMemo(() => seedFrom(identity), [identity]);
7
8
  const hasPaint = opacity > 0;
8
9
  const hasGrain = hasPaint && grain > 0 && grainAmount > 0;
@@ -1,21 +1,13 @@
1
- /** The same fixed timing for Motion-driven control movements. */
2
- export declare const controlTransition: {
3
- readonly type: "tween";
4
- readonly duration: 0.12;
5
- readonly ease: "easeOut";
6
- };
7
- /** Host opacity and filters are excluded: either can change backdrop sampling. */
8
- export declare const visualTransition: {
9
- transitionProperty: "background-color, color, border-color, border-radius";
10
- transitionDuration: "var(--phreshos-ui-motion-duration, 120ms)";
11
- transitionTimingFunction: "ease-out";
12
- };
13
- /** Radius is inherited from the host; only the painted values transition here. */
14
- export declare const paintTransition: {
15
- transitionProperty: "fill, stroke, opacity";
16
- transitionDuration: "var(--phreshos-ui-motion-duration, 120ms)";
17
- transitionTimingFunction: "ease-out";
18
- };
1
+ import type { CSSProperties } from "react";
2
+ import type { Transition } from "motion/react";
3
+ /** CSS timing for values painted directly by React UI. */
4
+ export declare function useVisualTransition(): CSSProperties;
5
+ /** CSS timing for SVG paint owned by a Material. */
6
+ export declare function usePaintTransition(): CSSProperties;
7
+ /** Motion timing for interactive geometry owned by React UI. */
8
+ export declare function useControlTransition(reduced?: boolean): Transition;
9
+ /** Per-overlay variables consumed by the shared entrance keyframes. */
10
+ export declare function useOverlayTransition(): CSSProperties;
19
11
  export declare const overlayMotionClass = "phreshos-ui-overlay";
20
- /** React hoists and deduplicates this stylesheet, including nested providers and portals. */
12
+ /** React hoists and deduplicates this stylesheet, including portals. */
21
13
  export default function MotionStyle(): import("react").JSX.Element;
@@ -1,33 +1,72 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- const timing = {
3
- transitionDuration: "var(--phreshos-ui-motion-duration, 120ms)",
4
- transitionTimingFunction: "ease-out"
5
- };
6
- /** The same fixed timing for Motion-driven control movements. */
7
- export const controlTransition = { type: "tween", duration: 0.12, ease: "easeOut" };
8
- /** Host opacity and filters are excluded: either can change backdrop sampling. */
9
- export const visualTransition = {
10
- ...timing,
11
- transitionProperty: "background-color, color, border-color, border-radius"
12
- };
13
- /** Radius is inherited from the host; only the painted values transition here. */
14
- export const paintTransition = {
15
- ...timing,
16
- transitionProperty: "fill, stroke, opacity"
17
- };
2
+ import { defaultAppearance } from "@phreshos/core";
3
+ import { useAppearance } from "./appearance-context.js";
4
+ const visualProperties = "background-color, color, border-color, border-radius";
5
+ const paintProperties = "fill, stroke, opacity";
6
+ /** CSS timing for values painted directly by React UI. */
7
+ export function useVisualTransition() {
8
+ return { ...useTransitionTiming(), transitionProperty: visualProperties };
9
+ }
10
+ /** CSS timing for SVG paint owned by a Material. */
11
+ export function usePaintTransition() {
12
+ return { ...useTransitionTiming(), transitionProperty: paintProperties };
13
+ }
14
+ /** Motion timing for interactive geometry owned by React UI. */
15
+ export function useControlTransition(reduced = false) {
16
+ const { duration, easing } = useAppearance().transaction;
17
+ return {
18
+ type: "tween",
19
+ duration: reduced ? 0 : duration / 1_000,
20
+ ease: motionEasing(easing)
21
+ };
22
+ }
23
+ /** Per-overlay variables consumed by the shared entrance keyframes. */
24
+ export function useOverlayTransition() {
25
+ const { duration, easing } = useAppearance().transaction;
26
+ return {
27
+ "--phreshos-ui-motion-duration": `${duration}ms`,
28
+ "--phreshos-ui-motion-easing": cssEasing(easing)
29
+ };
30
+ }
18
31
  export const overlayMotionClass = "phreshos-ui-overlay";
32
+ function useTransitionTiming() {
33
+ const { duration, easing } = useAppearance().transaction;
34
+ return {
35
+ transitionDuration: `${duration}ms`,
36
+ transitionTimingFunction: cssEasing(easing)
37
+ };
38
+ }
39
+ function cssEasing(easing) {
40
+ return typeof easing === "string" ? easing : `cubic-bezier(${easing.join(", ")})`;
41
+ }
42
+ function motionEasing(easing) {
43
+ if (typeof easing !== "string")
44
+ return [...easing];
45
+ if (easing === "ease")
46
+ return [0.25, 0.1, 0.25, 1];
47
+ if (easing === "ease-in")
48
+ return "easeIn";
49
+ if (easing === "ease-out")
50
+ return "easeOut";
51
+ if (easing === "ease-in-out")
52
+ return "easeInOut";
53
+ return easing;
54
+ }
19
55
  const stylesheet = `
20
- :root { --phreshos-ui-motion-duration: 120ms; }
56
+ :root {
57
+ --phreshos-ui-motion-duration: ${defaultAppearance.transaction.duration}ms;
58
+ --phreshos-ui-motion-easing: ${cssEasing(defaultAppearance.transaction.easing)};
59
+ }
21
60
 
22
61
  .phreshos-ui-overlay { --phreshos-ui-overlay-x: 0px; --phreshos-ui-overlay-y: -4px; }
23
62
  .phreshos-ui-overlay[data-placement="top"] { --phreshos-ui-overlay-y: 4px; }
24
63
  .phreshos-ui-overlay[data-placement="left"] { --phreshos-ui-overlay-x: 4px; --phreshos-ui-overlay-y: 0px; }
25
64
  .phreshos-ui-overlay[data-placement="right"] { --phreshos-ui-overlay-x: -4px; --phreshos-ui-overlay-y: 0px; }
26
65
  .phreshos-ui-overlay[data-entering] {
27
- animation: phreshos-ui-overlay-enter var(--phreshos-ui-motion-duration, 120ms) ease-out both;
66
+ animation: phreshos-ui-overlay-enter var(--phreshos-ui-motion-duration) var(--phreshos-ui-motion-easing) both;
28
67
  }
29
68
  .phreshos-ui-overlay[data-exiting] {
30
- animation: phreshos-ui-overlay-enter var(--phreshos-ui-motion-duration, 120ms) ease-out reverse both;
69
+ animation: phreshos-ui-overlay-enter var(--phreshos-ui-motion-duration) var(--phreshos-ui-motion-easing) reverse both;
31
70
  pointer-events: none;
32
71
  }
33
72
  @keyframes phreshos-ui-overlay-enter {
@@ -39,7 +78,7 @@ const stylesheet = `
39
78
  .phreshos-ui-overlay[data-entering], .phreshos-ui-overlay[data-exiting] { animation: none; }
40
79
  }
41
80
  `;
42
- /** React hoists and deduplicates this stylesheet, including nested providers and portals. */
81
+ /** React hoists and deduplicates this stylesheet, including portals. */
43
82
  export default function MotionStyle() {
44
83
  return _jsx("style", { href: "phreshos-react-ui-motion", precedence: "phreshos", children: stylesheet });
45
84
  }
package/dist/panel.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
- import { useAppearance, useResolveTheme } from "./appearance-provider.js";
3
+ import { useAppearance } from "./appearance-provider.js";
4
4
  import { scale } from "./scale.js";
5
5
  import { Surface } from "./surface.js";
6
6
  /** Owns the shared shell layout, not positioning, interaction, or lifecycle. */
7
7
  export const Panel = forwardRef(function Panel({ header, children, contentProps, style, ...properties }, ref) {
8
8
  const appearance = useAppearance();
9
- const inset = scale(useResolveTheme(appearance.spacing), "small");
9
+ const inset = scale(appearance.spacing, "small");
10
10
  const hasHeader = header !== undefined && header !== null && header !== false;
11
11
  return _jsxs(Surface, { ...properties, ref: ref, style: {
12
12
  display: "grid",
package/dist/radius.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import type { CSSProperties } from "react";
2
- import type { Appearance, Shapeable } from "@phreshos/core";
2
+ import type { Appearance } from "@phreshos/core";
3
3
  import { type ScaleLevel } from "./scale.js";
4
4
  /** An Appearance-derived level, pixel value, or explicit CSS corner radius. */
5
5
  export type Radius = ScaleLevel | number | (string & {});
6
6
  /** Shared semantic corner-radius capability for React UI components. */
7
- export interface RadiusProps extends Shapeable<Radius> {
7
+ export interface RadiusProps {
8
+ readonly radius?: Radius;
8
9
  }
9
10
  /** Resolves a Radius while preserving explicit CSS and pixel values. */
10
11
  export declare function resolveRadius(value: Radius | undefined, appearance: Appearance | null): CSSProperties["borderRadius"];
package/dist/radius.js CHANGED
@@ -5,5 +5,5 @@ export function resolveRadius(value, appearance) {
5
5
  return value;
6
6
  if (!appearance)
7
7
  throw new Error("A semantic radius requires an AppearanceProvider");
8
- return scale(appearance.radius.light, value);
8
+ return scale(appearance.radius, value);
9
9
  }
package/dist/select.js CHANGED
@@ -5,17 +5,19 @@ import { Select as AriaSelect, Button, SelectValue, Popover, ListBox, ListBoxIte
5
5
  import { controlPaint, controlStyle, FieldFeedback, FieldLabel, fieldStyle, useControlTheme } from "./control.js";
6
6
  import { SurfaceButton } from "./control-surface.js";
7
7
  import { Surface } from "./surface.js";
8
- import { controlTransition, overlayMotionClass, visualTransition } from "./motion-style.js";
8
+ import { overlayMotionClass, useControlTransition, useOverlayTransition } from "./motion-style.js";
9
9
  /** Single selection from string-valued options; keyboard navigation and typeahead stay native to React Aria. */
10
10
  export const Select = forwardRef(function Select({ label, description, errorMessage, disabled, required, invalid, options, value, defaultValue, onChange, size, color, radius, style, material, ...properties }, ref) {
11
11
  const theme = useControlTheme({ size, color, radius });
12
12
  const reduced = useReducedMotion();
13
+ const transition = useControlTransition(Boolean(reduced));
14
+ const overlayTransition = useOverlayTransition();
13
15
  return _jsx(AriaSelect, { ...properties, ref: ref, value: value, defaultValue: defaultValue, onChange: key => onChange?.(key == null ? null : String(key)), disabledKeys: options.filter(option => option.disabled).map(option => option.value), isDisabled: disabled, isRequired: required, isInvalid: invalid, style: fieldStyle(theme, disabled, style), children: state => _jsxs(_Fragment, { children: [_jsx(FieldLabel, { label: label }), _jsxs(Button, { render: (native, button) => _jsx(SurfaceButton, { native: native, material: material, paint: controlPaint(theme, state.isOpen || button.isFocused, state.isInvalid, button.isHovered) }), style: button => ({
14
16
  ...controlStyle(theme, state.isOpen || button.isFocused, state.isInvalid, button.isHovered, button.isFocusVisible),
15
17
  display: "flex", alignItems: "center", justifyContent: "space-between", gap: theme.gap,
16
18
  cursor: disabled ? "not-allowed" : "pointer", textAlign: "start"
17
- }), children: [_jsx(SelectValue, { style: ({ isPlaceholder }) => ({ opacity: isPlaceholder ? 0.6 : 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }) }), _jsx(motion.svg, { "aria-hidden": "true", width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "1.5", initial: false, animate: { rotate: state.isOpen ? 180 : 0 }, transition: { ...controlTransition, duration: reduced ? 0 : controlTransition.duration }, style: { flexShrink: 0 }, children: _jsx("path", { d: "m2 4 4 4 4-4" }) })] }), _jsx(Popover, { className: overlayMotionClass, placement: "bottom start", offset: theme.gap, maxHeight: 280 + theme.gap * 2, style: { width: "var(--trigger-width)", maxWidth: "calc(100vw - 16px)" }, children: _jsx(Surface, { style: { padding: theme.gap, display: "flex", flexDirection: "column", maxHeight: "inherit", boxSizing: "border-box" }, children: _jsx(ListBox, { items: options, style: { display: "grid", gap: theme.gap, minHeight: 0, overflow: "auto", outline: "none", fontSize: theme.fontSize, color: theme.foreground }, children: option => _jsx(ListBoxItem, { id: option.value, textValue: option.label, style: item => ({
18
- ...visualTransition,
19
+ }), children: [_jsx(SelectValue, { style: ({ isPlaceholder }) => ({ opacity: isPlaceholder ? 0.6 : 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }) }), _jsx(motion.svg, { "aria-hidden": "true", width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "1.5", initial: false, animate: { rotate: state.isOpen ? 180 : 0 }, transition: transition, style: { flexShrink: 0 }, children: _jsx("path", { d: "m2 4 4 4 4-4" }) })] }), _jsx(Popover, { className: overlayMotionClass, placement: "bottom start", offset: theme.gap, maxHeight: 280 + theme.gap * 2, style: { ...overlayTransition, width: "var(--trigger-width)", maxWidth: "calc(100vw - 16px)" }, children: _jsx(Surface, { style: { padding: theme.gap, display: "flex", flexDirection: "column", maxHeight: "inherit", boxSizing: "border-box" }, children: _jsx(ListBox, { items: options, style: { display: "grid", gap: theme.gap, minHeight: 0, overflow: "auto", outline: "none", fontSize: theme.fontSize, color: theme.foreground }, children: option => _jsx(ListBoxItem, { id: option.value, textValue: option.label, style: item => ({
20
+ ...theme.transition,
19
21
  display: "flex", alignItems: "center", justifyContent: "space-between", gap: theme.gap,
20
22
  minHeight: theme.height, paddingInline: Math.max(8, theme.spacing), boxSizing: "border-box",
21
23
  borderRadius: theme.radius,
package/dist/slider.js CHANGED
@@ -3,7 +3,7 @@ import { forwardRef, useId } from "react";
3
3
  import { motion, useReducedMotion } from "motion/react";
4
4
  import { Slider as AriaSlider, SliderTrack, SliderThumb, SliderFill, SliderOutput } from "react-aria-components";
5
5
  import { FieldLabel, fieldStyle, useControlTheme } from "./control.js";
6
- import { controlTransition, visualTransition } from "./motion-style.js";
6
+ import { useControlTransition } from "./motion-style.js";
7
7
  /** One numeric value with pointer, touch, keyboard, and form support. */
8
8
  export const Slider = forwardRef(function Slider({ label, description, name, disabled, size, color, style, orientation = "horizontal", ...properties }, ref) {
9
9
  const theme = useControlTheme({ size, color });
@@ -11,17 +11,18 @@ export const Slider = forwardRef(function Slider({ label, description, name, dis
11
11
  const vertical = orientation === "vertical";
12
12
  const diameter = theme.fontSize + 6;
13
13
  const reduced = useReducedMotion();
14
+ const transition = useControlTransition(Boolean(reduced));
14
15
  const rail = Math.max(4, Math.round(diameter / 3));
15
16
  return _jsxs(AriaSlider, { ...properties, ref: ref, isDisabled: disabled, orientation: orientation, style: fieldStyle(theme, disabled, style), "aria-describedby": [properties["aria-describedby"], description != null ? descriptionId : null].filter(Boolean).join(" ") || undefined, children: [_jsxs("div", { style: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: theme.gap }, children: [_jsx(FieldLabel, { label: label }), _jsx(SliderOutput, { style: { fontVariantNumeric: "tabular-nums", opacity: 0.7 } })] }), _jsxs(SliderTrack, { style: {
16
17
  position: "relative", touchAction: "none",
17
18
  width: vertical ? theme.height : "100%", height: vertical ? 160 : theme.height
18
19
  }, children: [_jsx("div", { "aria-hidden": "true", style: {
19
- ...visualTransition,
20
+ ...theme.transition,
20
21
  position: "absolute", borderRadius: rail,
21
22
  ...(vertical ? { width: rail, height: "100%", left: "50%", transform: "translateX(-50%)" } : { height: rail, width: "100%", top: "50%", transform: "translateY(-50%)" }),
22
23
  background: theme.paints.neutral.rest.background
23
24
  } }), _jsx(SliderFill, { style: {
24
- ...visualTransition,
25
+ ...theme.transition,
25
26
  position: "absolute", background: theme.paints.palette.rest.background, borderRadius: rail,
26
27
  ...(vertical ? { width: rail, left: "50%", transform: "translateX(-50%)" } : { height: rail, top: "50%", transform: "translateY(-50%)" })
27
28
  } }), _jsx(SliderThumb, { name: name, style: state => ({
@@ -29,7 +30,7 @@ export const Slider = forwardRef(function Slider({ label, description, name, dis
29
30
  ...(vertical ? { left: "50%" } : { top: "50%" }),
30
31
  outline: state.isFocusVisible ? `2px solid ${theme.foreground}` : "none", outlineOffset: 2,
31
32
  cursor: state.isDisabled ? "not-allowed" : state.isDragging ? "grabbing" : "grab"
32
- }), children: state => _jsx(motion.span, { "aria-hidden": "true", initial: false, animate: { scale: reduced || state.isDisabled ? 1 : state.isDragging ? 0.92 : state.isHovered ? 1.08 : 1 }, transition: { ...controlTransition, duration: reduced ? 0 : controlTransition.duration }, style: { ...visualTransition, position: "absolute", inset: 0, borderRadius: "inherit", pointerEvents: "none",
33
+ }), children: state => _jsx(motion.span, { "aria-hidden": "true", initial: false, animate: { scale: reduced || state.isDisabled ? 1 : state.isDragging ? 0.92 : state.isHovered ? 1.08 : 1 }, transition: transition, style: { ...theme.transition, position: "absolute", inset: 0, borderRadius: "inherit", pointerEvents: "none",
33
34
  background: (state.isDragging ? theme.paints.palette.pressed : state.isHovered ? theme.paints.palette.hover : theme.paints.palette.rest).color,
34
35
  border: `3px solid ${(state.isDragging ? theme.paints.palette.pressed : state.isHovered ? theme.paints.palette.hover : theme.paints.palette.rest).background}`,
35
36
  boxSizing: "border-box" } }) })] }), description != null && _jsx("span", { id: descriptionId, style: { fontSize: "0.92em", opacity: 0.7 }, children: description })] });
package/dist/spacing.js CHANGED
@@ -4,5 +4,5 @@ export function resolveSpacing(value, appearance) {
4
4
  return value;
5
5
  if (!appearance)
6
6
  throw new Error("Semantic spacing requires an AppearanceProvider");
7
- return scale(appearance.spacing.light, value);
7
+ return scale(appearance.spacing, value);
8
8
  }
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Fragment, useCallback, useLayoutEffect, useRef, useState } from "react";
3
3
  import { colorLightness, colorOpacity, orderColors } from "./color.js";
4
- import { paintTransition } from "./motion-style.js";
4
+ import { usePaintTransition } from "./motion-style.js";
5
5
  import { scale } from "./scale.js";
6
6
  /** Paints the boundary owned by one Surface from the material it contains. */
7
7
  export function SurfaceEdge({ material }) {
8
+ const paintTransition = usePaintTransition();
8
9
  const visible = material.opacity > 0;
9
10
  const palette = useRef(null);
10
11
  const [colors, setColors] = useState(null);
package/dist/surface.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import type { ComponentPropsWithRef, CSSProperties, ElementType, ReactElement, ReactNode } from "react";
2
2
  import { type Color } from "./color.js";
3
- import { type MaterialOptions } from "./material-options.js";
3
+ import { type MaterialOverrides } from "./material-options.js";
4
4
  import { type RadiusProps } from "./radius.js";
5
5
  export type { MaterialOptions } from "./material-options.js";
6
- export interface SurfaceOwnProps extends MaterialOptions, RadiusProps {
6
+ export interface SurfaceOwnProps extends MaterialOverrides, RadiusProps {
7
7
  readonly color?: Color;
8
8
  }
9
9
  /**
package/dist/surface.js CHANGED
@@ -1,22 +1,23 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createElement, forwardRef, useId } from "react";
3
- import { useAppearance, useResolveTheme } from "./appearance-provider.js";
3
+ import { useAppearance, useThemedValue } from "./appearance-provider.js";
4
4
  import { useResolveColor } from "./color.js";
5
5
  import { useMaterialOptions } from "./material-options.js";
6
6
  import { MaterialPaint } from "./material-paint.js";
7
- import MotionStyle, { visualTransition } from "./motion-style.js";
7
+ import MotionStyle, { useVisualTransition } from "./motion-style.js";
8
8
  import { resolveRadius } from "./radius.js";
9
9
  import { SurfaceEdge } from "./surface-edge.js";
10
10
  /** One material-owning element. The host is a div unless `as` selects another element. */
11
- const SurfaceRoot = forwardRef(function Surface({ as: Element = "div", color, opacity, backdrop, grain, grainAmount, distortion, saturation, radius, children, style, ...properties }, ref) {
11
+ const SurfaceRoot = forwardRef(function Surface({ as: Element = "div", color, material: options, radius, children, style, ...properties }, ref) {
12
12
  const appearance = useAppearance();
13
- const material = useResolvedSurface(color, { opacity, backdrop, grain, grainAmount, distortion, saturation });
13
+ const transition = useVisualTransition();
14
+ const material = useResolvedSurface(color, options);
14
15
  const resolvedRadius = resolveRadius(radius ?? "medium", appearance);
15
16
  return createElement(Element, {
16
17
  ...properties,
17
18
  ref,
18
19
  style: {
19
- ...visualTransition,
20
+ ...transition,
20
21
  ...style,
21
22
  background: "transparent",
22
23
  color: style?.color ?? material.foreground,
@@ -33,7 +34,7 @@ function useResolvedSurface(color, options) {
33
34
  return {
34
35
  ...material,
35
36
  color: useResolveColor(color),
36
- foreground: useResolveTheme(appearance.colors.foreground)
37
+ foreground: useThemedValue(appearance.colors).foreground
37
38
  };
38
39
  }
39
40
  const layerStyle = {
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { motion, useReducedMotion } from "motion/react";
3
3
  import { useLocale } from "react-aria-components";
4
- import { controlTransition, visualTransition } from "./motion-style.js";
4
+ import { useControlTransition } from "./motion-style.js";
5
5
  import { Surface } from "./surface.js";
6
6
  /** Shared paint only. Selection, focus, validation, and native input behavior belong to React Aria. */
7
7
  export function ToggleIndicator({ kind, selected, indeterminate = false, focused, invalid = false, hovered = false, pressed = false, theme, material: options }) {
8
8
  const reduced = useReducedMotion();
9
+ const transition = useControlTransition(Boolean(reduced));
9
10
  const { direction } = useLocale();
10
11
  const diameter = theme.fontSize + 6;
11
12
  const active = selected || indeterminate;
@@ -15,9 +16,9 @@ export function ToggleIndicator({ kind, selected, indeterminate = false, focused
15
16
  const switching = kind === "switch";
16
17
  const thumbWidth = diameter - 6 + (pressed && !reduced ? 3 : 0);
17
18
  const travel = (diameter * 2 - thumbWidth) / 2 - 3;
18
- return _jsx(Surface, { as: motion.span, ...options, color: paint.background, "aria-hidden": "true", initial: false, animate: { scale: reduced || switching ? 1 : pressed ? 0.94 : hovered ? 1.03 : 1 }, transition: { ...controlTransition, duration: reduced ? 0 : controlTransition.duration }, style: {
19
+ return _jsx(Surface, { as: motion.span, material: options, color: paint.background, "aria-hidden": "true", initial: false, animate: { scale: reduced || switching ? 1 : pressed ? 0.94 : hovered ? 1.03 : 1 }, transition: transition, style: {
19
20
  color: paint.color,
20
- ...visualTransition,
21
+ ...theme.transition,
21
22
  display: "inline-grid",
22
23
  placeItems: "center",
23
24
  position: "relative",
@@ -29,12 +30,12 @@ export function ToggleIndicator({ kind, selected, indeterminate = false, focused
29
30
  border: "none",
30
31
  outline: focused ? `2px solid ${theme.foreground}` : "none",
31
32
  outlineOffset: 2,
32
- }, children: switching ? _jsx(motion.span, { initial: false, animate: { width: thumbWidth, x: (selected ? travel : -travel) * (direction === "rtl" ? -1 : 1) }, transition: { ...controlTransition, duration: reduced ? 0 : controlTransition.duration }, style: {
33
- ...visualTransition,
33
+ }, children: switching ? _jsx(motion.span, { initial: false, animate: { width: thumbWidth, x: (selected ? travel : -travel) * (direction === "rtl" ? -1 : 1) }, transition: transition, style: {
34
+ ...theme.transition,
34
35
  height: diameter - 6, borderRadius: diameter,
35
36
  background: paint.color
36
- } }) : _jsx(motion.span, { initial: false, animate: { opacity: active ? 1 : 0, scale: active ? 1 : 0.7 }, transition: { ...controlTransition, duration: reduced ? 0 : controlTransition.duration }, style: { display: "grid", placeItems: "center" }, children: kind === "radio" ? _jsx("span", { style: { ...visualTransition, width: diameter / 2, height: diameter / 2, borderRadius: "50%", background: "currentColor" } })
37
- : _jsxs("svg", { width: diameter - 4, height: diameter - 4, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx(motion.path, { d: "m3 8 3 3 7-7", initial: false, animate: { pathLength: selected && !indeterminate ? 1 : 0, opacity: selected && !indeterminate ? 1 : 0 }, transition: { ...controlTransition, duration: reduced ? 0 : controlTransition.duration } }), _jsx(motion.path, { d: "M4 8h8", initial: false, animate: { pathLength: indeterminate ? 1 : 0, opacity: indeterminate ? 1 : 0 }, transition: { ...controlTransition, duration: reduced ? 0 : controlTransition.duration } })] }) }) });
37
+ } }) : _jsx(motion.span, { initial: false, animate: { opacity: active ? 1 : 0, scale: active ? 1 : 0.7 }, transition: transition, style: { display: "grid", placeItems: "center" }, children: kind === "radio" ? _jsx("span", { style: { ...theme.transition, width: diameter / 2, height: diameter / 2, borderRadius: "50%", background: "currentColor" } })
38
+ : _jsxs("svg", { width: diameter - 4, height: diameter - 4, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx(motion.path, { d: "m3 8 3 3 7-7", initial: false, animate: { pathLength: selected && !indeterminate ? 1 : 0, opacity: selected && !indeterminate ? 1 : 0 }, transition: transition }), _jsx(motion.path, { d: "M4 8h8", initial: false, animate: { pathLength: indeterminate ? 1 : 0, opacity: indeterminate ? 1 : 0 }, transition: transition })] }) }) });
38
39
  }
39
40
  export function toggleStyle(theme, disabled, readOnly) {
40
41
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/react-ui",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "React components for coherent PhreshOS Program interfaces.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -45,18 +45,17 @@
45
45
  "scripts": {
46
46
  "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
47
47
  "check": "tsc --noEmit && tsc -p tsconfig.test.json",
48
- "test": "vitest run --environment jsdom",
48
+ "test": "vitest run --project default",
49
49
  "build": "node --run clean && tsc --noEmit false --outDir dist --rootDir source",
50
- "verify:package": "node scripts/verify-package.mjs",
51
- "verify": "node --run check && node --run test && node --run build && node --run verify:package",
52
- "prepack": "node --run test && node --run build"
50
+ "verify": "node --run check && node --run build && node --run test",
51
+ "prepack": "node --run build"
53
52
  },
54
53
  "peerDependencies": {
55
54
  "react": "^19.2.0",
56
55
  "react-dom": "^19.2.0"
57
56
  },
58
57
  "dependencies": {
59
- "@phreshos/core": "^0.1.42",
58
+ "@phreshos/core": "^0.1.43",
60
59
  "colorjs.io": "^0.7.1",
61
60
  "motion": "^13.2.0",
62
61
  "react-aria-components": "^1.20.0"
@@ -72,6 +71,6 @@
72
71
  "react": "^19.2.8",
73
72
  "react-dom": "^19.2.8",
74
73
  "typescript": "^6.0.3",
75
- "vitest": "^4.1.1"
74
+ "vitest": "^4.1.10"
76
75
  }
77
76
  }