@pallasoft/ps-mobile-components 1.10.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ps-cartesian-chart/ps-cartesian-chart-props.d.ts +1 -0
- package/dist/components/ps-cartesian-chart/ps-cartesian-chart.d.ts +1 -1
- package/dist/components/ps-cartesian-chart/ps-cartesian-chart.js +6 -5
- package/dist/components/ps-chart-frame/ps-chart-frame.js +5 -1
- package/dist/components/ps-donut-chart/ps-donut-chart-props.d.ts +1 -0
- package/dist/components/ps-donut-chart/ps-donut-chart.d.ts +1 -1
- package/dist/components/ps-donut-chart/ps-donut-chart.js +2 -2
- package/dist/components/ps-header-menu/ps-header-menu.js +2 -0
- package/dist/components/ps-menu-popup/ps-menu-popup.js +10 -8
- package/dist/components/ps-sheet-presenter/ps-sheet-presenter.ios.js +8 -1
- package/dist/components/ps-sparkline/ps-sparkline-props.d.ts +1 -0
- package/dist/components/ps-sparkline/ps-sparkline.d.ts +1 -1
- package/dist/components/ps-sparkline/ps-sparkline.js +2 -2
- package/dist/components/ps-waterfall-chart/ps-waterfall-chart-props.d.ts +1 -0
- package/dist/components/ps-waterfall-chart/ps-waterfall-chart.d.ts +1 -1
- package/dist/components/ps-waterfall-chart/ps-waterfall-chart.js +6 -5
- package/dist/constants/ps-chart-config.d.ts +3 -0
- package/dist/constants/ps-chart-config.js +3 -0
- package/dist/hooks/use-ps-chart-reveal.d.ts +1 -1
- package/dist/hooks/use-ps-chart-reveal.js +4 -2
- package/dist/hooks/use-ps-chart-scrub.js +17 -18
- package/dist/hooks/use-ps-infinite-data.d.ts +1 -1
- package/dist/hooks/use-ps-infinite-data.js +23 -7
- package/dist/hooks/use-ps-keyboard-height.d.ts +1 -0
- package/dist/hooks/use-ps-keyboard-height.js +14 -0
- package/dist/interfaces/ps-chart-scrub-result.d.ts +2 -2
- package/dist/interfaces/ps-infinite-data-options.d.ts +1 -0
- package/dist/interfaces/ps-infinite-data-result.d.ts +1 -0
- package/dist/interfaces/ps-menu-popup-insets.d.ts +4 -0
- package/dist/interfaces/ps-menu-popup-insets.js +1 -0
- package/dist/services/ps-chart-series-service.d.ts +0 -1
- package/dist/services/ps-chart-series-service.js +0 -3
- package/dist/services/ps-infinite-data-service.d.ts +1 -0
- package/dist/services/ps-infinite-data-service.js +3 -0
- package/dist/services/ps-menu-popup-service.d.ts +2 -1
- package/dist/services/ps-menu-popup-service.js +3 -3
- package/dist/services/ps-sheet-service.d.ts +1 -0
- package/dist/services/ps-sheet-service.js +3 -0
- package/jest-setup.js +4 -0
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { PsCartesianChartProps } from './ps-cartesian-chart-props';
|
|
2
|
-
export declare const PsCartesianChart: ({ data, emptyLabel, formatTick, formatValue, height: baseHeight, isLoading, kind, series, showLegend, }: PsCartesianChartProps) => import("react").JSX.Element;
|
|
2
|
+
export declare const PsCartesianChart: ({ data, emptyLabel, formatTick, formatValue, height: baseHeight, isLoading, kind, revealKey, series, showLegend, }: PsCartesianChartProps) => import("react").JSX.Element;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
import { GestureDetector } from 'react-native-gesture-handler';
|
|
3
4
|
import Svg, { Circle, G, Line, Text as SvgText } from 'react-native-svg';
|
|
4
5
|
import { PsChartConfig } from '../../constants/ps-chart-config';
|
|
5
6
|
import { PsCartesianChartKind } from '../../constants/ps-chart-kind';
|
|
@@ -22,7 +23,7 @@ import { PsChartLegend } from '../ps-chart-legend/ps-chart-legend';
|
|
|
22
23
|
import { PsChartLine } from '../ps-chart-line/ps-chart-line';
|
|
23
24
|
import { PsChartReadout } from '../ps-chart-readout/ps-chart-readout';
|
|
24
25
|
import { PsChartSkeleton } from '../ps-chart-skeleton/ps-chart-skeleton';
|
|
25
|
-
export const PsCartesianChart = ({ data, emptyLabel, formatTick, formatValue, height: baseHeight = PsChartConfig.chartHeight, isLoading = false, kind, series, showLegend = true, }) => {
|
|
26
|
+
export const PsCartesianChart = ({ data, emptyLabel, formatTick, formatValue, height: baseHeight = PsChartConfig.chartHeight, isLoading = false, kind, revealKey, series, showLegend = true, }) => {
|
|
26
27
|
const theme = usePsMobileTheme();
|
|
27
28
|
const { onLayout, width } = usePsChartWidth();
|
|
28
29
|
const height = baseHeight + usePsWidgetFill();
|
|
@@ -41,8 +42,8 @@ export const PsCartesianChart = ({ data, emptyLabel, formatTick, formatValue, he
|
|
|
41
42
|
const tickLabels = PsChartScaleService.ticks(domain).map((tick) => formatTick(tick));
|
|
42
43
|
const yAxisWidth = PsChartAxisService.yAxisWidth(tickLabels, width);
|
|
43
44
|
const plot = PsChartScaleService.plot(width, height, yAxisWidth);
|
|
44
|
-
const {
|
|
45
|
-
const progress = usePsChartReveal(
|
|
45
|
+
const { gesture, index } = usePsChartScrub(data.length, isBars, plot);
|
|
46
|
+
const progress = usePsChartReveal(!isLoading && width > 0, revealKey);
|
|
46
47
|
if (isLoading)
|
|
47
48
|
return (_jsx(PsChartSkeleton, { height: baseHeight, variant: PsChartKindService.skeletonVariant(kind) }));
|
|
48
49
|
if (data.length === 0 || PsChartSeriesService.isEmpty(data, series))
|
|
@@ -65,7 +66,7 @@ export const PsCartesianChart = ({ data, emptyLabel, formatTick, formatValue, he
|
|
|
65
66
|
: isStacked
|
|
66
67
|
? PsChartBarService.stacked(data, barSeries, barColors, domain, plot)
|
|
67
68
|
: PsChartBarService.grouped(data, barSeries, barColors, domain, plot);
|
|
68
|
-
return (_jsxs(View, { style: { gap: 8 }, children: [_jsx(PsChartReadout, { items: items, lines: readoutLines, title: datum.label }), _jsx(View, { accessible: true, accessibilityLabel: PsChartReadoutService.summary(datum.label, items), accessibilityRole: 'image', onLayout: onLayout, style: { height }, children: width === 0 ?
|
|
69
|
+
return (_jsxs(View, { style: { gap: 8 }, children: [_jsx(PsChartReadout, { items: items, lines: readoutLines, title: datum.label }), _jsx(View, { accessible: true, accessibilityLabel: PsChartReadoutService.summary(datum.label, items), accessibilityRole: 'image', onLayout: onLayout, style: { height }, children: width === 0 ? (_jsx(PsChartSkeleton, { height: baseHeight, variant: PsChartKindService.skeletonVariant(kind) })) : (_jsxs(_Fragment, { children: [_jsxs(Svg, { height: height, pointerEvents: 'none', width: width, children: [PsChartScaleService.ticks(domain).map((tick, tickIndex) => {
|
|
69
70
|
const y = PsChartScaleService.y(tick, domain, plot);
|
|
70
71
|
return (_jsxs(G, { children: [_jsx(Line, { stroke: theme.line, strokeWidth: PsChartConfig.gridLineWidth, x1: plot.left, x2: plot.right, y1: y, y2: y }), _jsx(SvgText, { fill: theme.muted, fontFamily: theme.fonts.medium, fontSize: PsChartConfig.axisLabelSize, textAnchor: 'end', x: plot.left - PsChartConfig.axisTickGap, y: y + PsChartConfig.axisLabelSize / 3, children: PsChartTextService.fit(tickLabels[tickIndex], plot.left - PsChartConfig.axisTickGap, PsChartConfig.axisLabelSize) })] }, tick));
|
|
71
72
|
}), xLabels.indexes.map((position) => (_jsx(SvgText, { fill: theme.muted, fontFamily: theme.fonts.medium, fontSize: PsChartConfig.axisLabelSize, textAnchor: PsChartScaleService.labelAnchor(position, data.length, isBars), x: xAt(position), y: height - 6, children: PsChartTextService.fit(data[position].label, xLabels.maxWidth, PsChartConfig.axisLabelSize) }, position))), rects.map((rect) => (_jsx(PsChartBar, { progress: progress, rect: rect }, rect.id))), lineSeries.map((entry, lineIndex) => {
|
|
@@ -77,7 +78,7 @@ export const PsCartesianChart = ({ data, emptyLabel, formatTick, formatValue, he
|
|
|
77
78
|
return (_jsx(PsChartLine, { areaBaseline: kind === PsCartesianChartKind.Area
|
|
78
79
|
? PsChartScaleService.y(0, domain, plot)
|
|
79
80
|
: undefined, color: colors[position], points: points, progress: progress }, entry.dataKey));
|
|
80
|
-
}), index === null ? null : (_jsx(Line, { stroke: theme.muted, strokeWidth: PsChartConfig.scrubLineWidth, x1: xAt(selected), x2: xAt(selected), y1: plot.top, y2: plot.bottom })), lineSeries.map((entry, lineIndex) => (_jsx(Circle, { cx: xAt(selected), cy: PsChartScaleService.y(PsChartSeriesService.value(datum, entry.dataKey), domain, plot), fill: theme.surface, r: PsChartConfig.markerRadius, stroke: colors[lineIndexes[lineIndex]], strokeWidth: 2 }, entry.dataKey)))] }), _jsx(
|
|
81
|
+
}), index === null ? null : (_jsx(Line, { stroke: theme.muted, strokeWidth: PsChartConfig.scrubLineWidth, x1: xAt(selected), x2: xAt(selected), y1: plot.top, y2: plot.bottom })), lineSeries.map((entry, lineIndex) => (_jsx(Circle, { cx: xAt(selected), cy: PsChartScaleService.y(PsChartSeriesService.value(datum, entry.dataKey), domain, plot), fill: theme.surface, r: PsChartConfig.markerRadius, stroke: colors[lineIndexes[lineIndex]], strokeWidth: 2 }, entry.dataKey)))] }), _jsx(GestureDetector, { gesture: gesture, children: _jsx(View, { style: StyleSheet.absoluteFill, testID: 'ps-chart-touch' }) })] })) }), showLegend && series.length > 1 ? (_jsx(PsChartLegend, { items: series.map((entry, position) => ({
|
|
81
82
|
color: colors[position],
|
|
82
83
|
label: entry.label,
|
|
83
84
|
})) })) : null] }));
|
|
@@ -11,5 +11,9 @@ export const PsChartFrame = ({ children, delta, period, title, value }) => {
|
|
|
11
11
|
const size = value === undefined
|
|
12
12
|
? PsChartConfig.kpiValueFontMax
|
|
13
13
|
: PsChartTextService.fontSize(value, width, PsChartConfig.kpiValueFontMax, PsChartConfig.kpiValueFontMin);
|
|
14
|
-
return (_jsx(PsCard, { style: { gap: 12 }, children: _jsxs(View, { onLayout: onLayout, style: { gap: 12 }, testID: 'ps-chart-frame', children: [title === undefined && value === undefined ? null : (_jsxs(View, { style: { gap: 4 }, children: [title === undefined ? null : (_jsx(PsText, { numberOfLines: 1, tone: 'muted', variant: 'label', children: title })), value === undefined ? null : (_jsx(PsText, { numberOfLines: 1, style: {
|
|
14
|
+
return (_jsx(PsCard, { style: { gap: 12 }, children: _jsxs(View, { onLayout: onLayout, style: { gap: 12 }, testID: 'ps-chart-frame', children: [title === undefined && value === undefined ? null : (_jsxs(View, { style: { gap: 4 }, children: [title === undefined ? null : (_jsx(PsText, { numberOfLines: 1, tone: 'muted', variant: 'label', children: title })), value === undefined ? null : (_jsx(PsText, { numberOfLines: 1, style: {
|
|
15
|
+
fontSize: size,
|
|
16
|
+
lineHeight: Math.round(size * 1.25),
|
|
17
|
+
opacity: width === 0 ? 0 : 1,
|
|
18
|
+
}, variant: 'numeric', weight: 'bold', children: value })), delta === undefined ? null : _jsx(PsChartDeltaBadge, { ...delta }), period === undefined ? null : (_jsx(PsText, { tone: 'muted', variant: 'caption', children: period }))] })), children] }) }));
|
|
15
19
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { PsDonutChartProps } from './ps-donut-chart-props';
|
|
2
|
-
export declare const PsDonutChart: ({ centerLabel, centerValue, data, emptyLabel, formatShare, height, isLoading, }: PsDonutChartProps) => import("react").JSX.Element;
|
|
2
|
+
export declare const PsDonutChart: ({ centerLabel, centerValue, data, emptyLabel, formatShare, height, isLoading, revealKey, }: PsDonutChartProps) => import("react").JSX.Element;
|
|
@@ -14,12 +14,12 @@ import { PsChartLegend } from '../ps-chart-legend/ps-chart-legend';
|
|
|
14
14
|
import { PsChartSkeleton } from '../ps-chart-skeleton/ps-chart-skeleton';
|
|
15
15
|
import { PsDonutSlice } from '../ps-donut-slice/ps-donut-slice';
|
|
16
16
|
import { PsText } from '../ps-text/ps-text';
|
|
17
|
-
export const PsDonutChart = ({ centerLabel, centerValue, data, emptyLabel, formatShare, height = PsChartConfig.donutHeight, isLoading = false, }) => {
|
|
17
|
+
export const PsDonutChart = ({ centerLabel, centerValue, data, emptyLabel, formatShare, height = PsChartConfig.donutHeight, isLoading = false, revealKey, }) => {
|
|
18
18
|
const theme = usePsMobileTheme();
|
|
19
19
|
const { onLayout, width } = usePsChartWidth();
|
|
20
20
|
const fill = usePsWidgetFill();
|
|
21
21
|
const [selected, setSelected] = useState(null);
|
|
22
|
-
const progress = usePsChartReveal(
|
|
22
|
+
const progress = usePsChartReveal(!isLoading && width > 0, revealKey);
|
|
23
23
|
if (isLoading)
|
|
24
24
|
return _jsx(PsChartSkeleton, { height: height, variant: PsChartSkeletonVariant.Donut });
|
|
25
25
|
const layout = PsChartDonutService.layout(width, height);
|
|
@@ -5,6 +5,8 @@ import { usePsHaptics } from '../../hooks/use-ps-haptics';
|
|
|
5
5
|
import { PsActionMenuService } from '../../services/ps-action-menu-service';
|
|
6
6
|
export const PsHeaderMenu = ({ iconKey = PsMobileIconKey.More, items, label, }) => {
|
|
7
7
|
const haptic = usePsHaptics();
|
|
8
|
+
if (items.length === 0)
|
|
9
|
+
return null;
|
|
8
10
|
const entries = PsActionMenuService.consistent([{ id: 'items', items }])[0];
|
|
9
11
|
if (entries === undefined)
|
|
10
12
|
return null;
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Modal, Pressable, ScrollView, StyleSheet, useWindowDimensions } from 'react-native';
|
|
4
4
|
import Animated from 'react-native-reanimated';
|
|
5
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
6
|
import { PsMenuPopupConfig } from '../../constants/ps-menu-popup-config';
|
|
6
7
|
import { usePsMaterialScheme } from '../../hooks/use-ps-material-scheme';
|
|
7
8
|
import { usePsMenuPopupReveal } from '../../hooks/use-ps-menu-popup-reveal';
|
|
@@ -12,34 +13,35 @@ export const PsMenuPopup = ({ anchor, children, isFullWidth = false, onClose, })
|
|
|
12
13
|
const theme = usePsMobileTheme();
|
|
13
14
|
const scheme = usePsMaterialScheme();
|
|
14
15
|
const window = useWindowDimensions();
|
|
16
|
+
const insets = useSafeAreaInsets();
|
|
15
17
|
const isMaterial = PsMenuPopupStyleService.isMaterial();
|
|
16
18
|
const [size, setSize] = useState(undefined);
|
|
17
19
|
const layout = size === undefined
|
|
18
|
-
?
|
|
19
|
-
: PsMenuPopupService.layout(anchor, window, size.height, size.width, isFullWidth);
|
|
20
|
+
? PsMenuPopupService.layout(anchor, window, insets, Number.POSITIVE_INFINITY, PsMenuPopupConfig.minWidth, isFullWidth)
|
|
21
|
+
: PsMenuPopupService.layout(anchor, window, insets, size.height, size.width, isFullWidth);
|
|
20
22
|
const container = PsMenuPopupStyleService.container(theme, scheme, isMaterial);
|
|
21
|
-
const reveal = usePsMenuPopupReveal(
|
|
23
|
+
const reveal = usePsMenuPopupReveal(size !== undefined, layout.isBelow, isMaterial);
|
|
22
24
|
return (_jsxs(Modal, { animationType: isMaterial ? 'none' : 'fade', navigationBarTranslucent: true, onRequestClose: onClose, statusBarTranslucent: true, transparent: true, visible: true, children: [_jsx(Pressable, { accessibilityElementsHidden: true, onPress: onClose, style: StyleSheet.absoluteFill, testID: 'ps-menu-popup-backdrop' }), _jsx(Animated.View, { onLayout: (event) => {
|
|
23
25
|
if (size === undefined)
|
|
24
26
|
setSize({
|
|
25
27
|
height: event.nativeEvent.layout.height,
|
|
26
28
|
width: event.nativeEvent.layout.width,
|
|
27
29
|
});
|
|
28
|
-
}, pointerEvents:
|
|
30
|
+
}, pointerEvents: size === undefined ? 'none' : 'auto', style: [
|
|
29
31
|
{
|
|
30
32
|
backgroundColor: container.backgroundColor,
|
|
31
33
|
borderColor: container.borderColor,
|
|
32
34
|
borderRadius: container.borderRadius,
|
|
33
35
|
borderWidth: container.borderWidth,
|
|
34
36
|
elevation: container.elevation,
|
|
35
|
-
left: layout
|
|
36
|
-
maxHeight: layout
|
|
37
|
+
left: layout.left,
|
|
38
|
+
maxHeight: layout.maxHeight,
|
|
37
39
|
maxWidth: PsMenuPopupConfig.maxWidth,
|
|
38
40
|
minWidth: isFullWidth ? undefined : PsMenuPopupConfig.minWidth,
|
|
39
41
|
overflow: 'hidden',
|
|
40
42
|
position: 'absolute',
|
|
41
|
-
top: layout
|
|
42
|
-
width: layout
|
|
43
|
+
top: layout.top,
|
|
44
|
+
width: layout.width,
|
|
43
45
|
},
|
|
44
46
|
reveal,
|
|
45
47
|
], testID: 'ps-menu-popup', children: _jsx(ScrollView, { bounces: false, contentContainerStyle: { paddingVertical: container.paddingVertical }, children: children }) })] }));
|
|
@@ -2,8 +2,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { BottomSheet, Group, RNHostView } from '@expo/ui/swift-ui';
|
|
4
4
|
import { frame, interactiveDismissDisabled, presentationBackground, presentationDetents, presentationDragIndicator, } from '@expo/ui/swift-ui/modifiers';
|
|
5
|
+
import { useWindowDimensions } from 'react-native';
|
|
6
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
7
|
import { PsNativeTreeContext } from '../../contexts/ps-native-tree-context';
|
|
6
8
|
import { PsSheetMeasureContext } from '../../contexts/ps-sheet-measure-context';
|
|
9
|
+
import { usePsKeyboardHeight } from '../../hooks/use-ps-keyboard-height';
|
|
7
10
|
import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
|
|
8
11
|
import { PsSheetService } from '../../services/ps-sheet-service';
|
|
9
12
|
import { PsHost } from '../ps-host/ps-host';
|
|
@@ -12,6 +15,10 @@ export const PsSheetPresenter = ({ children, isDismissBlocked, isOpen, onClose,
|
|
|
12
15
|
const isOpenRef = useRef(isOpen);
|
|
13
16
|
const isBlockedRef = useRef(isDismissBlocked);
|
|
14
17
|
const [height, setHeight] = useState(0);
|
|
18
|
+
const keyboardHeight = usePsKeyboardHeight();
|
|
19
|
+
const window = useWindowDimensions();
|
|
20
|
+
const insets = useSafeAreaInsets();
|
|
21
|
+
const detentHeight = height > 0 ? PsSheetService.raisedHeight(height, keyboardHeight, window.height, insets.top) : 0;
|
|
15
22
|
isOpenRef.current = isOpen;
|
|
16
23
|
isBlockedRef.current = isDismissBlocked;
|
|
17
24
|
const measure = useCallback((next) => {
|
|
@@ -27,7 +34,7 @@ export const PsSheetPresenter = ({ children, isDismissBlocked, isOpen, onClose,
|
|
|
27
34
|
}
|
|
28
35
|
}, children: _jsx(Group, { modifiers: [
|
|
29
36
|
frame({ alignment: 'top', maxHeight: Infinity, maxWidth: Infinity }),
|
|
30
|
-
presentationDetents(
|
|
37
|
+
presentationDetents(detentHeight > 0 ? [{ height: detentHeight }] : ['medium']),
|
|
31
38
|
presentationDragIndicator('visible'),
|
|
32
39
|
presentationBackground(theme.background),
|
|
33
40
|
interactiveDismissDisabled(isDismissBlocked),
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { PsSparklineProps } from './ps-sparkline-props';
|
|
2
|
-
export declare const PsSparkline: ({ color, height: baseHeight, isLoading, showArea, values, }: PsSparklineProps) => import("react").JSX.Element;
|
|
2
|
+
export declare const PsSparkline: ({ color, height: baseHeight, isLoading, revealKey, showArea, values, }: PsSparklineProps) => import("react").JSX.Element;
|
|
@@ -10,11 +10,11 @@ import { usePsWidgetFill } from '../../hooks/use-ps-widget-fill';
|
|
|
10
10
|
import { PsChartScaleService } from '../../services/ps-chart-scale-service';
|
|
11
11
|
import { PsChartLine } from '../ps-chart-line/ps-chart-line';
|
|
12
12
|
import { PsChartSkeleton } from '../ps-chart-skeleton/ps-chart-skeleton';
|
|
13
|
-
export const PsSparkline = ({ color, height: baseHeight = PsChartConfig.sparklineHeight, isLoading = false, showArea = true, values, }) => {
|
|
13
|
+
export const PsSparkline = ({ color, height: baseHeight = PsChartConfig.sparklineHeight, isLoading = false, revealKey, showArea = true, values, }) => {
|
|
14
14
|
const theme = usePsMobileTheme();
|
|
15
15
|
const height = baseHeight + usePsWidgetFill();
|
|
16
16
|
const { onLayout, width } = usePsChartWidth();
|
|
17
|
-
const progress = usePsChartReveal(
|
|
17
|
+
const progress = usePsChartReveal(!isLoading && width > 0, revealKey);
|
|
18
18
|
if (isLoading)
|
|
19
19
|
return _jsx(PsChartSkeleton, { height: height, variant: PsChartSkeletonVariant.Line });
|
|
20
20
|
if (values.length === 0)
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { PsWaterfallChartProps } from './ps-waterfall-chart-props';
|
|
2
|
-
export declare const PsWaterfallChart: ({ data, emptyLabel, formatTick, formatValue, height: baseHeight, isLoading, }: PsWaterfallChartProps) => import("react").JSX.Element;
|
|
2
|
+
export declare const PsWaterfallChart: ({ data, emptyLabel, formatTick, formatValue, height: baseHeight, isLoading, revealKey, }: PsWaterfallChartProps) => import("react").JSX.Element;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
import { GestureDetector } from 'react-native-gesture-handler';
|
|
3
4
|
import Svg, { G, Line, Text as SvgText } from 'react-native-svg';
|
|
4
5
|
import { PsChartConfig } from '../../constants/ps-chart-config';
|
|
5
6
|
import { PsChartSkeletonVariant } from '../../constants/ps-chart-skeleton-variant';
|
|
@@ -16,7 +17,7 @@ import { PsChartBar } from '../ps-chart-bar/ps-chart-bar';
|
|
|
16
17
|
import { PsChartEmptyState } from '../ps-chart-empty-state/ps-chart-empty-state';
|
|
17
18
|
import { PsChartReadout } from '../ps-chart-readout/ps-chart-readout';
|
|
18
19
|
import { PsChartSkeleton } from '../ps-chart-skeleton/ps-chart-skeleton';
|
|
19
|
-
export const PsWaterfallChart = ({ data, emptyLabel, formatTick, formatValue, height: baseHeight = PsChartConfig.chartHeight, isLoading = false, }) => {
|
|
20
|
+
export const PsWaterfallChart = ({ data, emptyLabel, formatTick, formatValue, height: baseHeight = PsChartConfig.chartHeight, isLoading = false, revealKey, }) => {
|
|
20
21
|
const theme = usePsMobileTheme();
|
|
21
22
|
const { onLayout, width } = usePsChartWidth();
|
|
22
23
|
const height = baseHeight + usePsWidgetFill();
|
|
@@ -24,8 +25,8 @@ export const PsWaterfallChart = ({ data, emptyLabel, formatTick, formatValue, he
|
|
|
24
25
|
const domain = PsChartScaleService.domain(PsWaterfallService.values(steps));
|
|
25
26
|
const tickLabels = PsChartScaleService.ticks(domain).map((tick) => formatTick(tick));
|
|
26
27
|
const plot = PsChartScaleService.plot(width, height, PsChartAxisService.yAxisWidth(tickLabels, width));
|
|
27
|
-
const {
|
|
28
|
-
const progress = usePsChartReveal(
|
|
28
|
+
const { gesture, index } = usePsChartScrub(data.length, true, plot);
|
|
29
|
+
const progress = usePsChartReveal(!isLoading && width > 0, revealKey);
|
|
29
30
|
if (isLoading)
|
|
30
31
|
return _jsx(PsChartSkeleton, { height: baseHeight, variant: PsChartSkeletonVariant.Bars });
|
|
31
32
|
if (steps.length === 0 || PsWaterfallService.values(steps).every((value) => value === 0))
|
|
@@ -36,8 +37,8 @@ export const PsWaterfallChart = ({ data, emptyLabel, formatTick, formatValue, he
|
|
|
36
37
|
const step = steps[selected];
|
|
37
38
|
const rects = PsWaterfallService.rects(steps, colors, domain, plot);
|
|
38
39
|
const color = step.isTotal ? colors[2] : step.value < 0 ? colors[1] : colors[0];
|
|
39
|
-
return (_jsxs(View, { style: { gap: 8 }, children: [_jsx(PsChartReadout, { items: [{ color, label: step.label, value: formatValue(step.value) }], title: step.label }), _jsx(View, { accessible: true, accessibilityLabel: `${step.label}, ${formatValue(step.value)}`, accessibilityRole: 'image', onLayout: onLayout, style: { height }, children: width === 0 ?
|
|
40
|
+
return (_jsxs(View, { style: { gap: 8 }, children: [_jsx(PsChartReadout, { items: [{ color, label: step.label, value: formatValue(step.value) }], title: step.label }), _jsx(View, { accessible: true, accessibilityLabel: `${step.label}, ${formatValue(step.value)}`, accessibilityRole: 'image', onLayout: onLayout, style: { height }, children: width === 0 ? (_jsx(PsChartSkeleton, { height: baseHeight, variant: PsChartSkeletonVariant.Bars })) : (_jsxs(_Fragment, { children: [_jsxs(Svg, { height: height, pointerEvents: 'none', width: width, children: [PsChartScaleService.ticks(domain).map((tick, tickIndex) => {
|
|
40
41
|
const y = PsChartScaleService.y(tick, domain, plot);
|
|
41
42
|
return (_jsxs(G, { children: [_jsx(Line, { stroke: theme.line, strokeWidth: PsChartConfig.gridLineWidth, x1: plot.left, x2: plot.right, y1: y, y2: y }), _jsx(SvgText, { fill: theme.muted, fontFamily: theme.fonts.medium, fontSize: PsChartConfig.axisLabelSize, textAnchor: 'end', x: plot.left - PsChartConfig.axisTickGap, y: y + PsChartConfig.axisLabelSize / 3, children: PsChartTextService.fit(tickLabels[tickIndex], plot.left - PsChartConfig.axisTickGap, PsChartConfig.axisLabelSize) })] }, tick));
|
|
42
|
-
}), xLabels.indexes.map((position) => (_jsx(SvgText, { fill: theme.muted, fontFamily: theme.fonts.medium, fontSize: PsChartConfig.axisLabelSize, textAnchor: 'middle', x: PsChartScaleService.categoryX(position, steps.length, plot, true), y: height - 6, children: PsChartTextService.fit(steps[position].label, xLabels.maxWidth, PsChartConfig.axisLabelSize) }, steps[position].label))), rects.map((rect) => (_jsx(PsChartBar, { progress: progress, rect: rect }, rect.id)))] }), _jsx(
|
|
43
|
+
}), xLabels.indexes.map((position) => (_jsx(SvgText, { fill: theme.muted, fontFamily: theme.fonts.medium, fontSize: PsChartConfig.axisLabelSize, textAnchor: 'middle', x: PsChartScaleService.categoryX(position, steps.length, plot, true), y: height - 6, children: PsChartTextService.fit(steps[position].label, xLabels.maxWidth, PsChartConfig.axisLabelSize) }, steps[position].label))), rects.map((rect) => (_jsx(PsChartBar, { progress: progress, rect: rect }, rect.id)))] }), _jsx(GestureDetector, { gesture: gesture, children: _jsx(View, { style: StyleSheet.absoluteFill, testID: 'ps-chart-touch' }) })] })) })] }));
|
|
43
44
|
};
|
|
@@ -45,7 +45,10 @@ export declare const PsChartConfig: {
|
|
|
45
45
|
readonly readoutLineHeight: 22;
|
|
46
46
|
readonly readoutRowGap: 2;
|
|
47
47
|
readonly readoutValueSize: 15;
|
|
48
|
+
readonly scrubActiveOffset: 8;
|
|
49
|
+
readonly scrubFailOffsetY: 10;
|
|
48
50
|
readonly scrubLineWidth: 1;
|
|
51
|
+
readonly scrubTestId: "ps-chart-scrub";
|
|
49
52
|
readonly skeletonBarHeights: readonly [0.45, 0.7, 0.55, 0.85, 0.65, 0.95, 0.75];
|
|
50
53
|
readonly skeletonPulseMs: 900;
|
|
51
54
|
readonly sparklineHeight: 40;
|
|
@@ -45,7 +45,10 @@ export const PsChartConfig = {
|
|
|
45
45
|
readoutLineHeight: 22,
|
|
46
46
|
readoutRowGap: 2,
|
|
47
47
|
readoutValueSize: 15,
|
|
48
|
+
scrubActiveOffset: 8,
|
|
49
|
+
scrubFailOffsetY: 10,
|
|
48
50
|
scrubLineWidth: 1,
|
|
51
|
+
scrubTestId: 'ps-chart-scrub',
|
|
49
52
|
skeletonBarHeights: [0.45, 0.7, 0.55, 0.85, 0.65, 0.95, 0.75],
|
|
50
53
|
skeletonPulseMs: 900,
|
|
51
54
|
sparklineHeight: 40,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { type SharedValue } from 'react-native-reanimated';
|
|
2
|
-
export declare const usePsChartReveal: (
|
|
2
|
+
export declare const usePsChartReveal: (isReady: boolean, revealKey: string | undefined) => SharedValue<number>;
|
|
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
|
|
|
2
2
|
import { useSharedValue, withTiming } from 'react-native-reanimated';
|
|
3
3
|
import { PsChartConfig } from '../constants/ps-chart-config';
|
|
4
4
|
import { usePsMobileTheme } from './use-ps-mobile-theme';
|
|
5
|
-
export const usePsChartReveal = (
|
|
5
|
+
export const usePsChartReveal = (isReady, revealKey) => {
|
|
6
6
|
const { isReduceMotion: isReduced } = usePsMobileTheme();
|
|
7
7
|
const progress = useSharedValue(isReduced ? 1 : 0);
|
|
8
8
|
useEffect(() => {
|
|
@@ -10,8 +10,10 @@ export const usePsChartReveal = (signature) => {
|
|
|
10
10
|
progress.set(1);
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
|
+
if (!isReady)
|
|
14
|
+
return;
|
|
13
15
|
progress.set(0);
|
|
14
16
|
progress.set(withTiming(1, { duration: PsChartConfig.drawMs }));
|
|
15
|
-
}, [isReduced, progress,
|
|
17
|
+
}, [isReady, isReduced, progress, revealKey]);
|
|
16
18
|
return progress;
|
|
17
19
|
};
|
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { usePanGesture } from 'react-native-gesture-handler';
|
|
3
|
+
import { PsChartConfig } from '../constants/ps-chart-config';
|
|
3
4
|
import { PsChartScrubService } from '../services/ps-chart-scrub-service';
|
|
4
5
|
export const usePsChartScrub = (count, isBand, plot) => {
|
|
5
6
|
const [index, setIndex] = useState(null);
|
|
6
7
|
const { bottom, left, right, top } = plot;
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}, [bottom, count, isBand, left, right, top]);
|
|
22
|
-
return { index, panHandlers };
|
|
8
|
+
const area = { bottom, left, right, top };
|
|
9
|
+
const find = (x) => isBand
|
|
10
|
+
? PsChartScrubService.bandIndex(x, area, count)
|
|
11
|
+
: PsChartScrubService.pointIndex(x, area, count);
|
|
12
|
+
const gesture = usePanGesture({
|
|
13
|
+
activeOffsetX: [-PsChartConfig.scrubActiveOffset, PsChartConfig.scrubActiveOffset],
|
|
14
|
+
failOffsetY: [-PsChartConfig.scrubFailOffsetY, PsChartConfig.scrubFailOffsetY],
|
|
15
|
+
onActivate: (event) => setIndex(find(event.x)),
|
|
16
|
+
onFinalize: () => setIndex(null),
|
|
17
|
+
onUpdate: (event) => setIndex(find(event.x)),
|
|
18
|
+
runOnJS: true,
|
|
19
|
+
testID: PsChartConfig.scrubTestId,
|
|
20
|
+
});
|
|
21
|
+
return { gesture, index };
|
|
23
22
|
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { PsInfiniteDataOptions } from '../interfaces/ps-infinite-data-options';
|
|
2
2
|
import type { PsInfiniteDataResult } from '../interfaces/ps-infinite-data-result';
|
|
3
|
-
export declare const usePsInfiniteData: <T>({ getKey, isEnabled, key, loadPage, onSettled, onWriteFailed, refreshKey, }: PsInfiniteDataOptions<T>) => PsInfiniteDataResult<T>;
|
|
3
|
+
export declare const usePsInfiniteData: <T>({ getKey, isEnabled, key, keepPreviousOnKeyChange, loadPage, onSettled, onWriteFailed, refreshKey, }: PsInfiniteDataOptions<T>) => PsInfiniteDataResult<T>;
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { usePsChangeQueue, usePsOptimisticRows } from '@pallasoft/ps-utilities/react';
|
|
3
3
|
import { PsInfiniteDataService } from '../services/ps-infinite-data-service';
|
|
4
|
-
export const usePsInfiniteData = ({ getKey, isEnabled = true, key, loadPage, onSettled, onWriteFailed, refreshKey, }) => {
|
|
4
|
+
export const usePsInfiniteData = ({ getKey, isEnabled = true, key, keepPreviousOnKeyChange = false, loadPage, onSettled, onWriteFailed, refreshKey, }) => {
|
|
5
5
|
const [state, setState] = useState(() => PsInfiniteDataService.initial());
|
|
6
6
|
const [isSaving, setIsSaving] = useState(false);
|
|
7
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
7
8
|
const isSavingRef = useRef(false);
|
|
8
9
|
const submittedRef = useRef(0);
|
|
9
10
|
const stateRef = useRef(state);
|
|
10
11
|
const committedRef = useRef([]);
|
|
11
12
|
const versionRef = useRef(0);
|
|
12
|
-
const latestRef = useRef({
|
|
13
|
+
const latestRef = useRef({
|
|
14
|
+
getKey,
|
|
15
|
+
keepPreviousOnKeyChange,
|
|
16
|
+
loadPage,
|
|
17
|
+
onSettled,
|
|
18
|
+
onWriteFailed,
|
|
19
|
+
});
|
|
20
|
+
const loadedKeyRef = useRef(key);
|
|
13
21
|
useEffect(() => {
|
|
14
|
-
latestRef.current = { getKey, loadPage, onSettled, onWriteFailed };
|
|
22
|
+
latestRef.current = { getKey, keepPreviousOnKeyChange, loadPage, onSettled, onWriteFailed };
|
|
15
23
|
});
|
|
16
24
|
const commit = useCallback((next) => {
|
|
17
25
|
stateRef.current = next(stateRef.current);
|
|
@@ -21,15 +29,17 @@ export const usePsInfiniteData = ({ getKey, isEnabled = true, key, loadPage, onS
|
|
|
21
29
|
isSavingRef.current = value;
|
|
22
30
|
setIsSaving(value);
|
|
23
31
|
}, []);
|
|
24
|
-
const fetchFirst = useCallback(async (isSilent) => {
|
|
32
|
+
const fetchFirst = useCallback(async (isSilent, isTracked) => {
|
|
25
33
|
versionRef.current += 1;
|
|
26
34
|
const version = versionRef.current;
|
|
27
35
|
if (!isSilent)
|
|
28
36
|
commit(() => PsInfiniteDataService.initial());
|
|
37
|
+
setIsRefreshing(isTracked);
|
|
29
38
|
try {
|
|
30
39
|
const page = await latestRef.current.loadPage(null);
|
|
31
40
|
if (version !== versionRef.current)
|
|
32
41
|
return;
|
|
42
|
+
setIsRefreshing(false);
|
|
33
43
|
commit(() => ({
|
|
34
44
|
error: false,
|
|
35
45
|
isLoading: false,
|
|
@@ -42,6 +52,7 @@ export const usePsInfiniteData = ({ getKey, isEnabled = true, key, loadPage, onS
|
|
|
42
52
|
catch {
|
|
43
53
|
if (version !== versionRef.current)
|
|
44
54
|
return;
|
|
55
|
+
setIsRefreshing(false);
|
|
45
56
|
commit((current) => ({ ...current, error: true, isLoading: false, isLoadingMore: false }));
|
|
46
57
|
}
|
|
47
58
|
}, [commit]);
|
|
@@ -83,10 +94,14 @@ export const usePsInfiniteData = ({ getKey, isEnabled = true, key, loadPage, onS
|
|
|
83
94
|
setSaving(false);
|
|
84
95
|
if (!isEnabled) {
|
|
85
96
|
versionRef.current += 1;
|
|
97
|
+
setIsRefreshing(false);
|
|
86
98
|
commit(() => PsInfiniteDataService.initial());
|
|
87
99
|
return undefined;
|
|
88
100
|
}
|
|
89
|
-
|
|
101
|
+
const isKept = stateRef.current.items.length > 0 &&
|
|
102
|
+
PsInfiniteDataService.keepsPrevious(latestRef.current.keepPreviousOnKeyChange, loadedKeyRef.current, key);
|
|
103
|
+
loadedKeyRef.current = key;
|
|
104
|
+
void fetchFirst(isKept, isKept);
|
|
90
105
|
return () => {
|
|
91
106
|
versionRef.current += 1;
|
|
92
107
|
};
|
|
@@ -125,7 +140,7 @@ export const usePsInfiniteData = ({ getKey, isEnabled = true, key, loadPage, onS
|
|
|
125
140
|
commit((value) => ({ ...value, error: true, isLoadingMore: false }));
|
|
126
141
|
}
|
|
127
142
|
}, [commit]);
|
|
128
|
-
const refresh = useCallback(() => fetchFirst(true), [fetchFirst]);
|
|
143
|
+
const refresh = useCallback(() => fetchFirst(true, false), [fetchFirst]);
|
|
129
144
|
const apply = useCallback((rows, updates) => PsInfiniteDataService.apply(rows, updates), []);
|
|
130
145
|
const optimistic = usePsOptimisticRows({
|
|
131
146
|
apply,
|
|
@@ -184,9 +199,10 @@ export const usePsInfiniteData = ({ getKey, isEnabled = true, key, loadPage, onS
|
|
|
184
199
|
hasMore: PsInfiniteDataService.hasMore(state),
|
|
185
200
|
isLoading: state.isLoading,
|
|
186
201
|
isLoadingMore: state.isLoadingMore,
|
|
202
|
+
isRefreshing,
|
|
187
203
|
items: optimistic.rows,
|
|
188
204
|
loadMore,
|
|
189
205
|
refresh,
|
|
190
206
|
write,
|
|
191
|
-
}), [loadMore, optimistic.rows, refresh, state, write]);
|
|
207
|
+
}), [isRefreshing, loadMore, optimistic.rows, refresh, state, write]);
|
|
192
208
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const usePsKeyboardHeight: () => number;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Keyboard } from 'react-native';
|
|
3
|
+
export const usePsKeyboardHeight = () => {
|
|
4
|
+
const [height, setHeight] = useState(0);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const show = Keyboard.addListener('keyboardWillShow', (event) => setHeight(event.endCoordinates.height));
|
|
7
|
+
const hide = Keyboard.addListener('keyboardWillHide', () => setHeight(0));
|
|
8
|
+
return () => {
|
|
9
|
+
show.remove();
|
|
10
|
+
hide.remove();
|
|
11
|
+
};
|
|
12
|
+
}, []);
|
|
13
|
+
return height;
|
|
14
|
+
};
|
|
@@ -2,6 +2,7 @@ import type { PsInfinitePage } from './ps-infinite-page';
|
|
|
2
2
|
export interface PsInfiniteDataOptions<T> {
|
|
3
3
|
getKey: (item: T) => string;
|
|
4
4
|
isEnabled?: boolean;
|
|
5
|
+
keepPreviousOnKeyChange?: boolean | ((previousKey: string, nextKey: string) => boolean);
|
|
5
6
|
key: string;
|
|
6
7
|
loadPage: (cursor: string | null) => Promise<PsInfinitePage<T>>;
|
|
7
8
|
onSettled?: () => Promise<unknown>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -7,5 +7,4 @@ export declare class PsChartSeriesService {
|
|
|
7
7
|
static stackedValues(data: PsAreaChartDatum[], series: PsCartesianChartSeries[]): number[];
|
|
8
8
|
static color({ count, explicit, index, theme }: PsChartPaletteInput): string;
|
|
9
9
|
static isEmpty(data: PsAreaChartDatum[], series: PsCartesianChartSeries[]): boolean;
|
|
10
|
-
static signature(kind: string, data: PsAreaChartDatum[], series: PsCartesianChartSeries[]): string;
|
|
11
10
|
}
|
|
@@ -28,7 +28,4 @@ export class PsChartSeriesService {
|
|
|
28
28
|
static isEmpty(data, series) {
|
|
29
29
|
return PsChartSeriesService.values(data, series).every((value) => value === 0);
|
|
30
30
|
}
|
|
31
|
-
static signature(kind, data, series) {
|
|
32
|
-
return `${kind}:${PsChartSeriesService.values(data, series).join(',')}`;
|
|
33
|
-
}
|
|
34
31
|
}
|
|
@@ -4,5 +4,6 @@ export declare class PsInfiniteDataService {
|
|
|
4
4
|
static initial<T>(): PsInfiniteDataState<T>;
|
|
5
5
|
static merge<T>(existing: T[], incoming: T[], getKey: (item: T) => string): T[];
|
|
6
6
|
static apply<T>(rows: T[], updates: PsInfiniteDataUpdate<T>[]): T[];
|
|
7
|
+
static keepsPrevious(option: boolean | ((previousKey: string, nextKey: string) => boolean), previousKey: string, nextKey: string): boolean;
|
|
7
8
|
static hasMore<T>(state: PsInfiniteDataState<T>): boolean;
|
|
8
9
|
}
|
|
@@ -16,6 +16,9 @@ export class PsInfiniteDataService {
|
|
|
16
16
|
static apply(rows, updates) {
|
|
17
17
|
return updates.reduce((current, update) => update(current), rows);
|
|
18
18
|
}
|
|
19
|
+
static keepsPrevious(option, previousKey, nextKey) {
|
|
20
|
+
return typeof option === 'boolean' ? option : option(previousKey, nextKey);
|
|
21
|
+
}
|
|
19
22
|
static hasMore(state) {
|
|
20
23
|
return state.nextCursor !== null;
|
|
21
24
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PsMenuAnchor } from '../interfaces/ps-menu-anchor';
|
|
2
|
+
import type { PsMenuPopupInsets } from '../interfaces/ps-menu-popup-insets';
|
|
2
3
|
import type { PsMenuPopupLayout } from '../interfaces/ps-menu-popup-layout';
|
|
3
4
|
import type { PsMenuPopupWindow } from '../interfaces/ps-menu-popup-window';
|
|
4
5
|
export declare class PsMenuPopupService {
|
|
5
|
-
static layout(anchor: PsMenuAnchor, window: PsMenuPopupWindow, contentHeight: number, contentWidth: number, isFullWidth: boolean): PsMenuPopupLayout;
|
|
6
|
+
static layout(anchor: PsMenuAnchor, window: PsMenuPopupWindow, insets: PsMenuPopupInsets, contentHeight: number, contentWidth: number, isFullWidth: boolean): PsMenuPopupLayout;
|
|
6
7
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { PsMenuPopupConfig } from '../constants/ps-menu-popup-config';
|
|
2
2
|
export class PsMenuPopupService {
|
|
3
|
-
static layout(anchor, window, contentHeight, contentWidth, isFullWidth) {
|
|
3
|
+
static layout(anchor, window, insets, contentHeight, contentWidth, isFullWidth) {
|
|
4
4
|
const margin = PsMenuPopupConfig.edgeMargin;
|
|
5
5
|
const gap = PsMenuPopupConfig.anchorGap;
|
|
6
6
|
const width = isFullWidth ? anchor.width : undefined;
|
|
7
7
|
const menuWidth = width === undefined ? contentWidth : width;
|
|
8
|
-
const below = window.height - (anchor.y + anchor.height) - gap - margin;
|
|
9
|
-
const above = anchor.y - gap - margin;
|
|
8
|
+
const below = window.height - (anchor.y + anchor.height) - gap - Math.max(margin, insets.bottom);
|
|
9
|
+
const above = anchor.y - gap - Math.max(margin, insets.top);
|
|
10
10
|
const isBelow = contentHeight <= below || below >= above;
|
|
11
11
|
const room = isBelow ? below : above;
|
|
12
12
|
const height = Math.min(contentHeight, Math.max(room, 0));
|
|
@@ -6,6 +6,7 @@ export declare class PsSheetService {
|
|
|
6
6
|
static handlesInset(os: string): boolean;
|
|
7
7
|
static bottomPadding(isFloating: boolean, bottomInset: number): number;
|
|
8
8
|
static fittedHeight(headerHeight: number, bodyHeight: number, windowHeight: number): number;
|
|
9
|
+
static raisedHeight(height: number, keyboardHeight: number, windowHeight: number, topInset: number): number;
|
|
9
10
|
static closeAction(label: string | undefined, onPress: () => void): PsSheetAction | undefined;
|
|
10
11
|
static isActionInactive(action: PsSheetAction): boolean;
|
|
11
12
|
static actionOpacity(action: PsSheetAction, isPressed: boolean): number;
|
|
@@ -13,6 +13,9 @@ export class PsSheetService {
|
|
|
13
13
|
static fittedHeight(headerHeight, bodyHeight, windowHeight) {
|
|
14
14
|
return Math.min(Math.ceil(headerHeight + bodyHeight), PsSheetService.maxHeight(windowHeight));
|
|
15
15
|
}
|
|
16
|
+
static raisedHeight(height, keyboardHeight, windowHeight, topInset) {
|
|
17
|
+
return Math.min(height + keyboardHeight, windowHeight - topInset);
|
|
18
|
+
}
|
|
16
19
|
static closeAction(label, onPress) {
|
|
17
20
|
return label === undefined ? undefined : { label, onPress };
|
|
18
21
|
}
|
package/jest-setup.js
CHANGED
|
@@ -103,6 +103,10 @@ jest.mock('expo-router', () => {
|
|
|
103
103
|
Stack.Toolbar.Button = ({ children, disabled, onPress }) =>
|
|
104
104
|
React.createElement(View, { accessibilityLabel: children, disabled, onPress });
|
|
105
105
|
Stack.Toolbar.Spacer = () => null;
|
|
106
|
+
Stack.Toolbar.Menu = ({ accessibilityLabel, children }) =>
|
|
107
|
+
React.createElement(View, { accessibilityLabel, testID: 'stack-toolbar-menu' }, children);
|
|
108
|
+
Stack.Toolbar.MenuAction = ({ children, onPress }) =>
|
|
109
|
+
React.createElement(View, { accessibilityLabel: children, onPress });
|
|
106
110
|
|
|
107
111
|
const ThemeProvider = ({ children }) => React.createElement(React.Fragment, null, children);
|
|
108
112
|
|
package/package.json
CHANGED