@hubspot/ui-extensions 0.12.3 → 0.12.4

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 (64) hide show
  1. package/dist/__tests__/crm/utils/fetchAssociations.spec.js +2 -1
  2. package/dist/crm/hooks/useAssociations.d.ts +2 -10
  3. package/dist/crm/hooks/useAssociations.js +2 -1
  4. package/dist/crm/utils/fetchAssociations.d.ts +0 -8
  5. package/dist/crm/utils/fetchAssociations.js +0 -10
  6. package/dist/experimental/pages/components/index.d.ts +1 -0
  7. package/dist/experimental/pages/components/index.js +1 -0
  8. package/dist/experimental/pages/components/page-routes.d.ts +83 -0
  9. package/dist/experimental/pages/components/page-routes.js +66 -0
  10. package/dist/experimental/pages/create-page-router.d.ts +35 -0
  11. package/dist/experimental/pages/create-page-router.js +123 -0
  12. package/dist/experimental/pages/create-page-router.test.d.ts +1 -0
  13. package/dist/experimental/pages/create-page-router.test.js +296 -0
  14. package/dist/experimental/pages/hooks.d.ts +8 -0
  15. package/dist/experimental/pages/hooks.js +15 -0
  16. package/dist/experimental/pages/index.d.ts +5 -3
  17. package/dist/experimental/pages/index.js +4 -3
  18. package/dist/experimental/pages/internal/app-page-route-context.d.ts +16 -0
  19. package/dist/experimental/pages/internal/app-page-route-context.js +12 -0
  20. package/dist/experimental/pages/internal/convert-page-routes-react-elements.d.ts +9 -0
  21. package/dist/experimental/pages/internal/convert-page-routes-react-elements.js +138 -0
  22. package/dist/experimental/pages/internal/page-router-internal-types.d.ts +40 -0
  23. package/dist/experimental/pages/internal/page-router-internal-types.js +5 -0
  24. package/dist/experimental/pages/internal/trie-router.d.ts +31 -0
  25. package/dist/experimental/pages/internal/trie-router.js +141 -0
  26. package/dist/experimental/pages/internal/trie-router.test.d.ts +1 -0
  27. package/dist/experimental/pages/internal/trie-router.test.js +263 -0
  28. package/dist/experimental/pages/internal/useAppPageLocation.d.ts +1 -0
  29. package/dist/experimental/pages/internal/useAppPageLocation.js +13 -0
  30. package/dist/experimental/pages/types.d.ts +29 -0
  31. package/dist/experimental/pages/types.js +1 -0
  32. package/dist/internal/hook-utils.d.ts +7 -6
  33. package/dist/internal/hook-utils.js +8 -9
  34. package/dist/pages/index.d.ts +1 -0
  35. package/dist/pages/index.js +1 -0
  36. package/dist/shared/remoteComponents.d.ts +24 -0
  37. package/dist/shared/remoteComponents.js +28 -0
  38. package/dist/shared/types/components/button.d.ts +2 -2
  39. package/dist/shared/types/components/image.d.ts +2 -2
  40. package/dist/shared/types/components/inputs.d.ts +7 -1
  41. package/dist/shared/types/components/link.d.ts +2 -2
  42. package/dist/shared/types/pages/components/index.d.ts +3 -1
  43. package/dist/shared/types/pages/components/page-breadcrumbs.d.ts +34 -0
  44. package/dist/shared/types/pages/components/page-breadcrumbs.js +1 -0
  45. package/dist/shared/types/pages/components/page-header.d.ts +82 -0
  46. package/dist/shared/types/pages/components/page-header.js +1 -0
  47. package/dist/shared/types/pages/components/page-link.d.ts +4 -2
  48. package/dist/shared/types/pages/components/page-title.d.ts +12 -0
  49. package/dist/shared/types/pages/components/page-title.js +1 -0
  50. package/dist/shared/types/pages/index.d.ts +1 -0
  51. package/dist/shared/types/pages/index.js +1 -0
  52. package/dist/shared/types/pages.d.ts +1 -0
  53. package/dist/shared/types/pages.js +1 -0
  54. package/dist/shared/types/worker-globals.d.ts +3 -12
  55. package/dist/testing/internal/mocks/index.d.ts +14 -6
  56. package/dist/testing/internal/mocks/index.js +19 -5
  57. package/dist/testing/internal/mocks/mock-app-page-location.d.ts +7 -0
  58. package/dist/testing/internal/mocks/mock-app-page-location.js +35 -0
  59. package/dist/testing/internal/mocks/mock-hooks.js +12 -7
  60. package/dist/testing/render.js +7 -6
  61. package/dist/testing/types.d.ts +19 -3
  62. package/dist/utils/pagination.d.ts +17 -0
  63. package/dist/utils/pagination.js +10 -0
  64. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { vi } from 'vitest';
2
- import { calculatePaginationFlags, fetchAssociations, } from "../../../crm/utils/fetchAssociations.js";
2
+ import { fetchAssociations } from "../../../crm/utils/fetchAssociations.js";
3
+ import { calculatePaginationFlags } from "../../../utils/pagination.js";
3
4
  // Set up the mock before importing the module
4
5
  const mockFetchAssociations = vi.fn();
5
6
  const mockSelf = {
@@ -1,24 +1,16 @@
1
+ import { type PaginationResult } from '../../utils/pagination.ts';
1
2
  import type { AssociationResult, FetchAssociationsRequest } from '../utils/fetchAssociations.ts';
2
3
  import { type FetchCrmPropertiesOptions } from '../utils/fetchCrmProperties.ts';
3
4
  export interface UseAssociationsOptions {
4
5
  propertiesToFormat?: 'all' | string[];
5
6
  formattingOptions?: FetchCrmPropertiesOptions['formattingOptions'];
6
7
  }
7
- export interface UseAssociationsPagination {
8
- hasNextPage: boolean;
9
- hasPreviousPage: boolean;
10
- currentPage: number;
11
- pageSize: number;
12
- nextPage: () => void;
13
- previousPage: () => void;
14
- reset: () => void;
15
- }
16
8
  export interface UseAssociationsResult {
17
9
  results: AssociationResult[];
18
10
  error: Error | null;
19
11
  isLoading: boolean;
20
12
  isRefetching: boolean;
21
- pagination: UseAssociationsPagination;
13
+ pagination: PaginationResult;
22
14
  refetch: () => Promise<void>;
23
15
  }
24
16
  /**
@@ -1,6 +1,7 @@
1
1
  import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react';
2
2
  import { createMockAwareHook } from "../../internal/hook-utils.js";
3
- import { calculatePaginationFlags, DEFAULT_PAGE_SIZE, fetchAssociations, } from "../utils/fetchAssociations.js";
3
+ import { calculatePaginationFlags, DEFAULT_PAGE_SIZE, } from "../../utils/pagination.js";
4
+ import { fetchAssociations } from "../utils/fetchAssociations.js";
4
5
  function createInitialState(pageSize) {
5
6
  return {
6
7
  results: [],
@@ -1,12 +1,4 @@
1
1
  import type { FetchCrmPropertiesOptions } from './fetchCrmProperties.ts';
2
- export declare const DEFAULT_PAGE_SIZE = 10;
3
- /**
4
- * Calculate pagination flags based on current page and API hasMore flag
5
- */
6
- export declare function calculatePaginationFlags(currentPage: number, hasMore: boolean): {
7
- hasNextPage: boolean;
8
- hasPreviousPage: boolean;
9
- };
10
2
  export type AssociationResult = {
11
3
  toObjectId: number;
12
4
  associationTypes: {
@@ -1,13 +1,3 @@
1
- export const DEFAULT_PAGE_SIZE = 10;
2
- /**
3
- * Calculate pagination flags based on current page and API hasMore flag
4
- */
5
- export function calculatePaginationFlags(currentPage, hasMore) {
6
- return {
7
- hasNextPage: hasMore,
8
- hasPreviousPage: currentPage > 1,
9
- };
10
- }
11
1
  function isAssociationsResponse(data) {
12
2
  if (data === null ||
13
3
  typeof data !== 'object' ||
@@ -0,0 +1 @@
1
+ export * from './page-routes.ts';
@@ -0,0 +1 @@
1
+ export * from "./page-routes.js";
@@ -0,0 +1,83 @@
1
+ import type { ComponentType, ReactNode } from 'react';
2
+ import type { EmptyProps } from '../../../shared/types/shared.ts';
3
+ /**
4
+ * The props type for the layout component of [PageRoutes](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-routes#pageroutes).
5
+ */
6
+ export interface PageRoutesLayoutProps {
7
+ /**
8
+ * The content to render inside the layout.
9
+ */
10
+ children: ReactNode;
11
+ }
12
+ interface BaseRouteProps {
13
+ /**
14
+ * The component to render when the route is matched.
15
+ */
16
+ component: ComponentType<EmptyProps>;
17
+ /**
18
+ * A unique identifier for the route.
19
+ */
20
+ id?: string;
21
+ }
22
+ /**
23
+ * The props type for [PageRoutes.IndexRoute](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-routes#pageroutes-indexroute).
24
+ */
25
+ export type IndexRouteProps = BaseRouteProps;
26
+ /**
27
+ * The props type for [PageRoutes.Route](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-routes#pageroutes-route).
28
+ */
29
+ export interface RouteProps extends BaseRouteProps {
30
+ /**
31
+ * The path pattern of the route to match.
32
+ */
33
+ path: string;
34
+ }
35
+ /**
36
+ * The props type for [PageRoutes.AnyRoute](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-routes#pageroutes-anyroute).
37
+ */
38
+ export type AnyRouteProps = BaseRouteProps;
39
+ /**
40
+ * The component type for the layout component of [PageRoutes](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-routes#pageroutes).
41
+ */
42
+ export type PageRoutesLayoutComponent = ComponentType<PageRoutesLayoutProps>;
43
+ /**
44
+ * The props type for [PageRoutes](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-routes#pageroutes).
45
+ */
46
+ export interface PageRoutesProps {
47
+ /**
48
+ * The path pattern of the route to include with all nested routes.
49
+ */
50
+ path?: string;
51
+ /**
52
+ * The component to render for the layout of the page routes.
53
+ */
54
+ layoutComponent?: PageRoutesLayoutComponent;
55
+ /**
56
+ * The nested route definitions.
57
+ */
58
+ children: ReactNode;
59
+ }
60
+ /**
61
+ * Used as a descriptor for a collection of page routes.
62
+ *
63
+ * Example usage:
64
+ *
65
+ * ```tsx
66
+ * const PageRouter = createPageRouter(<PageRoutes>
67
+ * <PageRoutes.IndexRoute component={HomePage} />
68
+ * <PageRoutes.Route path="/docs" component={DocsPage} />
69
+ * <PageRoutes.AnyRoute component={NotFoundPage} />
70
+ * </PageRoutes>);
71
+ * ```
72
+ *
73
+ * See [PageRoutes](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-routes#pageroutes) for more information.
74
+ *
75
+ * @experimental This component is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
76
+ */
77
+ export declare function PageRoutes(__props: PageRoutesProps): null;
78
+ export declare namespace PageRoutes {
79
+ var IndexRoute: (__props: IndexRouteProps) => null;
80
+ var Route: (__props: RouteProps) => null;
81
+ var AnyRoute: (__props: AnyRouteProps) => null;
82
+ }
83
+ export {};
@@ -0,0 +1,66 @@
1
+ class PageRoutesRenderError extends Error {
2
+ constructor(componentName) {
3
+ super(`<${componentName}> should not be rendered directly. Use createPageRouter instead.`);
4
+ }
5
+ }
6
+ /**
7
+ * Used as a descriptor for a collection of page routes.
8
+ *
9
+ * Example usage:
10
+ *
11
+ * ```tsx
12
+ * const PageRouter = createPageRouter(<PageRoutes>
13
+ * <PageRoutes.IndexRoute component={HomePage} />
14
+ * <PageRoutes.Route path="/docs" component={DocsPage} />
15
+ * <PageRoutes.AnyRoute component={NotFoundPage} />
16
+ * </PageRoutes>);
17
+ * ```
18
+ *
19
+ * See [PageRoutes](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-routes#pageroutes) for more information.
20
+ *
21
+ * @experimental This component is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
22
+ */
23
+ export function PageRoutes(__props) {
24
+ throw new PageRoutesRenderError('PageRoutes');
25
+ }
26
+ /**
27
+ * Used as a descriptor for an index (i.e., "/") route.
28
+ *
29
+ * Example usage:
30
+ *
31
+ * ```tsx
32
+ * <PageRoutes.IndexRoute component={HomePage} />
33
+ * ```
34
+ *
35
+ * @experimental This component is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
36
+ */
37
+ PageRoutes.IndexRoute = function IndexRoute(__props) {
38
+ throw new PageRoutesRenderError('PageRoutes.IndexRoute');
39
+ };
40
+ /**
41
+ * Used as a descriptor for a route with a path pattern.
42
+ *
43
+ * Example usage:
44
+ *
45
+ * ```tsx
46
+ * <PageRoutes.Route path="/docs" component={DocsPage} />
47
+ * ```
48
+ * @experimental This component is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
49
+ */
50
+ PageRoutes.Route = function Route(__props) {
51
+ throw new PageRoutesRenderError('PageRoutes.Route');
52
+ };
53
+ /**
54
+ * Used as a descriptor for a route that matches any path.
55
+ *
56
+ * Example usage:
57
+ *
58
+ * ```tsx
59
+ * <PageRoutes.AnyRoute component={NotFoundPage} />
60
+ * ```
61
+ *
62
+ * @experimental This component is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
63
+ */
64
+ PageRoutes.AnyRoute = function AnyRoute(__props) {
65
+ throw new PageRoutesRenderError('PageRoutes.AnyRoute');
66
+ };
@@ -0,0 +1,35 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { ReactFragmentProps } from '../../shared/types/index.ts';
3
+ import type { PageRoutesProps } from './components/page-routes.ts';
4
+ import type { PageRouterComponent } from './types.ts';
5
+ /**
6
+ * The function type for [createPageRouter](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/app-pages/reference#createpagerouter).
7
+ *
8
+ * Example usage:
9
+ *
10
+ * ```tsx
11
+ * const PageRouter = createPageRouter(
12
+ * <PageRoutes layoutComponent={AppLayout}>
13
+ * <PageRoutes.IndexRoute component={HomePage} id="home" />
14
+ * <PageRoutes.Route path="/docs" component={DocsPage} />
15
+ * <PageRoutes path="/customers" layoutComponent={CustomersLayout}>
16
+ * <PageRoutes.IndexRoute component={ListCustomersPage} />
17
+ * <PageRoutes.Route path="/:customerId" component={ViewCustomerPage} />
18
+ * </PageRoutes>
19
+ * <PageRoutes.AnyRoute component={NotFoundPage} />
20
+ * </PageRoutes>
21
+ * );
22
+ *
23
+ * function AppPages() {
24
+ * return <PageRouter />;
25
+ * }
26
+ *
27
+ * hubspot.extend<"pages">(() => <AppPages />);
28
+ * ```
29
+ *
30
+ * @param routes The routes to render.
31
+ * @returns The page router component.
32
+ *
33
+ * @experimental This function is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
34
+ */
35
+ export declare const createPageRouter: (reactPageRoutesElement: ReactElement<PageRoutesProps> | ReactElement<ReactFragmentProps>) => PageRouterComponent;
@@ -0,0 +1,123 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import { EmptyState, Text } from "../../shared/remoteComponents.js";
4
+ import { AppPageRouteProvider } from "./internal/app-page-route-context.js";
5
+ import { convertReactPageRoutesElement } from "./internal/convert-page-routes-react-elements.js";
6
+ import { RouteNodeType, } from "./internal/page-router-internal-types.js";
7
+ import { createTrieRouter } from "./internal/trie-router.js";
8
+ import { useAppPageLocation } from "./internal/useAppPageLocation.js";
9
+ /**
10
+ * Adds the routes to the trie router.
11
+ *
12
+ * @param trieRouter - The trie router to add the routes to.
13
+ * @param seenRouteIds - The set of route ids that have already been seen.
14
+ * @param routeNode - The node to add the routes to.
15
+ * @param parentPath - The path to the parent node.
16
+ * @param parentLayouts - The layouts to apply to the node.
17
+ */
18
+ const addRoutes = (trieRouter, seenRouteIds, routeNode, parentPath, parentLayouts) => {
19
+ if (routeNode.type === RouteNodeType.Routes) {
20
+ const prefix = parentPath + (routeNode.path ?? '');
21
+ const layouts = routeNode.layoutComponent
22
+ ? [...parentLayouts, routeNode.layoutComponent]
23
+ : parentLayouts;
24
+ if (routeNode.children) {
25
+ for (const childRouteNode of routeNode.children) {
26
+ addRoutes(trieRouter, seenRouteIds, childRouteNode /* routeNode */, prefix /* parentPath */, layouts /* parentLayouts */);
27
+ }
28
+ }
29
+ }
30
+ else {
31
+ const fullPath = parentPath + routeNode.path;
32
+ if (routeNode.id !== undefined) {
33
+ if (seenRouteIds.has(routeNode.id)) {
34
+ throw new Error(`Duplicate route id: "${routeNode.id}"`);
35
+ }
36
+ seenRouteIds.add(routeNode.id);
37
+ }
38
+ trieRouter.addRoute(fullPath, {
39
+ component: routeNode.component,
40
+ layouts: parentLayouts,
41
+ id: routeNode.id,
42
+ });
43
+ }
44
+ };
45
+ /**
46
+ * The function type for [createPageRouter](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/app-pages/reference#createpagerouter).
47
+ *
48
+ * Example usage:
49
+ *
50
+ * ```tsx
51
+ * const PageRouter = createPageRouter(
52
+ * <PageRoutes layoutComponent={AppLayout}>
53
+ * <PageRoutes.IndexRoute component={HomePage} id="home" />
54
+ * <PageRoutes.Route path="/docs" component={DocsPage} />
55
+ * <PageRoutes path="/customers" layoutComponent={CustomersLayout}>
56
+ * <PageRoutes.IndexRoute component={ListCustomersPage} />
57
+ * <PageRoutes.Route path="/:customerId" component={ViewCustomerPage} />
58
+ * </PageRoutes>
59
+ * <PageRoutes.AnyRoute component={NotFoundPage} />
60
+ * </PageRoutes>
61
+ * );
62
+ *
63
+ * function AppPages() {
64
+ * return <PageRouter />;
65
+ * }
66
+ *
67
+ * hubspot.extend<"pages">(() => <AppPages />);
68
+ * ```
69
+ *
70
+ * @param routes The routes to render.
71
+ * @returns The page router component.
72
+ *
73
+ * @experimental This function is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
74
+ */
75
+ export const createPageRouter = (reactPageRoutesElement) => {
76
+ // Convert the React element to a route node and add them to a trie router.
77
+ const rootRouteNode = convertReactPageRoutesElement(reactPageRoutesElement);
78
+ const trieRouter = createTrieRouter();
79
+ const seenRouteIds = new Set();
80
+ addRoutes(trieRouter, seenRouteIds, rootRouteNode /* routeNode */, '' /* parentPath */, [] /* parentLayouts */);
81
+ // Now create the PageRouter component that uses the trie router to match the current app page location.
82
+ // The component will render the content of the matched route.
83
+ // The component will also provide the matched app page route to the content of the matched route.
84
+ const PageRouter = () => {
85
+ const appPageLocation = useAppPageLocation();
86
+ const { path: appPagePath, params: appPageParams } = appPageLocation;
87
+ const matchedRoute = useMemo(() => {
88
+ return trieRouter.matchPath(appPagePath);
89
+ }, [appPagePath]);
90
+ const matchedAppPageRoute = useMemo(() => {
91
+ return matchedRoute
92
+ ? {
93
+ routeId: matchedRoute.data.id,
94
+ path: appPagePath,
95
+ params: {
96
+ ...matchedRoute.params,
97
+ ...appPageParams,
98
+ },
99
+ }
100
+ : {
101
+ path: appPagePath,
102
+ params: appPageParams,
103
+ };
104
+ }, [matchedRoute, appPagePath, appPageParams]);
105
+ const routeData = matchedRoute?.data;
106
+ const content = useMemo(() => {
107
+ if (!routeData) {
108
+ // TODO: Localize the title and body text.
109
+ return (_jsx(EmptyState, { title: "Page not found", layout: "vertical", reverseOrder: true, children: _jsx(Text, { children: "This app page does not exist." }) }));
110
+ }
111
+ const Component = routeData.component;
112
+ let node = _jsx(Component, {});
113
+ // Wrap the route component with any ancestor layout components found in the route tree.
114
+ for (let i = routeData.layouts.length - 1; i >= 0; i--) {
115
+ const Layout = routeData.layouts[i];
116
+ node = _jsx(Layout, { children: node });
117
+ }
118
+ return node;
119
+ }, [routeData]);
120
+ return (_jsx(AppPageRouteProvider, { pageRoute: matchedAppPageRoute, children: content }));
121
+ };
122
+ return PageRouter;
123
+ };