@ankhorage/surface 9.4.10 → 9.4.12
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/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/core/responsive/ResponsiveProvider.d.ts.map +1 -1
- package/dist/core/responsive/ResponsiveProvider.js +2 -2
- package/dist/core/responsive/ResponsiveProvider.js.map +1 -1
- package/dist/core/responsive/useBreakpoint.d.ts +1 -0
- package/dist/core/responsive/useBreakpoint.d.ts.map +1 -1
- package/dist/core/responsive/useBreakpoint.js +3 -2
- package/dist/core/responsive/useBreakpoint.js.map +1 -1
- package/dist/core/responsive/useResponsiveWidth.d.ts +3 -0
- package/dist/core/responsive/useResponsiveWidth.d.ts.map +1 -0
- package/dist/core/responsive/useResponsiveWidth.js +21 -0
- package/dist/core/responsive/useResponsiveWidth.js.map +1 -0
- package/dist/features/layout/adapters/inbound/ScrollView.d.ts.map +1 -1
- package/dist/features/layout/adapters/inbound/ScrollView.js +9 -3
- package/dist/features/layout/adapters/inbound/ScrollView.js.map +1 -1
- package/dist/types/layout.d.ts +4 -0
- package/dist/types/layout.d.ts.map +1 -1
- package/dist/types/layout.js.map +1 -1
- package/package.json +2 -2
- package/src/core/responsive/ResponsiveProvider.tsx +2 -2
- package/src/core/responsive/useBreakpoint.ts +3 -3
- package/src/core/responsive/useResponsiveWidth.ts +25 -0
- package/src/features/layout/adapters/inbound/ScrollView.test.tsx +60 -0
- package/src/features/layout/adapters/inbound/ScrollView.tsx +28 -2
- package/src/types/layout.ts +4 -0
- package/src/webAcceptance.test.tsx +94 -19
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @ankhorage/surface
|
|
2
2
|
|
|
3
|
+
## 9.4.12
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5340e24: Support controlled pull-to-refresh state and callbacks through `ScrollView`.
|
|
8
|
+
|
|
9
|
+
## 9.4.11
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 45f3725: Keep responsive rendering stable through server hydration before applying browser viewport changes.
|
|
14
|
+
|
|
3
15
|
## 9.4.10
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# @ankhorage/surface
|
|
5
5
|
|
|
6
|
-
         
|
|
7
7
|
|
|
8
8
|
Cross-platform UI foundation for React Native and React Native Web. Provides theming, layout primitives, and core building blocks for modern apps.
|
|
9
9
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResponsiveProvider.d.ts","sourceRoot":"","sources":["../../../src/core/responsive/ResponsiveProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ResponsiveProvider.d.ts","sourceRoot":"","sources":["../../../src/core/responsive/ResponsiveProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAG3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAKjD,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,qBAY7E;AAED,wBAAgB,oBAAoB,IAAI,iBAAiB,CAQxD"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { createContext, use, useMemo } from 'react';
|
|
2
|
-
import { useWindowDimensions } from 'react-native';
|
|
3
2
|
import { getBreakpointFromWidth } from './getBreakpointFromWidth';
|
|
3
|
+
import { useResponsiveWidth } from './useResponsiveWidth';
|
|
4
4
|
const ResponsiveContext = createContext(null);
|
|
5
5
|
export function ResponsiveProvider({ children }) {
|
|
6
|
-
const
|
|
6
|
+
const width = useResponsiveWidth();
|
|
7
7
|
const value = useMemo(() => ({
|
|
8
8
|
breakpoint: getBreakpointFromWidth(width),
|
|
9
9
|
width,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResponsiveProvider.js","sourceRoot":"","sources":["../../../src/core/responsive/ResponsiveProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ResponsiveProvider.js","sourceRoot":"","sources":["../../../src/core/responsive/ResponsiveProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAE3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,iBAAiB,GAAG,aAAa,CAA2B,IAAI,CAAC,CAAC;AAExE,MAAM,UAAU,kBAAkB,CAAC,EAAE,QAAQ,EAAiC;IAC5E,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;IAEnC,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC;QACL,UAAU,EAAE,sBAAsB,CAAC,KAAK,CAAC;QACzC,KAAK;KACN,CAAC,EACF,CAAC,KAAK,CAAC,CACR,CAAC;IAEF,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,MAAM,OAAO,GAAG,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAEvC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;IACnF,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["import React, { createContext, use, useMemo } from 'react';\n\nimport { getBreakpointFromWidth } from './getBreakpointFromWidth';\nimport type { ResponsiveRuntime } from './types';\nimport { useResponsiveWidth } from './useResponsiveWidth';\n\nconst ResponsiveContext = createContext<ResponsiveRuntime | null>(null);\n\nexport function ResponsiveProvider({ children }: { children: React.ReactNode }) {\n const width = useResponsiveWidth();\n\n const value = useMemo<ResponsiveRuntime>(\n () => ({\n breakpoint: getBreakpointFromWidth(width),\n width,\n }),\n [width],\n );\n\n return <ResponsiveContext value={value}>{children}</ResponsiveContext>;\n}\n\nexport function useResponsiveRuntime(): ResponsiveRuntime {\n const runtime = use(ResponsiveContext);\n\n if (!runtime) {\n throw new Error('useResponsiveRuntime must be used within a ResponsiveProvider');\n }\n\n return runtime;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBreakpoint.d.ts","sourceRoot":"","sources":["../../../src/core/responsive/useBreakpoint.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useBreakpoint.d.ts","sourceRoot":"","sources":["../../../src/core/responsive/useBreakpoint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAG1C,wFAAwF;AACxF,wBAAgB,aAAa,IAAI,UAAU,CAG1C"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { useWindowDimensions } from 'react-native';
|
|
2
1
|
import { getBreakpointFromWidth } from './getBreakpointFromWidth';
|
|
2
|
+
import { useResponsiveWidth } from './useResponsiveWidth';
|
|
3
|
+
/*** Returns the hydration-stable responsive breakpoint without requiring a provider. */
|
|
3
4
|
export function useBreakpoint() {
|
|
4
|
-
const
|
|
5
|
+
const width = useResponsiveWidth();
|
|
5
6
|
return getBreakpointFromWidth(width);
|
|
6
7
|
}
|
|
7
8
|
//# sourceMappingURL=useBreakpoint.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBreakpoint.js","sourceRoot":"","sources":["../../../src/core/responsive/useBreakpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"useBreakpoint.js","sourceRoot":"","sources":["../../../src/core/responsive/useBreakpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,wFAAwF;AACxF,MAAM,UAAU,aAAa;IAC3B,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;IACnC,OAAO,sBAAsB,CAAC,KAAK,CAAC,CAAC;AACvC,CAAC","sourcesContent":["import { getBreakpointFromWidth } from './getBreakpointFromWidth';\nimport type { Breakpoint } from './types';\nimport { useResponsiveWidth } from './useResponsiveWidth';\n\n/*** Returns the hydration-stable responsive breakpoint without requiring a provider. */\nexport function useBreakpoint(): Breakpoint {\n const width = useResponsiveWidth();\n return getBreakpointFromWidth(width);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useResponsiveWidth.d.ts","sourceRoot":"","sources":["../../../src/core/responsive/useResponsiveWidth.ts"],"names":[],"mappings":"AAKA,kFAAkF;AAClF,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
import { Dimensions } from 'react-native';
|
|
3
|
+
const serverWidth = 0;
|
|
4
|
+
/*** Reads the viewport width without changing the server and hydration render. */
|
|
5
|
+
export function useResponsiveWidth() {
|
|
6
|
+
return useSyncExternalStore(subscribeToWindowDimensions, getWindowWidth, getServerWidth);
|
|
7
|
+
}
|
|
8
|
+
/*** Subscribes to viewport changes after hydration. */
|
|
9
|
+
function subscribeToWindowDimensions(onStoreChange) {
|
|
10
|
+
const subscription = Dimensions.addEventListener('change', onStoreChange);
|
|
11
|
+
return () => subscription.remove();
|
|
12
|
+
}
|
|
13
|
+
/*** Returns the current native or browser viewport width. */
|
|
14
|
+
function getWindowWidth() {
|
|
15
|
+
return Dimensions.get('window').width;
|
|
16
|
+
}
|
|
17
|
+
/*** Supplies the deterministic responsive width used by SSR and first hydration. */
|
|
18
|
+
function getServerWidth() {
|
|
19
|
+
return serverWidth;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=useResponsiveWidth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useResponsiveWidth.js","sourceRoot":"","sources":["../../../src/core/responsive/useResponsiveWidth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,MAAM,WAAW,GAAG,CAAC,CAAC;AAEtB,kFAAkF;AAClF,MAAM,UAAU,kBAAkB;IAChC,OAAO,oBAAoB,CAAC,2BAA2B,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;AAC3F,CAAC;AAED,uDAAuD;AACvD,SAAS,2BAA2B,CAAC,aAAyB;IAC5D,MAAM,YAAY,GAAG,UAAU,CAAC,gBAAgB,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IAC1E,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;AACrC,CAAC;AAED,6DAA6D;AAC7D,SAAS,cAAc;IACrB,OAAO,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC;AACxC,CAAC;AAED,oFAAoF;AACpF,SAAS,cAAc;IACrB,OAAO,WAAW,CAAC;AACrB,CAAC","sourcesContent":["import { useSyncExternalStore } from 'react';\nimport { Dimensions } from 'react-native';\n\nconst serverWidth = 0;\n\n/*** Reads the viewport width without changing the server and hydration render. */\nexport function useResponsiveWidth(): number {\n return useSyncExternalStore(subscribeToWindowDimensions, getWindowWidth, getServerWidth);\n}\n\n/*** Subscribes to viewport changes after hydration. */\nfunction subscribeToWindowDimensions(onStoreChange: () => void): () => void {\n const subscription = Dimensions.addEventListener('change', onStoreChange);\n return () => subscription.remove();\n}\n\n/*** Returns the current native or browser viewport width. */\nfunction getWindowWidth(): number {\n return Dimensions.get('window').width;\n}\n\n/*** Supplies the deterministic responsive width used by SSR and first hydration. */\nfunction getServerWidth(): number {\n return serverWidth;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScrollView.d.ts","sourceRoot":"","sources":["../../../../../src/features/layout/adapters/inbound/ScrollView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ScrollView.d.ts","sourceRoot":"","sources":["../../../../../src/features/layout/adapters/inbound/ScrollView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAIhE,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,qBAiChD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { ScrollView as ReactNativeScrollView } from 'react-native';
|
|
2
|
+
import { RefreshControl, ScrollView as ReactNativeScrollView, } from 'react-native';
|
|
3
3
|
import { useResponsiveRuntime } from '../../../../core/responsive';
|
|
4
4
|
import { resolveViewStyles } from '../../../../utils/resolveViewStyles';
|
|
5
5
|
import { useTheme } from '../../../theme/runtime';
|
|
@@ -11,17 +11,23 @@ export function ScrollView(props) {
|
|
|
11
11
|
const visual = splitVisualProps(spacing.rest);
|
|
12
12
|
const dimensions = splitDimensionProps(visual.rest);
|
|
13
13
|
const layout = splitLayoutProps(dimensions.rest);
|
|
14
|
-
const { children, contentContainerStyle, style, ...scrollProps } = layout.rest;
|
|
14
|
+
const { children, contentContainerStyle, style, refreshing, onRefresh, refreshControl: nativeRefreshControl, ...scrollProps } = layout.rest;
|
|
15
15
|
const resolved = resolveViewStyles(theme, breakpoint, {
|
|
16
16
|
...spacing.styleProps,
|
|
17
17
|
...visual.styleProps,
|
|
18
18
|
...dimensions.styleProps,
|
|
19
19
|
...layout.styleProps,
|
|
20
20
|
});
|
|
21
|
-
return (<ReactNativeScrollView {...scrollProps} contentContainerStyle={contentContainerStyle} style={[resolved, style]}>
|
|
21
|
+
return (<ReactNativeScrollView {...scrollProps} contentContainerStyle={contentContainerStyle} refreshControl={resolveRefreshControl({ nativeRefreshControl, onRefresh, refreshing })} style={[resolved, style]}>
|
|
22
22
|
{children}
|
|
23
23
|
</ReactNativeScrollView>);
|
|
24
24
|
}
|
|
25
|
+
/*** Creates the standard controlled React Native refresh control only when Surface refresh props are supplied. */
|
|
26
|
+
function resolveRefreshControl({ nativeRefreshControl, onRefresh, refreshing, }) {
|
|
27
|
+
if (onRefresh === undefined && refreshing === undefined)
|
|
28
|
+
return nativeRefreshControl;
|
|
29
|
+
return <RefreshControl onRefresh={onRefresh} refreshing={refreshing ?? false}/>;
|
|
30
|
+
}
|
|
25
31
|
/*** Splits spacing-aware Surface props from native ScrollView props. */
|
|
26
32
|
function splitSpacingProps(props) {
|
|
27
33
|
const { p, px, py, pt, pb, pl, pr, m, mx, my, mt, mb, ml, mr, gap, rowGap, columnGap, ...rest } = props;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScrollView.js","sourceRoot":"","sources":["../../../../../src/features/layout/adapters/inbound/ScrollView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,
|
|
1
|
+
{"version":3,"file":"ScrollView.js","sourceRoot":"","sources":["../../../../../src/features/layout/adapters/inbound/ScrollView.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport {\n RefreshControl,\n ScrollView as ReactNativeScrollView,\n type ScrollViewProps as ReactNativeScrollViewProps,\n} from 'react-native';\n\nimport { useResponsiveRuntime } from '../../../../core/responsive';\nimport type { ScrollViewProps } from '../../../../types/layout';\nimport { resolveViewStyles } from '../../../../utils/resolveViewStyles';\nimport { useTheme } from '../../../theme/runtime';\n\n/*** Renders the token-aware responsive Surface adapter for React Native ScrollView. */\nexport function ScrollView(props: ScrollViewProps) {\n const { theme } = useTheme();\n const { breakpoint } = useResponsiveRuntime();\n const spacing = splitSpacingProps(props);\n const visual = splitVisualProps(spacing.rest);\n const dimensions = splitDimensionProps(visual.rest);\n const layout = splitLayoutProps(dimensions.rest);\n const {\n children,\n contentContainerStyle,\n style,\n refreshing,\n onRefresh,\n refreshControl: nativeRefreshControl,\n ...scrollProps\n } = layout.rest;\n const resolved = resolveViewStyles(theme, breakpoint, {\n ...spacing.styleProps,\n ...visual.styleProps,\n ...dimensions.styleProps,\n ...layout.styleProps,\n });\n\n return (\n <ReactNativeScrollView\n {...scrollProps}\n contentContainerStyle={contentContainerStyle}\n refreshControl={resolveRefreshControl({ nativeRefreshControl, onRefresh, refreshing })}\n style={[resolved, style]}\n >\n {children}\n </ReactNativeScrollView>\n );\n}\n\n/*** Creates the standard controlled React Native refresh control only when Surface refresh props are supplied. */\nfunction resolveRefreshControl({\n nativeRefreshControl,\n onRefresh,\n refreshing,\n}: Pick<ScrollViewProps, 'onRefresh' | 'refreshing'> & {\n nativeRefreshControl: ReactNativeScrollViewProps['refreshControl'];\n}) {\n if (onRefresh === undefined && refreshing === undefined) return nativeRefreshControl;\n\n return <RefreshControl onRefresh={onRefresh} refreshing={refreshing ?? false} />;\n}\n\n/*** Splits spacing-aware Surface props from native ScrollView props. */\nfunction splitSpacingProps(props: ScrollViewProps) {\n const { p, px, py, pt, pb, pl, pr, m, mx, my, mt, mb, ml, mr, gap, rowGap, columnGap, ...rest } =\n props;\n return {\n rest,\n styleProps: { p, px, py, pt, pb, pl, pr, m, mx, my, mt, mb, ml, mr, gap, rowGap, columnGap },\n };\n}\n\n/*** Splits token-aware visual Surface props from native ScrollView props. */\nfunction splitVisualProps(props: ReturnType<typeof splitSpacingProps>['rest']) {\n const { bg, radius, borderWidth, borderColor, ...rest } = props;\n return { rest, styleProps: { bg, radius, borderWidth, borderColor } };\n}\n\n/*** Splits responsive dimension Surface props from native ScrollView props. */\nfunction splitDimensionProps(props: ReturnType<typeof splitVisualProps>['rest']) {\n const { width, height, minWidth, maxWidth, minHeight, maxHeight, ...rest } = props;\n return { rest, styleProps: { width, height, minWidth, maxWidth, minHeight, maxHeight } };\n}\n\n/*** Splits responsive flex and positioning Surface props from native ScrollView props. */\nfunction splitLayoutProps(props: ReturnType<typeof splitDimensionProps>['rest']) {\n const {\n flex,\n flexGrow,\n flexShrink,\n flexBasis,\n direction,\n align,\n justify,\n wrap,\n alignSelf,\n position,\n top,\n bottom,\n left,\n right,\n overflow,\n zIndex,\n opacity,\n ...rest\n } = props;\n return {\n rest,\n styleProps: {\n flex,\n flexGrow,\n flexShrink,\n flexBasis,\n direction,\n align,\n justify,\n wrap,\n alignSelf,\n position,\n top,\n bottom,\n left,\n right,\n overflow,\n zIndex,\n opacity,\n },\n };\n}\n"]}
|
package/dist/types/layout.d.ts
CHANGED
|
@@ -82,6 +82,10 @@ export interface GridProps extends Omit<ViewProps, 'children'> {
|
|
|
82
82
|
export interface ScrollViewProps extends ViewStyleProps, Omit<ReactNativeScrollViewProps, 'children' | 'contentContainerStyle' | 'style'> {
|
|
83
83
|
children?: React.ReactNode;
|
|
84
84
|
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
85
|
+
/** Controlled visibility of the native refresh indicator. */
|
|
86
|
+
refreshing?: boolean;
|
|
87
|
+
/** Called by the native refresh control after a pull-to-refresh gesture. */
|
|
88
|
+
onRefresh?: () => void;
|
|
85
89
|
}
|
|
86
90
|
export {};
|
|
87
91
|
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/types/layout.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/types/layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EACV,eAAe,IAAI,0BAA0B,EAC7C,SAAS,EACT,SAAS,IAAI,oBAAoB,EACjC,SAAS,EACV,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,KAAK,UAAU,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,CAAC;AAChD,KAAK,WAAW,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;AAC/C,KAAK,UAAU,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;AAC/C,KAAK,WAAW,GAAG,MAAM,GAAG,WAAW,CAAC;AAExC,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;AAC7C,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,CAAC,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC3B,EAAE,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,EAAE,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,EAAE,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,EAAE,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,EAAE,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,EAAE,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,CAAC,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC3B,EAAE,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,EAAE,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,EAAE,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,EAAE,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,EAAE,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,EAAE,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,GAAG,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAChC,SAAS,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IACnC,EAAE,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5B,MAAM,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,EAAE,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACvC,SAAS,CAAC,EAAE,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACxC,SAAS,CAAC,EAAE,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC1B,QAAQ,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC9B,UAAU,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAChC,SAAS,CAAC,EAAE,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACxC,SAAS,CAAC,EAAE,UAAU,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC;IACzC,KAAK,CAAC,EAAE,UAAU,CAAC,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC,CAAC;IAClF,OAAO,CAAC,EAAE,UAAU,CAClB,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,eAAe,GAAG,cAAc,GAAG,cAAc,CACzF,CAAC;IACF,IAAI,CAAC,EAAE,UAAU,CAAC,QAAQ,GAAG,MAAM,CAAC,CAAC;IACrC,SAAS,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;IAC/C,QAAQ,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7C,GAAG,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IACzB,MAAM,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC5B,IAAI,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC1B,KAAK,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC3B,QAAQ,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7C,MAAM,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,SAAU,SAAQ,cAAc;IAC/C,kBAAkB,CAAC,EAAE,oBAAoB,CAAC,oBAAoB,CAAC,CAAC;IAChE,uBAAuB,CAAC,EAAE,oBAAoB,CAAC,yBAAyB,CAAC,CAAC;IAC1E,iBAAiB,CAAC,EAAE,oBAAoB,CAAC,mBAAmB,CAAC,CAAC;IAC9D,kBAAkB,CAAC,EAAE,oBAAoB,CAAC,oBAAoB,CAAC,CAAC;IAChE,UAAU,CAAC,EAAE,oBAAoB,CAAC,YAAY,CAAC,CAAC;IAChD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAC5C,aAAa,CAAC,EAAE,oBAAoB,CAAC,eAAe,CAAC,CAAC;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC9E,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACxC,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAU,SAAQ,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC;IAC5D,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,IAAI,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC1B,GAAG,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,eACf,SACE,cAAc,EACd,IAAI,CAAC,0BAA0B,EAAE,UAAU,GAAG,uBAAuB,GAAG,OAAO,CAAC;IAClF,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,qBAAqB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7C,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB"}
|
package/dist/types/layout.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/types/layout.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\nimport type {\n ScrollViewProps as ReactNativeScrollViewProps,\n StyleProp,\n ViewProps as ReactNativeViewProps,\n ViewStyle,\n} from 'react-native';\n\nimport type { Responsive } from '../core/responsive';\nimport type { SurfaceTheme } from './theme';\n\ntype SpaceToken = keyof SurfaceTheme['spacing'];\ntype RadiusToken = keyof SurfaceTheme['radii'];\ntype ColorToken = keyof SurfaceTheme['colors'];\ntype RadiusValue = number | RadiusToken;\n\nexport type SpaceValue = number | SpaceToken;\nexport type ColorValue = string | ColorToken;\n\nexport interface ViewStyleProps {\n p?: Responsive<SpaceValue>;\n px?: Responsive<SpaceValue>;\n py?: Responsive<SpaceValue>;\n pt?: Responsive<SpaceValue>;\n pb?: Responsive<SpaceValue>;\n pl?: Responsive<SpaceValue>;\n pr?: Responsive<SpaceValue>;\n m?: Responsive<SpaceValue>;\n mx?: Responsive<SpaceValue>;\n my?: Responsive<SpaceValue>;\n mt?: Responsive<SpaceValue>;\n mb?: Responsive<SpaceValue>;\n ml?: Responsive<SpaceValue>;\n mr?: Responsive<SpaceValue>;\n gap?: Responsive<SpaceValue>;\n rowGap?: Responsive<SpaceValue>;\n columnGap?: Responsive<SpaceValue>;\n bg?: Responsive<ColorValue>;\n radius?: Responsive<RadiusValue>;\n borderWidth?: Responsive<number>;\n borderColor?: Responsive<ColorValue>;\n width?: Responsive<number | string>;\n height?: Responsive<number | string>;\n minWidth?: Responsive<number | string>;\n maxWidth?: Responsive<number | string>;\n minHeight?: Responsive<number | string>;\n maxHeight?: Responsive<number | string>;\n flex?: Responsive<number>;\n flexGrow?: Responsive<number>;\n flexShrink?: Responsive<number>;\n flexBasis?: Responsive<number | string>;\n direction?: Responsive<'row' | 'column'>;\n align?: Responsive<'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline'>;\n justify?: Responsive<\n 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly'\n >;\n wrap?: Responsive<'nowrap' | 'wrap'>;\n alignSelf?: Responsive<ViewStyle['alignSelf']>;\n position?: Responsive<ViewStyle['position']>;\n top?: Responsive<number>;\n bottom?: Responsive<number>;\n left?: Responsive<number>;\n right?: Responsive<number>;\n overflow?: Responsive<ViewStyle['overflow']>;\n zIndex?: Responsive<number>;\n opacity?: Responsive<number>;\n style?: StyleProp<ViewStyle>;\n}\n\nexport interface ViewProps extends ViewStyleProps {\n accessibilityLabel?: ReactNativeViewProps['accessibilityLabel'];\n accessibilityLabelledBy?: ReactNativeViewProps['accessibilityLabelledBy'];\n accessibilityRole?: ReactNativeViewProps['accessibilityRole'];\n accessibilityState?: ReactNativeViewProps['accessibilityState'];\n accessible?: ReactNativeViewProps['accessible'];\n children?: React.ReactNode;\n nativeID?: ReactNativeViewProps['nativeID'];\n pointerEvents?: ReactNativeViewProps['pointerEvents'];\n testID?: string;\n}\n\nexport interface DividerProps extends Omit<ViewProps, 'bg' | 'height' | 'width'> {\n orientation?: 'horizontal' | 'vertical';\n color?: ColorValue;\n thickness?: number;\n}\n\nexport interface GridProps extends Omit<ViewProps, 'children'> {\n children?: React.ReactNode;\n cols?: Responsive<number>;\n gap?: Responsive<SpaceValue>;\n rowGap?: Responsive<SpaceValue>;\n colGap?: Responsive<SpaceValue>;\n minItemWidth?: Responsive<number>;\n}\n\nexport interface ScrollViewProps\n extends\n ViewStyleProps,\n Omit<ReactNativeScrollViewProps, 'children' | 'contentContainerStyle' | 'style'> {\n children?: React.ReactNode;\n contentContainerStyle?: StyleProp<ViewStyle>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/types/layout.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\nimport type {\n ScrollViewProps as ReactNativeScrollViewProps,\n StyleProp,\n ViewProps as ReactNativeViewProps,\n ViewStyle,\n} from 'react-native';\n\nimport type { Responsive } from '../core/responsive';\nimport type { SurfaceTheme } from './theme';\n\ntype SpaceToken = keyof SurfaceTheme['spacing'];\ntype RadiusToken = keyof SurfaceTheme['radii'];\ntype ColorToken = keyof SurfaceTheme['colors'];\ntype RadiusValue = number | RadiusToken;\n\nexport type SpaceValue = number | SpaceToken;\nexport type ColorValue = string | ColorToken;\n\nexport interface ViewStyleProps {\n p?: Responsive<SpaceValue>;\n px?: Responsive<SpaceValue>;\n py?: Responsive<SpaceValue>;\n pt?: Responsive<SpaceValue>;\n pb?: Responsive<SpaceValue>;\n pl?: Responsive<SpaceValue>;\n pr?: Responsive<SpaceValue>;\n m?: Responsive<SpaceValue>;\n mx?: Responsive<SpaceValue>;\n my?: Responsive<SpaceValue>;\n mt?: Responsive<SpaceValue>;\n mb?: Responsive<SpaceValue>;\n ml?: Responsive<SpaceValue>;\n mr?: Responsive<SpaceValue>;\n gap?: Responsive<SpaceValue>;\n rowGap?: Responsive<SpaceValue>;\n columnGap?: Responsive<SpaceValue>;\n bg?: Responsive<ColorValue>;\n radius?: Responsive<RadiusValue>;\n borderWidth?: Responsive<number>;\n borderColor?: Responsive<ColorValue>;\n width?: Responsive<number | string>;\n height?: Responsive<number | string>;\n minWidth?: Responsive<number | string>;\n maxWidth?: Responsive<number | string>;\n minHeight?: Responsive<number | string>;\n maxHeight?: Responsive<number | string>;\n flex?: Responsive<number>;\n flexGrow?: Responsive<number>;\n flexShrink?: Responsive<number>;\n flexBasis?: Responsive<number | string>;\n direction?: Responsive<'row' | 'column'>;\n align?: Responsive<'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline'>;\n justify?: Responsive<\n 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly'\n >;\n wrap?: Responsive<'nowrap' | 'wrap'>;\n alignSelf?: Responsive<ViewStyle['alignSelf']>;\n position?: Responsive<ViewStyle['position']>;\n top?: Responsive<number>;\n bottom?: Responsive<number>;\n left?: Responsive<number>;\n right?: Responsive<number>;\n overflow?: Responsive<ViewStyle['overflow']>;\n zIndex?: Responsive<number>;\n opacity?: Responsive<number>;\n style?: StyleProp<ViewStyle>;\n}\n\nexport interface ViewProps extends ViewStyleProps {\n accessibilityLabel?: ReactNativeViewProps['accessibilityLabel'];\n accessibilityLabelledBy?: ReactNativeViewProps['accessibilityLabelledBy'];\n accessibilityRole?: ReactNativeViewProps['accessibilityRole'];\n accessibilityState?: ReactNativeViewProps['accessibilityState'];\n accessible?: ReactNativeViewProps['accessible'];\n children?: React.ReactNode;\n nativeID?: ReactNativeViewProps['nativeID'];\n pointerEvents?: ReactNativeViewProps['pointerEvents'];\n testID?: string;\n}\n\nexport interface DividerProps extends Omit<ViewProps, 'bg' | 'height' | 'width'> {\n orientation?: 'horizontal' | 'vertical';\n color?: ColorValue;\n thickness?: number;\n}\n\nexport interface GridProps extends Omit<ViewProps, 'children'> {\n children?: React.ReactNode;\n cols?: Responsive<number>;\n gap?: Responsive<SpaceValue>;\n rowGap?: Responsive<SpaceValue>;\n colGap?: Responsive<SpaceValue>;\n minItemWidth?: Responsive<number>;\n}\n\nexport interface ScrollViewProps\n extends\n ViewStyleProps,\n Omit<ReactNativeScrollViewProps, 'children' | 'contentContainerStyle' | 'style'> {\n children?: React.ReactNode;\n contentContainerStyle?: StyleProp<ViewStyle>;\n /** Controlled visibility of the native refresh indicator. */\n refreshing?: boolean;\n /** Called by the native refresh control after a pull-to-refresh gesture. */\n onRefresh?: () => void;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/surface",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "9.4.
|
|
4
|
+
"version": "9.4.12",
|
|
5
5
|
"description": "Stable UI foundation for React Native and React Native Web.",
|
|
6
6
|
"homepage": "https://github.com/ankhorage/surface#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
},
|
|
114
114
|
"devDependencies": {
|
|
115
115
|
"@ankhorage/capability": "^0.2.2",
|
|
116
|
-
"@ankhorage/devtools": "^2.3.
|
|
116
|
+
"@ankhorage/devtools": "^2.3.24",
|
|
117
117
|
"@ankhorage/paradox": "^0.3.5",
|
|
118
118
|
"@react-native-vector-icons/fontawesome": "^13.1.5",
|
|
119
119
|
"@react-native-vector-icons/fontawesome5": "^13.1.5",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React, { createContext, use, useMemo } from 'react';
|
|
2
|
-
import { useWindowDimensions } from 'react-native';
|
|
3
2
|
|
|
4
3
|
import { getBreakpointFromWidth } from './getBreakpointFromWidth';
|
|
5
4
|
import type { ResponsiveRuntime } from './types';
|
|
5
|
+
import { useResponsiveWidth } from './useResponsiveWidth';
|
|
6
6
|
|
|
7
7
|
const ResponsiveContext = createContext<ResponsiveRuntime | null>(null);
|
|
8
8
|
|
|
9
9
|
export function ResponsiveProvider({ children }: { children: React.ReactNode }) {
|
|
10
|
-
const
|
|
10
|
+
const width = useResponsiveWidth();
|
|
11
11
|
|
|
12
12
|
const value = useMemo<ResponsiveRuntime>(
|
|
13
13
|
() => ({
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useWindowDimensions } from 'react-native';
|
|
2
|
-
|
|
3
1
|
import { getBreakpointFromWidth } from './getBreakpointFromWidth';
|
|
4
2
|
import type { Breakpoint } from './types';
|
|
3
|
+
import { useResponsiveWidth } from './useResponsiveWidth';
|
|
5
4
|
|
|
5
|
+
/*** Returns the hydration-stable responsive breakpoint without requiring a provider. */
|
|
6
6
|
export function useBreakpoint(): Breakpoint {
|
|
7
|
-
const
|
|
7
|
+
const width = useResponsiveWidth();
|
|
8
8
|
return getBreakpointFromWidth(width);
|
|
9
9
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
import { Dimensions } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const serverWidth = 0;
|
|
5
|
+
|
|
6
|
+
/*** Reads the viewport width without changing the server and hydration render. */
|
|
7
|
+
export function useResponsiveWidth(): number {
|
|
8
|
+
return useSyncExternalStore(subscribeToWindowDimensions, getWindowWidth, getServerWidth);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/*** Subscribes to viewport changes after hydration. */
|
|
12
|
+
function subscribeToWindowDimensions(onStoreChange: () => void): () => void {
|
|
13
|
+
const subscription = Dimensions.addEventListener('change', onStoreChange);
|
|
14
|
+
return () => subscription.remove();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/*** Returns the current native or browser viewport width. */
|
|
18
|
+
function getWindowWidth(): number {
|
|
19
|
+
return Dimensions.get('window').width;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/*** Supplies the deterministic responsive width used by SSR and first hydration. */
|
|
23
|
+
function getServerWidth(): number {
|
|
24
|
+
return serverWidth;
|
|
25
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { expect, mock, test } from 'bun:test';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { renderToString } from 'react-dom/server';
|
|
4
|
+
import * as ReactNativeWeb from 'react-native-web';
|
|
5
|
+
|
|
6
|
+
type RecordedScrollViewProps = Readonly<Record<string, unknown>>;
|
|
7
|
+
|
|
8
|
+
const recordedScrollViewProps: RecordedScrollViewProps[] = [];
|
|
9
|
+
|
|
10
|
+
await mock.module('react-native', () => ({
|
|
11
|
+
...ReactNativeWeb,
|
|
12
|
+
RefreshControl: () => null,
|
|
13
|
+
ScrollView: (props: RecordedScrollViewProps) => {
|
|
14
|
+
recordedScrollViewProps.push(props);
|
|
15
|
+
return null;
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
const { ScrollView } = await import('./ScrollView');
|
|
20
|
+
const { ThemeProvider } = await import('../../../theme/runtime');
|
|
21
|
+
|
|
22
|
+
/*** Renders Surface ScrollView under the production theme boundary and captures native props. */
|
|
23
|
+
function renderScrollView(props: React.ComponentProps<typeof ScrollView>) {
|
|
24
|
+
recordedScrollViewProps.splice(0);
|
|
25
|
+
renderToString(
|
|
26
|
+
<ThemeProvider>
|
|
27
|
+
<ScrollView {...props} />
|
|
28
|
+
</ThemeProvider>,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const renderedProps = recordedScrollViewProps.at(-1);
|
|
32
|
+
if (renderedProps === undefined) throw new Error('ScrollView did not render.');
|
|
33
|
+
return renderedProps;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test('creates a controlled native RefreshControl from Surface refresh props', () => {
|
|
37
|
+
const onRefresh = () => undefined;
|
|
38
|
+
const rendered = renderScrollView({ onRefresh, refreshing: true });
|
|
39
|
+
const refreshControl = rendered.refreshControl as React.ReactElement<{
|
|
40
|
+
onRefresh?: () => void;
|
|
41
|
+
refreshing: boolean;
|
|
42
|
+
}>;
|
|
43
|
+
|
|
44
|
+
expect(refreshControl.type).toBeDefined();
|
|
45
|
+
expect(refreshControl.props).toEqual({ onRefresh, refreshing: true });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('defaults a requested refresh control to its inactive controlled state', () => {
|
|
49
|
+
const rendered = renderScrollView({ onRefresh: () => undefined });
|
|
50
|
+
const refreshControl = rendered.refreshControl as React.ReactElement<{ refreshing: boolean }>;
|
|
51
|
+
|
|
52
|
+
expect(refreshControl.props.refreshing).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('preserves a caller-provided native refresh control when Surface refresh props are absent', () => {
|
|
56
|
+
const nativeRefreshControl = <ReactNativeWeb.View />;
|
|
57
|
+
const rendered = renderScrollView({ refreshControl: nativeRefreshControl });
|
|
58
|
+
|
|
59
|
+
expect(rendered.refreshControl).toBe(nativeRefreshControl);
|
|
60
|
+
});
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
RefreshControl,
|
|
4
|
+
ScrollView as ReactNativeScrollView,
|
|
5
|
+
type ScrollViewProps as ReactNativeScrollViewProps,
|
|
6
|
+
} from 'react-native';
|
|
3
7
|
|
|
4
8
|
import { useResponsiveRuntime } from '../../../../core/responsive';
|
|
5
9
|
import type { ScrollViewProps } from '../../../../types/layout';
|
|
@@ -14,7 +18,15 @@ export function ScrollView(props: ScrollViewProps) {
|
|
|
14
18
|
const visual = splitVisualProps(spacing.rest);
|
|
15
19
|
const dimensions = splitDimensionProps(visual.rest);
|
|
16
20
|
const layout = splitLayoutProps(dimensions.rest);
|
|
17
|
-
const {
|
|
21
|
+
const {
|
|
22
|
+
children,
|
|
23
|
+
contentContainerStyle,
|
|
24
|
+
style,
|
|
25
|
+
refreshing,
|
|
26
|
+
onRefresh,
|
|
27
|
+
refreshControl: nativeRefreshControl,
|
|
28
|
+
...scrollProps
|
|
29
|
+
} = layout.rest;
|
|
18
30
|
const resolved = resolveViewStyles(theme, breakpoint, {
|
|
19
31
|
...spacing.styleProps,
|
|
20
32
|
...visual.styleProps,
|
|
@@ -26,6 +38,7 @@ export function ScrollView(props: ScrollViewProps) {
|
|
|
26
38
|
<ReactNativeScrollView
|
|
27
39
|
{...scrollProps}
|
|
28
40
|
contentContainerStyle={contentContainerStyle}
|
|
41
|
+
refreshControl={resolveRefreshControl({ nativeRefreshControl, onRefresh, refreshing })}
|
|
29
42
|
style={[resolved, style]}
|
|
30
43
|
>
|
|
31
44
|
{children}
|
|
@@ -33,6 +46,19 @@ export function ScrollView(props: ScrollViewProps) {
|
|
|
33
46
|
);
|
|
34
47
|
}
|
|
35
48
|
|
|
49
|
+
/*** Creates the standard controlled React Native refresh control only when Surface refresh props are supplied. */
|
|
50
|
+
function resolveRefreshControl({
|
|
51
|
+
nativeRefreshControl,
|
|
52
|
+
onRefresh,
|
|
53
|
+
refreshing,
|
|
54
|
+
}: Pick<ScrollViewProps, 'onRefresh' | 'refreshing'> & {
|
|
55
|
+
nativeRefreshControl: ReactNativeScrollViewProps['refreshControl'];
|
|
56
|
+
}) {
|
|
57
|
+
if (onRefresh === undefined && refreshing === undefined) return nativeRefreshControl;
|
|
58
|
+
|
|
59
|
+
return <RefreshControl onRefresh={onRefresh} refreshing={refreshing ?? false} />;
|
|
60
|
+
}
|
|
61
|
+
|
|
36
62
|
/*** Splits spacing-aware Surface props from native ScrollView props. */
|
|
37
63
|
function splitSpacingProps(props: ScrollViewProps) {
|
|
38
64
|
const { p, px, py, pt, pb, pl, pr, m, mx, my, mt, mb, ml, mr, gap, rowGap, columnGap, ...rest } =
|
package/src/types/layout.ts
CHANGED
|
@@ -100,4 +100,8 @@ export interface ScrollViewProps
|
|
|
100
100
|
Omit<ReactNativeScrollViewProps, 'children' | 'contentContainerStyle' | 'style'> {
|
|
101
101
|
children?: React.ReactNode;
|
|
102
102
|
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
103
|
+
/** Controlled visibility of the native refresh indicator. */
|
|
104
|
+
refreshing?: boolean;
|
|
105
|
+
/** Called by the native refresh control after a pull-to-refresh gesture. */
|
|
106
|
+
onRefresh?: () => void;
|
|
103
107
|
}
|
|
@@ -2,7 +2,18 @@ import { expect, mock, test } from 'bun:test';
|
|
|
2
2
|
import { Window } from 'happy-dom';
|
|
3
3
|
import React, { act } from 'react';
|
|
4
4
|
import { renderToString } from 'react-dom/server';
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
const browserWindow = new Window({ url: 'https://surface.test/' });
|
|
7
|
+
Object.assign(globalThis, {
|
|
8
|
+
IS_REACT_ACT_ENVIRONMENT: true,
|
|
9
|
+
Node: browserWindow.Node,
|
|
10
|
+
ShadowRoot: browserWindow.ShadowRoot,
|
|
11
|
+
document: browserWindow.document,
|
|
12
|
+
navigator: browserWindow.navigator,
|
|
13
|
+
window: browserWindow,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const ReactNativeWeb = await import('react-native-web');
|
|
6
17
|
|
|
7
18
|
await mock.module('react-native', () => ReactNativeWeb);
|
|
8
19
|
|
|
@@ -10,7 +21,7 @@ const scrollContentStyle = { padding: 4 };
|
|
|
10
21
|
|
|
11
22
|
const { Grid, ScrollView, View } = await import('./features/layout/public');
|
|
12
23
|
const { KeyboardAvoidingView } = await import('./features/keyboard-avoiding-view/public');
|
|
13
|
-
const { Show } = await import('./core/responsive
|
|
24
|
+
const { Show, useBreakpoint } = await import('./core/responsive');
|
|
14
25
|
const { OverlayProvider } = await import('./internal/overlay/OverlayProvider');
|
|
15
26
|
const { ThemeProvider } = await import('./features/theme/runtime');
|
|
16
27
|
|
|
@@ -24,34 +35,38 @@ function ResponsiveAcceptanceTree() {
|
|
|
24
35
|
<ReactNativeWeb.View testID="grid-second" />
|
|
25
36
|
</Grid>
|
|
26
37
|
<Show
|
|
27
|
-
fallback={
|
|
38
|
+
fallback={
|
|
39
|
+
<ReactNativeWeb.View testID="mobile-branch">
|
|
40
|
+
<ReactNativeWeb.Text>static-fallback</ReactNativeWeb.Text>
|
|
41
|
+
</ReactNativeWeb.View>
|
|
42
|
+
}
|
|
28
43
|
when={{ base: false, md: true }}
|
|
29
44
|
>
|
|
30
|
-
<ReactNativeWeb.Text>wide-content</ReactNativeWeb.Text>
|
|
45
|
+
<ReactNativeWeb.Text testID="desktop-branch">wide-content</ReactNativeWeb.Text>
|
|
31
46
|
</Show>
|
|
32
47
|
</ScrollView>
|
|
33
48
|
</View>
|
|
49
|
+
<BreakpointProbe />
|
|
34
50
|
</ThemeProvider>
|
|
35
51
|
);
|
|
36
52
|
}
|
|
37
53
|
|
|
38
|
-
|
|
54
|
+
function BreakpointProbe() {
|
|
55
|
+
return <ReactNativeWeb.Text testID="breakpoint-probe">{useBreakpoint()}</ReactNativeWeb.Text>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
test('RN Web 0.21 preserves SSR responsive structure during desktop hydration', async () => {
|
|
59
|
+
act(() => setViewportWidth(0));
|
|
39
60
|
const markup = renderToString(<ResponsiveAcceptanceTree />);
|
|
40
61
|
|
|
41
62
|
expect(markup).toContain('data-testid="container"');
|
|
42
63
|
expect(markup).toContain('data-testid="scroll-view"');
|
|
43
64
|
expect(markup).toContain('data-testid="grid"');
|
|
65
|
+
expect(markup).toContain('base');
|
|
44
66
|
expect(markup).toContain('static-fallback');
|
|
45
67
|
expect(markup).not.toContain('wide-content');
|
|
46
68
|
|
|
47
|
-
|
|
48
|
-
Object.assign(globalThis, {
|
|
49
|
-
IS_REACT_ACT_ENVIRONMENT: true,
|
|
50
|
-
Node: browserWindow.Node,
|
|
51
|
-
document: browserWindow.document,
|
|
52
|
-
navigator: browserWindow.navigator,
|
|
53
|
-
window: browserWindow,
|
|
54
|
-
});
|
|
69
|
+
act(() => setViewportWidth(1440));
|
|
55
70
|
const container = browserWindow.document.createElement('div');
|
|
56
71
|
container.innerHTML = markup;
|
|
57
72
|
browserWindow.document.body.append(container);
|
|
@@ -64,25 +79,66 @@ test('RN Web 0.21 statically renders and hydrates representative responsive prim
|
|
|
64
79
|
try {
|
|
65
80
|
const { hydrateRoot } = await import('react-dom/client');
|
|
66
81
|
const root = hydrateRoot(container as unknown as Element, <ResponsiveAcceptanceTree />);
|
|
82
|
+
|
|
83
|
+
expect(container.querySelector('[data-testid="mobile-branch"]')).not.toBeNull();
|
|
84
|
+
expect(container.querySelector('[data-testid="desktop-branch"]')).toBeNull();
|
|
85
|
+
expect(container.querySelector('[data-testid="breakpoint-probe"]')?.textContent).toBe('base');
|
|
67
86
|
await act(async () => Promise.resolve());
|
|
68
87
|
|
|
69
88
|
expect(container.querySelector('[data-testid="container"]')).not.toBeNull();
|
|
70
89
|
expect(container.querySelector('[data-testid="scroll-view"]')).not.toBeNull();
|
|
71
90
|
expect(container.querySelectorAll('[data-testid^="grid-"]')).toHaveLength(2);
|
|
91
|
+
expect(container.querySelector('[data-testid="mobile-branch"]')).toBeNull();
|
|
92
|
+
expect(container.querySelector('[data-testid="desktop-branch"]')).not.toBeNull();
|
|
93
|
+
expect(container.querySelector('[data-testid="breakpoint-probe"]')?.textContent).toBe('xl');
|
|
94
|
+
expect(hydrationErrors).toEqual([]);
|
|
95
|
+
|
|
96
|
+
act(() => root.unmount());
|
|
97
|
+
} finally {
|
|
98
|
+
console.error = originalError;
|
|
99
|
+
container.remove();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('RN Web 0.21 keeps the server responsive structure at a narrow viewport', async () => {
|
|
104
|
+
act(() => setViewportWidth(0));
|
|
105
|
+
const markup = renderToString(<ResponsiveAcceptanceTree />);
|
|
106
|
+
act(() => setViewportWidth(375));
|
|
107
|
+
const container = browserWindow.document.createElement('div');
|
|
108
|
+
container.innerHTML = markup;
|
|
109
|
+
browserWindow.document.body.append(container);
|
|
110
|
+
const hydrationErrors: string[] = [];
|
|
111
|
+
const originalError = console.error;
|
|
112
|
+
console.error = (...values: unknown[]) => {
|
|
113
|
+
hydrationErrors.push(values.map(String).join(' '));
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const { hydrateRoot } = await import('react-dom/client');
|
|
118
|
+
const root = hydrateRoot(container as unknown as Element, <ResponsiveAcceptanceTree />);
|
|
119
|
+
await act(async () => Promise.resolve());
|
|
120
|
+
|
|
121
|
+
expect(container.querySelector('[data-testid="mobile-branch"]')).not.toBeNull();
|
|
122
|
+
expect(container.querySelector('[data-testid="desktop-branch"]')).toBeNull();
|
|
123
|
+
expect(container.querySelector('[data-testid="breakpoint-probe"]')?.textContent).toBe('base');
|
|
124
|
+
expect(container.querySelectorAll('[data-testid^="grid-"]')).toHaveLength(2);
|
|
72
125
|
expect(hydrationErrors).toEqual([]);
|
|
73
126
|
|
|
74
127
|
act(() => root.unmount());
|
|
75
128
|
} finally {
|
|
76
129
|
console.error = originalError;
|
|
77
|
-
|
|
78
|
-
Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT');
|
|
79
|
-
Reflect.deleteProperty(globalThis, 'Node');
|
|
80
|
-
Reflect.deleteProperty(globalThis, 'document');
|
|
81
|
-
Reflect.deleteProperty(globalThis, 'navigator');
|
|
82
|
-
Reflect.deleteProperty(globalThis, 'window');
|
|
130
|
+
container.remove();
|
|
83
131
|
}
|
|
84
132
|
});
|
|
85
133
|
|
|
134
|
+
function setViewportWidth(width: number) {
|
|
135
|
+
Object.defineProperty(browserWindow.document.documentElement, 'clientWidth', {
|
|
136
|
+
configurable: true,
|
|
137
|
+
value: width,
|
|
138
|
+
});
|
|
139
|
+
browserWindow.dispatchEvent(new browserWindow.Event('resize'));
|
|
140
|
+
}
|
|
141
|
+
|
|
86
142
|
test('RN Web 0.21 compiles box-none overlay pointer events into a hit-test-safe class', () => {
|
|
87
143
|
const markup = renderToString(
|
|
88
144
|
<OverlayProvider>
|
|
@@ -109,3 +165,22 @@ test('RN Web 0.21 renders keyboard-safe content through the Surface layout bound
|
|
|
109
165
|
expect(markup).toContain('data-testid="keyboard-safe"');
|
|
110
166
|
expect(markup).toContain('Keyboard-safe content');
|
|
111
167
|
});
|
|
168
|
+
|
|
169
|
+
test('RN Web renders controlled refresh state without simulating a native pull gesture', () => {
|
|
170
|
+
const refreshSignals: undefined[] = [];
|
|
171
|
+
const markup = renderToString(
|
|
172
|
+
<ThemeProvider>
|
|
173
|
+
<ScrollView
|
|
174
|
+
onRefresh={() => refreshSignals.push(undefined)}
|
|
175
|
+
refreshing
|
|
176
|
+
testID="refreshable-scroll-view"
|
|
177
|
+
>
|
|
178
|
+
<ReactNativeWeb.Text>refreshable content</ReactNativeWeb.Text>
|
|
179
|
+
</ScrollView>
|
|
180
|
+
</ThemeProvider>,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
expect(markup).toContain('data-testid="refreshable-scroll-view"');
|
|
184
|
+
expect(markup).toContain('refreshable content');
|
|
185
|
+
expect(refreshSignals).toEqual([]);
|
|
186
|
+
});
|