@pallasoft/ps-mobile-components 1.14.0 → 1.15.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 (30) hide show
  1. package/dist/components/ps-link-preview-card/index.d.ts +2 -0
  2. package/dist/components/ps-link-preview-card/index.js +2 -0
  3. package/dist/components/ps-link-preview-card/ps-link-preview-card-props.d.ts +21 -0
  4. package/dist/components/ps-link-preview-card/ps-link-preview-card-props.js +1 -0
  5. package/dist/components/ps-link-preview-card/ps-link-preview-card-skeleton.d.ts +2 -0
  6. package/dist/components/ps-link-preview-card/ps-link-preview-card-skeleton.js +28 -0
  7. package/dist/components/ps-link-preview-card/ps-link-preview-card.d.ts +2 -0
  8. package/dist/components/ps-link-preview-card/ps-link-preview-card.js +66 -0
  9. package/dist/components/ps-list/ps-list-person-row.js +1 -3
  10. package/dist/components/ps-list/ps-list-row-body.js +1 -3
  11. package/dist/components/ps-list/ps-list-row-skeleton.js +1 -3
  12. package/dist/components/ps-native-menu/ps-native-menu-row.js +1 -1
  13. package/dist/constants/ps-chart-config.d.ts +1 -0
  14. package/dist/constants/ps-chart-config.js +1 -0
  15. package/dist/constants/ps-layout-config.d.ts +2 -2
  16. package/dist/constants/ps-layout-config.js +2 -2
  17. package/dist/constants/ps-link-preview-config.d.ts +19 -0
  18. package/dist/constants/ps-link-preview-config.js +19 -0
  19. package/dist/constants/ps-link-preview-size.d.ts +5 -0
  20. package/dist/constants/ps-link-preview-size.js +4 -0
  21. package/dist/hooks/use-ps-chart-reveal.js +6 -2
  22. package/dist/index.d.ts +3 -0
  23. package/dist/index.js +3 -0
  24. package/dist/interfaces/ps-link-preview-meta.d.ts +5 -0
  25. package/dist/interfaces/ps-link-preview-meta.js +1 -0
  26. package/dist/services/ps-chart-reveal-service.d.ts +4 -0
  27. package/dist/services/ps-chart-reveal-service.js +9 -0
  28. package/dist/services/ps-link-preview-service.d.ts +12 -0
  29. package/dist/services/ps-link-preview-service.js +38 -0
  30. package/package.json +1 -1
@@ -0,0 +1,2 @@
1
+ export * from './ps-link-preview-card';
2
+ export * from './ps-link-preview-card-props';
@@ -0,0 +1,2 @@
1
+ export * from './ps-link-preview-card';
2
+ export * from './ps-link-preview-card-props';
@@ -0,0 +1,21 @@
1
+ import type { PsLinkPreviewSize } from '../../constants/ps-link-preview-size';
2
+ import type { PsDocumentSource } from '../../interfaces/ps-document-source';
3
+ export interface PsLinkPreviewCardProps {
4
+ description?: string | null;
5
+ faviconSource?: PsDocumentSource | null;
6
+ href: string;
7
+ imageSource?: PsDocumentSource | null;
8
+ isFramed?: boolean;
9
+ isLoading?: boolean;
10
+ loadingLabel?: string;
11
+ onPress: () => void;
12
+ opensInBrowserLabel?: string;
13
+ siteName?: string | null;
14
+ size?: PsLinkPreviewSize;
15
+ title?: string | null;
16
+ }
17
+ export interface PsLinkPreviewCardSkeletonProps {
18
+ isCompact: boolean;
19
+ isFramed: boolean;
20
+ label?: string;
21
+ }
@@ -0,0 +1,2 @@
1
+ import type { PsLinkPreviewCardSkeletonProps } from './ps-link-preview-card-props';
2
+ export declare const PsLinkPreviewCardSkeleton: ({ isCompact, isFramed, label, }: PsLinkPreviewCardSkeletonProps) => import("react").JSX.Element;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { View } from 'react-native';
4
+ import { PsLinkPreviewConfig } from '../../constants/ps-link-preview-config';
5
+ import { PsMobileControlConfig } from '../../constants/ps-mobile-control-config';
6
+ import { PsSkeletonShape } from '../../constants/ps-skeleton-shape';
7
+ import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
8
+ import { PsLinkPreviewService } from '../../services/ps-link-preview-service';
9
+ import { PsSkeleton } from '../ps-skeleton/ps-skeleton';
10
+ export const PsLinkPreviewCardSkeleton = ({ isCompact, isFramed, label, }) => {
11
+ const theme = usePsMobileTheme();
12
+ const [width, setWidth] = useState(0);
13
+ const bodyPadding = isCompact
14
+ ? PsLinkPreviewConfig.compactBodyPadding
15
+ : PsLinkPreviewConfig.fullBodyPadding;
16
+ return (_jsxs(View, { accessibilityLabel: label, accessible: label !== undefined, onLayout: (event) => setWidth(event.nativeEvent.layout.width), style: {
17
+ backgroundColor: theme.surface,
18
+ borderColor: theme.line,
19
+ borderRadius: PsMobileControlConfig.cardRadius,
20
+ borderWidth: isFramed ? 1 : 0,
21
+ flexDirection: isCompact ? 'row' : 'column',
22
+ overflow: 'hidden',
23
+ }, testID: 'ps-link-preview-card-skeleton', children: [isCompact ? (_jsx(PsSkeleton, { height: PsLinkPreviewConfig.compactImageMinHeight, radius: 0, shape: PsSkeletonShape.Block, width: PsLinkPreviewConfig.compactImageWidth })) : (_jsx(PsSkeleton, { height: PsLinkPreviewService.imageHeight(width), radius: 0, shape: PsSkeletonShape.Block })), _jsxs(View, { style: {
24
+ flex: isCompact ? 1 : undefined,
25
+ gap: PsLinkPreviewConfig.skeletonLineGap,
26
+ padding: bodyPadding,
27
+ }, children: [_jsx(PsSkeleton, { shape: PsSkeletonShape.Line, width: PsLinkPreviewConfig.skeletonMetaWidth }), _jsx(PsSkeleton, { shape: PsSkeletonShape.Line, width: PsLinkPreviewConfig.skeletonTitleWidth }), isCompact ? null : _jsx(PsSkeleton, { shape: PsSkeletonShape.Line })] })] }));
28
+ };
@@ -0,0 +1,2 @@
1
+ import type { PsLinkPreviewCardProps } from './ps-link-preview-card-props';
2
+ export declare const PsLinkPreviewCard: ({ description, faviconSource, href, imageSource, isFramed, isLoading, loadingLabel, onPress, opensInBrowserLabel, siteName, size, title, }: PsLinkPreviewCardProps) => import("react").JSX.Element | null;
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { PsIconKey } from '@pallasoft/ps-icons';
4
+ import { Image, Pressable, View } from 'react-native';
5
+ import { PsLinkPreviewConfig } from '../../constants/ps-link-preview-config';
6
+ import { PsLinkPreviewSize } from '../../constants/ps-link-preview-size';
7
+ import { PsMobileControlConfig } from '../../constants/ps-mobile-control-config';
8
+ import { PsRippleKind } from '../../constants/ps-ripple-kind';
9
+ import { usePsHaptics } from '../../hooks/use-ps-haptics';
10
+ import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
11
+ import { PsLinkPreviewService } from '../../services/ps-link-preview-service';
12
+ import { PsRippleService } from '../../services/ps-ripple-service';
13
+ import { PsStateService } from '../../services/ps-state-service';
14
+ import { PsIcon } from '../ps-icon/ps-icon';
15
+ import { PsText } from '../ps-text/ps-text';
16
+ import { PsLinkPreviewCardSkeleton } from './ps-link-preview-card-skeleton';
17
+ export const PsLinkPreviewCard = ({ description, faviconSource, href, imageSource, isFramed = true, isLoading = false, loadingLabel, onPress, opensInBrowserLabel, siteName, size = PsLinkPreviewSize.Full, title, }) => {
18
+ const theme = usePsMobileTheme();
19
+ const haptic = usePsHaptics();
20
+ const [failedImageUri, setFailedImageUri] = useState(null);
21
+ const [failedFaviconUri, setFailedFaviconUri] = useState(null);
22
+ if (!PsLinkPreviewService.isWebAddress(href))
23
+ return null;
24
+ const isCompact = PsLinkPreviewService.isCompact(size);
25
+ if (isLoading)
26
+ return (_jsx(PsLinkPreviewCardSkeleton, { isCompact: isCompact, isFramed: isFramed, label: loadingLabel }));
27
+ const meta = PsLinkPreviewService.meta(href, title, siteName, description, size);
28
+ const hasImage = PsLinkPreviewService.hasSource(imageSource, failedImageUri);
29
+ const hasFavicon = PsLinkPreviewService.hasSource(faviconSource, failedFaviconUri);
30
+ return (_jsxs(Pressable, { accessibilityHint: opensInBrowserLabel, accessibilityRole: 'link', android_ripple: PsRippleService.resolve(theme, PsRippleKind.Surface), onPress: () => {
31
+ haptic('light');
32
+ onPress();
33
+ }, style: ({ pressed }) => ({
34
+ backgroundColor: pressed ? PsStateService.pressedFill(theme, theme.surface) : theme.surface,
35
+ borderColor: theme.line,
36
+ borderRadius: PsMobileControlConfig.cardRadius,
37
+ borderWidth: isFramed ? 1 : 0,
38
+ flexDirection: isCompact ? 'row' : 'column',
39
+ overflow: 'hidden',
40
+ }), testID: 'ps-link-preview-card', children: [hasImage && imageSource !== null && imageSource !== undefined ? (_jsx(Image, { onError: () => setFailedImageUri(imageSource.uri), resizeMode: 'cover', source: { headers: imageSource.headers, uri: imageSource.uri }, style: isCompact
41
+ ? {
42
+ alignSelf: 'stretch',
43
+ backgroundColor: theme.skeletonBase,
44
+ minHeight: PsLinkPreviewConfig.compactImageMinHeight,
45
+ width: PsLinkPreviewConfig.compactImageWidth,
46
+ }
47
+ : {
48
+ aspectRatio: PsLinkPreviewConfig.imageAspectRatio,
49
+ backgroundColor: theme.skeletonBase,
50
+ width: '100%',
51
+ }, testID: 'ps-link-preview-card-image' })) : null, _jsxs(View, { style: {
52
+ flex: isCompact ? 1 : undefined,
53
+ gap: PsLinkPreviewConfig.textGap,
54
+ padding: isCompact
55
+ ? PsLinkPreviewConfig.compactBodyPadding
56
+ : PsLinkPreviewConfig.fullBodyPadding,
57
+ }, children: [_jsxs(View, { style: {
58
+ alignItems: 'center',
59
+ flexDirection: 'row',
60
+ gap: PsLinkPreviewConfig.metaGap,
61
+ }, children: [hasFavicon && faviconSource !== null && faviconSource !== undefined ? (_jsx(Image, { onError: () => setFailedFaviconUri(faviconSource.uri), resizeMode: 'contain', source: { headers: faviconSource.headers, uri: faviconSource.uri }, style: {
62
+ borderRadius: PsLinkPreviewConfig.faviconRadius,
63
+ height: PsLinkPreviewConfig.faviconSize,
64
+ width: PsLinkPreviewConfig.faviconSize,
65
+ }, testID: 'ps-link-preview-card-favicon' })) : (_jsx(PsIcon, { color: theme.muted, iconKey: PsIconKey.Globe, size: PsLinkPreviewConfig.faviconSize })), _jsx(View, { style: { flex: 1 }, children: _jsx(PsText, { numberOfLines: 1, tone: 'muted', variant: 'caption', weight: 'medium', children: meta.siteLabel }) }), _jsx(PsIcon, { color: theme.muted, iconKey: PsIconKey.ExternalLink, size: PsLinkPreviewConfig.faviconSize })] }), meta.hasTitle ? (_jsx(PsText, { numberOfLines: PsLinkPreviewConfig.titleLines, weight: 'bold', children: title })) : (_jsx(PsText, { numberOfLines: 1, tone: 'muted', variant: 'caption', children: href })), meta.hasDescription ? (_jsx(PsText, { numberOfLines: PsLinkPreviewConfig.descriptionLines, tone: 'muted', variant: 'small', children: description })) : null] })] }));
66
+ };
@@ -12,9 +12,7 @@ export const PsListPersonRow = ({ badge, initials, subtitle, title }) => {
12
12
  backgroundColor: theme.surface,
13
13
  flexDirection: 'row',
14
14
  gap: PsLayoutConfig.headerSlotGap,
15
- minHeight: subtitle === undefined
16
- ? PsLayoutConfig.rowMinHeight
17
- : PsLayoutConfig.rowMinHeightWithSubtitle,
15
+ minHeight: PsLayoutConfig.rowMinHeight,
18
16
  paddingHorizontal: PsLayoutConfig.gutter,
19
17
  paddingVertical: PsLayoutConfig.rowPaddingVertical,
20
18
  }, testID: 'ps-list-person-row', children: [_jsx(PsAvatar, { initials: initials, size: 'small' }), _jsxs(View, { style: { flex: 1, gap: PsLayoutConfig.rowLineGap }, children: [_jsx(PsText, { numberOfLines: 1, weight: 'semibold', children: title }), subtitle === undefined ? null : (_jsx(PsText, { numberOfLines: 1, tone: 'muted', variant: 'small', children: subtitle }))] }), badge === undefined ? null : (_jsx(PsBadge, { size: 'small', tone: badge.tone, children: badge.label }))] }));
@@ -21,9 +21,7 @@ export const PsListRowBody = ({ badge, hasChevron = false, hasMenu, isSelectable
21
21
  backgroundColor: PsListRowStyleService.background(isSelectable && isSelected, pressed, theme),
22
22
  flexDirection: 'row',
23
23
  gap: PsLayoutConfig.headerSlotGap,
24
- minHeight: hasSecondLine
25
- ? PsLayoutConfig.rowMinHeightWithSubtitle
26
- : PsLayoutConfig.rowMinHeight,
24
+ minHeight: PsLayoutConfig.rowMinHeight,
27
25
  paddingHorizontal: PsLayoutConfig.gutter,
28
26
  paddingVertical: PsLayoutConfig.rowPaddingVertical,
29
27
  }), testID: 'ps-list-row', children: [isSelectable ? _jsx(PsListRowSelectMark, { isSelected: isSelected }) : null, leadingIcon === undefined ? null : (_jsx(View, { style: {
@@ -16,9 +16,7 @@ export const PsListRowSkeleton = ({ badge, isSection = false, leadingIcon, skele
16
16
  alignItems: 'center',
17
17
  flexDirection: 'row',
18
18
  gap: PsLayoutConfig.headerSlotGap,
19
- minHeight: hasSubtitle
20
- ? PsLayoutConfig.rowMinHeightWithSubtitle
21
- : PsLayoutConfig.rowMinHeight,
19
+ minHeight: PsLayoutConfig.rowMinHeight,
22
20
  paddingHorizontal: PsLayoutConfig.gutter,
23
21
  paddingVertical: PsLayoutConfig.rowPaddingVertical,
24
22
  }, testID: 'ps-skeleton-row', children: [hasIcon ? (_jsx(PsSkeleton, { height: PsLayoutConfig.rowIconBox, radius: PsLayoutConfig.rowIconBoxRadius, width: PsLayoutConfig.rowIconBox })) : null, _jsxs(View, { style: { flex: 1, gap: PsLayoutConfig.rowLineGap * 3 }, children: [_jsxs(View, { style: {
@@ -16,7 +16,7 @@ export const PsNativeMenuRow = ({ item, onSelect }) => {
16
16
  backgroundColor: pressed ? PsStateService.pressedFill(theme, theme.surface) : theme.surface,
17
17
  flexDirection: 'row',
18
18
  gap: PsMobileControlConfig.rowSpacing,
19
- minHeight: PsLayoutConfig.rowMinHeight,
19
+ minHeight: PsLayoutConfig.menuRowMinHeight,
20
20
  paddingHorizontal: PsLayoutConfig.gutter,
21
21
  }), children: [_jsx(PsText, { style: { flex: 1 }, weight: item.isSelected ? 'semibold' : 'regular', children: item.label }), item.isSelected ? _jsx(PsIcon, { color: theme.primary, iconKey: PsIconKey.Check }) : null] }));
22
22
  };
@@ -1,4 +1,5 @@
1
1
  export declare const PsChartConfig: {
2
+ readonly revealDefaultToken: "default";
2
3
  readonly areaOpacity: 0.18;
3
4
  readonly axisLabelGap: 8;
4
5
  readonly axisLabelMaxWidth: 48;
@@ -1,4 +1,5 @@
1
1
  export const PsChartConfig = {
2
+ revealDefaultToken: 'default',
2
3
  areaOpacity: 0.18,
3
4
  axisLabelGap: 8,
4
5
  axisLabelMaxWidth: 48,
@@ -18,8 +18,8 @@ export declare const PsLayoutConfig: {
18
18
  readonly rowIconSize: 16;
19
19
  readonly scrollEventThrottle: 16;
20
20
  readonly selectMarkSize: 22;
21
- readonly rowMinHeight: 48;
22
- readonly rowMinHeightWithSubtitle: 62;
21
+ readonly menuRowMinHeight: 48;
22
+ readonly rowMinHeight: 62;
23
23
  readonly rowLineGap: 2;
24
24
  readonly rowPaddingVertical: 12;
25
25
  readonly sheetFooterGap: 8;
@@ -18,8 +18,8 @@ export const PsLayoutConfig = {
18
18
  rowIconSize: 16,
19
19
  scrollEventThrottle: 16,
20
20
  selectMarkSize: 22,
21
- rowMinHeight: 48,
22
- rowMinHeightWithSubtitle: 62,
21
+ menuRowMinHeight: 48,
22
+ rowMinHeight: 62,
23
23
  rowLineGap: 2,
24
24
  rowPaddingVertical: 12,
25
25
  sheetFooterGap: 8,
@@ -0,0 +1,19 @@
1
+ export declare const PsLinkPreviewConfig: {
2
+ readonly compactBodyPadding: 12;
3
+ readonly compactImageMinHeight: 80;
4
+ readonly compactImageWidth: 96;
5
+ readonly descriptionLines: 2;
6
+ readonly domainPrefixPattern: RegExp;
7
+ readonly faviconRadius: 2;
8
+ readonly faviconSize: 16;
9
+ readonly fullBodyPadding: 16;
10
+ readonly imageAspectRatio: 1.91;
11
+ readonly metaGap: 8;
12
+ readonly portSuffixPattern: RegExp;
13
+ readonly skeletonLineGap: 8;
14
+ readonly skeletonMetaWidth: 96;
15
+ readonly skeletonTitleWidth: 192;
16
+ readonly textGap: 4;
17
+ readonly titleLines: 2;
18
+ readonly webAddressPattern: RegExp;
19
+ };
@@ -0,0 +1,19 @@
1
+ export const PsLinkPreviewConfig = {
2
+ compactBodyPadding: 12,
3
+ compactImageMinHeight: 80,
4
+ compactImageWidth: 96,
5
+ descriptionLines: 2,
6
+ domainPrefixPattern: /^www\./i,
7
+ faviconRadius: 2,
8
+ faviconSize: 16,
9
+ fullBodyPadding: 16,
10
+ imageAspectRatio: 1.91,
11
+ metaGap: 8,
12
+ portSuffixPattern: /:\d*$/,
13
+ skeletonLineGap: 8,
14
+ skeletonMetaWidth: 96,
15
+ skeletonTitleWidth: 192,
16
+ textGap: 4,
17
+ titleLines: 2,
18
+ webAddressPattern: /^https?:\/\/([^/?#\s]+)/i,
19
+ };
@@ -0,0 +1,5 @@
1
+ export declare const PsLinkPreviewSize: {
2
+ readonly Compact: "compact";
3
+ readonly Full: "full";
4
+ };
5
+ export type PsLinkPreviewSize = (typeof PsLinkPreviewSize)[keyof typeof PsLinkPreviewSize];
@@ -0,0 +1,4 @@
1
+ export const PsLinkPreviewSize = {
2
+ Compact: 'compact',
3
+ Full: 'full',
4
+ };
@@ -1,17 +1,21 @@
1
- import { useEffect } from 'react';
1
+ import { useEffect, useRef } from 'react';
2
2
  import { useSharedValue, withTiming } from 'react-native-reanimated';
3
3
  import { PsChartConfig } from '../constants/ps-chart-config';
4
+ import { PsChartRevealService } from '../services/ps-chart-reveal-service';
4
5
  import { usePsMobileTheme } from './use-ps-mobile-theme';
5
6
  export const usePsChartReveal = (isReady, revealKey) => {
6
7
  const { isReduceMotion: isReduced } = usePsMobileTheme();
7
8
  const progress = useSharedValue(isReduced ? 1 : 0);
9
+ const played = useRef(null);
8
10
  useEffect(() => {
9
11
  if (isReduced) {
10
12
  progress.set(1);
11
13
  return;
12
14
  }
13
- if (!isReady)
15
+ const token = PsChartRevealService.token(revealKey);
16
+ if (!PsChartRevealService.shouldPlay(isReady, played.current, token))
14
17
  return;
18
+ played.current = token;
15
19
  progress.set(0);
16
20
  progress.set(withTiming(1, { duration: PsChartConfig.drawMs }));
17
21
  }, [isReady, isReduced, progress, revealKey]);
package/dist/index.d.ts CHANGED
@@ -133,6 +133,9 @@ export * from './components/ps-date-range-field';
133
133
  export * from './components/ps-statement-table';
134
134
  export * from './components/ps-action-sheet';
135
135
  export * from './components/ps-card';
136
+ export * from './components/ps-link-preview-card';
137
+ export * from './constants/ps-link-preview-size';
138
+ export { PsLinkPreviewService } from './services/ps-link-preview-service';
136
139
  export * from './components/ps-checkbox';
137
140
  export * from './components/ps-chip';
138
141
  export * from './components/ps-icon-button';
package/dist/index.js CHANGED
@@ -128,6 +128,9 @@ export * from './components/ps-date-range-field';
128
128
  export * from './components/ps-statement-table';
129
129
  export * from './components/ps-action-sheet';
130
130
  export * from './components/ps-card';
131
+ export * from './components/ps-link-preview-card';
132
+ export * from './constants/ps-link-preview-size';
133
+ export { PsLinkPreviewService } from './services/ps-link-preview-service';
131
134
  export * from './components/ps-checkbox';
132
135
  export * from './components/ps-chip';
133
136
  export * from './components/ps-icon-button';
@@ -0,0 +1,5 @@
1
+ export interface PsLinkPreviewMeta {
2
+ hasDescription: boolean;
3
+ hasTitle: boolean;
4
+ siteLabel: string;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export declare class PsChartRevealService {
2
+ static token(revealKey: string | undefined): string;
3
+ static shouldPlay(isReady: boolean, played: string | null, token: string): boolean;
4
+ }
@@ -0,0 +1,9 @@
1
+ import { PsChartConfig } from '../constants/ps-chart-config';
2
+ export class PsChartRevealService {
3
+ static token(revealKey) {
4
+ return revealKey === undefined ? PsChartConfig.revealDefaultToken : `key:${revealKey}`;
5
+ }
6
+ static shouldPlay(isReady, played, token) {
7
+ return isReady && played !== token;
8
+ }
9
+ }
@@ -0,0 +1,12 @@
1
+ import { PsLinkPreviewSize } from '../constants/ps-link-preview-size';
2
+ import type { PsDocumentSource } from '../interfaces/ps-document-source';
3
+ import type { PsLinkPreviewMeta } from '../interfaces/ps-link-preview-meta';
4
+ export declare class PsLinkPreviewService {
5
+ static isWebAddress(href: string): boolean;
6
+ static domain(href: string): string;
7
+ static hasText(value: string | null | undefined): boolean;
8
+ static hasSource(source: PsDocumentSource | null | undefined, failedUri: string | null): boolean;
9
+ static isCompact(size: PsLinkPreviewSize): boolean;
10
+ static meta(href: string, title: string | null | undefined, siteName: string | null | undefined, description: string | null | undefined, size: PsLinkPreviewSize): PsLinkPreviewMeta;
11
+ static imageHeight(width: number): number;
12
+ }
@@ -0,0 +1,38 @@
1
+ import { PsLinkPreviewConfig } from '../constants/ps-link-preview-config';
2
+ import { PsLinkPreviewSize } from '../constants/ps-link-preview-size';
3
+ export class PsLinkPreviewService {
4
+ static isWebAddress(href) {
5
+ return PsLinkPreviewConfig.webAddressPattern.test(href);
6
+ }
7
+ static domain(href) {
8
+ const match = PsLinkPreviewConfig.webAddressPattern.exec(href);
9
+ if (match === null)
10
+ return href;
11
+ const authority = match[1];
12
+ const host = authority
13
+ .slice(authority.lastIndexOf('@') + 1)
14
+ .replace(PsLinkPreviewConfig.portSuffixPattern, '');
15
+ return host.replace(PsLinkPreviewConfig.domainPrefixPattern, '');
16
+ }
17
+ static hasText(value) {
18
+ return value !== null && value !== undefined && value.trim() !== '';
19
+ }
20
+ static hasSource(source, failedUri) {
21
+ return source !== null && source !== undefined && source.uri !== '' && source.uri !== failedUri;
22
+ }
23
+ static isCompact(size) {
24
+ return size === PsLinkPreviewSize.Compact;
25
+ }
26
+ static meta(href, title, siteName, description, size) {
27
+ return {
28
+ hasDescription: PsLinkPreviewService.hasText(description) && !PsLinkPreviewService.isCompact(size),
29
+ hasTitle: PsLinkPreviewService.hasText(title),
30
+ siteLabel: siteName !== null && siteName !== undefined && PsLinkPreviewService.hasText(siteName)
31
+ ? siteName
32
+ : PsLinkPreviewService.domain(href),
33
+ };
34
+ }
35
+ static imageHeight(width) {
36
+ return Math.round(width / PsLinkPreviewConfig.imageAspectRatio);
37
+ }
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pallasoft/ps-mobile-components",
3
- "version": "1.14.0",
3
+ "version": "1.15.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",