@pallasoft/ps-mobile-components 1.13.0 → 1.14.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 (50) hide show
  1. package/dist/components/ps-action-menu/ps-action-menu.android.js +2 -2
  2. package/dist/components/ps-action-menu/ps-menu-tile-drawing.js +7 -2
  3. package/dist/components/ps-chart-bar/ps-chart-bar.js +11 -4
  4. package/dist/components/ps-donut-chart/ps-donut-chart.js +5 -4
  5. package/dist/components/ps-donut-reveal/index.d.ts +1 -0
  6. package/dist/components/ps-donut-reveal/index.js +1 -0
  7. package/dist/components/ps-donut-reveal/ps-donut-reveal-props.d.ts +6 -0
  8. package/dist/components/ps-donut-reveal/ps-donut-reveal-props.js +1 -0
  9. package/dist/components/ps-donut-reveal/ps-donut-reveal.d.ts +2 -0
  10. package/dist/components/ps-donut-reveal/ps-donut-reveal.js +14 -0
  11. package/dist/components/ps-donut-slice/ps-donut-slice-props.d.ts +0 -4
  12. package/dist/components/ps-donut-slice/ps-donut-slice.d.ts +1 -1
  13. package/dist/components/ps-donut-slice/ps-donut-slice.js +2 -14
  14. package/dist/components/ps-menu-popup/ps-menu-popup-row.d.ts +4 -1
  15. package/dist/components/ps-menu-popup/ps-menu-popup-row.js +3 -2
  16. package/dist/components/ps-menu-popup/ps-menu-popup-tile.d.ts +6 -0
  17. package/dist/components/ps-menu-popup/ps-menu-popup-tile.js +27 -0
  18. package/dist/components/ps-sheet-header/ps-sheet-header.js +2 -2
  19. package/dist/components/ps-sortable-grid/ps-sortable-grid.js +3 -3
  20. package/dist/components/ps-sortable-grid/ps-sortable-item-props.d.ts +1 -1
  21. package/dist/components/ps-sortable-grid/ps-sortable-item.js +19 -5
  22. package/dist/constants/ps-chart-bar-corner.d.ts +6 -0
  23. package/dist/constants/ps-chart-bar-corner.js +5 -0
  24. package/dist/constants/ps-chart-config.d.ts +4 -1
  25. package/dist/constants/ps-chart-config.js +4 -1
  26. package/dist/constants/ps-menu-popup-config.d.ts +4 -0
  27. package/dist/constants/ps-menu-popup-config.js +4 -0
  28. package/dist/constants/ps-sheet-config.d.ts +1 -0
  29. package/dist/constants/ps-sheet-config.js +1 -0
  30. package/dist/constants/ps-sortable-grid-config.d.ts +2 -0
  31. package/dist/constants/ps-sortable-grid-config.js +2 -0
  32. package/dist/interfaces/ps-chart-arc-data.d.ts +9 -0
  33. package/dist/interfaces/ps-chart-arc-data.js +1 -0
  34. package/dist/interfaces/ps-chart-bar-rect.d.ts +2 -0
  35. package/dist/interfaces/ps-donut-slice-data.d.ts +1 -3
  36. package/dist/interfaces/ps-menu-item.d.ts +1 -0
  37. package/dist/interfaces/ps-menu-tile-request.d.ts +1 -0
  38. package/dist/interfaces/ps-sortable-measure-data.d.ts +4 -0
  39. package/dist/interfaces/ps-sortable-measure-data.js +1 -0
  40. package/dist/services/ps-chart-arc-service.d.ts +14 -0
  41. package/dist/services/ps-chart-arc-service.js +83 -0
  42. package/dist/services/ps-chart-bar-service.d.ts +2 -0
  43. package/dist/services/ps-chart-bar-service.js +18 -3
  44. package/dist/services/ps-chart-donut-service.d.ts +2 -0
  45. package/dist/services/ps-chart-donut-service.js +42 -12
  46. package/dist/services/ps-menu-tile-service.js +10 -4
  47. package/dist/services/ps-sheet-service.d.ts +1 -0
  48. package/dist/services/ps-sheet-service.js +3 -0
  49. package/dist/services/ps-waterfall-service.js +2 -0
  50. package/package.json +1 -1
@@ -19,9 +19,9 @@ export const PsActionMenu = ({ children, header, isDisabled = false, items, labe
19
19
  if (!isDisabled)
20
20
  menu.open();
21
21
  },
22
- }, children: children }), menu.anchor === undefined ? null : (_jsxs(PsMenuPopup, { anchor: menu.anchor, onClose: menu.close, children: [header === undefined ? null : (_jsx(PsMenuPopupHeader, { subtitle: header.subtitle, title: header.title })), groups.map((group, index) => (_jsxs(Fragment, { children: [index > 0 || header !== undefined ? _jsx(PsMenuPopupDivider, {}) : null, PsActionMenuService.flatten([group]).map((item) => (_jsx(PsMenuPopupRow, { iconKey: item.iconKey, isChecked: item.isChecked, isDanger: item.isDanger, isDisabled: item.isDisabled, isWarning: item.isWarning, label: item.label, onPress: () => {
22
+ }, children: children }), menu.anchor === undefined ? null : (_jsxs(PsMenuPopup, { anchor: menu.anchor, onClose: menu.close, children: [header === undefined ? null : (_jsx(PsMenuPopupHeader, { subtitle: header.subtitle, title: header.title })), groups.map((group, index) => (_jsxs(Fragment, { children: [index > 0 || header !== undefined ? _jsx(PsMenuPopupDivider, {}) : null, PsActionMenuService.flatten([group]).map((item) => (_jsx(PsMenuPopupRow, { iconKey: item.iconKey, imageUri: item.imageUri, isChecked: item.isChecked, isDanger: item.isDanger, isDisabled: item.isDisabled, isWarning: item.isWarning, label: item.label, monogram: item.monogram, onPress: () => {
23
23
  menu.close();
24
24
  haptic('selection');
25
25
  item.onSelect();
26
- }, subtitle: item.subtitle }, item.id)))] }, group.id)))] }))] }));
26
+ }, subtitle: item.subtitle, tileColor: item.tileColor }, item.id)))] }, group.id)))] }))] }));
27
27
  };
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useId, useRef, useState } from 'react';
3
+ import { PallasoftColorContrastService } from '@pallasoft/ps-design-system';
3
4
  import Svg, { ClipPath, Defs, G, Rect, Image as SvgImage, Text as SvgText } from 'react-native-svg';
4
5
  import { PsMenuTileConfig } from '../../constants/ps-menu-tile-config';
5
6
  import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
@@ -14,6 +15,10 @@ export const PsMenuTileDrawing = ({ onTile, request }) => {
14
15
  const isMounted = useRef(true);
15
16
  const [isLaidOut, setIsLaidOut] = useState(false);
16
17
  const [isImageLoaded, setIsImageLoaded] = useState(false);
18
+ const fill = request.tileColor === undefined ? theme.primary : request.tileColor;
19
+ const textColor = request.tileColor === undefined
20
+ ? theme.primaryContrast
21
+ : PallasoftColorContrastService.textOn(request.tileColor);
17
22
  const isReady = isLaidOut && (request.imageUri === undefined || isImageLoaded);
18
23
  useEffect(() => {
19
24
  isMounted.current = true;
@@ -29,6 +34,6 @@ export const PsMenuTileDrawing = ({ onTile, request }) => {
29
34
  if (isMounted.current)
30
35
  onTile(request.key, `${PsMenuTileConfig.dataUriPrefix}${base64}`);
31
36
  });
32
- }, [isReady, onTile, request.key, theme.primary, theme.primaryContrast]);
33
- return (_jsxs(Svg, { height: size, onLayout: () => setIsLaidOut(true), ref: ref, width: size, children: [_jsx(Defs, { children: _jsx(ClipPath, { id: clipId, children: _jsx(Rect, { height: size, rx: isCircle ? size / 2 : PsMenuTileConfig.roundedRadius, width: size }) }) }), _jsxs(G, { clipPath: `url(#${clipId})`, children: [_jsx(Rect, { fill: request.imageUri === undefined ? theme.primary : theme.white, height: size, width: size }), request.imageUri === undefined ? (_jsx(SvgText, { fill: theme.primaryContrast, fontFamily: theme.fonts.semibold, fontSize: fontSize, textAnchor: 'middle', x: size / 2, y: size / 2 + fontSize * PsMenuTileConfig.textBaseline, children: request.text })) : (_jsx(SvgImage, { height: size - inset * 2, href: { uri: request.imageUri }, onLoad: () => setIsImageLoaded(true), preserveAspectRatio: isCircle ? 'xMidYMid slice' : 'xMidYMid meet', width: size - inset * 2, x: inset, y: inset }))] })] }));
37
+ }, [isReady, onTile, request.key, fill, textColor]);
38
+ return (_jsxs(Svg, { height: size, onLayout: () => setIsLaidOut(true), ref: ref, width: size, children: [_jsx(Defs, { children: _jsx(ClipPath, { id: clipId, children: _jsx(Rect, { height: size, rx: isCircle ? size / 2 : PsMenuTileConfig.roundedRadius, width: size }) }) }), _jsxs(G, { clipPath: `url(#${clipId})`, children: [_jsx(Rect, { fill: request.imageUri === undefined ? fill : theme.white, height: size, width: size }), request.imageUri === undefined ? (_jsx(SvgText, { fill: textColor, fontFamily: theme.fonts.semibold, fontSize: fontSize, textAnchor: 'middle', x: size / 2, y: size / 2 + fontSize * PsMenuTileConfig.textBaseline, children: request.text })) : (_jsx(SvgImage, { height: size - inset * 2, href: { uri: request.imageUri }, onLoad: () => setIsImageLoaded(true), preserveAspectRatio: isCircle ? 'xMidYMid slice' : 'xMidYMid meet', width: size - inset * 2, x: inset, y: inset }))] })] }));
34
39
  };
@@ -1,12 +1,19 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Animated, { useAnimatedProps } from 'react-native-reanimated';
3
- import { Rect } from 'react-native-svg';
4
- import { PsChartConfig } from '../../constants/ps-chart-config';
3
+ import { G, Rect } from 'react-native-svg';
4
+ import { PsChartBarCorner } from '../../constants/ps-chart-bar-corner';
5
+ import { PsChartBarService } from '../../services/ps-chart-bar-service';
5
6
  const AnimatedRect = Animated.createAnimatedComponent(Rect);
6
7
  export const PsChartBar = ({ progress, rect }) => {
8
+ const radius = PsChartBarService.radius(rect);
9
+ const hasFlatBase = rect.corner === PsChartBarCorner.Value && radius > 0;
7
10
  const animatedProps = useAnimatedProps(() => ({
8
11
  height: rect.height * progress.value,
9
12
  y: rect.isNegative ? rect.y : rect.y + rect.height * (1 - progress.value),
10
13
  }));
11
- return (_jsx(AnimatedRect, { animatedProps: animatedProps, fill: rect.color, height: rect.height, rx: Math.min(PsChartConfig.barRadius, rect.width / 2), width: rect.width, x: rect.x, y: rect.y }));
14
+ const baseProps = useAnimatedProps(() => {
15
+ const height = Math.min(radius, rect.height * progress.value);
16
+ return { height, y: rect.isNegative ? rect.y : rect.y + rect.height - height };
17
+ });
18
+ return (_jsxs(G, { children: [_jsx(AnimatedRect, { animatedProps: animatedProps, fill: rect.color, height: rect.height, rx: radius, width: rect.width, x: rect.x, y: rect.y }), hasFlatBase ? (_jsx(AnimatedRect, { animatedProps: baseProps, fill: rect.color, height: Math.min(radius, rect.height), width: rect.width, x: rect.x, y: rect.isNegative ? rect.y : rect.y + rect.height - Math.min(radius, rect.height) })) : null] }));
12
19
  };
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
2
+ import { useId, useState } from 'react';
3
3
  import { View } from 'react-native';
4
- import Svg from 'react-native-svg';
4
+ import Svg, { G } from 'react-native-svg';
5
5
  import { PsChartConfig } from '../../constants/ps-chart-config';
6
6
  import { PsChartSkeletonVariant } from '../../constants/ps-chart-skeleton-variant';
7
7
  import { usePsChartReveal } from '../../hooks/use-ps-chart-reveal';
@@ -12,6 +12,7 @@ import { PsChartDonutService } from '../../services/ps-chart-donut-service';
12
12
  import { PsChartEmptyState } from '../ps-chart-empty-state/ps-chart-empty-state';
13
13
  import { PsChartLegend } from '../ps-chart-legend/ps-chart-legend';
14
14
  import { PsChartSkeleton } from '../ps-chart-skeleton/ps-chart-skeleton';
15
+ import { PsDonutReveal } from '../ps-donut-reveal/ps-donut-reveal';
15
16
  import { PsDonutSlice } from '../ps-donut-slice/ps-donut-slice';
16
17
  import { PsText } from '../ps-text/ps-text';
17
18
  export const PsDonutChart = ({ centerLabel, centerValue, data, emptyLabel, formatShare, height = PsChartConfig.donutHeight, isLoading = false, revealKey, }) => {
@@ -19,6 +20,7 @@ export const PsDonutChart = ({ centerLabel, centerValue, data, emptyLabel, forma
19
20
  const { onLayout, width } = usePsChartWidth();
20
21
  const fill = usePsWidgetFill();
21
22
  const [selected, setSelected] = useState(null);
23
+ const maskId = `donut${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
22
24
  const progress = usePsChartReveal(!isLoading && width > 0, revealKey);
23
25
  if (isLoading)
24
26
  return _jsx(PsChartSkeleton, { height: height, variant: PsChartSkeletonVariant.Donut });
@@ -27,7 +29,6 @@ export const PsDonutChart = ({ centerLabel, centerValue, data, emptyLabel, forma
27
29
  const slices = PsChartDonutService.slices(data, theme.chart, size);
28
30
  if (slices.length === 0)
29
31
  return _jsx(PsChartEmptyState, { height: height, label: emptyLabel });
30
- const circumference = PsChartDonutService.circumference(size);
31
32
  const chosen = selected === null ? null : data[selected];
32
33
  const readValue = chosen === null
33
34
  ? centerValue
@@ -47,7 +48,7 @@ export const PsDonutChart = ({ centerLabel, centerValue, data, emptyLabel, forma
47
48
  justifyContent: 'center',
48
49
  minHeight: height + fill,
49
50
  rowGap: PsChartConfig.donutGap,
50
- }, testID: 'ps-donut-chart', children: [_jsxs(View, { style: { height: size, width: size }, children: [_jsx(Svg, { height: size, width: size, children: slices.map((slice, index) => (_jsx(PsDonutSlice, { circumference: circumference, isDimmed: selected !== null && selected !== index, onPress: () => setSelected(selected === index ? null : index), progress: progress, size: size, slice: slice }, slice.id))) }), _jsxs(View, { pointerEvents: 'none', style: {
51
+ }, testID: 'ps-donut-chart', children: [_jsxs(View, { style: { height: size, width: size }, children: [_jsxs(Svg, { height: size, width: size, children: [_jsx(PsDonutReveal, { id: maskId, progress: progress, size: size }), _jsx(G, { mask: `url(#${maskId})`, children: slices.map((slice, index) => slice.path === '' ? null : (_jsx(PsDonutSlice, { isDimmed: selected !== null && selected !== index, onPress: () => setSelected(selected === index ? null : index), slice: slice }, slice.id))) })] }), _jsxs(View, { pointerEvents: 'none', style: {
51
52
  alignItems: 'center',
52
53
  bottom: 0,
53
54
  justifyContent: 'center',
@@ -0,0 +1 @@
1
+ export * from './ps-donut-reveal';
@@ -0,0 +1 @@
1
+ export * from './ps-donut-reveal';
@@ -0,0 +1,6 @@
1
+ import type { SharedValue } from 'react-native-reanimated';
2
+ export interface PsDonutRevealProps {
3
+ id: string;
4
+ progress: SharedValue<number>;
5
+ size: number;
6
+ }
@@ -0,0 +1,2 @@
1
+ import type { PsDonutRevealProps } from './ps-donut-reveal-props';
2
+ export declare const PsDonutReveal: ({ id, progress, size }: PsDonutRevealProps) => import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import Animated, { useAnimatedProps } from 'react-native-reanimated';
3
+ import { Circle, Defs, Mask } from 'react-native-svg';
4
+ import { PsChartConfig } from '../../constants/ps-chart-config';
5
+ import { PsChartDonutService } from '../../services/ps-chart-donut-service';
6
+ const AnimatedCircle = Animated.createAnimatedComponent(Circle);
7
+ export const PsDonutReveal = ({ id, progress, size }) => {
8
+ const centre = size / 2;
9
+ const circumference = PsChartDonutService.circumference(size);
10
+ const animatedProps = useAnimatedProps(() => ({
11
+ strokeDasharray: [progress.value * circumference, circumference],
12
+ }));
13
+ return (_jsx(Defs, { children: _jsx(Mask, { height: size, id: id, maskUnits: 'userSpaceOnUse', width: size, x: 0, y: 0, children: _jsx(AnimatedCircle, { animatedProps: animatedProps, cx: centre, cy: centre, fill: 'none', origin: `${centre}, ${centre}`, r: PsChartDonutService.radius(size), rotation: -90, stroke: 'white', strokeDasharray: [0, circumference], strokeWidth: PsChartConfig.donutThickness + PsChartConfig.donutRevealOverlap }) }) }));
14
+ };
@@ -1,10 +1,6 @@
1
- import type { SharedValue } from 'react-native-reanimated';
2
1
  import type { PsDonutSliceData } from '../../interfaces/ps-donut-slice-data';
3
2
  export interface PsDonutSliceProps {
4
- circumference: number;
5
3
  isDimmed: boolean;
6
4
  onPress: () => void;
7
- progress: SharedValue<number>;
8
- size: number;
9
5
  slice: PsDonutSliceData;
10
6
  }
@@ -1,2 +1,2 @@
1
1
  import type { PsDonutSliceProps } from './ps-donut-slice-props';
2
- export declare const PsDonutSlice: ({ circumference, isDimmed, onPress, progress, size, slice, }: PsDonutSliceProps) => import("react").JSX.Element;
2
+ export declare const PsDonutSlice: ({ isDimmed, onPress, slice }: PsDonutSliceProps) => import("react").JSX.Element;
@@ -1,16 +1,4 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import Animated, { useAnimatedProps } from 'react-native-reanimated';
3
- import { Circle } from 'react-native-svg';
2
+ import { Path } from 'react-native-svg';
4
3
  import { PsChartConfig } from '../../constants/ps-chart-config';
5
- import { PsChartDonutService } from '../../services/ps-chart-donut-service';
6
- const AnimatedCircle = Animated.createAnimatedComponent(Circle);
7
- export const PsDonutSlice = ({ circumference, isDimmed, onPress, progress, size, slice, }) => {
8
- const centre = size / 2;
9
- const animatedProps = useAnimatedProps(() => ({
10
- strokeDasharray: [
11
- Math.max(0, Math.min(slice.dashLength, progress.value * circumference - slice.start)),
12
- circumference,
13
- ],
14
- }));
15
- return (_jsx(AnimatedCircle, { animatedProps: animatedProps, cx: centre, cy: centre, fill: 'none', onPress: onPress, opacity: isDimmed ? 0.4 : 1, origin: `${centre}, ${centre}`, r: PsChartDonutService.radius(size), rotation: -90, stroke: slice.color, strokeDasharray: [slice.dashLength, circumference], strokeDashoffset: slice.dashOffset, strokeWidth: PsChartConfig.donutThickness }));
16
- };
4
+ export const PsDonutSlice = ({ isDimmed, onPress, slice }) => (_jsx(Path, { d: slice.path, fill: slice.color, fillRule: 'evenodd', onPress: onPress, opacity: isDimmed ? PsChartConfig.donutDimmedOpacity : 1 }));
@@ -1,12 +1,15 @@
1
1
  import type { PsMobileIconKey } from '../../constants/ps-mobile-icon-key';
2
2
  export interface PsMenuPopupRowProps {
3
3
  iconKey?: PsMobileIconKey;
4
+ imageUri?: string;
4
5
  isChecked?: boolean;
5
6
  isDanger?: boolean;
6
7
  isDisabled?: boolean;
7
8
  isWarning?: boolean;
8
9
  label: string;
10
+ monogram?: string;
9
11
  onPress: () => void;
10
12
  subtitle?: string;
13
+ tileColor?: string;
11
14
  }
12
- export declare const PsMenuPopupRow: ({ iconKey, isChecked, isDanger, isDisabled, isWarning, label, onPress, subtitle, }: PsMenuPopupRowProps) => import("react").JSX.Element;
15
+ export declare const PsMenuPopupRow: ({ iconKey, imageUri, isChecked, isDanger, isDisabled, isWarning, label, monogram, onPress, subtitle, tileColor, }: PsMenuPopupRowProps) => import("react").JSX.Element;
@@ -11,7 +11,8 @@ import { PsRippleService } from '../../services/ps-ripple-service';
11
11
  import { PsStateService } from '../../services/ps-state-service';
12
12
  import { PsIcon } from '../ps-icon/ps-icon';
13
13
  import { PsText } from '../ps-text/ps-text';
14
- export const PsMenuPopupRow = ({ iconKey, isChecked = false, isDanger = false, isDisabled = false, isWarning = false, label, onPress, subtitle, }) => {
14
+ import { PsMenuPopupTile } from './ps-menu-popup-tile';
15
+ export const PsMenuPopupRow = ({ iconKey, imageUri, isChecked = false, isDanger = false, isDisabled = false, isWarning = false, label, monogram, onPress, subtitle, tileColor, }) => {
15
16
  const theme = usePsMobileTheme();
16
17
  const scheme = usePsMaterialScheme();
17
18
  const isMaterial = PsMenuPopupStyleService.isMaterial();
@@ -30,5 +31,5 @@ export const PsMenuPopupRow = ({ iconKey, isChecked = false, isDanger = false, i
30
31
  overflow: 'hidden',
31
32
  paddingHorizontal: row.paddingHorizontal,
32
33
  };
33
- }, children: [iconKey === undefined ? null : _jsx(PsIcon, { color: tint, iconKey: iconKey }), _jsxs(View, { style: { flex: 1 }, children: [_jsx(PsText, { numberOfLines: 1, tone: isDanger ? 'danger' : 'ink', weight: PsMenuPopupStyleService.labelWeight(isMaterial, isChecked), children: label }), subtitle === undefined ? null : (_jsx(PsText, { numberOfLines: 1, tone: isWarning ? 'warning' : 'muted', variant: 'small', children: subtitle }))] }), isChecked ? _jsx(PsIcon, { color: theme.primaryText, iconKey: PsIconKey.Check }) : null] }));
34
+ }, children: [imageUri !== undefined || monogram !== undefined ? (_jsx(PsMenuPopupTile, { imageUri: imageUri, monogram: monogram, tileColor: tileColor })) : iconKey === undefined ? null : (_jsx(PsIcon, { color: tint, iconKey: iconKey })), _jsxs(View, { style: { flex: 1 }, children: [_jsx(PsText, { numberOfLines: 1, tone: isDanger ? 'danger' : 'ink', weight: PsMenuPopupStyleService.labelWeight(isMaterial, isChecked), children: label }), subtitle === undefined ? null : (_jsx(PsText, { numberOfLines: 1, tone: isWarning ? 'warning' : 'muted', variant: 'small', children: subtitle }))] }), isChecked ? _jsx(PsIcon, { color: theme.primaryText, iconKey: PsIconKey.Check }) : null] }));
34
35
  };
@@ -0,0 +1,6 @@
1
+ export interface PsMenuPopupTileProps {
2
+ imageUri?: string;
3
+ monogram?: string;
4
+ tileColor?: string;
5
+ }
6
+ export declare const PsMenuPopupTile: ({ imageUri, monogram, tileColor }: PsMenuPopupTileProps) => import("react").JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PallasoftColorContrastService } from '@pallasoft/ps-design-system';
3
+ import { Image, View } from 'react-native';
4
+ import { PsMenuPopupConfig } from '../../constants/ps-menu-popup-config';
5
+ import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
6
+ import { PsMenuTileService } from '../../services/ps-menu-tile-service';
7
+ import { PsText } from '../ps-text/ps-text';
8
+ export const PsMenuPopupTile = ({ imageUri, monogram, tileColor }) => {
9
+ const theme = usePsMobileTheme();
10
+ const size = PsMenuPopupConfig.tileSize;
11
+ const fill = tileColor === undefined ? theme.primary : tileColor;
12
+ const textColor = tileColor === undefined
13
+ ? theme.primaryContrast
14
+ : PallasoftColorContrastService.textOn(tileColor);
15
+ return (_jsx(View, { style: {
16
+ alignItems: 'center',
17
+ backgroundColor: imageUri === undefined ? fill : theme.white,
18
+ borderRadius: PsMenuPopupConfig.tileRadius,
19
+ height: size,
20
+ justifyContent: 'center',
21
+ overflow: 'hidden',
22
+ width: size,
23
+ }, testID: 'ps-menu-popup-tile', children: imageUri === undefined ? (_jsx(PsText, { style: { color: textColor, fontSize: PsMenuPopupConfig.tileFontSize }, weight: 'semibold', children: PsMenuTileService.letter(monogram === undefined ? '' : monogram) })) : (_jsx(Image, { resizeMode: 'contain', source: { uri: imageUri }, style: {
24
+ height: size - PsMenuPopupConfig.tileLogoPadding * 2,
25
+ width: size - PsMenuPopupConfig.tileLogoPadding * 2,
26
+ } })) }));
27
+ };
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PsIconKey } from '@pallasoft/ps-icons';
3
- import { StyleSheet, View } from 'react-native';
3
+ import { Platform, StyleSheet, View } from 'react-native';
4
4
  import { PsPageHeaderConfig } from '../../constants/ps-page-header-config';
5
5
  import { PsSheetConfig } from '../../constants/ps-sheet-config';
6
6
  import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
@@ -20,7 +20,7 @@ export const PsSheetHeader = ({ eyebrow, isLeadingDisabled = false, leadingKind
20
20
  minHeight: PsSheetConfig.headerMinHeight,
21
21
  paddingBottom: PsSheetConfig.headerPaddingBottom,
22
22
  paddingHorizontal: PsSheetConfig.headerPaddingHorizontal,
23
- paddingTop: PsSheetConfig.headerPaddingTop,
23
+ paddingTop: PsSheetService.headerPaddingTop(Platform.OS),
24
24
  }, testID: 'ps-sheet-header', children: [_jsx(View, { style: { alignItems: 'flex-start', minWidth: PsSheetConfig.actionMinWidth }, children: leadingLabel === undefined ? null : (_jsx(View, { style: PsSheetService.leadingIconStyle(leadingKind), children: _jsx(PsIconButton, { iconKey: leadingKind === 'back' ? PsIconKey.ChevronRight : PsIconKey.Close, isDisabled: isLeadingDisabled, label: leadingLabel, onPress: onLeadingPress, testID: 'ps-sheet-leading', variant: 'header' }) })) }), _jsxs(View, { style: { alignItems: 'center', flex: 1 }, children: [_jsx(PsText, { accessibilityRole: 'header', align: 'center', numberOfLines: 2, weight: 'bold', children: title }), eyebrow === undefined ? null : (_jsx(PsText, { align: 'center', numberOfLines: 1, tone: 'muted', variant: 'small', children: eyebrow }))] }), _jsx(View, { style: { alignItems: 'flex-end', minWidth: PsSheetConfig.actionMinWidth }, children: trailing === undefined ? null : trailing.isSubmitting === true ? (_jsx(View, { accessibilityLabel: trailing.label, accessibilityState: { busy: true }, style: {
25
25
  alignItems: 'center',
26
26
  backgroundColor: trailingColor,
@@ -135,12 +135,12 @@ export const PsSortableGrid = ({ getItemSpan, isDisabled = false, items, labels,
135
135
  notify(next);
136
136
  PsHapticService.play('selection', hasHaptics);
137
137
  }, [hasHaptics]);
138
- const measure = useCallback((id, total, renderedExtra) => {
139
- const natural = PsSortableGridService.natural(total, renderedExtra);
138
+ const measure = useCallback((id, total, fill) => {
139
+ const natural = PsSortableGridService.natural(total, fill);
140
140
  setMeasured((current) => current[id] !== undefined &&
141
141
  Math.abs(current[id] - natural) < PsSortableGridConfig.measureTolerance
142
142
  ? current
143
143
  : { ...current, [id]: natural });
144
144
  }, []);
145
- return (_jsx(View, { onLayout: (event) => setWidth(event.nativeEvent.layout.width), style: { height: PsSortableGridService.contentHeight(rects) }, children: order.map((item, index) => (_jsx(PsSortableItem, { canMoveDown: index < order.length - 1, canMoveUp: index > 0, dragX: dragX, dragY: dragY, extra: extras[index], id: item.id, isActive: item.id === activeId, isDisabled: isDisabled, isReady: isReady, label: item.label, labels: labels, onEnd: end, onMeasure: measure, onMove: moveBy, onStart: start, onUpdate: update, rect: rects[index], children: renderItem({ isActive: item.id === activeId, item }) }, item.id))) }));
145
+ return (_jsx(View, { onLayout: (event) => setWidth(event.nativeEvent.layout.width), style: { height: PsSortableGridService.contentHeight(rects) }, testID: PsSortableGridConfig.testId, children: order.map((item, index) => (_jsx(PsSortableItem, { canMoveDown: index < order.length - 1, canMoveUp: index > 0, dragX: dragX, dragY: dragY, extra: extras[index], id: item.id, isActive: item.id === activeId, isDisabled: isDisabled, isReady: isReady, label: item.label, labels: labels, onEnd: end, onMeasure: measure, onMove: moveBy, onStart: start, onUpdate: update, rect: rects[index], children: renderItem({ isActive: item.id === activeId, item }) }, item.id))) }));
146
146
  };
@@ -16,7 +16,7 @@ export interface PsSortableItemProps {
16
16
  label: string;
17
17
  labels: PsSortableGridLabels;
18
18
  onEnd: () => void;
19
- onMeasure: (id: string, height: number, renderedExtra: number) => void;
19
+ onMeasure: (id: string, height: number, fill: number) => void;
20
20
  onMove: (id: string, delta: number) => void;
21
21
  onStart: (id: string, absoluteY: number) => void;
22
22
  onUpdate: (translationX: number, translationY: number, absoluteY: number) => void;
@@ -1,5 +1,6 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect, useRef } from 'react';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef } from 'react';
3
+ import { View } from 'react-native';
3
4
  import { GestureDetector, usePanGesture } from 'react-native-gesture-handler';
4
5
  import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
5
6
  import { PsSortableGridConfig } from '../../constants/ps-sortable-grid-config';
@@ -11,6 +12,13 @@ export const PsSortableItem = ({ canMoveDown, canMoveUp, children, dragX, dragY,
11
12
  const top = useSharedValue(rect.y);
12
13
  const scale = useSharedValue(1);
13
14
  const wasActive = useRef(false);
15
+ const laidOut = useRef({ fill: 0, total: null });
16
+ const fill = isReady ? extra : 0;
17
+ const report = useCallback(() => {
18
+ const { fill: laidOutFill, total } = laidOut.current;
19
+ if (total !== null)
20
+ onMeasure(id, total, laidOutFill);
21
+ }, [id, onMeasure]);
14
22
  useEffect(() => {
15
23
  if (isActive) {
16
24
  wasActive.current = true;
@@ -49,12 +57,15 @@ export const PsSortableItem = ({ canMoveDown, canMoveUp, children, dragX, dragY,
49
57
  top: isActive ? dragY.value : top.value,
50
58
  transform: [{ scale: scale.value }],
51
59
  }));
52
- return (_jsx(GestureDetector, { gesture: pan, children: _jsx(Animated.View, { accessibilityActions: isDisabled
60
+ return (_jsx(GestureDetector, { gesture: pan, children: _jsxs(Animated.View, { accessibilityActions: isDisabled
53
61
  ? undefined
54
62
  : [
55
63
  ...(canMoveUp ? [{ label: labels.moveUp, name: 'moveUp' }] : []),
56
64
  ...(canMoveDown ? [{ label: labels.moveDown, name: 'moveDown' }] : []),
57
- ], accessibilityLabel: label, accessible: !isDisabled, collapsable: false, onAccessibilityAction: (event) => onMove(id, event.nativeEvent.actionName === 'moveUp' ? -1 : 1), onLayout: (event) => onMeasure(id, event.nativeEvent.layout.height, isReady ? extra : 0), style: [
65
+ ], accessibilityLabel: label, accessible: !isDisabled, collapsable: false, onAccessibilityAction: (event) => onMove(id, event.nativeEvent.actionName === 'moveUp' ? -1 : 1), onLayout: (event) => {
66
+ laidOut.current.total = event.nativeEvent.layout.height;
67
+ report();
68
+ }, style: [
58
69
  {
59
70
  borderRadius: PsSortableGridConfig.radius,
60
71
  opacity: isReady ? 1 : 0,
@@ -71,5 +82,8 @@ export const PsSortableItem = ({ canMoveDown, canMoveUp, children, dragX, dragY,
71
82
  }
72
83
  : undefined,
73
84
  animatedStyle,
74
- ], children: _jsx(PsWidgetFillContext.Provider, { value: isReady ? extra : 0, children: children }) }) }));
85
+ ], children: [_jsx(View, { onLayout: (event) => {
86
+ laidOut.current.fill = event.nativeEvent.layout.height;
87
+ report();
88
+ }, pointerEvents: 'none', style: { height: fill, left: 0, position: 'absolute', top: 0, width: 0 }, testID: PsSortableGridConfig.fillTestId }), _jsx(PsWidgetFillContext.Provider, { value: fill, children: children })] }) }));
75
89
  };
@@ -0,0 +1,6 @@
1
+ export declare const PsChartBarCorner: {
2
+ readonly Both: "both";
3
+ readonly None: "none";
4
+ readonly Value: "value";
5
+ };
6
+ export type PsChartBarCorner = (typeof PsChartBarCorner)[keyof typeof PsChartBarCorner];
@@ -0,0 +1,5 @@
1
+ export const PsChartBarCorner = {
2
+ Both: 'both',
3
+ None: 'none',
4
+ Value: 'value',
5
+ };
@@ -6,6 +6,7 @@ export declare const PsChartConfig: {
6
6
  readonly axisTickGap: 6;
7
7
  readonly barGapRatio: 0.3;
8
8
  readonly barRadius: 4;
9
+ readonly barRadiusWidthRatio: number;
9
10
  readonly charWidthRatio: 0.62;
10
11
  readonly compactBarGapRatio: 0.15;
11
12
  readonly compactSlot: 24;
@@ -18,9 +19,11 @@ export declare const PsChartConfig: {
18
19
  readonly donutCenterFontMax: 18;
19
20
  readonly donutCenterFontMin: 11;
20
21
  readonly donutCenterPadding: 8;
22
+ readonly donutDimmedOpacity: 0.4;
21
23
  readonly donutGap: 16;
22
24
  readonly donutMinLegendWidth: 120;
23
- readonly donutSliceGap: 2;
25
+ readonly donutRevealOverlap: 2;
26
+ readonly donutSliceGap: 3;
24
27
  readonly drawMs: 700;
25
28
  readonly ellipsis: "…";
26
29
  readonly gridLineCount: 3;
@@ -6,6 +6,7 @@ export const PsChartConfig = {
6
6
  axisTickGap: 6,
7
7
  barGapRatio: 0.3,
8
8
  barRadius: 4,
9
+ barRadiusWidthRatio: 1 / 3,
9
10
  charWidthRatio: 0.62,
10
11
  compactBarGapRatio: 0.15,
11
12
  compactSlot: 24,
@@ -18,9 +19,11 @@ export const PsChartConfig = {
18
19
  donutCenterFontMax: 18,
19
20
  donutCenterFontMin: 11,
20
21
  donutCenterPadding: 8,
22
+ donutDimmedOpacity: 0.4,
21
23
  donutGap: 16,
22
24
  donutMinLegendWidth: 120,
23
- donutSliceGap: 2,
25
+ donutRevealOverlap: 2,
26
+ donutSliceGap: 3,
24
27
  drawMs: 700,
25
28
  ellipsis: '…',
26
29
  gridLineCount: 3,
@@ -8,4 +8,8 @@ export declare const PsMenuPopupConfig: {
8
8
  readonly remeasureMs: 250;
9
9
  readonly rowHeight: 48;
10
10
  readonly rowPaddingHorizontal: 16;
11
+ readonly tileFontSize: 14;
12
+ readonly tileLogoPadding: 3;
13
+ readonly tileRadius: 8;
14
+ readonly tileSize: 28;
11
15
  };
@@ -8,4 +8,8 @@ export const PsMenuPopupConfig = {
8
8
  remeasureMs: 250,
9
9
  rowHeight: 48,
10
10
  rowPaddingHorizontal: 16,
11
+ tileFontSize: 14,
12
+ tileLogoPadding: 3,
13
+ tileRadius: 8,
14
+ tileSize: 28,
11
15
  };
@@ -1,6 +1,7 @@
1
1
  export declare const PsSheetConfig: {
2
2
  readonly actionMinWidth: 44;
3
3
  readonly actionSpinnerGap: 6;
4
+ readonly androidHeaderPaddingTop: 0;
4
5
  readonly cornerRadius: 20;
5
6
  readonly formSizingMinIos: 18;
6
7
  readonly headerMinHeight: 64;
@@ -1,6 +1,7 @@
1
1
  export const PsSheetConfig = {
2
2
  actionMinWidth: 44,
3
3
  actionSpinnerGap: 6,
4
+ androidHeaderPaddingTop: 0,
4
5
  cornerRadius: 20,
5
6
  formSizingMinIos: 18,
6
7
  headerMinHeight: 64,
@@ -4,6 +4,7 @@ export declare const PsSortableGridConfig: {
4
4
  readonly columnGap: 12;
5
5
  readonly compactUnits: 2;
6
6
  readonly edgeRatio: 0.14;
7
+ readonly fillTestId: "ps-sortable-fill";
7
8
  readonly fullUnits: 2;
8
9
  readonly holdTolerance: 8;
9
10
  readonly longPressMs: 400;
@@ -17,4 +18,5 @@ export declare const PsSortableGridConfig: {
17
18
  readonly scaleMs: 140;
18
19
  readonly scrollRamp: 0.25;
19
20
  readonly scrollTickMs: 16;
21
+ readonly testId: "ps-sortable-grid";
20
22
  };
@@ -6,6 +6,7 @@ export const PsSortableGridConfig = {
6
6
  columnGap: PsChartConfig.kpiGap,
7
7
  compactUnits: 2,
8
8
  edgeRatio: 0.14,
9
+ fillTestId: 'ps-sortable-fill',
9
10
  fullUnits: 2,
10
11
  holdTolerance: 8,
11
12
  longPressMs: 400,
@@ -19,4 +20,5 @@ export const PsSortableGridConfig = {
19
20
  scaleMs: 140,
20
21
  scrollRamp: 0.25,
21
22
  scrollTickMs: 16,
23
+ testId: 'ps-sortable-grid',
22
24
  };
@@ -0,0 +1,9 @@
1
+ export interface PsChartArcData {
2
+ centre: number;
3
+ end: number;
4
+ gap: number;
5
+ inner: number;
6
+ outer: number;
7
+ radius: number;
8
+ start: number;
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,7 @@
1
+ import type { PsChartBarCorner } from '../constants/ps-chart-bar-corner';
1
2
  export interface PsChartBarRect {
2
3
  color: string;
4
+ corner: PsChartBarCorner;
3
5
  height: number;
4
6
  id: string;
5
7
  isNegative: boolean;
@@ -1,7 +1,5 @@
1
1
  export interface PsDonutSliceData {
2
2
  color: string;
3
- dashLength: number;
4
- dashOffset: number;
5
3
  id: string;
6
- start: number;
4
+ path: string;
7
5
  }
@@ -13,5 +13,6 @@ export interface PsMenuItem {
13
13
  monogram?: string;
14
14
  onSelect: () => void;
15
15
  subtitle?: string;
16
+ tileColor?: string;
16
17
  tone?: PsMenuItemTone;
17
18
  }
@@ -3,5 +3,6 @@ export interface PsMenuTileRequest {
3
3
  imageUri?: string;
4
4
  key: string;
5
5
  shape: PsMenuTileShape;
6
+ tileColor?: string;
6
7
  text: string;
7
8
  }
@@ -0,0 +1,4 @@
1
+ export interface PsSortableMeasureData {
2
+ fill: number;
3
+ total: number | null;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { PsChartArcData } from '../interfaces/ps-chart-arc-data';
2
+ export declare class PsChartArcService {
3
+ static sector(arc: PsChartArcData): string;
4
+ static ring(centre: number, inner: number, outer: number): string;
5
+ static minimumAngle(gap: number, radius: number, inner: number): number;
6
+ private static largeFlag;
7
+ private static angleOf;
8
+ private static cornerRadius;
9
+ private static corner;
10
+ private static edge;
11
+ private static at;
12
+ private static scale;
13
+ private static text;
14
+ }
@@ -0,0 +1,83 @@
1
+ export class PsChartArcService {
2
+ static sector(arc) {
3
+ const half = arc.gap / 2;
4
+ const radius = PsChartArcService.cornerRadius(arc);
5
+ const outerStart = PsChartArcService.corner(arc, arc.start, 1, arc.outer - radius, radius, half);
6
+ const outerEnd = PsChartArcService.corner(arc, arc.end, -1, arc.outer - radius, radius, half);
7
+ const innerEnd = PsChartArcService.corner(arc, arc.end, -1, arc.inner + radius, radius, half);
8
+ const innerStart = PsChartArcService.corner(arc, arc.start, 1, arc.inner + radius, radius, half);
9
+ const outerStartRim = PsChartArcService.scale(arc, outerStart, arc.outer / (arc.outer - radius));
10
+ const outerEndRim = PsChartArcService.scale(arc, outerEnd, arc.outer / (arc.outer - radius));
11
+ const innerEndRim = PsChartArcService.scale(arc, innerEnd, arc.inner / (arc.inner + radius));
12
+ const innerStartRim = PsChartArcService.scale(arc, innerStart, arc.inner / (arc.inner + radius));
13
+ const startOuterEdge = PsChartArcService.edge(arc, arc.start, 1, outerStart, half);
14
+ const endOuterEdge = PsChartArcService.edge(arc, arc.end, -1, outerEnd, half);
15
+ const endInnerEdge = PsChartArcService.edge(arc, arc.end, -1, innerEnd, half);
16
+ const startInnerEdge = PsChartArcService.edge(arc, arc.start, 1, innerStart, half);
17
+ return [
18
+ `M${PsChartArcService.text(startOuterEdge)}`,
19
+ `A${radius} ${radius} 0 0 1 ${PsChartArcService.text(outerStartRim)}`,
20
+ `A${arc.outer} ${arc.outer} 0 ${PsChartArcService.largeFlag(arc, outerStartRim, outerEndRim)} 1 ${PsChartArcService.text(outerEndRim)}`,
21
+ `A${radius} ${radius} 0 0 1 ${PsChartArcService.text(endOuterEdge)}`,
22
+ `L${PsChartArcService.text(endInnerEdge)}`,
23
+ `A${radius} ${radius} 0 0 1 ${PsChartArcService.text(innerEndRim)}`,
24
+ `A${arc.inner} ${arc.inner} 0 ${PsChartArcService.largeFlag(arc, innerStartRim, innerEndRim)} 0 ${PsChartArcService.text(innerStartRim)}`,
25
+ `A${radius} ${radius} 0 0 1 ${PsChartArcService.text(startInnerEdge)}`,
26
+ 'Z',
27
+ ].join(' ');
28
+ }
29
+ static ring(centre, inner, outer) {
30
+ return [
31
+ `M${centre} ${centre - outer}`,
32
+ `A${outer} ${outer} 0 1 1 ${centre} ${centre + outer}`,
33
+ `A${outer} ${outer} 0 1 1 ${centre} ${centre - outer}`,
34
+ `M${centre} ${centre - inner}`,
35
+ `A${inner} ${inner} 0 1 0 ${centre} ${centre + inner}`,
36
+ `A${inner} ${inner} 0 1 0 ${centre} ${centre - inner}`,
37
+ 'Z',
38
+ ].join(' ');
39
+ }
40
+ static minimumAngle(gap, radius, inner) {
41
+ return inner > 0 ? (gap + 2 * radius + 1) / inner : 0;
42
+ }
43
+ static largeFlag(arc, from, to) {
44
+ const turn = 2 * Math.PI;
45
+ const span = (((PsChartArcService.angleOf(arc, to) - PsChartArcService.angleOf(arc, from)) % turn) +
46
+ turn) %
47
+ turn;
48
+ return span > Math.PI ? 1 : 0;
49
+ }
50
+ static angleOf(arc, point) {
51
+ return Math.atan2(point.x - arc.centre, arc.centre - point.y);
52
+ }
53
+ static cornerRadius(arc) {
54
+ const room = ((arc.end - arc.start) * arc.inner - arc.gap) / 2;
55
+ return Math.max(0, Math.min(arc.radius, (arc.outer - arc.inner) / 2, room));
56
+ }
57
+ static corner(arc, angle, side, distance, radius, half) {
58
+ const offset = half + radius;
59
+ const along = Math.sqrt(Math.max(distance * distance - offset * offset, 0));
60
+ return PsChartArcService.at(arc, angle, side, along, offset);
61
+ }
62
+ static edge(arc, angle, side, centre, half) {
63
+ const dx = centre.x - arc.centre;
64
+ const dy = centre.y - arc.centre;
65
+ const along = dx * Math.sin(angle) - dy * Math.cos(angle);
66
+ return PsChartArcService.at(arc, angle, side, along, half);
67
+ }
68
+ static at(arc, angle, side, along, across) {
69
+ return {
70
+ x: arc.centre + along * Math.sin(angle) + side * across * Math.cos(angle),
71
+ y: arc.centre - along * Math.cos(angle) + side * across * Math.sin(angle),
72
+ };
73
+ }
74
+ static scale(arc, point, ratio) {
75
+ return {
76
+ x: arc.centre + (point.x - arc.centre) * ratio,
77
+ y: arc.centre + (point.y - arc.centre) * ratio,
78
+ };
79
+ }
80
+ static text(point) {
81
+ return `${point.x.toFixed(2)} ${point.y.toFixed(2)}`;
82
+ }
83
+ }
@@ -9,5 +9,7 @@ export declare class PsChartBarService {
9
9
  static stacked(data: PsAreaChartDatum[], series: PsCartesianChartSeries[], colors: string[], domain: PsChartDomain, plot: PsChartPlot): PsChartBarRect[];
10
10
  static slot(count: number, plot: PsChartPlot): number;
11
11
  static group(slot: number, size: number): PsChartBarGroup;
12
+ static lastIndex(values: number[], sign: number): number;
13
+ static radius(rect: PsChartBarRect): number;
12
14
  private static rect;
13
15
  }
@@ -1,3 +1,4 @@
1
+ import { PsChartBarCorner } from '../constants/ps-chart-bar-corner';
1
2
  import { PsChartConfig } from '../constants/ps-chart-config';
2
3
  import { PsChartScaleService } from './ps-chart-scale-service';
3
4
  import { PsChartSeriesService } from './ps-chart-series-service';
@@ -10,7 +11,7 @@ export class PsChartBarService {
10
11
  const value = PsChartSeriesService.value(datum, entry.dataKey);
11
12
  const top = PsChartScaleService.y(value, domain, plot);
12
13
  const left = plot.left + row * slot + offset + column * width;
13
- return PsChartBarService.rect(`${row}:${entry.dataKey}`, colors[column], left, width, zero, top, value < 0);
14
+ return PsChartBarService.rect(`${row}:${entry.dataKey}`, colors[column], left, width, zero, top, value < 0, PsChartBarCorner.Value);
14
15
  }));
15
16
  }
16
17
  static stacked(data, series, colors, domain, plot) {
@@ -19,6 +20,9 @@ export class PsChartBarService {
19
20
  return data.flatMap((datum, row) => {
20
21
  let positive = 0;
21
22
  let negative = 0;
23
+ const values = series.map((entry) => PsChartSeriesService.value(datum, entry.dataKey));
24
+ const outerPositive = PsChartBarService.lastIndex(values, 1);
25
+ const outerNegative = PsChartBarService.lastIndex(values, -1);
22
26
  return series.map((entry, column) => {
23
27
  const value = PsChartSeriesService.value(datum, entry.dataKey);
24
28
  const from = value < 0 ? negative : positive;
@@ -27,7 +31,9 @@ export class PsChartBarService {
27
31
  negative = to;
28
32
  else
29
33
  positive = to;
30
- return PsChartBarService.rect(`${row}:${entry.dataKey}`, colors[column], plot.left + row * slot + offset, width, PsChartScaleService.y(from, domain, plot), PsChartScaleService.y(to, domain, plot), value < 0);
34
+ return PsChartBarService.rect(`${row}:${entry.dataKey}`, colors[column], plot.left + row * slot + offset, width, PsChartScaleService.y(from, domain, plot), PsChartScaleService.y(to, domain, plot), value < 0, column === outerPositive || column === outerNegative
35
+ ? PsChartBarCorner.Value
36
+ : PsChartBarCorner.None);
31
37
  });
32
38
  });
33
39
  }
@@ -41,9 +47,18 @@ export class PsChartBarService {
41
47
  const total = Math.max(slot * (1 - gap), Math.min(PsChartConfig.minBarWidth * size, slot));
42
48
  return { offset: (slot - total) / 2, width: total / size };
43
49
  }
44
- static rect(id, color, x, width, from, to, isNegative) {
50
+ static lastIndex(values, sign) {
51
+ return values.reduce((last, value, index) => (Math.sign(value) === sign ? index : last), -1);
52
+ }
53
+ static radius(rect) {
54
+ if (rect.corner === PsChartBarCorner.None)
55
+ return 0;
56
+ return Math.min(PsChartConfig.barRadius, rect.width * PsChartConfig.barRadiusWidthRatio);
57
+ }
58
+ static rect(id, color, x, width, from, to, isNegative, corner) {
45
59
  return {
46
60
  color,
61
+ corner,
47
62
  height: Math.abs(to - from),
48
63
  id,
49
64
  isNegative,
@@ -6,5 +6,7 @@ export declare class PsChartDonutService {
6
6
  static centerFontSize(text: string, size: number): number;
7
7
  static radius(size: number): number;
8
8
  static circumference(size: number): number;
9
+ static inner(size: number): number;
9
10
  static slices(data: PsDonutChartDatum[], colors: string[], size: number): PsDonutSliceData[];
11
+ static angles(shares: number[], minimum: number): number[];
10
12
  }
@@ -1,4 +1,5 @@
1
1
  import { PsChartConfig } from '../constants/ps-chart-config';
2
+ import { PsChartArcService } from './ps-chart-arc-service';
2
3
  import { PsChartTextService } from './ps-chart-text-service';
3
4
  export class PsChartDonutService {
4
5
  static layout(width, height) {
@@ -17,23 +18,52 @@ export class PsChartDonutService {
17
18
  static circumference(size) {
18
19
  return 2 * Math.PI * PsChartDonutService.radius(size);
19
20
  }
21
+ static inner(size) {
22
+ return Math.max(size / 2 - PsChartConfig.donutThickness, 0);
23
+ }
20
24
  static slices(data, colors, size) {
21
- const circumference = PsChartDonutService.circumference(size);
22
- const total = data.reduce((sum, datum) => sum + datum.share, 0);
25
+ const shares = data.map((datum) => Math.max(datum.share, 0));
26
+ const total = shares.reduce((sum, share) => sum + share, 0);
23
27
  if (total <= 0)
24
28
  return [];
25
- let start = 0;
26
- return data.map((datum, index) => {
27
- const length = (datum.share / total) * circumference;
28
- const slice = {
29
+ const centre = size / 2;
30
+ const outer = centre;
31
+ const inner = PsChartDonutService.inner(size);
32
+ const shown = shares.filter((share) => share > 0).length;
33
+ if (shown === 1)
34
+ return data.map((datum, index) => ({
29
35
  color: colors[index % colors.length],
30
- dashLength: Math.max(length - PsChartConfig.donutSliceGap, 0),
31
- dashOffset: -start,
32
36
  id: datum.id,
33
- start,
34
- };
35
- start += length;
36
- return slice;
37
+ path: shares[index] > 0 ? PsChartArcService.ring(centre, inner, outer) : '',
38
+ }));
39
+ const angles = PsChartDonutService.angles(shares, PsChartArcService.minimumAngle(PsChartConfig.donutSliceGap, PsChartConfig.barRadius, inner));
40
+ let start = 0;
41
+ return data.map((datum, index) => {
42
+ const end = start + angles[index];
43
+ const path = angles[index] > 0
44
+ ? PsChartArcService.sector({
45
+ centre,
46
+ end,
47
+ gap: PsChartConfig.donutSliceGap,
48
+ inner,
49
+ outer,
50
+ radius: PsChartConfig.barRadius,
51
+ start,
52
+ })
53
+ : '';
54
+ start = end;
55
+ return { color: colors[index % colors.length], id: datum.id, path };
37
56
  });
38
57
  }
58
+ static angles(shares, minimum) {
59
+ const turn = 2 * Math.PI;
60
+ const shown = shares.filter((share) => share > 0).length;
61
+ const floor = Math.min(minimum, turn / Math.max(shown, 1));
62
+ const total = shares.reduce((sum, share) => sum + share, 0);
63
+ const isSmall = shares.map((share) => share > 0 && (share / total) * turn < floor);
64
+ const smallCount = isSmall.filter(Boolean).length;
65
+ const largeTotal = shares.reduce((sum, share, index) => (isSmall[index] ? sum : sum + share), 0);
66
+ const left = turn - smallCount * floor;
67
+ return shares.map((share, index) => share <= 0 ? 0 : isSmall[index] ? floor : largeTotal > 0 ? (share / largeTotal) * left : 0);
68
+ }
39
69
  }
@@ -4,14 +4,14 @@ export class PsMenuTileService {
4
4
  if (!PsActionMenuService.hasAvatar(item))
5
5
  return null;
6
6
  const text = PsMenuTileService.letter(item.monogram === undefined ? item.label : item.monogram);
7
- return PsMenuTileService.request('rounded', item.imageUri, text);
7
+ return PsMenuTileService.request('rounded', item.imageUri, text, item.tileColor);
8
8
  }
9
9
  static headerRequest(header) {
10
10
  if (header === undefined)
11
11
  return null;
12
12
  if (header.imageUri === undefined && header.initials === undefined)
13
13
  return null;
14
- return PsMenuTileService.request('circle', header.imageUri, header.initials === undefined ? '' : header.initials.trim().toUpperCase());
14
+ return PsMenuTileService.request('circle', header.imageUri, header.initials === undefined ? '' : header.initials.trim().toUpperCase(), undefined);
15
15
  }
16
16
  static requests(header, sections) {
17
17
  const all = [
@@ -59,12 +59,18 @@ export class PsMenuTileService {
59
59
  return [item];
60
60
  return [item, ...item.children.flatMap((child) => PsMenuTileService.tree(child))];
61
61
  }
62
- static request(shape, imageUri, text) {
62
+ static request(shape, imageUri, text, tileColor) {
63
63
  return {
64
64
  imageUri,
65
- key: [shape, imageUri === undefined ? '' : imageUri, text].join('|'),
65
+ key: [
66
+ shape,
67
+ imageUri === undefined ? '' : imageUri,
68
+ text,
69
+ tileColor === undefined ? '' : tileColor,
70
+ ].join('|'),
66
71
  shape,
67
72
  text,
73
+ tileColor,
68
74
  };
69
75
  }
70
76
  }
@@ -7,6 +7,7 @@ export declare class PsSheetService {
7
7
  static fittedMaxWidth(windowWidth: number, layoutClass: PsLayoutClass): number | undefined;
8
8
  static supportsFormSizing(iosVersion: string | number): boolean;
9
9
  static handlesInset(os: string): boolean;
10
+ static headerPaddingTop(os: string): number;
10
11
  static bottomPadding(isFloating: boolean, bottomInset: number): number;
11
12
  static fittedHeight(headerHeight: number, bodyHeight: number, windowHeight: number, layoutClass?: PsLayoutClass): number;
12
13
  static raisedHeight(height: number, keyboardHeight: number, windowHeight: number, topInset: number): number;
@@ -20,6 +20,9 @@ export class PsSheetService {
20
20
  static handlesInset(os) {
21
21
  return os === 'ios' || os === 'android';
22
22
  }
23
+ static headerPaddingTop(os) {
24
+ return os === 'android' ? PsSheetConfig.androidHeaderPaddingTop : PsSheetConfig.headerPaddingTop;
25
+ }
23
26
  static bottomPadding(isFloating, bottomInset) {
24
27
  return PsLayoutConfig.sheetGutter + (isFloating ? 0 : bottomInset);
25
28
  }
@@ -1,3 +1,4 @@
1
+ import { PsChartBarCorner } from '../constants/ps-chart-bar-corner';
1
2
  import { PsChartBarService } from './ps-chart-bar-service';
2
3
  import { PsChartScaleService } from './ps-chart-scale-service';
3
4
  export class PsWaterfallService {
@@ -25,6 +26,7 @@ export class PsWaterfallService {
25
26
  const to = PsChartScaleService.y(step.end, domain, plot);
26
27
  return {
27
28
  color: step.isTotal ? colors[2] : step.value < 0 ? colors[1] : colors[0],
29
+ corner: step.isTotal ? PsChartBarCorner.Value : PsChartBarCorner.Both,
28
30
  height: Math.abs(to - from),
29
31
  id: `${row}:${step.label}`,
30
32
  isNegative: step.end < step.start,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pallasoft/ps-mobile-components",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "private": false,
5
5
  "description": "Native iOS (SwiftUI) and Android (Jetpack Compose) Ps components for Pallasoft mobile apps",
6
6
  "main": "./dist/index.js",