@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
@@ -9,6 +9,7 @@ export interface PsCartesianChartProps {
9
9
  height?: number;
10
10
  isLoading?: boolean;
11
11
  kind: PsCartesianChartKind;
12
+ revealKey?: string;
12
13
  series: PsCartesianChartSeries[];
13
14
  showLegend?: boolean;
14
15
  }
@@ -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 { index, panHandlers } = usePsChartScrub(data.length, isBars, plot);
45
- const progress = usePsChartReveal(PsChartSeriesService.signature(kind, data, series));
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 ? null : (_jsxs(_Fragment, { children: [_jsxs(Svg, { height: height, pointerEvents: 'none', width: width, children: [PsChartScaleService.ticks(domain).map((tick, tickIndex) => {
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(View, { ...panHandlers, style: StyleSheet.absoluteFill, testID: 'ps-chart-touch' })] })) }), showLegend && series.length > 1 ? (_jsx(PsChartLegend, { items: series.map((entry, position) => ({
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: { fontSize: size, lineHeight: Math.round(size * 1.25) }, variant: 'numeric', weight: 'bold', children: value })), delta === undefined ? null : _jsx(PsChartDeltaBadge, { ...delta }), period === undefined ? null : (_jsx(PsText, { tone: 'muted', variant: 'caption', children: period }))] })), children] }) }));
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
  };
@@ -0,0 +1,2 @@
1
+ export * from './ps-document-highlight-layer';
2
+ export * from './ps-document-highlight-layer-props';
@@ -0,0 +1,2 @@
1
+ export * from './ps-document-highlight-layer';
2
+ export * from './ps-document-highlight-layer-props';
@@ -0,0 +1,8 @@
1
+ import type { PsDocumentSearchState } from '../../interfaces/ps-document-search-state';
2
+ import type { PsDocumentTextLayer } from '../../interfaces/ps-document-text-layer';
3
+ import type { PsSize } from '../../interfaces/ps-size';
4
+ export interface PsDocumentHighlightLayerProps {
5
+ layer: PsDocumentTextLayer;
6
+ search: PsDocumentSearchState;
7
+ size: PsSize;
8
+ }
@@ -0,0 +1,2 @@
1
+ import type { PsDocumentHighlightLayerProps } from './ps-document-highlight-layer-props';
2
+ export declare const PsDocumentHighlightLayer: ({ layer, search, size, }: PsDocumentHighlightLayerProps) => import("react").JSX.Element;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { View } from 'react-native';
3
+ import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
4
+ import { PsDocumentSearchService } from '../../services/ps-document-search-service';
5
+ import { PsDocumentSearchStyleService } from '../../services/ps-document-search-style-service';
6
+ export const PsDocumentHighlightLayer = ({ layer, search, size, }) => {
7
+ const theme = usePsMobileTheme();
8
+ const rects = PsDocumentSearchService.highlights(layer, search.query, size, search.activeIndex);
9
+ return (_jsx(View, { pointerEvents: 'none', style: { bottom: 0, left: 0, position: 'absolute', right: 0, top: 0 }, testID: 'ps-document-highlight-layer', children: rects.map((rect, index) => (_jsx(View, { style: [
10
+ PsDocumentSearchStyleService.rect(theme, rect.isActive),
11
+ {
12
+ height: rect.box.height,
13
+ left: rect.box.left,
14
+ position: 'absolute',
15
+ top: rect.box.top,
16
+ width: rect.box.width,
17
+ },
18
+ ], testID: rect.isActive ? 'ps-document-highlight-active' : 'ps-document-highlight' }, index))) }));
19
+ };
@@ -1,11 +1,13 @@
1
1
  import type { PsDocumentLoadStatus } from '../../constants/ps-document-load-status';
2
2
  import type { PsDocumentItem } from '../../interfaces/ps-document-item';
3
3
  import type { PsDocumentLabels } from '../../interfaces/ps-document-labels';
4
+ import type { PsDocumentSearchState } from '../../interfaces/ps-document-search-state';
4
5
  export interface PsDocumentPageProps {
5
6
  item: PsDocumentItem;
6
7
  labels: PsDocumentLabels;
7
8
  onDismiss: () => void;
8
9
  onRetry?: () => void;
9
10
  onZoomChange: (isZoomed: boolean) => void;
11
+ search?: PsDocumentSearchState;
10
12
  status?: PsDocumentLoadStatus;
11
13
  }
@@ -1,2 +1,2 @@
1
1
  import type { PsDocumentPageProps } from './ps-document-page-props';
2
- export declare const PsDocumentPage: ({ item, labels, onDismiss, onRetry, onZoomChange, status, }: PsDocumentPageProps) => import("react").JSX.Element;
2
+ export declare const PsDocumentPage: ({ item, labels, onDismiss, onRetry, onZoomChange, search, status, }: PsDocumentPageProps) => import("react").JSX.Element;
@@ -9,7 +9,7 @@ import { PsDocumentTextPage } from '../ps-document-text-page/ps-document-text-pa
9
9
  import { PsDocumentUnsupported } from '../ps-document-unsupported/ps-document-unsupported';
10
10
  import { PsDocumentZoomImage } from '../ps-document-zoom-image/ps-document-zoom-image';
11
11
  import { PsSpinner } from '../ps-spinner/ps-spinner';
12
- export const PsDocumentPage = ({ item, labels, onDismiss, onRetry, onZoomChange, status = PsDocumentLoadStatus.Ready, }) => {
12
+ export const PsDocumentPage = ({ item, labels, onDismiss, onRetry, onZoomChange, search, status = PsDocumentLoadStatus.Ready, }) => {
13
13
  const [attempt, setAttempt] = useState(0);
14
14
  const [hasFailed, setHasFailed] = useState(false);
15
15
  if (item.kind === PsDocumentKind.Pdf) {
@@ -23,9 +23,9 @@ export const PsDocumentPage = ({ item, labels, onDismiss, onRetry, onZoomChange,
23
23
  return hasFailed ? (_jsx(PsDocumentMessage, { actionLabel: labels.retry, iconKey: PsIconKey.Warning, isOnDark: true, onAction: () => {
24
24
  setHasFailed(false);
25
25
  setAttempt((current) => current + 1);
26
- }, title: labels.loadFailed })) : (_jsx(PsDocumentZoomImage, { name: item.name, onDismiss: onDismiss, onError: () => setHasFailed(true), onZoomChange: onZoomChange, source: item.source }, attempt));
26
+ }, title: labels.loadFailed })) : (_jsx(PsDocumentZoomImage, { name: item.name, onDismiss: onDismiss, onError: () => setHasFailed(true), onZoomChange: onZoomChange, search: search, source: item.source, textLayer: item.textLayer }, attempt));
27
27
  }
28
28
  if (item.kind === PsDocumentKind.Text)
29
- return _jsx(PsDocumentTextPage, { item: item, labels: labels });
29
+ return _jsx(PsDocumentTextPage, { item: item, labels: labels, search: search });
30
30
  return _jsx(PsDocumentUnsupported, { isOnDark: true, item: item, labels: labels });
31
31
  };
@@ -0,0 +1,2 @@
1
+ export * from './ps-document-search-bar';
2
+ export * from './ps-document-search-bar-props';
@@ -0,0 +1,2 @@
1
+ export * from './ps-document-search-bar';
2
+ export * from './ps-document-search-bar-props';
@@ -0,0 +1,11 @@
1
+ import type { PsDocumentSearchLabels } from '../../interfaces/ps-document-search-labels';
2
+ export interface PsDocumentSearchBarProps {
3
+ activeIndex: number;
4
+ labels: PsDocumentSearchLabels;
5
+ onChange: (query: string) => void;
6
+ onClose: () => void;
7
+ onNext: () => void;
8
+ onPrevious: () => void;
9
+ query: string;
10
+ total: number;
11
+ }
@@ -0,0 +1,2 @@
1
+ import type { PsDocumentSearchBarProps } from './ps-document-search-bar-props';
2
+ export declare const PsDocumentSearchBar: ({ activeIndex, labels, onChange, onClose, onNext, onPrevious, query, total, }: PsDocumentSearchBarProps) => import("react").JSX.Element;
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PsIconKey } from '@pallasoft/ps-icons';
3
+ import { Platform, Pressable, TextInput, View } from 'react-native';
4
+ import { PsDocumentConfig } from '../../constants/ps-document-config';
5
+ import { PsDocumentSearchConfig } from '../../constants/ps-document-search-config';
6
+ import { PsLayoutConfig } from '../../constants/ps-layout-config';
7
+ import { PsMobileControlConfig } from '../../constants/ps-mobile-control-config';
8
+ import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
9
+ import { PsDocumentSearchService } from '../../services/ps-document-search-service';
10
+ import { PsDocumentSearchStyleService } from '../../services/ps-document-search-style-service';
11
+ import { PsMobileColorService } from '../../services/ps-mobile-color-service';
12
+ import { PsDocumentSearchStep } from '../ps-document-search-step/ps-document-search-step';
13
+ import { PsIconButton } from '../ps-icon-button/ps-icon-button';
14
+ import { PsIcon } from '../ps-icon/ps-icon';
15
+ import { PsText } from '../ps-text/ps-text';
16
+ export const PsDocumentSearchBar = ({ activeIndex, labels, onChange, onClose, onNext, onPrevious, query, total, }) => {
17
+ const theme = usePsMobileTheme();
18
+ const isMaterial = Platform.OS === 'android';
19
+ const hasQuery = PsDocumentSearchService.terms(query).length > 0;
20
+ const status = !hasQuery
21
+ ? null
22
+ : total === 0
23
+ ? labels.noMatches
24
+ : PsDocumentSearchService.countLabel(labels.matchCount, activeIndex + 1, total);
25
+ return (_jsxs(View, { style: {
26
+ alignItems: 'center',
27
+ flexDirection: 'row',
28
+ gap: PsDocumentSearchConfig.barGap,
29
+ paddingHorizontal: PsDocumentConfig.barPaddingHorizontal,
30
+ paddingVertical: PsDocumentConfig.barPaddingVertical,
31
+ }, testID: 'ps-document-search-bar', children: [_jsxs(View, { style: {
32
+ alignItems: 'center',
33
+ backgroundColor: PsMobileColorService.withAlpha(theme.white, PsDocumentSearchConfig.fieldAlpha),
34
+ borderRadius: PsDocumentSearchStyleService.fieldRadius(isMaterial),
35
+ flex: 1,
36
+ flexDirection: 'row',
37
+ gap: PsMobileControlConfig.buttonIconSpacing,
38
+ height: PsMobileControlConfig.controlHeights.compact,
39
+ paddingHorizontal: PsDocumentSearchConfig.fieldPaddingX,
40
+ }, children: [_jsx(PsIcon, { color: theme.white, iconKey: PsIconKey.Search, size: PsMobileControlConfig.iconSizeInline }), _jsx(TextInput, { accessibilityLabel: labels.searchPlaceholder, autoCapitalize: 'none', autoCorrect: false, autoFocus: true, clearButtonMode: 'while-editing', onChangeText: onChange, onSubmitEditing: onNext, placeholder: labels.searchPlaceholder, placeholderTextColor: PsMobileColorService.withAlpha(theme.white, PsDocumentConfig.chromeAlpha), returnKeyType: 'search', selectionColor: theme.primary, style: {
41
+ color: theme.white,
42
+ flex: 1,
43
+ fontSize: PsMobileControlConfig.controlFontSize,
44
+ padding: 0,
45
+ }, testID: 'ps-document-search-input', value: query }), status === null ? null : (_jsx(PsText, { tone: 'white', variant: 'caption', children: status }))] }), _jsx(PsDocumentSearchStep, { isDisabled: total === 0, isUp: true, label: labels.previousMatch, onPress: onPrevious, testID: 'ps-document-search-previous' }), _jsx(PsDocumentSearchStep, { isDisabled: total === 0, isUp: false, label: labels.nextMatch, onPress: onNext, testID: 'ps-document-search-next' }), isMaterial ? (_jsx(PsIconButton, { color: theme.white, iconKey: PsIconKey.Close, label: labels.closeSearch, onPress: onClose, size: 'compact', testID: 'ps-document-search-close' })) : (_jsx(Pressable, { accessibilityLabel: labels.closeSearch, accessibilityRole: 'button', hitSlop: PsLayoutConfig.hitSlop, onPress: onClose, testID: 'ps-document-search-close', children: _jsx(PsText, { tone: 'white', weight: 'semibold', children: labels.closeSearch }) }))] }));
46
+ };
@@ -0,0 +1,2 @@
1
+ export * from './ps-document-search-step';
2
+ export * from './ps-document-search-step-props';
@@ -0,0 +1,2 @@
1
+ export * from './ps-document-search-step';
2
+ export * from './ps-document-search-step-props';
@@ -0,0 +1,7 @@
1
+ export interface PsDocumentSearchStepProps {
2
+ isDisabled: boolean;
3
+ isUp: boolean;
4
+ label: string;
5
+ onPress: () => void;
6
+ testID: string;
7
+ }
@@ -0,0 +1,2 @@
1
+ import type { PsDocumentSearchStepProps } from './ps-document-search-step-props';
2
+ export declare const PsDocumentSearchStep: ({ isDisabled, isUp, label, onPress, testID, }: PsDocumentSearchStepProps) => import("react").JSX.Element;
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PsIconKey } from '@pallasoft/ps-icons';
3
+ import { Pressable, View } from 'react-native';
4
+ import { PsDocumentSearchConfig } from '../../constants/ps-document-search-config';
5
+ import { PsLayoutConfig } from '../../constants/ps-layout-config';
6
+ import { PsMobileControlConfig } from '../../constants/ps-mobile-control-config';
7
+ import { PsRippleKind } from '../../constants/ps-ripple-kind';
8
+ import { usePsHaptics } from '../../hooks/use-ps-haptics';
9
+ import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
10
+ import { PsRippleService } from '../../services/ps-ripple-service';
11
+ import { PsStateService } from '../../services/ps-state-service';
12
+ import { PsIcon } from '../ps-icon/ps-icon';
13
+ export const PsDocumentSearchStep = ({ isDisabled, isUp, label, onPress, testID, }) => {
14
+ const haptic = usePsHaptics();
15
+ const theme = usePsMobileTheme();
16
+ return (_jsx(Pressable, { android_ripple: PsRippleService.resolve(theme, PsRippleKind.Borderless), accessibilityLabel: label, accessibilityRole: 'button', accessibilityState: { disabled: isDisabled }, disabled: isDisabled, hitSlop: PsLayoutConfig.hitSlop, onPress: () => {
17
+ haptic('light');
18
+ onPress();
19
+ }, style: {
20
+ alignItems: 'center',
21
+ height: PsMobileControlConfig.controlHeights.compact,
22
+ justifyContent: 'center',
23
+ opacity: isDisabled ? PsStateService.disabledOpacity() : 1,
24
+ width: PsMobileControlConfig.controlHeights.compact,
25
+ }, testID: testID, children: _jsx(View, { style: {
26
+ transform: [
27
+ { rotate: isUp ? PsDocumentSearchConfig.rotateUp : PsDocumentSearchConfig.rotateDown },
28
+ ],
29
+ }, children: _jsx(PsIcon, { color: theme.white, iconKey: PsIconKey.Chevron }) }) }));
30
+ };
@@ -1,6 +1,8 @@
1
1
  import type { PsDocumentItem } from '../../interfaces/ps-document-item';
2
2
  import type { PsDocumentLabels } from '../../interfaces/ps-document-labels';
3
+ import type { PsDocumentSearchState } from '../../interfaces/ps-document-search-state';
3
4
  export interface PsDocumentTextPageProps {
4
5
  item: PsDocumentItem;
5
6
  labels: PsDocumentLabels;
7
+ search?: PsDocumentSearchState;
6
8
  }
@@ -1,2 +1,2 @@
1
1
  import type { PsDocumentTextPageProps } from './ps-document-text-page-props';
2
- export declare const PsDocumentTextPage: ({ item, labels }: PsDocumentTextPageProps) => import("react").JSX.Element;
2
+ export declare const PsDocumentTextPage: ({ item, labels, search }: PsDocumentTextPageProps) => import("react").JSX.Element;
@@ -1,18 +1,42 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
2
3
  import { PsIconKey } from '@pallasoft/ps-icons';
3
- import { ScrollView, View } from 'react-native';
4
+ import { ScrollView, Text, View } from 'react-native';
4
5
  import { PsDocumentConfig } from '../../constants/ps-document-config';
5
6
  import { PsDocumentLoadStatus } from '../../constants/ps-document-load-status';
7
+ import { PsDocumentSearchConfig } from '../../constants/ps-document-search-config';
6
8
  import { usePsDocumentText } from '../../hooks/use-ps-document-text';
9
+ import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
10
+ import { PsDocumentSearchService } from '../../services/ps-document-search-service';
11
+ import { PsDocumentSearchStyleService } from '../../services/ps-document-search-style-service';
7
12
  import { PsDocumentMessage } from '../ps-document-message/ps-document-message';
8
13
  import { PsDocumentUnsupported } from '../ps-document-unsupported/ps-document-unsupported';
9
14
  import { PsSpinner } from '../ps-spinner/ps-spinner';
10
15
  import { PsText } from '../ps-text/ps-text';
11
- export const PsDocumentTextPage = ({ item, labels }) => {
16
+ export const PsDocumentTextPage = ({ item, labels, search }) => {
12
17
  const loaded = usePsDocumentText(item);
18
+ const theme = usePsMobileTheme();
19
+ const scroll = useRef(null);
20
+ const [lines, setLines] = useState([]);
21
+ const matches = loaded.text === null || search === undefined
22
+ ? []
23
+ : PsDocumentSearchService.matches(loaded.text, search.query);
24
+ const active = search === undefined ? undefined : matches[search.activeIndex];
25
+ useEffect(() => {
26
+ if (active === undefined)
27
+ return;
28
+ scroll.current?.scrollTo({
29
+ animated: true,
30
+ y: Math.max(0, PsDocumentSearchService.scrollOffset(lines, active.start) +
31
+ PsDocumentConfig.pageGutter -
32
+ PsDocumentSearchConfig.scrollMargin),
33
+ });
34
+ }, [active?.start, lines, search?.query]);
13
35
  if (loaded.status === PsDocumentLoadStatus.Loading)
14
36
  return (_jsx(View, { style: { alignItems: 'center', flex: 1, justifyContent: 'center' }, children: _jsx(PsSpinner, {}) }));
15
37
  if (loaded.text === null)
16
38
  return item.source === null ? (_jsx(PsDocumentUnsupported, { isOnDark: true, item: item, labels: labels })) : (_jsx(PsDocumentMessage, { description: item.name, iconKey: PsIconKey.Warning, isOnDark: true, title: labels.loadFailed }));
17
- return (_jsx(View, { style: { flex: 1 }, testID: 'ps-document-text-page', children: _jsx(ScrollView, { contentContainerStyle: { padding: PsDocumentConfig.pageGutter }, showsVerticalScrollIndicator: true, children: _jsx(PsText, { isSelectable: true, tone: 'white', children: loaded.text }) }) }));
39
+ return (_jsx(View, { style: { flex: 1 }, testID: 'ps-document-text-page', children: _jsx(ScrollView, { contentContainerStyle: { padding: PsDocumentConfig.pageGutter }, ref: scroll, showsVerticalScrollIndicator: true, children: _jsx(PsText, { isSelectable: true, onTextLayout: (event) => setLines(event.nativeEvent.lines.map((line) => ({ text: line.text, y: line.y }))), tone: 'white', children: matches.length === 0
40
+ ? loaded.text
41
+ : PsDocumentSearchService.segments(loaded.text, matches).map((segment, index) => segment.matchIndex === null ? (segment.text) : (_jsx(Text, { style: PsDocumentSearchStyleService.span(theme, segment.matchIndex === search?.activeIndex), children: segment.text }, index))) }) }) }));
18
42
  };
@@ -5,13 +5,17 @@ import { FlatList, Modal, StatusBar, View } from 'react-native';
5
5
  import { GestureHandlerRootView } from 'react-native-gesture-handler';
6
6
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
7
7
  import { PsDocumentConfig } from '../../constants/ps-document-config';
8
+ import { PsDocumentSearchConfig } from '../../constants/ps-document-search-config';
8
9
  import { PsMobileControlConfig } from '../../constants/ps-mobile-control-config';
9
10
  import { usePsDocumentPdfPages } from '../../hooks/use-ps-document-pdf-pages';
11
+ import { usePsDocumentSearchTexts } from '../../hooks/use-ps-document-search-texts';
10
12
  import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
11
13
  import { PsDocumentPagerService } from '../../services/ps-document-pager-service';
14
+ import { PsDocumentSearchService } from '../../services/ps-document-search-service';
12
15
  import { PsDocumentViewerEntryService } from '../../services/ps-document-viewer-entry-service';
13
16
  import { PsDocumentPageStrip } from '../ps-document-page-strip/ps-document-page-strip';
14
17
  import { PsDocumentPage } from '../ps-document-page/ps-document-page';
18
+ import { PsDocumentSearchBar } from '../ps-document-search-bar/ps-document-search-bar';
15
19
  import { PsIconButton } from '../ps-icon-button/ps-icon-button';
16
20
  import { PsText } from '../ps-text/ps-text';
17
21
  export const PsDocumentViewer = ({ initialIndex = 0, isOpen, items, labels, onClose, }) => {
@@ -27,6 +31,43 @@ export const PsDocumentViewer = ({ initialIndex = 0, isOpen, items, labels, onCl
27
31
  ? PsDocumentViewerEntryService.startIndex(items, entries, initialIndex)
28
32
  : PsDocumentViewerEntryService.resolveIndex(currentEntry, entries, 0);
29
33
  const current = entries[index];
34
+ const searchLabels = PsDocumentSearchService.labels(labels);
35
+ const [isSearching, setIsSearching] = useState(false);
36
+ const [query, setQuery] = useState('');
37
+ const [activeMatch, setActiveMatch] = useState(PsDocumentSearchConfig.noActive);
38
+ const searchTexts = usePsDocumentSearchTexts(items, isSearching);
39
+ const counts = PsDocumentSearchService.counts(entries, searchTexts, query);
40
+ const total = PsDocumentSearchService.total(counts);
41
+ const canSearch = searchLabels !== null && PsDocumentSearchService.isSearchable(items);
42
+ const showMatch = (match, nextCounts) => {
43
+ setActiveMatch(match);
44
+ const location = PsDocumentSearchService.locate(nextCounts, match);
45
+ if (location === null)
46
+ return;
47
+ const entry = entries[location.entryIndex];
48
+ if (entry !== undefined && location.entryIndex !== index)
49
+ setCurrentEntry(entry);
50
+ };
51
+ const closeSearch = () => {
52
+ setIsSearching(false);
53
+ setQuery('');
54
+ setActiveMatch(PsDocumentSearchConfig.noActive);
55
+ };
56
+ useEffect(() => {
57
+ if (!isSearching)
58
+ return;
59
+ if (total === 0) {
60
+ if (activeMatch !== PsDocumentSearchConfig.noActive)
61
+ setActiveMatch(PsDocumentSearchConfig.noActive);
62
+ return;
63
+ }
64
+ if (activeMatch < 0 || activeMatch >= total)
65
+ showMatch(PsDocumentSearchService.startIndex(counts, index), counts);
66
+ }, [isSearching, total, activeMatch]);
67
+ useEffect(() => {
68
+ if (!isOpen && isSearching)
69
+ closeSearch();
70
+ }, [isOpen]);
30
71
  useEffect(() => {
31
72
  if (width > 0 && entries.length > 0)
32
73
  list.current?.scrollToIndex({ animated: false, index });
@@ -41,7 +82,10 @@ export const PsDocumentViewer = ({ initialIndex = 0, isOpen, items, labels, onCl
41
82
  flexDirection: 'row',
42
83
  paddingHorizontal: PsDocumentConfig.barPaddingHorizontal,
43
84
  paddingVertical: PsDocumentConfig.barPaddingVertical,
44
- }, children: [_jsx(PsIconButton, { color: theme.white, iconKey: PsIconKey.Close, label: labels.close, onPress: onClose }), _jsxs(View, { style: { alignItems: 'center', flex: 1 }, children: [PsDocumentPagerService.hasMany(entries.length) ? (_jsx(PsText, { tone: 'white', weight: 'semibold', children: PsDocumentPagerService.counter(index, entries.length) })) : null, current === undefined ? null : (_jsx(PsText, { numberOfLines: 1, tone: 'white', variant: 'caption', children: current.item.name }))] }), _jsx(View, { style: { width: PsMobileControlConfig.controlHeights.default } })] }), _jsx(View, { onLayout: (event) => setWidth(event.nativeEvent.layout.width), style: { flex: 1 }, testID: 'ps-document-viewer-pager', children: width === 0 ? null : (_jsx(FlatList, { data: entries, getItemLayout: (_data, position) => ({
85
+ }, children: [_jsx(PsIconButton, { color: theme.white, iconKey: PsIconKey.Close, label: labels.close, onPress: onClose }), _jsxs(View, { style: { alignItems: 'center', flex: 1 }, children: [PsDocumentPagerService.hasMany(entries.length) ? (_jsx(PsText, { tone: 'white', weight: 'semibold', children: PsDocumentPagerService.counter(index, entries.length) })) : null, current === undefined ? null : (_jsx(PsText, { numberOfLines: 1, tone: 'white', variant: 'caption', children: current.item.name }))] }), canSearch && !isSearching ? (_jsx(PsIconButton, { color: theme.white, iconKey: PsIconKey.Search, label: searchLabels.search, onPress: () => setIsSearching(true), testID: 'ps-document-search-button' })) : (_jsx(View, { style: { width: PsMobileControlConfig.controlHeights.default } }))] }), canSearch && isSearching ? (_jsx(PsDocumentSearchBar, { activeIndex: activeMatch, labels: searchLabels, onChange: (value) => {
86
+ setQuery(value);
87
+ setActiveMatch(PsDocumentSearchConfig.noActive);
88
+ }, onClose: closeSearch, onNext: () => showMatch(PsDocumentSearchService.step(activeMatch, total, 1), counts), onPrevious: () => showMatch(PsDocumentSearchService.step(activeMatch, total, -1), counts), query: query, total: total })) : null, _jsx(View, { onLayout: (event) => setWidth(event.nativeEvent.layout.width), style: { flex: 1 }, testID: 'ps-document-viewer-pager', children: width === 0 ? null : (_jsx(FlatList, { data: entries, extraData: `${isSearching}|${query}|${activeMatch}|${total}`, getItemLayout: (_data, position) => ({
45
89
  index: position,
46
90
  length: width,
47
91
  offset: PsDocumentPagerService.offset(position, width),
@@ -50,7 +94,12 @@ export const PsDocumentViewer = ({ initialIndex = 0, isOpen, items, labels, onCl
50
94
  if (next !== undefined)
51
95
  setCurrentEntry(next);
52
96
  setIsZoomed(false);
53
- }, pagingEnabled: true, ref: list, renderItem: ({ item: entry }) => (_jsx(View, { style: { width }, children: _jsx(PsDocumentPage, { item: entry.item, labels: labels, onDismiss: onClose, onRetry: pdf.retry, onZoomChange: setIsZoomed, status: entry.status }) })), scrollEnabled: !isZoomed, showsHorizontalScrollIndicator: false, testID: 'ps-document-viewer-list' })) }), PsDocumentViewerEntryService.hasStrip(items, entries) ? (_jsx(PsDocumentPageStrip, { entries: entries, index: index, onSelect: (position) => {
97
+ }, pagingEnabled: true, ref: list, renderItem: ({ index: position, item: entry }) => (_jsx(View, { style: { width }, children: _jsx(PsDocumentPage, { item: entry.item, labels: labels, onDismiss: onClose, onRetry: pdf.retry, onZoomChange: setIsZoomed, search: isSearching
98
+ ? {
99
+ activeIndex: PsDocumentSearchService.activeFor(counts, activeMatch, position),
100
+ query,
101
+ }
102
+ : undefined, status: entry.status }) })), scrollEnabled: !isZoomed, showsHorizontalScrollIndicator: false, testID: 'ps-document-viewer-list' })) }), PsDocumentViewerEntryService.hasStrip(items, entries) ? (_jsx(PsDocumentPageStrip, { entries: entries, index: index, onSelect: (position) => {
54
103
  const entry = entries[position];
55
104
  if (entry !== undefined)
56
105
  setCurrentEntry(entry);
@@ -1,8 +1,12 @@
1
+ import type { PsDocumentSearchState } from '../../interfaces/ps-document-search-state';
1
2
  import type { PsDocumentSource } from '../../interfaces/ps-document-source';
3
+ import type { PsDocumentTextLayer } from '../../interfaces/ps-document-text-layer';
2
4
  export interface PsDocumentZoomImageProps {
3
5
  name: string;
4
6
  onDismiss: () => void;
5
7
  onError: () => void;
6
8
  onZoomChange: (isZoomed: boolean) => void;
9
+ search?: PsDocumentSearchState;
7
10
  source: PsDocumentSource;
11
+ textLayer?: PsDocumentTextLayer;
8
12
  }
@@ -1,2 +1,2 @@
1
1
  import type { PsDocumentZoomImageProps } from './ps-document-zoom-image-props';
2
- export declare const PsDocumentZoomImage: ({ name, onDismiss, onError, onZoomChange, source, }: PsDocumentZoomImageProps) => import("react").JSX.Element;
2
+ export declare const PsDocumentZoomImage: ({ name, onDismiss, onError, onZoomChange, search, source, textLayer, }: PsDocumentZoomImageProps) => import("react").JSX.Element;
@@ -1,12 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
2
+ import { useEffect, useState } from 'react';
3
3
  import { Image, View } from 'react-native';
4
4
  import { GestureDetector, usePanGesture, usePinchGesture, useSimultaneousGestures, useTapGesture, } from 'react-native-gesture-handler';
5
5
  import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
6
6
  import { PsDocumentConfig } from '../../constants/ps-document-config';
7
+ import { PsDocumentSearchService } from '../../services/ps-document-search-service';
7
8
  import { PsDocumentZoomService } from '../../services/ps-document-zoom-service';
9
+ import { PsDocumentHighlightLayer } from '../ps-document-highlight-layer/ps-document-highlight-layer';
8
10
  import { PsSpinner } from '../ps-spinner/ps-spinner';
9
- export const PsDocumentZoomImage = ({ name, onDismiss, onError, onZoomChange, source, }) => {
11
+ export const PsDocumentZoomImage = ({ name, onDismiss, onError, onZoomChange, search, source, textLayer, }) => {
10
12
  const [size, setSize] = useState({ height: 0, width: 0 });
11
13
  const [isZoomed, setIsZoomed] = useState(false);
12
14
  const [isLoaded, setIsLoaded] = useState(false);
@@ -83,6 +85,21 @@ export const PsDocumentZoomImage = ({ name, onDismiss, onError, onZoomChange, so
83
85
  },
84
86
  runOnJS: true,
85
87
  });
88
+ const hasSearch = search !== undefined && textLayer !== undefined && search.activeIndex >= 0;
89
+ useEffect(() => {
90
+ if (!hasSearch || !isZoomed || size.width === 0)
91
+ return;
92
+ const active = PsDocumentSearchService.highlights(textLayer, search.query, size, search.activeIndex).find((rect) => rect.isActive);
93
+ if (active === undefined)
94
+ return;
95
+ const center = PsDocumentSearchService.center(active.box);
96
+ const nextX = PsDocumentZoomService.centerOffset(center.x, size.width, scale.value);
97
+ const nextY = PsDocumentZoomService.centerOffset(center.y, size.height, scale.value);
98
+ x.value = withTiming(nextX, { duration: PsDocumentConfig.settleMs });
99
+ y.value = withTiming(nextY, { duration: PsDocumentConfig.settleMs });
100
+ baseX.value = nextX;
101
+ baseY.value = nextY;
102
+ }, [search?.activeIndex, search?.query]);
86
103
  const gesture = useSimultaneousGestures(pinch, move, dismiss, doubleTap);
87
104
  const animatedStyle = useAnimatedStyle(() => ({
88
105
  transform: [
@@ -92,7 +109,7 @@ export const PsDocumentZoomImage = ({ name, onDismiss, onError, onZoomChange, so
92
109
  { scale: scale.value },
93
110
  ],
94
111
  }));
95
- return (_jsx(GestureDetector, { gesture: gesture, children: _jsxs(View, { onLayout: (event) => setSize(event.nativeEvent.layout), style: { flex: 1, overflow: 'hidden' }, testID: 'ps-document-zoom-image', children: [_jsx(Animated.View, { style: [{ flex: 1 }, animatedStyle], children: _jsx(Image, { accessibilityLabel: name, onError: onError, onLoad: () => setIsLoaded(true), resizeMode: 'contain', source: { headers: source.headers, uri: source.uri }, style: { flex: 1 } }) }), isLoaded ? null : (_jsx(View, { pointerEvents: 'none', style: {
112
+ return (_jsx(GestureDetector, { gesture: gesture, children: _jsxs(View, { onLayout: (event) => setSize(event.nativeEvent.layout), style: { flex: 1, overflow: 'hidden' }, testID: 'ps-document-zoom-image', children: [_jsxs(Animated.View, { style: [{ flex: 1 }, animatedStyle], children: [_jsx(Image, { accessibilityLabel: name, onError: onError, onLoad: () => setIsLoaded(true), resizeMode: 'contain', source: { headers: source.headers, uri: source.uri }, style: { flex: 1 } }), search === undefined || textLayer === undefined ? null : (_jsx(PsDocumentHighlightLayer, { layer: textLayer, search: search, size: size }))] }), isLoaded ? null : (_jsx(View, { pointerEvents: 'none', style: {
96
113
  alignItems: 'center',
97
114
  bottom: 0,
98
115
  justifyContent: 'center',
@@ -7,4 +7,5 @@ export interface PsDonutChartProps {
7
7
  formatShare: (share: number) => string;
8
8
  height?: number;
9
9
  isLoading?: boolean;
10
+ revealKey?: string;
10
11
  }
@@ -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(data.map((datum) => `${datum.id}:${datum.share}`).join(','));
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);