@hubspot/ui-extensions 0.12.3 → 0.13.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 (84) hide show
  1. package/dist/__tests__/crm/hooks/useAssociations.spec.js +28 -0
  2. package/dist/__tests__/crm/utils/fetchAssociations.spec.js +2 -1
  3. package/dist/__tests__/hooks/useDebounce.spec.js +123 -0
  4. package/dist/__tests__/hooks/utils/useFetchLifecycle.spec.js +324 -0
  5. package/dist/__tests__/internal/hook-utils.spec.d.ts +1 -0
  6. package/dist/__tests__/internal/hook-utils.spec.js +17 -0
  7. package/dist/__tests__/test-d/extension-points.test-d.js +1 -0
  8. package/dist/crm/hooks/useAssociations.d.ts +4 -12
  9. package/dist/crm/hooks/useAssociations.js +46 -138
  10. package/dist/crm/hooks/useCrmProperties.js +29 -125
  11. package/dist/crm/utils/fetchAssociations.d.ts +0 -8
  12. package/dist/crm/utils/fetchAssociations.js +0 -10
  13. package/dist/hooks/useDebounce.d.ts +19 -0
  14. package/dist/hooks/useDebounce.js +32 -0
  15. package/dist/hooks/utils/useFetchLifecycle.d.ts +35 -0
  16. package/dist/hooks/utils/useFetchLifecycle.js +103 -0
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/internal/hook-utils.d.ts +13 -6
  20. package/dist/internal/hook-utils.js +24 -10
  21. package/dist/pages/components/index.d.ts +1 -0
  22. package/dist/pages/components/index.js +1 -0
  23. package/dist/{shared/types/pages → pages}/components/page-routes.d.ts +6 -39
  24. package/dist/pages/components/page-routes.js +62 -0
  25. package/dist/pages/create-page-router.d.ts +33 -0
  26. package/dist/pages/create-page-router.js +121 -0
  27. package/dist/pages/create-page-router.test.d.ts +1 -0
  28. package/dist/pages/create-page-router.test.js +296 -0
  29. package/dist/pages/hooks.d.ts +7 -0
  30. package/dist/pages/hooks.js +14 -0
  31. package/dist/pages/index.d.ts +6 -0
  32. package/dist/pages/index.js +4 -0
  33. package/dist/pages/internal/app-page-route-context.d.ts +16 -0
  34. package/dist/pages/internal/app-page-route-context.js +12 -0
  35. package/dist/pages/internal/convert-page-routes-react-elements.d.ts +9 -0
  36. package/dist/pages/internal/convert-page-routes-react-elements.js +138 -0
  37. package/dist/pages/internal/page-router-internal-types.d.ts +40 -0
  38. package/dist/pages/internal/page-router-internal-types.js +5 -0
  39. package/dist/pages/internal/trie-router.d.ts +31 -0
  40. package/dist/pages/internal/trie-router.js +141 -0
  41. package/dist/pages/internal/trie-router.test.d.ts +1 -0
  42. package/dist/pages/internal/trie-router.test.js +263 -0
  43. package/dist/pages/internal/useAppPageLocation.d.ts +1 -0
  44. package/dist/pages/internal/useAppPageLocation.js +13 -0
  45. package/dist/pages/types.d.ts +28 -0
  46. package/dist/pages/types.js +1 -0
  47. package/dist/shared/remoteComponents.d.ts +24 -0
  48. package/dist/shared/remoteComponents.js +28 -0
  49. package/dist/shared/types/actions.d.ts +12 -2
  50. package/dist/shared/types/components/button.d.ts +2 -2
  51. package/dist/shared/types/components/image.d.ts +2 -2
  52. package/dist/shared/types/components/inputs.d.ts +7 -1
  53. package/dist/shared/types/components/link.d.ts +2 -2
  54. package/dist/shared/types/context.d.ts +5 -0
  55. package/dist/shared/types/crm.d.ts +4 -0
  56. package/dist/shared/types/extension-points.d.ts +9 -3
  57. package/dist/shared/types/extension-points.js +1 -0
  58. package/dist/shared/types/pages/components/index.d.ts +3 -1
  59. package/dist/shared/types/pages/components/page-breadcrumbs.d.ts +34 -0
  60. package/dist/shared/types/pages/components/page-breadcrumbs.js +1 -0
  61. package/dist/shared/types/pages/components/page-header.d.ts +82 -0
  62. package/dist/shared/types/pages/components/page-header.js +1 -0
  63. package/dist/shared/types/pages/components/page-link.d.ts +4 -2
  64. package/dist/shared/types/pages/components/page-title.d.ts +12 -0
  65. package/dist/shared/types/pages/components/page-title.js +1 -0
  66. package/dist/shared/types/shared.d.ts +8 -0
  67. package/dist/shared/types/worker-globals.d.ts +3 -12
  68. package/dist/testing/__tests__/createRenderer.spec.js +1 -1
  69. package/dist/testing/internal/mocks/index.d.ts +14 -6
  70. package/dist/testing/internal/mocks/index.js +19 -5
  71. package/dist/testing/internal/mocks/mock-app-page-location.d.ts +7 -0
  72. package/dist/testing/internal/mocks/mock-app-page-location.js +35 -0
  73. package/dist/testing/internal/mocks/mock-extension-point-api.js +21 -1
  74. package/dist/testing/internal/mocks/mock-hooks.js +12 -7
  75. package/dist/testing/render.js +7 -6
  76. package/dist/testing/types.d.ts +19 -3
  77. package/dist/utils/pagination.d.ts +17 -0
  78. package/dist/utils/pagination.js +10 -0
  79. package/package.json +2 -2
  80. package/dist/experimental/pages/index.d.ts +0 -4
  81. package/dist/experimental/pages/index.js +0 -3
  82. package/dist/shared/types/pages/app-pages-types.d.ts +0 -75
  83. /package/dist/{shared/types/pages/app-pages-types.js → __tests__/hooks/useDebounce.spec.d.ts} +0 -0
  84. /package/dist/{shared/types/pages/components/page-routes.js → __tests__/hooks/utils/useFetchLifecycle.spec.d.ts} +0 -0
@@ -0,0 +1,263 @@
1
+ import { createTrieRouter } from "./trie-router.js";
2
+ describe('addRoute — conflict detection', () => {
3
+ it('throws on duplicate exact route', () => {
4
+ const router = createTrieRouter();
5
+ router.addRoute('/foo', 'first');
6
+ expect(() => router.addRoute('/foo', 'second')).toThrowError('Route conflict: "/foo" conflicts with existing route "/foo"');
7
+ });
8
+ it('throws on duplicate root route', () => {
9
+ const router = createTrieRouter();
10
+ router.addRoute('/', 'first');
11
+ expect(() => router.addRoute('/', 'second')).toThrowError('Route conflict: "/" conflicts with existing route "/"');
12
+ });
13
+ it('throws when two param-only routes conflict', () => {
14
+ const router = createTrieRouter();
15
+ router.addRoute('/:foo', 'first');
16
+ expect(() => router.addRoute('/:bar', 'second')).toThrowError('Route conflict: "/:bar" conflicts with existing route "/:foo"');
17
+ });
18
+ it('throws when param routes with matching suffix conflict', () => {
19
+ const router = createTrieRouter();
20
+ router.addRoute('/:foo/suffix', 'first');
21
+ expect(() => router.addRoute('/:bar/suffix', 'second')).toThrowError('Route conflict: "/:bar/suffix" conflicts with existing route "/:foo/suffix"');
22
+ });
23
+ it('allows param routes with different sub-paths', () => {
24
+ const router = createTrieRouter();
25
+ router.addRoute('/:foo', 'terminal');
26
+ router.addRoute('/:bar/baz', 'with-baz');
27
+ expect(router.matchPath('/hello')).toEqual({
28
+ data: 'terminal',
29
+ params: { foo: 'hello' },
30
+ });
31
+ expect(router.matchPath('/hello/baz')).toEqual({
32
+ data: 'with-baz',
33
+ params: { bar: 'hello' },
34
+ });
35
+ });
36
+ it('throws when wildcard is not the last segment', () => {
37
+ const router = createTrieRouter();
38
+ expect(() => router.addRoute('/foo/*/bar', 'data')).toThrowError('Wildcard must be the last segment in route: /foo/*/bar');
39
+ });
40
+ it('throws when a param segment has an empty name', () => {
41
+ const router = createTrieRouter();
42
+ expect(() => router.addRoute('/:', 'data')).toThrowError('Invalid route "/:": param segment at position 0 has an empty name');
43
+ });
44
+ it('throws for mid-path wildcard without corrupting the trie', () => {
45
+ const router = createTrieRouter();
46
+ expect(() => router.addRoute('/foo/*/bar', 'data')).toThrowError('Wildcard must be the last segment in route: /foo/*/bar');
47
+ router.addRoute('/foo/bar', 'data');
48
+ expect(router.matchPath('/foo/bar')).toEqual({
49
+ data: 'data',
50
+ params: {},
51
+ });
52
+ });
53
+ it('throws on duplicate wildcard route', () => {
54
+ const router = createTrieRouter();
55
+ router.addRoute('/foo/*', 'first');
56
+ expect(() => router.addRoute('/foo/*', 'second')).toThrowError('Route conflict: "/foo/*" conflicts with existing route "/foo/*"');
57
+ });
58
+ it("throws when empty string and '/' are both registered as root routes", () => {
59
+ const router = createTrieRouter();
60
+ router.addRoute('', 'first');
61
+ expect(() => router.addRoute('/', 'second')).toThrowError('Route conflict: "/" conflicts with existing route "/"');
62
+ });
63
+ it('allows different sub-paths under the same param name', () => {
64
+ const router = createTrieRouter();
65
+ router.addRoute('/:id/foo', 'first');
66
+ router.addRoute('/:id/bar', 'second');
67
+ expect(router.matchPath('/123/foo')).toEqual({
68
+ data: 'first',
69
+ params: { id: '123' },
70
+ });
71
+ expect(router.matchPath('/123/bar')).toEqual({
72
+ data: 'second',
73
+ params: { id: '123' },
74
+ });
75
+ });
76
+ });
77
+ describe('matchPath — static routes', () => {
78
+ it('matches root path', () => {
79
+ const router = createTrieRouter();
80
+ router.addRoute('/', 'root');
81
+ expect(router.matchPath('/')).toEqual({
82
+ data: 'root',
83
+ params: {},
84
+ });
85
+ });
86
+ it('matches single segment', () => {
87
+ const router = createTrieRouter();
88
+ router.addRoute('/foo', 'foo-page');
89
+ expect(router.matchPath('/foo')).toEqual({
90
+ data: 'foo-page',
91
+ params: {},
92
+ });
93
+ });
94
+ it('matches multi-segment path', () => {
95
+ const router = createTrieRouter();
96
+ router.addRoute('/foo/bar', 'foo-bar');
97
+ expect(router.matchPath('/foo/bar')).toEqual({
98
+ data: 'foo-bar',
99
+ params: {},
100
+ });
101
+ });
102
+ it('returns null for unregistered path', () => {
103
+ const router = createTrieRouter();
104
+ router.addRoute('/foo', 'foo-page');
105
+ expect(router.matchPath('/bar')).toBeNull();
106
+ });
107
+ it('returns null when path is a prefix of a registered route', () => {
108
+ const router = createTrieRouter();
109
+ router.addRoute('/foo/bar', 'foo-bar');
110
+ expect(router.matchPath('/foo')).toBeNull();
111
+ });
112
+ it('returns null when path extends beyond a registered route', () => {
113
+ const router = createTrieRouter();
114
+ router.addRoute('/foo', 'foo-page');
115
+ expect(router.matchPath('/foo/bar')).toBeNull();
116
+ });
117
+ });
118
+ describe('matchPath — param routes', () => {
119
+ it('matches param route and extracts param', () => {
120
+ const router = createTrieRouter();
121
+ router.addRoute('/:someParam/foo/bar', 'param-route');
122
+ expect(router.matchPath('/hello/foo/bar')).toEqual({
123
+ data: 'param-route',
124
+ params: { someParam: 'hello' },
125
+ });
126
+ });
127
+ it('matches route with multiple params', () => {
128
+ const router = createTrieRouter();
129
+ router.addRoute('/:a/:b', 'multi-param');
130
+ expect(router.matchPath('/x/y')).toEqual({
131
+ data: 'multi-param',
132
+ params: { a: 'x', b: 'y' },
133
+ });
134
+ });
135
+ it('decodes URI-encoded param values', () => {
136
+ const router = createTrieRouter();
137
+ router.addRoute('/:query', 'search');
138
+ expect(router.matchPath('/hello%20world')).toEqual({
139
+ data: 'search',
140
+ params: { query: 'hello world' },
141
+ });
142
+ });
143
+ it('matches different param names with different sub-paths', () => {
144
+ const router = createTrieRouter();
145
+ router.addRoute('/:param1/foo', 'foo-route');
146
+ router.addRoute('/:param2/bar', 'bar-route');
147
+ expect(router.matchPath('/x/foo')).toEqual({
148
+ data: 'foo-route',
149
+ params: { param1: 'x' },
150
+ });
151
+ expect(router.matchPath('/x/bar')).toEqual({
152
+ data: 'bar-route',
153
+ params: { param2: 'x' },
154
+ });
155
+ });
156
+ });
157
+ describe('matchPath — wildcard routes', () => {
158
+ it('matches wildcard and captures remaining segments', () => {
159
+ const router = createTrieRouter();
160
+ router.addRoute('/foo/*', 'wildcard');
161
+ expect(router.matchPath('/foo/a/b/c')).toEqual({
162
+ data: 'wildcard',
163
+ params: { '*': 'a/b/c' },
164
+ });
165
+ });
166
+ it('matches root wildcard', () => {
167
+ const router = createTrieRouter();
168
+ router.addRoute('/*', 'catch-all');
169
+ expect(router.matchPath('/anything/here')).toEqual({
170
+ data: 'catch-all',
171
+ params: { '*': 'anything/here' },
172
+ });
173
+ });
174
+ it('matches wildcard with single remaining segment', () => {
175
+ const router = createTrieRouter();
176
+ router.addRoute('/foo/*', 'wildcard');
177
+ expect(router.matchPath('/foo/bar')).toEqual({
178
+ data: 'wildcard',
179
+ params: { '*': 'bar' },
180
+ });
181
+ });
182
+ it('returns null when path matches wildcard prefix but has no remaining segment', () => {
183
+ const router = createTrieRouter();
184
+ router.addRoute('/foo/*', 'wildcard');
185
+ expect(router.matchPath('/foo')).toBeNull();
186
+ });
187
+ });
188
+ describe('matchPath — undefined data', () => {
189
+ it('matches static route with undefined data', () => {
190
+ const router = createTrieRouter();
191
+ router.addRoute('/foo', undefined);
192
+ expect(router.matchPath('/foo')).toEqual({
193
+ data: undefined,
194
+ params: {},
195
+ });
196
+ });
197
+ it('matches param route with undefined data', () => {
198
+ const router = createTrieRouter();
199
+ router.addRoute('/:id', undefined);
200
+ expect(router.matchPath('/123')).toEqual({
201
+ data: undefined,
202
+ params: { id: '123' },
203
+ });
204
+ });
205
+ it('matches wildcard route with undefined data', () => {
206
+ const router = createTrieRouter();
207
+ router.addRoute('/foo/*', undefined);
208
+ expect(router.matchPath('/foo/bar/baz')).toEqual({
209
+ data: undefined,
210
+ params: { '*': 'bar/baz' },
211
+ });
212
+ });
213
+ it('returns null for unregistered path when other routes have undefined data', () => {
214
+ const router = createTrieRouter();
215
+ router.addRoute('/foo', undefined);
216
+ expect(router.matchPath('/bar')).toBeNull();
217
+ });
218
+ });
219
+ describe('matchPath — priority', () => {
220
+ it('static wins over param', () => {
221
+ const router = createTrieRouter();
222
+ router.addRoute('/:param/bar', 'param-route');
223
+ router.addRoute('/foo/bar', 'static-route');
224
+ expect(router.matchPath('/foo/bar')).toEqual({
225
+ data: 'static-route',
226
+ params: {},
227
+ });
228
+ });
229
+ it('static wins over wildcard', () => {
230
+ const router = createTrieRouter();
231
+ router.addRoute('/foo/*', 'wildcard-route');
232
+ router.addRoute('/foo/bar', 'static-route');
233
+ expect(router.matchPath('/foo/bar')).toEqual({
234
+ data: 'static-route',
235
+ params: {},
236
+ });
237
+ });
238
+ it('param wins over wildcard', () => {
239
+ const router = createTrieRouter();
240
+ router.addRoute('/*', 'wildcard-route');
241
+ router.addRoute('/:id', 'param-route');
242
+ expect(router.matchPath('/hello')).toEqual({
243
+ data: 'param-route',
244
+ params: { id: 'hello' },
245
+ });
246
+ });
247
+ it('falls back to wildcard when static does not match deeper', () => {
248
+ const router = createTrieRouter();
249
+ router.addRoute('/foo/bar/baz', 'deep-static');
250
+ router.addRoute('/foo/*', 'wildcard-route');
251
+ expect(router.matchPath('/foo/qux')).toEqual({
252
+ data: 'wildcard-route',
253
+ params: { '*': 'qux' },
254
+ });
255
+ });
256
+ });
257
+ describe('matchPath — invalid URI-encoded param', () => {
258
+ it('does not throw on malformed URI-encoded param', () => {
259
+ const router = createTrieRouter();
260
+ router.addRoute('/:id', 'route');
261
+ expect(() => router.matchPath('/%ZZ')).not.toThrow();
262
+ });
263
+ });
@@ -0,0 +1 @@
1
+ export declare const useAppPageLocation: () => import("../../index.ts").AppPageLocation;
@@ -0,0 +1,13 @@
1
+ import { getWorkerGlobals } from "../../internal/global-utils.js";
2
+ import { useMocksContext } from "../../internal/hook-utils.js";
3
+ export const useAppPageLocation = () => {
4
+ const mocksContext = useMocksContext();
5
+ if (!mocksContext) {
6
+ // If no mocks context is provided, call the original hook implementation provided by
7
+ // the worker globals.
8
+ return getWorkerGlobals().hsWorkerAPI.useAppPageLocation();
9
+ }
10
+ // Otherwise, call the mock implementation provided by the mocks context.
11
+ const { useAppPageLocation } = mocksContext;
12
+ return useAppPageLocation();
13
+ };
@@ -0,0 +1,28 @@
1
+ import type { ComponentType } from 'react';
2
+ import type { EmptyProps } from '../shared/types/shared.ts';
3
+ /**
4
+ * Represents the current page route that has been matched by the page router.
5
+ */
6
+ export interface MatchedPageRoute {
7
+ /**
8
+ * The id of the route that has been matched.
9
+ */
10
+ routeId?: string;
11
+ /**
12
+ * The path of the route that has been matched.
13
+ */
14
+ path: string;
15
+ /**
16
+ * The parameters of the route that has been matched.
17
+ */
18
+ params: Record<string, string>;
19
+ }
20
+ /**
21
+ * The props type for a PageRouter component.
22
+ */
23
+ export type PageRouterProps = EmptyProps;
24
+ /**
25
+ * The component type for the page router that handles rendering matched routes.
26
+ *
27
+ */
28
+ export type PageRouterComponent = ComponentType<PageRouterProps>;
@@ -0,0 +1 @@
1
+ export {};
@@ -626,7 +626,31 @@ export declare const PrimaryHeaderActionButton: import("./types/shared.ts").HubS
626
626
  *
627
627
  */
628
628
  export declare const SecondaryHeaderActionButton: import("./types/shared.ts").HubSpotReactComponent<componentTypes.HeaderActionButtonProps>;
629
+ /**
630
+ * The `PageHeader` component renders the actions within the header of the page. It accepts `PrimaryAction` and `SecondaryActions` as children.
631
+ *
632
+ * **Links:**
633
+ * - {@link https://developers.hubspot.com/docs/reference/ui-components/app-page-components/page-header Docs}
634
+ */
635
+ export declare const PageHeader: import("./types/shared.ts").HubSpotReactComponent<import("./types/shared.ts").UnknownComponentProps> & {
636
+ PrimaryAction: import("./types/shared.ts").HubSpotReactComponent<pageComponentTypes.PageHeaderPrimaryActionProps>;
637
+ SecondaryActions: import("./types/shared.ts").HubSpotReactComponent<pageComponentTypes.PageHeaderSecondaryActionsProps>;
638
+ Link: import("./types/shared.ts").HubSpotReactComponent<pageComponentTypes.PageHeaderLinkProps>;
639
+ PageLink: import("./types/shared.ts").HubSpotReactComponent<pageComponentTypes.PageHeaderPageLinkProps>;
640
+ };
641
+ /**
642
+ * The 'PageBreadcrumbs' component renders a list of links to show the user's current location within the app and allow them to navigate back to previous pages.
643
+ *
644
+ * **Links:**
645
+ *
646
+ * - {@link https://developers.hubspot.com/docs/reference/ui-components/app-page-components/page-breadcrumbs Docs}
647
+ */
648
+ export declare const PageBreadcrumbs: import("./types/shared.ts").HubSpotReactComponent<import("./types/shared.ts").UnknownComponentProps> & {
649
+ PageLink: import("./types/shared.ts").HubSpotReactComponent<pageComponentTypes.PageBreadcrumbsPageLinkProps>;
650
+ Current: import("./types/shared.ts").HubSpotReactComponent<pageComponentTypes.PageBreadcrumbsCurrentProps>;
651
+ };
629
652
  export declare const PageLink: import("./types/shared.ts").HubSpotReactComponent<pageComponentTypes.PageLinkProps>;
653
+ export declare const PageTitle: import("./types/shared.ts").HubSpotReactComponent<pageComponentTypes.PageTitleProps>;
630
654
  /**
631
655
  * @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.
632
656
  */
@@ -648,7 +648,35 @@ export const SecondaryHeaderActionButton = createAndRegisterRemoteReactComponent
648
648
  ////////////////////////////////////////////////////////
649
649
  // APP PAGE COMPONENTS
650
650
  ////////////////////////////////////////////////////////
651
+ /**
652
+ * The `PageHeader` component renders the actions within the header of the page. It accepts `PrimaryAction` and `SecondaryActions` as children.
653
+ *
654
+ * **Links:**
655
+ * - {@link https://developers.hubspot.com/docs/reference/ui-components/app-page-components/page-header Docs}
656
+ */
657
+ export const PageHeader = createAndRegisterRemoteCompoundReactComponent('PageHeader', {
658
+ compoundComponentProperties: {
659
+ PrimaryAction: createAndRegisterRemoteReactComponent('PageHeaderPrimaryAction'),
660
+ SecondaryActions: createAndRegisterRemoteReactComponent('PageHeaderSecondaryActions'),
661
+ Link: createAndRegisterRemoteReactComponent('PageHeaderLink'),
662
+ PageLink: createAndRegisterRemoteReactComponent('PageHeaderPageLink'),
663
+ },
664
+ });
665
+ /**
666
+ * The 'PageBreadcrumbs' component renders a list of links to show the user's current location within the app and allow them to navigate back to previous pages.
667
+ *
668
+ * **Links:**
669
+ *
670
+ * - {@link https://developers.hubspot.com/docs/reference/ui-components/app-page-components/page-breadcrumbs Docs}
671
+ */
672
+ export const PageBreadcrumbs = createAndRegisterRemoteCompoundReactComponent('PageBreadcrumbs', {
673
+ compoundComponentProperties: {
674
+ PageLink: createAndRegisterRemoteReactComponent('PageBreadcrumbsPageLink'),
675
+ Current: createAndRegisterRemoteReactComponent('PageBreadcrumbsCurrent'),
676
+ },
677
+ });
651
678
  export const PageLink = createAndRegisterRemoteReactComponent('PageLink');
679
+ export const PageTitle = createAndRegisterRemoteReactComponent('PageTitle');
652
680
  ////////////////////////////////////////////////////////
653
681
  // EXPERIMENTAL COMPONENTS
654
682
  ////////////////////////////////////////////////////////
@@ -31,6 +31,11 @@ export type onCrmPropertiesUpdateAction = (properties: string[] | '*', callback:
31
31
  /** @ignore */
32
32
  export type CloseOverlayAction = (id: string) => void;
33
33
  /** @ignore */
34
+ export type NavigateToPageAction = (options: {
35
+ to: string;
36
+ params?: Record<string, string>;
37
+ }) => void;
38
+ /** @ignore */
34
39
  export interface CrmHostActions {
35
40
  addAlert: AddAlertAction;
36
41
  reloadPage: ReloadPageAction;
@@ -48,10 +53,15 @@ export interface AppHomeActions {
48
53
  addAlert: AddAlertAction;
49
54
  }
50
55
  /** @ignore */
56
+ export interface PagesActions {
57
+ addAlert: AddAlertAction;
58
+ }
59
+ /** @ignore */
51
60
  export interface UiePlatformActions {
52
- copyTextToClipboard: Clipboard['writeText'];
53
61
  closeOverlay: CloseOverlayAction;
54
- reloadPage: ReloadPageAction;
62
+ copyTextToClipboard: Clipboard['writeText'];
63
+ navigateToPage: NavigateToPageAction;
55
64
  openIframeModal: OpenIframeModalAction;
65
+ reloadPage: ReloadPageAction;
56
66
  }
57
67
  export {};
@@ -10,9 +10,9 @@ export interface BaseButtonProps extends BaseComponentProps {
10
10
  */
11
11
  onClick?: ReactionsHandler<ExtensionEvent>;
12
12
  /**
13
- * Include this prop to open a URL on click. Contains the following fields:
13
+ * Include this prop to open a URL on click. Can be set to a URL string or an object with the following fields:
14
14
  * - `url` (string): the URL that will open on click.
15
- * - `external` (boolean): set to `true` to open the URL in a new tab and display an external link icon. By default:
15
+ * - `external` (boolean, optional): set to `true` to open the URL in a new tab and display an external link icon. By default:
16
16
  * - Links to HubSpot app pages will open in the same tab and will not include an icon.
17
17
  * - Links to non-HubSpot app pages will open in a new tab and include the icon.
18
18
  *
@@ -12,9 +12,9 @@ export interface ImageProps extends OverlayComponentProps, BaseComponentProps {
12
12
  */
13
13
  alt?: string;
14
14
  /**
15
- * When provided, sets the URL that will open when the image is clicked. Contains the following fields:
15
+ * When provided, sets the URL that will open when the image is clicked. Can be set to a URL string or an object with the following fields:
16
16
  * - `url` (string): the URL that will open on click.
17
- * - `external` (boolean): set to `true` to open the URL in a new tab. By default:
17
+ * - `external` (boolean, optional): set to `true` to open the URL in a new tab. By default:
18
18
  * - Links to HubSpot app pages will open in the same tab.
19
19
  * - Links to non-HubSpot app pages will open in a new tab.
20
20
  *
@@ -155,6 +155,12 @@ export interface NumberInputProps extends BaseInputForNumber {
155
155
  formatStyle?: 'decimal' | 'percentage';
156
156
  /** @deprecated use onChange instead. It doesn't guarantee valid format */
157
157
  onInput?: (value: number) => void;
158
+ /**
159
+ * Sets the horizontal alignment of the text in the input.
160
+ *
161
+ * @defaultValue `"left"`
162
+ */
163
+ textAlign?: 'left' | 'right';
158
164
  }
159
165
  /**
160
166
  * The props type for [CurrencyInput](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/currency-input).
@@ -185,7 +191,7 @@ export interface CurrencyInputProps extends BaseInputForNumber {
185
191
  *
186
192
  * @category Component Props
187
193
  */
188
- export interface StepperInputProps extends Omit<NumberInputProps, 'onInput'> {
194
+ export interface StepperInputProps extends Omit<NumberInputProps, 'onInput' | 'textAlign'> {
189
195
  /** The amount that the current value will increase or decrease by. Default is `1`.
190
196
  * @defaultValue `1`
191
197
  */
@@ -13,9 +13,9 @@ export interface LinkProps extends OverlayComponentProps, BaseComponentProps {
13
13
  */
14
14
  children: ReactNode;
15
15
  /**
16
- * Sets the link's URL and open behavior. Contains the following fields:
16
+ * Sets the link's URL and open behavior. Can be set to a URL string or an object with the following fields:
17
17
  * - `url` (string): the URL that will open on click.
18
- * - `external` (boolean): set to `true` to open the URL in a new tab and display an external link icon. By default:
18
+ * - `external` (boolean, optional): set to `true` to open the URL in a new tab and display an external link icon. By default:
19
19
  * - Links to HubSpot app pages will open in the same tab and will not include an icon.
20
20
  * - Links to non-HubSpot app pages will open in a new tab and include the icon.
21
21
  */
@@ -60,6 +60,11 @@ export interface AppHomeContext extends BaseContext {
60
60
  extension?: AppContext;
61
61
  }
62
62
  /** @ignore */
63
+ export interface PagesContext extends BaseContext {
64
+ location: 'pages';
65
+ extension?: AppContext;
66
+ }
67
+ /** @ignore */
63
68
  export interface GenericContext extends BaseContext {
64
69
  location: 'uie.playground.middle';
65
70
  }
@@ -372,6 +372,10 @@ export interface CrmActionLinkProps extends BaseActionComponent {
372
372
  * @defaultValue `"primary"`
373
373
  */
374
374
  variant?: 'primary' | 'light' | 'dark' | 'destructive';
375
+ /**
376
+ * A function that will be invoked when the link is clicked. It receives no arguments and its return value is ignored.
377
+ */
378
+ onClick?: () => void;
375
379
  }
376
380
  export interface ActionLibraryButtonCardActionConfig<SpecificActionType extends ActionType = ActionType> {
377
381
  /**
@@ -1,6 +1,6 @@
1
1
  import type { ComponentType } from 'react';
2
- import type { AppHomeActions, CrmHostActions, SettingsActions, UiePlatformActions } from './actions.ts';
3
- import type { AppHomeContext, BaseContext, CrmContext, GenericContext, SettingsContext } from './context.ts';
2
+ import type { AppHomeActions, CrmHostActions, PagesActions, SettingsActions, UiePlatformActions } from './actions.ts';
3
+ import type { AppHomeContext, BaseContext, CrmContext, GenericContext, PagesContext, SettingsContext } from './context.ts';
4
4
  import type { CrmActionButtonProps, CrmActionLinkProps, CrmAssociationPivotProps, CrmAssociationPropertyListProps, CrmAssociationStageTrackerProps, CrmAssociationTableProps, CrmCardActionsProps, CrmDataHighlightProps, CrmPropertyListProps, CrmRelativeTimelineProps, CrmReportProps, CrmSimpleDeadlineProps, CrmStageTrackerProps, CrmStatisticsProps } from './crm.ts';
5
5
  import type { ServerlessFuncRunner } from './http-requests.ts';
6
6
  import type { TypesOfReadOnlyArray } from './shared.ts';
@@ -75,6 +75,11 @@ export interface AppHomeExtensionPoint extends ExtensionPointContract {
75
75
  actions: AppHomeActions & UiePlatformActions;
76
76
  context: AppHomeContext;
77
77
  }
78
+ /** @ignore */
79
+ export interface PagesExtensionPoint extends ExtensionPointContract {
80
+ actions: PagesActions & UiePlatformActions;
81
+ context: PagesContext;
82
+ }
78
83
  export interface ExampleCrmComponentProps {
79
84
  name: string;
80
85
  size: 'sm' | 'md' | 'lg';
@@ -90,7 +95,7 @@ interface RemotePlaygroundExtensionPoint extends ExtensionPointContract {
90
95
  ExampleCrmComponent: ComponentType<ExampleCrmComponentProps>;
91
96
  };
92
97
  }
93
- export declare const EXTENSION_POINT_LOCATIONS: readonly ["crm.preview", "crm.record.sidebar", "crm.record.tab", "helpdesk.sidebar", "uie.playground.middle", "settings", "home"];
98
+ export declare const EXTENSION_POINT_LOCATIONS: readonly ["crm.preview", "crm.record.sidebar", "crm.record.tab", "helpdesk.sidebar", "uie.playground.middle", "settings", "home", "pages"];
94
99
  export type ExtensionPointLocation = TypesOfReadOnlyArray<typeof EXTENSION_POINT_LOCATIONS>;
95
100
  /** @ignore */
96
101
  interface LocationToExtensionPoint {
@@ -101,6 +106,7 @@ interface LocationToExtensionPoint {
101
106
  'helpdesk.sidebar': StandardCrmExtensionPoint;
102
107
  settings: SettingsExtensionPoint;
103
108
  home: AppHomeExtensionPoint;
109
+ pages: PagesExtensionPoint;
104
110
  }
105
111
  /**
106
112
  * While this resolves to the same result as LocationToExtensionPoint, it ensures that every location
@@ -6,4 +6,5 @@ export const EXTENSION_POINT_LOCATIONS = [
6
6
  'uie.playground.middle',
7
7
  'settings',
8
8
  'home',
9
+ 'pages',
9
10
  ];
@@ -1,2 +1,4 @@
1
+ export type * from './page-breadcrumbs.ts';
2
+ export type * from './page-header.ts';
1
3
  export type * from './page-link.ts';
2
- export type * from './page-routes.ts';
4
+ export type * from './page-title.ts';
@@ -0,0 +1,34 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { BaseComponentProps } from '../../shared.ts';
3
+ /**
4
+ * The props type for [PageBreadcrumbs](https://developers.hubspot.com/docs/reference/ui-components/app-page-components/page-breadcrumbs)
5
+ */
6
+ export interface PageBreadcrumbsProps extends BaseComponentProps {
7
+ /**
8
+ * The links and text to render as breadcrumbs.
9
+ * Use `PageBreadcrumbs.PageLink` for navigable crumbs and `PageBreadcrumbs.Current` for the current page.
10
+ */
11
+ children: ReactNode;
12
+ }
13
+ export interface PageBreadcrumbsPageLinkProps extends BaseComponentProps {
14
+ /**
15
+ * The path to navigate to when the link is clicked. Supports path parameters (e.g. `/view-contact/:contactId`).
16
+ */
17
+ to: string;
18
+ /**
19
+ * Values for path parameters and query string entries.
20
+ * Parameters matching `:paramName` tokens in `to` are substituted into the path.
21
+ * Any remaining parameters are appended as query string entries.
22
+ */
23
+ params?: Record<string, string>;
24
+ /**
25
+ * The visible text of the breadcrumb link.
26
+ */
27
+ children: ReactNode;
28
+ }
29
+ export interface PageBreadcrumbsCurrentProps extends BaseComponentProps {
30
+ /**
31
+ * Label for the current location.
32
+ */
33
+ children: ReactNode;
34
+ }