@pallasoft/ps-mobile-components 1.10.2 → 1.12.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.
Files changed (110) hide show
  1. package/dist/components/ps-cartesian-chart/ps-cartesian-chart-props.d.ts +1 -0
  2. package/dist/components/ps-cartesian-chart/ps-cartesian-chart.d.ts +1 -1
  3. package/dist/components/ps-cartesian-chart/ps-cartesian-chart.js +6 -5
  4. package/dist/components/ps-chart-frame/ps-chart-frame.js +5 -1
  5. package/dist/components/ps-document-highlight-layer/index.d.ts +2 -0
  6. package/dist/components/ps-document-highlight-layer/index.js +2 -0
  7. package/dist/components/ps-document-highlight-layer/ps-document-highlight-layer-props.d.ts +8 -0
  8. package/dist/components/ps-document-highlight-layer/ps-document-highlight-layer-props.js +1 -0
  9. package/dist/components/ps-document-highlight-layer/ps-document-highlight-layer.d.ts +2 -0
  10. package/dist/components/ps-document-highlight-layer/ps-document-highlight-layer.js +19 -0
  11. package/dist/components/ps-document-page/ps-document-page-props.d.ts +2 -0
  12. package/dist/components/ps-document-page/ps-document-page.d.ts +1 -1
  13. package/dist/components/ps-document-page/ps-document-page.js +3 -3
  14. package/dist/components/ps-document-search-bar/index.d.ts +2 -0
  15. package/dist/components/ps-document-search-bar/index.js +2 -0
  16. package/dist/components/ps-document-search-bar/ps-document-search-bar-props.d.ts +11 -0
  17. package/dist/components/ps-document-search-bar/ps-document-search-bar-props.js +1 -0
  18. package/dist/components/ps-document-search-bar/ps-document-search-bar.d.ts +2 -0
  19. package/dist/components/ps-document-search-bar/ps-document-search-bar.js +46 -0
  20. package/dist/components/ps-document-search-step/index.d.ts +2 -0
  21. package/dist/components/ps-document-search-step/index.js +2 -0
  22. package/dist/components/ps-document-search-step/ps-document-search-step-props.d.ts +7 -0
  23. package/dist/components/ps-document-search-step/ps-document-search-step-props.js +1 -0
  24. package/dist/components/ps-document-search-step/ps-document-search-step.d.ts +2 -0
  25. package/dist/components/ps-document-search-step/ps-document-search-step.js +30 -0
  26. package/dist/components/ps-document-text-page/ps-document-text-page-props.d.ts +2 -0
  27. package/dist/components/ps-document-text-page/ps-document-text-page.d.ts +1 -1
  28. package/dist/components/ps-document-text-page/ps-document-text-page.js +27 -3
  29. package/dist/components/ps-document-viewer/ps-document-viewer.js +51 -2
  30. package/dist/components/ps-document-zoom-image/ps-document-zoom-image-props.d.ts +4 -0
  31. package/dist/components/ps-document-zoom-image/ps-document-zoom-image.d.ts +1 -1
  32. package/dist/components/ps-document-zoom-image/ps-document-zoom-image.js +20 -3
  33. package/dist/components/ps-donut-chart/ps-donut-chart-props.d.ts +1 -0
  34. package/dist/components/ps-donut-chart/ps-donut-chart.d.ts +1 -1
  35. package/dist/components/ps-donut-chart/ps-donut-chart.js +2 -2
  36. package/dist/components/ps-menu-popup/ps-menu-popup.js +10 -8
  37. package/dist/components/ps-sheet-presenter/ps-sheet-presenter.ios.js +8 -1
  38. package/dist/components/ps-sparkline/ps-sparkline-props.d.ts +1 -0
  39. package/dist/components/ps-sparkline/ps-sparkline.d.ts +1 -1
  40. package/dist/components/ps-sparkline/ps-sparkline.js +2 -2
  41. package/dist/components/ps-text/ps-text-props.d.ts +1 -0
  42. package/dist/components/ps-text/ps-text.d.ts +1 -1
  43. package/dist/components/ps-text/ps-text.js +2 -2
  44. package/dist/components/ps-waterfall-chart/ps-waterfall-chart-props.d.ts +1 -0
  45. package/dist/components/ps-waterfall-chart/ps-waterfall-chart.d.ts +1 -1
  46. package/dist/components/ps-waterfall-chart/ps-waterfall-chart.js +6 -5
  47. package/dist/constants/ps-chart-config.d.ts +3 -0
  48. package/dist/constants/ps-chart-config.js +3 -0
  49. package/dist/constants/ps-document-search-config.d.ts +19 -0
  50. package/dist/constants/ps-document-search-config.js +19 -0
  51. package/dist/hooks/use-ps-chart-reveal.d.ts +1 -1
  52. package/dist/hooks/use-ps-chart-reveal.js +4 -2
  53. package/dist/hooks/use-ps-chart-scrub.js +17 -18
  54. package/dist/hooks/use-ps-document-search-texts.d.ts +2 -0
  55. package/dist/hooks/use-ps-document-search-texts.js +26 -0
  56. package/dist/hooks/use-ps-infinite-data.d.ts +1 -1
  57. package/dist/hooks/use-ps-infinite-data.js +23 -7
  58. package/dist/hooks/use-ps-keyboard-height.d.ts +1 -0
  59. package/dist/hooks/use-ps-keyboard-height.js +14 -0
  60. package/dist/index.d.ts +5 -0
  61. package/dist/index.js +5 -0
  62. package/dist/interfaces/ps-chart-scrub-result.d.ts +2 -2
  63. package/dist/interfaces/ps-document-highlight-rect.d.ts +5 -0
  64. package/dist/interfaces/ps-document-highlight-rect.js +1 -0
  65. package/dist/interfaces/ps-document-item.d.ts +3 -0
  66. package/dist/interfaces/ps-document-labels.d.ts +7 -0
  67. package/dist/interfaces/ps-document-normalized-text.d.ts +5 -0
  68. package/dist/interfaces/ps-document-normalized-text.js +1 -0
  69. package/dist/interfaces/ps-document-point.d.ts +4 -0
  70. package/dist/interfaces/ps-document-point.js +1 -0
  71. package/dist/interfaces/ps-document-search-labels.d.ts +9 -0
  72. package/dist/interfaces/ps-document-search-labels.js +1 -0
  73. package/dist/interfaces/ps-document-search-location.d.ts +4 -0
  74. package/dist/interfaces/ps-document-search-location.js +1 -0
  75. package/dist/interfaces/ps-document-search-match.d.ts +4 -0
  76. package/dist/interfaces/ps-document-search-match.js +1 -0
  77. package/dist/interfaces/ps-document-search-state.d.ts +4 -0
  78. package/dist/interfaces/ps-document-search-state.js +1 -0
  79. package/dist/interfaces/ps-document-text-box.d.ts +6 -0
  80. package/dist/interfaces/ps-document-text-box.js +1 -0
  81. package/dist/interfaces/ps-document-text-layer-word.d.ts +5 -0
  82. package/dist/interfaces/ps-document-text-layer-word.js +1 -0
  83. package/dist/interfaces/ps-document-text-layer.d.ts +6 -0
  84. package/dist/interfaces/ps-document-text-layer.js +1 -0
  85. package/dist/interfaces/ps-document-text-line.d.ts +4 -0
  86. package/dist/interfaces/ps-document-text-line.js +1 -0
  87. package/dist/interfaces/ps-document-text-segment.d.ts +4 -0
  88. package/dist/interfaces/ps-document-text-segment.js +1 -0
  89. package/dist/interfaces/ps-document-word-match.d.ts +3 -0
  90. package/dist/interfaces/ps-document-word-match.js +1 -0
  91. package/dist/interfaces/ps-infinite-data-options.d.ts +1 -0
  92. package/dist/interfaces/ps-infinite-data-result.d.ts +1 -0
  93. package/dist/interfaces/ps-menu-popup-insets.d.ts +4 -0
  94. package/dist/interfaces/ps-menu-popup-insets.js +1 -0
  95. package/dist/services/ps-chart-series-service.d.ts +0 -1
  96. package/dist/services/ps-chart-series-service.js +0 -3
  97. package/dist/services/ps-document-search-service.d.ts +40 -0
  98. package/dist/services/ps-document-search-service.js +201 -0
  99. package/dist/services/ps-document-search-style-service.d.ts +7 -0
  100. package/dist/services/ps-document-search-style-service.js +22 -0
  101. package/dist/services/ps-document-viewer-entry-service.js +1 -0
  102. package/dist/services/ps-document-zoom-service.d.ts +1 -0
  103. package/dist/services/ps-document-zoom-service.js +3 -0
  104. package/dist/services/ps-infinite-data-service.d.ts +1 -0
  105. package/dist/services/ps-infinite-data-service.js +3 -0
  106. package/dist/services/ps-menu-popup-service.d.ts +2 -1
  107. package/dist/services/ps-menu-popup-service.js +3 -3
  108. package/dist/services/ps-sheet-service.d.ts +1 -0
  109. package/dist/services/ps-sheet-service.js +3 -0
  110. package/package.json +1 -1
@@ -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
- ? undefined
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(layout !== undefined, layout === undefined || layout.isBelow, isMaterial);
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: layout === undefined ? 'none' : 'auto', style: [
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 === undefined ? 0 : layout.left,
36
- maxHeight: layout === undefined ? undefined : layout.maxHeight,
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 === undefined ? 0 : layout.top,
42
- width: layout === undefined ? undefined : layout.width,
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(height > 0 ? [{ height }] : ['medium']),
37
+ presentationDetents(detentHeight > 0 ? [{ height: detentHeight }] : ['medium']),
31
38
  presentationDragIndicator('visible'),
32
39
  presentationBackground(theme.background),
33
40
  interactiveDismissDisabled(isDismissBlocked),
@@ -2,6 +2,7 @@ export interface PsSparklineProps {
2
2
  color?: string;
3
3
  height?: number;
4
4
  isLoading?: boolean;
5
+ revealKey?: string;
5
6
  showArea?: boolean;
6
7
  values: number[];
7
8
  }
@@ -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(values.join(','));
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)
@@ -12,6 +12,7 @@ export interface PsTextProps {
12
12
  isSelectable?: boolean;
13
13
  numberOfLines?: number;
14
14
  onPress?: () => void;
15
+ onTextLayout?: TextProps['onTextLayout'];
15
16
  style?: StyleProp<TextStyle>;
16
17
  testID?: string;
17
18
  tone?: PsTextTone;
@@ -1,2 +1,2 @@
1
1
  import type { PsTextProps } from './ps-text-props';
2
- export declare const PsText: ({ accessibilityRole, align, children, ellipsizeMode, isSelectable, numberOfLines, onPress, style, testID, tone, variant, weight, }: PsTextProps) => import("react").JSX.Element;
2
+ export declare const PsText: ({ accessibilityRole, align, children, ellipsizeMode, isSelectable, numberOfLines, onPress, onTextLayout, style, testID, tone, variant, weight, }: PsTextProps) => import("react").JSX.Element;
@@ -2,9 +2,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { StyleSheet, Text } from 'react-native';
3
3
  import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
4
4
  import { PsTextService } from '../../services/ps-text-service';
5
- export const PsText = ({ accessibilityRole, align, children, ellipsizeMode = 'tail', isSelectable = false, numberOfLines, onPress, style, testID, tone = 'ink', variant = 'body', weight, }) => {
5
+ export const PsText = ({ accessibilityRole, align, children, ellipsizeMode = 'tail', isSelectable = false, numberOfLines, onPress, onTextLayout, style, testID, tone = 'ink', variant = 'body', weight, }) => {
6
6
  const theme = usePsMobileTheme();
7
- return (_jsx(Text, { accessibilityRole: accessibilityRole, ellipsizeMode: ellipsizeMode, numberOfLines: numberOfLines, onPress: onPress, selectable: isSelectable, style: StyleSheet.flatten([
7
+ return (_jsx(Text, { accessibilityRole: accessibilityRole, ellipsizeMode: ellipsizeMode, numberOfLines: numberOfLines, onPress: onPress, onTextLayout: onTextLayout, selectable: isSelectable, style: StyleSheet.flatten([
8
8
  PsTextService.style(variant, tone, weight, theme),
9
9
  { textAlign: align },
10
10
  style,
@@ -6,4 +6,5 @@ export interface PsWaterfallChartProps {
6
6
  formatValue: (value: number) => string;
7
7
  height?: number;
8
8
  isLoading?: boolean;
9
+ revealKey?: string;
9
10
  }
@@ -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 { index, panHandlers } = usePsChartScrub(data.length, true, plot);
28
- const progress = usePsChartReveal(`waterfall:${PsWaterfallService.values(steps).join(',')}`);
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 ? null : (_jsxs(_Fragment, { children: [_jsxs(Svg, { height: height, pointerEvents: 'none', width: width, children: [PsChartScaleService.ticks(domain).map((tick, tickIndex) => {
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(View, { ...panHandlers, style: StyleSheet.absoluteFill, testID: 'ps-chart-touch' })] })) })] }));
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,
@@ -0,0 +1,19 @@
1
+ export declare const PsDocumentSearchConfig: {
2
+ readonly activeAlpha: 0.9;
3
+ readonly androidFieldRadius: 999;
4
+ readonly barGap: 8;
5
+ readonly combiningMarkPattern: RegExp;
6
+ readonly currentToken: "{current}";
7
+ readonly fieldAlpha: 0.14;
8
+ readonly fieldPaddingX: 12;
9
+ readonly highlightAlpha: 0.4;
10
+ readonly highlightRadius: 3;
11
+ readonly iosFieldRadius: 10;
12
+ readonly noActive: -1;
13
+ readonly rotateDown: "0deg";
14
+ readonly rotateUp: "180deg";
15
+ readonly scrollMargin: 48;
16
+ readonly termSeparatorPattern: RegExp;
17
+ readonly totalToken: "{total}";
18
+ readonly wordSeparator: " ";
19
+ };
@@ -0,0 +1,19 @@
1
+ export const PsDocumentSearchConfig = {
2
+ activeAlpha: 0.9,
3
+ androidFieldRadius: 999,
4
+ barGap: 8,
5
+ combiningMarkPattern: /\p{M}/gu,
6
+ currentToken: '{current}',
7
+ fieldAlpha: 0.14,
8
+ fieldPaddingX: 12,
9
+ highlightAlpha: 0.4,
10
+ highlightRadius: 3,
11
+ iosFieldRadius: 10,
12
+ noActive: -1,
13
+ rotateDown: '0deg',
14
+ rotateUp: '180deg',
15
+ scrollMargin: 48,
16
+ termSeparatorPattern: /\s+/u,
17
+ totalToken: '{total}',
18
+ wordSeparator: ' ',
19
+ };
@@ -1,2 +1,2 @@
1
1
  import { type SharedValue } from 'react-native-reanimated';
2
- export declare const usePsChartReveal: (signature: string) => SharedValue<number>;
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 = (signature) => {
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, signature]);
17
+ }, [isReady, isReduced, progress, revealKey]);
16
18
  return progress;
17
19
  };
@@ -1,23 +1,22 @@
1
- import { useMemo, useState } from 'react';
2
- import { PanResponder } from 'react-native';
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 panHandlers = useMemo(() => {
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
- return PanResponder.create({
13
- onMoveShouldSetPanResponder: (_event, gesture) => Math.abs(gesture.dx) > Math.abs(gesture.dy),
14
- onPanResponderGrant: (event) => setIndex(find(event.nativeEvent.locationX)),
15
- onPanResponderMove: (event) => setIndex(find(event.nativeEvent.locationX)),
16
- onPanResponderRelease: () => setIndex(null),
17
- onPanResponderTerminate: () => setIndex(null),
18
- onPanResponderTerminationRequest: () => true,
19
- onStartShouldSetPanResponder: () => true,
20
- }).panHandlers;
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
  };
@@ -0,0 +1,2 @@
1
+ import type { PsDocumentItem } from '../interfaces/ps-document-item';
2
+ export declare const usePsDocumentSearchTexts: (items: PsDocumentItem[], isActive: boolean) => Record<string, string>;
@@ -0,0 +1,26 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { PsDocumentTextService } from '../services/ps-document-text-service';
3
+ export const usePsDocumentSearchTexts = (items, isActive) => {
4
+ const [texts, setTexts] = useState({});
5
+ const key = items
6
+ .filter((item) => PsDocumentTextService.needsLoad(item))
7
+ .map((item) => item.id)
8
+ .join('|');
9
+ useEffect(() => {
10
+ if (!isActive || key === '')
11
+ return undefined;
12
+ let isCurrent = true;
13
+ items
14
+ .filter((item) => PsDocumentTextService.needsLoad(item))
15
+ .forEach((item) => {
16
+ void PsDocumentTextService.load(item).then((text) => {
17
+ if (isCurrent && text !== null)
18
+ setTexts((previous) => ({ ...previous, [item.id]: text }));
19
+ });
20
+ });
21
+ return () => {
22
+ isCurrent = false;
23
+ };
24
+ }, [isActive, key]);
25
+ return texts;
26
+ };
@@ -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({ getKey, loadPage, onSettled, onWriteFailed });
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
- void fetchFirst(false);
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
+ };
package/dist/index.d.ts CHANGED
@@ -77,7 +77,11 @@ export * from './interfaces/ps-device-file';
77
77
  export * from './interfaces/ps-document-item';
78
78
  export * from './interfaces/ps-document-labels';
79
79
  export * from './interfaces/ps-document-scanner-options';
80
+ export * from './interfaces/ps-document-search-labels';
80
81
  export * from './interfaces/ps-document-source';
82
+ export * from './interfaces/ps-document-text-box';
83
+ export * from './interfaces/ps-document-text-layer';
84
+ export * from './interfaces/ps-document-text-layer-word';
81
85
  export * from './interfaces/ps-file-upload-options';
82
86
  export * from './interfaces/use-ps-file-upload-result';
83
87
  export * from './interfaces/ps-tab-bar-context-data';
@@ -107,6 +111,7 @@ export { PsActionMenuService } from './services/ps-action-menu-service';
107
111
  export { PsSelectionService } from './services/ps-selection-service';
108
112
  export { PsSortableGridService } from './services/ps-sortable-grid-service';
109
113
  export { PsDocumentPagerService } from './services/ps-document-pager-service';
114
+ export { PsDocumentSearchService } from './services/ps-document-search-service';
110
115
  export { PsDocumentZoomService } from './services/ps-document-zoom-service';
111
116
  export { PsDocumentScannerService } from './services/ps-document-scanner-service';
112
117
  export { PsCapturePickerService } from './services/ps-capture-picker-service';
package/dist/index.js CHANGED
@@ -76,7 +76,11 @@ export * from './interfaces/ps-device-file';
76
76
  export * from './interfaces/ps-document-item';
77
77
  export * from './interfaces/ps-document-labels';
78
78
  export * from './interfaces/ps-document-scanner-options';
79
+ export * from './interfaces/ps-document-search-labels';
79
80
  export * from './interfaces/ps-document-source';
81
+ export * from './interfaces/ps-document-text-box';
82
+ export * from './interfaces/ps-document-text-layer';
83
+ export * from './interfaces/ps-document-text-layer-word';
80
84
  export * from './interfaces/ps-file-upload-options';
81
85
  export * from './interfaces/use-ps-file-upload-result';
82
86
  export * from './interfaces/ps-tab-bar-context-data';
@@ -106,6 +110,7 @@ export { PsActionMenuService } from './services/ps-action-menu-service';
106
110
  export { PsSelectionService } from './services/ps-selection-service';
107
111
  export { PsSortableGridService } from './services/ps-sortable-grid-service';
108
112
  export { PsDocumentPagerService } from './services/ps-document-pager-service';
113
+ export { PsDocumentSearchService } from './services/ps-document-search-service';
109
114
  export { PsDocumentZoomService } from './services/ps-document-zoom-service';
110
115
  export { PsDocumentScannerService } from './services/ps-document-scanner-service';
111
116
  export { PsCapturePickerService } from './services/ps-capture-picker-service';
@@ -1,5 +1,5 @@
1
- import type { GestureResponderHandlers } from 'react-native';
1
+ import type { PanGesture } from 'react-native-gesture-handler';
2
2
  export interface PsChartScrubResult {
3
+ gesture: PanGesture;
3
4
  index: number | null;
4
- panHandlers: GestureResponderHandlers;
5
5
  }
@@ -0,0 +1,5 @@
1
+ import type { PsDocumentTextBox } from './ps-document-text-box';
2
+ export interface PsDocumentHighlightRect {
3
+ box: PsDocumentTextBox;
4
+ isActive: boolean;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,9 +1,12 @@
1
1
  import type { PsDocumentKind } from '../constants/ps-document-kind';
2
2
  import type { PsDocumentSource } from './ps-document-source';
3
+ import type { PsDocumentTextLayer } from './ps-document-text-layer';
3
4
  export interface PsDocumentItem {
4
5
  id: string;
5
6
  kind: PsDocumentKind;
6
7
  name: string;
8
+ pageTextLayers?: PsDocumentTextLayer[];
7
9
  source: PsDocumentSource | null;
8
10
  text: string | null;
11
+ textLayer?: PsDocumentTextLayer;
9
12
  }
@@ -1,10 +1,17 @@
1
1
  export interface PsDocumentLabels {
2
2
  close: string;
3
+ closeSearch?: string;
3
4
  empty: string;
4
5
  error: string;
5
6
  expand: string;
6
7
  loadFailed: string;
8
+ matchCount?: string;
9
+ nextMatch?: string;
10
+ noMatches?: string;
7
11
  open?: string;
12
+ previousMatch?: string;
8
13
  retry: string;
14
+ search?: string;
15
+ searchPlaceholder?: string;
9
16
  unsupported: string;
10
17
  }
@@ -0,0 +1,5 @@
1
+ export interface PsDocumentNormalizedText {
2
+ ends: number[];
3
+ starts: number[];
4
+ value: string;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export interface PsDocumentPoint {
2
+ x: number;
3
+ y: number;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ export interface PsDocumentSearchLabels {
2
+ closeSearch: string;
3
+ matchCount: string;
4
+ nextMatch: string;
5
+ noMatches: string;
6
+ previousMatch: string;
7
+ search: string;
8
+ searchPlaceholder: string;
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export interface PsDocumentSearchLocation {
2
+ entryIndex: number;
3
+ localIndex: number;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export interface PsDocumentSearchMatch {
2
+ end: number;
3
+ start: number;
4
+ }
@@ -0,0 +1 @@
1
+ export {};