@hubspot/ui-extensions 0.11.6 → 0.12.1
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.
- package/dist/hooks/useExtensionActions.d.ts +17 -1
- package/dist/hooks/useExtensionActions.js +16 -0
- package/dist/hooks/useExtensionApi.d.ts +20 -0
- package/dist/hooks/useExtensionApi.js +22 -0
- package/dist/hooks/useExtensionContext.d.ts +17 -1
- package/dist/hooks/useExtensionContext.js +16 -0
- package/dist/hs-internal/__tests__/createRemoteComponentInternal.spec.d.ts +1 -0
- package/dist/hs-internal/__tests__/createRemoteComponentInternal.spec.js +139 -0
- package/dist/hs-internal/index.d.ts +35 -0
- package/dist/hs-internal/index.js +20 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/shared/remoteComponents.d.ts +11 -2
- package/dist/shared/remoteComponents.js +9 -0
- package/dist/shared/types/components/chart.d.ts +69 -8
- package/dist/shared/types/components/index.d.ts +1 -0
- package/dist/shared/types/components/inputs.d.ts +11 -0
- package/dist/shared/types/components/score.d.ts +13 -0
- package/dist/shared/types/components/score.js +1 -0
- package/dist/shared/types/context.d.ts +2 -0
- package/dist/shared/types/extension-points.d.ts +6 -1
- package/dist/shared/types/index.d.ts +1 -1
- package/dist/shared/types/index.js +1 -0
- package/dist/shared/types/shared.d.ts +5 -0
- package/dist/shared/types/worker-globals.d.ts +16 -11
- package/dist/testing/__tests__/mocks.useExtensionApi.spec.d.ts +1 -0
- package/dist/testing/__tests__/mocks.useExtensionApi.spec.js +72 -0
- package/dist/testing/internal/mocks/mock-hooks.js +9 -0
- package/dist/testing/internal/types-internal.d.ts +2 -0
- package/dist/testing/types.d.ts +10 -0
- package/package.json +4 -3
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import type { ExtensionPoints } from '../shared/types/extension-points.ts';
|
|
2
|
-
declare function useExtensionActionsInternal<
|
|
2
|
+
declare function useExtensionActionsInternal<TExtensionPoint extends keyof ExtensionPoints>(): import("../shared/types/extension-points.ts").ExtensionPointApiActions<TExtensionPoint>;
|
|
3
|
+
/**
|
|
4
|
+
* Hook that provides access to extension actions for the specified extension point.
|
|
5
|
+
*
|
|
6
|
+
* Actions are methods provided by the host application that extensions can invoke to
|
|
7
|
+
* interact with the platform, such as showing notifications, opening modals, or
|
|
8
|
+
* navigating to different views.
|
|
9
|
+
*
|
|
10
|
+
* @typeParam TExtensionPoint - The extension point location (e.g., 'crm.record.tab', 'settings')
|
|
11
|
+
* @returns The actions object containing all available action methods for the extension point
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* const actions = useExtensionActions<'crm.record.tab'>();
|
|
16
|
+
* // Use actions like: actions.openIframeModal(...)
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
3
19
|
export declare const useExtensionActions: typeof useExtensionActionsInternal;
|
|
4
20
|
export {};
|
|
@@ -3,4 +3,20 @@ import { createMockAwareHook } from "../internal/hook-utils.js";
|
|
|
3
3
|
function useExtensionActionsInternal() {
|
|
4
4
|
return getWorkerGlobals().hsWorkerAPI.useExtensionActions();
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Hook that provides access to extension actions for the specified extension point.
|
|
8
|
+
*
|
|
9
|
+
* Actions are methods provided by the host application that extensions can invoke to
|
|
10
|
+
* interact with the platform, such as showing notifications, opening modals, or
|
|
11
|
+
* navigating to different views.
|
|
12
|
+
*
|
|
13
|
+
* @typeParam TExtensionPoint - The extension point location (e.g., 'crm.record.tab', 'settings')
|
|
14
|
+
* @returns The actions object containing all available action methods for the extension point
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* const actions = useExtensionActions<'crm.record.tab'>();
|
|
19
|
+
* // Use actions like: actions.openIframeModal(...)
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
6
22
|
export const useExtensionActions = createMockAwareHook('useExtensionActions', useExtensionActionsInternal);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ExtensionPoints } from '../shared/types/extension-points.ts';
|
|
2
|
+
declare function useExtensionApiInternal<TExtensionPoint extends keyof ExtensionPoints>(): import("../shared/types/extension-points.ts").ExtensionPointFullApi<TExtensionPoint>;
|
|
3
|
+
/**
|
|
4
|
+
* Hook that provides access to the full extension API for the specified extension point.
|
|
5
|
+
*
|
|
6
|
+
* The extension API includes:
|
|
7
|
+
* - `actions`: Methods to interact with the host application
|
|
8
|
+
* - `context`: Contextual data about the current environment
|
|
9
|
+
* - `runServerlessFunction`: Function to execute serverless functions (deprecated)
|
|
10
|
+
*
|
|
11
|
+
* @typeParam TExtensionPoint - The extension point location (e.g., 'crm.record.tab', 'settings')
|
|
12
|
+
* @returns The complete extension API object containing actions, context, and runServerlessFunction
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* const { actions, context, runServerlessFunction } = useExtensionApi<'crm.record.tab'>();
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare const useExtensionApi: typeof useExtensionApiInternal;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getWorkerGlobals } from "../internal/global-utils.js";
|
|
2
|
+
import { createMockAwareHook } from "../internal/hook-utils.js";
|
|
3
|
+
function useExtensionApiInternal() {
|
|
4
|
+
return getWorkerGlobals().hsWorkerAPI.useExtensionApi();
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Hook that provides access to the full extension API for the specified extension point.
|
|
8
|
+
*
|
|
9
|
+
* The extension API includes:
|
|
10
|
+
* - `actions`: Methods to interact with the host application
|
|
11
|
+
* - `context`: Contextual data about the current environment
|
|
12
|
+
* - `runServerlessFunction`: Function to execute serverless functions (deprecated)
|
|
13
|
+
*
|
|
14
|
+
* @typeParam TExtensionPoint - The extension point location (e.g., 'crm.record.tab', 'settings')
|
|
15
|
+
* @returns The complete extension API object containing actions, context, and runServerlessFunction
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* const { actions, context, runServerlessFunction } = useExtensionApi<'crm.record.tab'>();
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export const useExtensionApi = createMockAwareHook('useExtensionApi', useExtensionApiInternal);
|
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import type { ExtensionPoints } from '../shared/types/extension-points.ts';
|
|
2
|
-
declare function useExtensionContextInternal<
|
|
2
|
+
declare function useExtensionContextInternal<TExtensionPoint extends keyof ExtensionPoints>(): import("../shared/types/extension-points.ts").ExtensionPointApiContext<TExtensionPoint>;
|
|
3
|
+
/**
|
|
4
|
+
* Hook that provides access to contextual data for the specified extension point.
|
|
5
|
+
*
|
|
6
|
+
* The context contains information about the current environment where the extension
|
|
7
|
+
* is running, such as the current CRM object, user information, portal settings,
|
|
8
|
+
* and other relevant data specific to the extension point location.
|
|
9
|
+
*
|
|
10
|
+
* @typeParam TExtensionPoint - The extension point location (e.g., 'crm.record.tab', 'settings')
|
|
11
|
+
* @returns The context object containing environment-specific data for the extension point
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* const context = useExtensionContext<'crm.record.tab'>();
|
|
16
|
+
* // Access properties like: context.crm.objectId, context.user, context.portal
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
3
19
|
export declare const useExtensionContext: typeof useExtensionContextInternal;
|
|
4
20
|
export {};
|
|
@@ -3,4 +3,20 @@ import { createMockAwareHook } from "../internal/hook-utils.js";
|
|
|
3
3
|
function useExtensionContextInternal() {
|
|
4
4
|
return getWorkerGlobals().hsWorkerAPI.useExtensionContext();
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Hook that provides access to contextual data for the specified extension point.
|
|
8
|
+
*
|
|
9
|
+
* The context contains information about the current environment where the extension
|
|
10
|
+
* is running, such as the current CRM object, user information, portal settings,
|
|
11
|
+
* and other relevant data specific to the extension point location.
|
|
12
|
+
*
|
|
13
|
+
* @typeParam TExtensionPoint - The extension point location (e.g., 'crm.record.tab', 'settings')
|
|
14
|
+
* @returns The context object containing environment-specific data for the extension point
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* const context = useExtensionContext<'crm.record.tab'>();
|
|
19
|
+
* // Access properties like: context.crm.objectId, context.user, context.portal
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
6
22
|
export const useExtensionContext = createMockAwareHook('useExtensionContext', useExtensionContextInternal);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { createRenderer } from "../../testing/index.js";
|
|
4
|
+
import { createRemoteComponentInternal } from "../index.js";
|
|
5
|
+
import { __hubSpotComponentRegistry } from "../../shared/remoteComponents.js";
|
|
6
|
+
import { Button, Text } from "../../index.js";
|
|
7
|
+
import { InvalidComponentsError } from "../../testing/internal/errors.js";
|
|
8
|
+
import { useState } from 'react';
|
|
9
|
+
// Create custom components once at the top level
|
|
10
|
+
const CustomButton = createRemoteComponentInternal('TestCustomButton');
|
|
11
|
+
const CustomCard = createRemoteComponentInternal('TestCustomCard', {
|
|
12
|
+
fragmentProps: ['content'],
|
|
13
|
+
});
|
|
14
|
+
describe('createRemoteComponentInternal', () => {
|
|
15
|
+
it('should create a component that can be rendered', () => {
|
|
16
|
+
const { render, find } = createRenderer('crm.record.tab');
|
|
17
|
+
render(_jsx(CustomButton, { label: "Click me", onClick: () => { }, testId: "my-button" }));
|
|
18
|
+
const button = find(CustomButton);
|
|
19
|
+
expect(button.props.label).toBe('Click me');
|
|
20
|
+
expect(button.props.testId).toBe('my-button');
|
|
21
|
+
});
|
|
22
|
+
it('should register component in the registry', () => {
|
|
23
|
+
expect(__hubSpotComponentRegistry.isAllowedComponentName('TestCustomButton')).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
it('should be findable using renderer.find()', () => {
|
|
26
|
+
const { render, find } = createRenderer('crm.record.tab');
|
|
27
|
+
render(_jsx(CustomButton, { label: "Find me", onClick: () => { } }));
|
|
28
|
+
const button = find(CustomButton);
|
|
29
|
+
expect(button).toBeDefined();
|
|
30
|
+
expect(button.props.label).toBe('Find me');
|
|
31
|
+
});
|
|
32
|
+
it('should work with find() using prop matchers', () => {
|
|
33
|
+
const { render, find } = createRenderer('crm.record.tab');
|
|
34
|
+
render(_jsxs(_Fragment, { children: [_jsx(CustomButton, { label: "Button 1", onClick: () => { }, variant: "primary" }), _jsx(CustomButton, { label: "Button 2", onClick: () => { }, variant: "secondary" })] }));
|
|
35
|
+
const primaryButton = find(CustomButton, { variant: 'primary' });
|
|
36
|
+
expect(primaryButton.props.label).toBe('Button 1');
|
|
37
|
+
const secondaryButton = find(CustomButton, { variant: 'secondary' });
|
|
38
|
+
expect(secondaryButton.props.label).toBe('Button 2');
|
|
39
|
+
});
|
|
40
|
+
it('should work with findAll()', () => {
|
|
41
|
+
const { render, findAll } = createRenderer('crm.record.tab');
|
|
42
|
+
render(_jsxs(_Fragment, { children: [_jsx(CustomButton, { label: "Button 1", onClick: () => { } }), _jsx(CustomButton, { label: "Button 2", onClick: () => { } }), _jsx(CustomButton, { label: "Button 3", onClick: () => { } })] }));
|
|
43
|
+
const buttons = findAll(CustomButton);
|
|
44
|
+
expect(buttons).toHaveLength(3);
|
|
45
|
+
expect(buttons[0].props.label).toBe('Button 1');
|
|
46
|
+
expect(buttons[1].props.label).toBe('Button 2');
|
|
47
|
+
expect(buttons[2].props.label).toBe('Button 3');
|
|
48
|
+
});
|
|
49
|
+
it('should work with findByTestId()', () => {
|
|
50
|
+
const { render, findByTestId } = createRenderer('crm.record.tab');
|
|
51
|
+
render(_jsx(CustomButton, { label: "Find by ID", onClick: () => { }, testId: "my-custom-button" }));
|
|
52
|
+
const button = findByTestId(CustomButton, 'my-custom-button');
|
|
53
|
+
expect(button.props.label).toBe('Find by ID');
|
|
54
|
+
});
|
|
55
|
+
it('should work with maybeFind()', () => {
|
|
56
|
+
const { render, maybeFind } = createRenderer('crm.record.tab');
|
|
57
|
+
render(_jsx(CustomButton, { label: "Maybe", onClick: () => { } }));
|
|
58
|
+
const button = maybeFind(CustomButton);
|
|
59
|
+
expect(button).not.toBeNull();
|
|
60
|
+
expect(button?.props.label).toBe('Maybe');
|
|
61
|
+
const nonExistent = maybeFind(Button);
|
|
62
|
+
expect(nonExistent).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
it('should support fragment props', () => {
|
|
65
|
+
const { render, find } = createRenderer('crm.record.tab');
|
|
66
|
+
render(_jsx(CustomCard, { title: "My Card", content: _jsx(Text, { children: "Card content" }), testId: "my-card" }));
|
|
67
|
+
const card = find(CustomCard);
|
|
68
|
+
expect(card.props.title).toBe('My Card');
|
|
69
|
+
expect(card.props.testId).toBe('my-card');
|
|
70
|
+
expect(card.props.content?.find(Text).text).toBe('Card content');
|
|
71
|
+
});
|
|
72
|
+
it('should support event triggering via trigger()', () => {
|
|
73
|
+
const { render, find } = createRenderer('crm.record.tab');
|
|
74
|
+
let clicked = false;
|
|
75
|
+
const handleClick = () => {
|
|
76
|
+
clicked = true;
|
|
77
|
+
};
|
|
78
|
+
render(_jsx(CustomButton, { label: "Click me", onClick: handleClick }));
|
|
79
|
+
const button = find(CustomButton);
|
|
80
|
+
button.trigger('onClick');
|
|
81
|
+
expect(clicked).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
it('should not throw InvalidComponentsError during rendering', () => {
|
|
84
|
+
const { render } = createRenderer('crm.record.tab');
|
|
85
|
+
expect(() => render(_jsx(CustomButton, { label: "Valid", onClick: () => { } }))).not.toThrow(InvalidComponentsError);
|
|
86
|
+
});
|
|
87
|
+
it('should work with state updates', () => {
|
|
88
|
+
const { render, find } = createRenderer('crm.record.tab');
|
|
89
|
+
function Counter() {
|
|
90
|
+
const [count, setCount] = useState(0);
|
|
91
|
+
const handleClick = () => {
|
|
92
|
+
setCount(count + 1);
|
|
93
|
+
};
|
|
94
|
+
return (_jsx(CustomButton, { label: `Count: ${count}`, onClick: handleClick, testId: "counter" }));
|
|
95
|
+
}
|
|
96
|
+
render(_jsx(Counter, {}));
|
|
97
|
+
const button = find(CustomButton);
|
|
98
|
+
expect(button.props.label).toBe('Count: 0');
|
|
99
|
+
button.trigger('onClick');
|
|
100
|
+
const updatedButton = find(CustomButton);
|
|
101
|
+
expect(updatedButton.props.label).toBe('Count: 1');
|
|
102
|
+
});
|
|
103
|
+
it('should work with nested components', () => {
|
|
104
|
+
const { render, find } = createRenderer('crm.record.tab');
|
|
105
|
+
render(_jsx(CustomCard, { title: "Card with button", content: _jsx(CustomButton, { label: "Nested button", onClick: () => { } }) }));
|
|
106
|
+
const card = find(CustomCard);
|
|
107
|
+
expect(card.props.title).toBe('Card with button');
|
|
108
|
+
// Fragment prop children are found globally, not as direct children
|
|
109
|
+
const nestedButton = find(CustomButton);
|
|
110
|
+
expect(nestedButton.props.label).toBe('Nested button');
|
|
111
|
+
});
|
|
112
|
+
it('should work with findChild() and findAllChildren()', () => {
|
|
113
|
+
const { render, findAll, find } = createRenderer('crm.record.tab');
|
|
114
|
+
render(_jsx(CustomCard, { title: "Card with buttons", content: _jsxs(_Fragment, { children: [_jsx(CustomButton, { label: "Button 1", onClick: () => { } }), _jsx(CustomButton, { label: "Button 2", onClick: () => { } })] }) }));
|
|
115
|
+
const card = find(CustomCard);
|
|
116
|
+
expect(card.props.title).toBe('Card with buttons');
|
|
117
|
+
// Fragment prop children are found globally, not as direct children
|
|
118
|
+
const buttons = findAll(CustomButton);
|
|
119
|
+
expect(buttons).toHaveLength(2);
|
|
120
|
+
expect(buttons[0].props.label).toBe('Button 1');
|
|
121
|
+
expect(buttons[1].props.label).toBe('Button 2');
|
|
122
|
+
});
|
|
123
|
+
it('should allow multiple custom components to coexist', () => {
|
|
124
|
+
const { render, find } = createRenderer('crm.record.tab');
|
|
125
|
+
render(_jsxs(_Fragment, { children: [_jsx(CustomButton, { label: "Button", onClick: () => { } }), _jsx(CustomCard, { title: "Card" })] }));
|
|
126
|
+
const button = find(CustomButton);
|
|
127
|
+
const card = find(CustomCard);
|
|
128
|
+
expect(button.props.label).toBe('Button');
|
|
129
|
+
expect(card.props.title).toBe('Card');
|
|
130
|
+
});
|
|
131
|
+
it('should work alongside built-in HubSpot components', () => {
|
|
132
|
+
const { render, find } = createRenderer('crm.record.tab');
|
|
133
|
+
render(_jsxs(_Fragment, { children: [_jsx(CustomButton, { label: "Custom", onClick: () => { } }), _jsx(Button, { variant: "primary", children: "Built-in" })] }));
|
|
134
|
+
const customButton = find(CustomButton);
|
|
135
|
+
const builtInButton = find(Button);
|
|
136
|
+
expect(customButton.props.label).toBe('Custom');
|
|
137
|
+
expect(builtInButton.props.variant).toBe('primary');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { HubSpotReactComponent, HubSpotReactFragmentProp, UnknownComponentProps } from '../shared/types/shared.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Utility type for filtering out all of the prop names where the value is a ReactNode.
|
|
4
|
+
* We use this to know which props are allowed to be used as fragment props.
|
|
5
|
+
*/
|
|
6
|
+
type ComponentFragmentPropName<TProps extends UnknownComponentProps> = {
|
|
7
|
+
[K in keyof TProps]: HubSpotReactFragmentProp extends Required<TProps>[K] ? K : never;
|
|
8
|
+
}[keyof TProps & string];
|
|
9
|
+
/**
|
|
10
|
+
* Options for creating a custom remote component.
|
|
11
|
+
*/
|
|
12
|
+
export interface CreateRemoteComponentInternalOptions<TProps extends UnknownComponentProps = UnknownComponentProps> {
|
|
13
|
+
/**
|
|
14
|
+
* An array of prop names that are allowed to be used as fragment props (props that accept ReactNode children).
|
|
15
|
+
*/
|
|
16
|
+
fragmentProps?: ComponentFragmentPropName<TProps>[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Creates and registers a custom remote component for internal HubSpot use.
|
|
20
|
+
*
|
|
21
|
+
* This function is intended for HubSpot internal teams who need to create
|
|
22
|
+
* custom remote components that work with the UI Extensions testing utilities.
|
|
23
|
+
*
|
|
24
|
+
* Components created with this function will:
|
|
25
|
+
* - Be registered in the global component registry
|
|
26
|
+
* - Pass validation in testing utilities (isAllowedComponentName)
|
|
27
|
+
* - Work with renderer.find(), renderer.findAll(), and other testing utilities
|
|
28
|
+
*
|
|
29
|
+
* @param componentName - Unique name for the component (e.g., "FlywheelCustomButton").
|
|
30
|
+
* Recommended to prefix with team name to avoid collisions.
|
|
31
|
+
* @param options - Optional configuration including fragment props
|
|
32
|
+
* @returns A typed remote React component that can be rendered in a remote environment
|
|
33
|
+
*/
|
|
34
|
+
export declare function createRemoteComponentInternal<TProps extends UnknownComponentProps>(componentName: string, options?: CreateRemoteComponentInternalOptions<TProps>): HubSpotReactComponent<TProps>;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { __hubSpotComponentRegistry } from "../shared/remoteComponents.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates and registers a custom remote component for internal HubSpot use.
|
|
4
|
+
*
|
|
5
|
+
* This function is intended for HubSpot internal teams who need to create
|
|
6
|
+
* custom remote components that work with the UI Extensions testing utilities.
|
|
7
|
+
*
|
|
8
|
+
* Components created with this function will:
|
|
9
|
+
* - Be registered in the global component registry
|
|
10
|
+
* - Pass validation in testing utilities (isAllowedComponentName)
|
|
11
|
+
* - Work with renderer.find(), renderer.findAll(), and other testing utilities
|
|
12
|
+
*
|
|
13
|
+
* @param componentName - Unique name for the component (e.g., "FlywheelCustomButton").
|
|
14
|
+
* Recommended to prefix with team name to avoid collisions.
|
|
15
|
+
* @param options - Optional configuration including fragment props
|
|
16
|
+
* @returns A typed remote React component that can be rendered in a remote environment
|
|
17
|
+
*/
|
|
18
|
+
export function createRemoteComponentInternal(componentName, options = {}) {
|
|
19
|
+
return __hubSpotComponentRegistry.createAndRegisterRemoteReactComponent(componentName, options);
|
|
20
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import './clientTypes.ts';
|
|
|
2
2
|
export { hubspot } from './hubspot.ts';
|
|
3
3
|
export { logger } from './logger.ts';
|
|
4
4
|
export * from './shared/types/index.ts';
|
|
5
|
-
export { Accordion, Alert, AutoGrid, BarChart, Box, Button, ButtonRow, Card, Checkbox, CurrencyInput, DateInput, DescriptionList, DescriptionListItem, Divider, Dropdown, EmptyState, ErrorState, Flex, Form, Heading, Icon, Illustration, Image, Inline, Input, LineChart, Link, List, LoadingButton, LoadingSpinner, Modal, ModalBody, ModalFooter, MultiSelect, NumberInput, Panel, PanelBody, PanelFooter, PanelSection, ProgressBar, RadioButton, SearchInput, Select, Stack, Statistics, StatisticsItem, StatisticsTrend, StatusTag, StepIndicator, StepperInput, Tab, Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, Tag, Text, TextArea, Textarea, Tile, TimeInput, Toggle, ToggleGroup, Tooltip, } from './shared/remoteComponents.tsx';
|
|
5
|
+
export { Accordion, Alert, AutoGrid, BarChart, Box, Button, ButtonRow, Card, Checkbox, CurrencyInput, DateInput, DescriptionList, DescriptionListItem, Divider, Dropdown, EmptyState, ErrorState, Flex, Form, Heading, Icon, Illustration, Image, Inline, Input, LineChart, Link, List, LoadingButton, LoadingSpinner, Modal, ModalBody, ModalFooter, MultiSelect, NumberInput, Panel, PanelBody, PanelFooter, PanelSection, ProgressBar, RadioButton, ScoreCircle, SearchInput, Select, Stack, Statistics, StatisticsItem, StatisticsTrend, StatusTag, StepIndicator, StepperInput, Tab, Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, Tag, Text, TextArea, Textarea, Tile, TimeInput, Toggle, ToggleGroup, Tooltip, } from './shared/remoteComponents.tsx';
|
|
6
6
|
export { useExtensionContext } from './hooks/useExtensionContext.tsx';
|
|
7
7
|
export { useExtensionActions } from './hooks/useExtensionActions.tsx';
|
|
8
|
+
export { useExtensionApi } from './hooks/useExtensionApi.tsx';
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import "./clientTypes.js";
|
|
|
3
3
|
export { hubspot } from "./hubspot.js";
|
|
4
4
|
export { logger } from "./logger.js";
|
|
5
5
|
export * from "./shared/types/index.js";
|
|
6
|
-
export { Accordion, Alert, AutoGrid, BarChart, Box, Button, ButtonRow, Card, Checkbox, CurrencyInput, DateInput, DescriptionList, DescriptionListItem, Divider, Dropdown, EmptyState, ErrorState, Flex, Form, Heading, Icon, Illustration, Image, Inline, Input, LineChart, Link, List, LoadingButton, LoadingSpinner, Modal, ModalBody, ModalFooter, MultiSelect, NumberInput, Panel, PanelBody, PanelFooter, PanelSection, ProgressBar, RadioButton, SearchInput, Select, Stack, Statistics, StatisticsItem, StatisticsTrend, StatusTag, StepIndicator, StepperInput, Tab, Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, Tag, Text, TextArea, Textarea, Tile, TimeInput, Toggle, ToggleGroup, Tooltip, } from "./shared/remoteComponents.js";
|
|
6
|
+
export { Accordion, Alert, AutoGrid, BarChart, Box, Button, ButtonRow, Card, Checkbox, CurrencyInput, DateInput, DescriptionList, DescriptionListItem, Divider, Dropdown, EmptyState, ErrorState, Flex, Form, Heading, Icon, Illustration, Image, Inline, Input, LineChart, Link, List, LoadingButton, LoadingSpinner, Modal, ModalBody, ModalFooter, MultiSelect, NumberInput, Panel, PanelBody, PanelFooter, PanelSection, ProgressBar, RadioButton, ScoreCircle, SearchInput, Select, Stack, Statistics, StatisticsItem, StatisticsTrend, StatusTag, StepIndicator, StepperInput, Tab, Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, Tag, Text, TextArea, Textarea, Tile, TimeInput, Toggle, ToggleGroup, Tooltip, } from "./shared/remoteComponents.js";
|
|
7
7
|
export { useExtensionContext } from "./hooks/useExtensionContext.js";
|
|
8
8
|
export { useExtensionActions } from "./hooks/useExtensionActions.js";
|
|
9
|
+
export { useExtensionApi } from "./hooks/useExtensionApi.js";
|
|
@@ -483,7 +483,7 @@ export declare const LoadingButton: import("./types/shared.ts").HubSpotReactComp
|
|
|
483
483
|
* - {@link https://developers.hubspot.com/docs/reference/ui-components/standard-components/charts Charts Docs}
|
|
484
484
|
* - {@link https://github.com/HubSpot/ui-extensions-examples/tree/main/charts-example Charts Example}
|
|
485
485
|
*/
|
|
486
|
-
export declare const BarChart: import("./types/shared.ts").HubSpotReactComponent<componentTypes.
|
|
486
|
+
export declare const BarChart: import("./types/shared.ts").HubSpotReactComponent<componentTypes.BarChartProps>;
|
|
487
487
|
/**
|
|
488
488
|
* The `LineChart` component renders a line chart for visualizing data. This type of chart is best suited for time series plots or trend data.
|
|
489
489
|
*
|
|
@@ -493,7 +493,16 @@ export declare const BarChart: import("./types/shared.ts").HubSpotReactComponent
|
|
|
493
493
|
* - {@link https://developers.hubspot.com/docs/reference/ui-components/standard-components/charts Charts Docs}
|
|
494
494
|
* - {@link https://github.com/HubSpot/ui-extensions-examples/tree/main/charts-example Charts Example}
|
|
495
495
|
*/
|
|
496
|
-
export declare const LineChart: import("./types/shared.ts").HubSpotReactComponent<componentTypes.
|
|
496
|
+
export declare const LineChart: import("./types/shared.ts").HubSpotReactComponent<componentTypes.LineChartProps>;
|
|
497
|
+
/**
|
|
498
|
+
* The `ScoreCircle` component displays a score value (0-100) as a circular progress indicator with color-coded bands.
|
|
499
|
+
* Scores are color-coded: 0-32 (alert/red), 33-65 (warning/yellow), 66-100 (success/green).
|
|
500
|
+
* @example
|
|
501
|
+
* ```tsx
|
|
502
|
+
* <ScoreCircle score={75} />
|
|
503
|
+
* ```
|
|
504
|
+
*/
|
|
505
|
+
export declare const ScoreCircle: import("./types/shared.ts").HubSpotReactComponent<componentTypes.ScoreProps>;
|
|
497
506
|
/**
|
|
498
507
|
* `Tabs` allow you to group related content in a compact space, allowing users to switch between views without leaving the page.
|
|
499
508
|
* @example
|
|
@@ -504,6 +504,15 @@ export const BarChart = createAndRegisterRemoteReactComponent('BarChart');
|
|
|
504
504
|
* - {@link https://github.com/HubSpot/ui-extensions-examples/tree/main/charts-example Charts Example}
|
|
505
505
|
*/
|
|
506
506
|
export const LineChart = createAndRegisterRemoteReactComponent('LineChart');
|
|
507
|
+
/**
|
|
508
|
+
* The `ScoreCircle` component displays a score value (0-100) as a circular progress indicator with color-coded bands.
|
|
509
|
+
* Scores are color-coded: 0-32 (alert/red), 33-65 (warning/yellow), 66-100 (success/green).
|
|
510
|
+
* @example
|
|
511
|
+
* ```tsx
|
|
512
|
+
* <ScoreCircle score={75} />
|
|
513
|
+
* ```
|
|
514
|
+
*/
|
|
515
|
+
export const ScoreCircle = createAndRegisterRemoteReactComponent('ScoreCircle');
|
|
507
516
|
/**
|
|
508
517
|
* `Tabs` allow you to group related content in a compact space, allowing users to switch between views without leaving the page.
|
|
509
518
|
* @example
|
|
@@ -1,32 +1,93 @@
|
|
|
1
1
|
import { BaseComponentProps } from '../shared.ts';
|
|
2
|
+
export interface ChartProps extends BaseComponentProps {
|
|
3
|
+
/**
|
|
4
|
+
* An object containing the chart's data in an array.
|
|
5
|
+
*/
|
|
6
|
+
data: ChartDataRow[] | {
|
|
7
|
+
data: ChartDataRow[];
|
|
8
|
+
options?: ChartDataOptions;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Configures the chart's axes.
|
|
12
|
+
*/
|
|
13
|
+
axes: ChartAxisPair;
|
|
14
|
+
/**
|
|
15
|
+
* Additional chart configuration options. Options include:
|
|
16
|
+
* - `title` (string): a title for the chart.
|
|
17
|
+
* - `showLegend` (boolean): set to `true` to display a legend above the chart.
|
|
18
|
+
* - `showDataLabels` (boolean): set to `true` to display labels above data points.
|
|
19
|
+
* - `showTooltips` (boolean): set to `true` to display tooltips for data points on hover.
|
|
20
|
+
* - `colorList` (array): specify a custom order for colors to be used in the report.
|
|
21
|
+
*
|
|
22
|
+
* Learn more about [chart options](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/overview#chart-options).
|
|
23
|
+
*/
|
|
24
|
+
options?: ChartOptions;
|
|
25
|
+
}
|
|
2
26
|
/**
|
|
3
27
|
* The props type for {@link !components.BarChart}.
|
|
4
28
|
*
|
|
5
29
|
* @category Component Props
|
|
6
30
|
*/
|
|
7
|
-
export
|
|
31
|
+
export interface BarChartProps extends ChartProps {
|
|
32
|
+
/**
|
|
33
|
+
* An object containing the chart's data in an array.
|
|
34
|
+
* - Data should be formatted as comma-separated objects containing key-value pairs.
|
|
35
|
+
* - Data will be displayed in the order it's provided, so any sorting will need to be done before passing it to the component.
|
|
36
|
+
* - While it's recommended to pre-format your data to be human-readable, you can also provide the `propertyLabels` parameter via this prop's `options` to relabel data values. See example in the [Stacking section](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/bar-chart#stacking).
|
|
37
|
+
*
|
|
38
|
+
* Learn more about [formatting data](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/overview#formatting-data).
|
|
39
|
+
*/
|
|
40
|
+
data: ChartDataRow[] | {
|
|
41
|
+
data: ChartDataRow[];
|
|
42
|
+
options?: ChartDataOptions;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Configures the chart's axes. Using the `x` and `y` fields, you'll configure each axis individually with `field` and `fieldType` parameters, along with an optional `label` parameter:
|
|
46
|
+
* - `field` (Required): the field from your dataset to use. This value will be used as the displayed axis label if no `label` is specified.
|
|
47
|
+
* - `fieldType` (Required): the type of field. Can be `category`, `datetime`, or `linear`. Learn more about [field types](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/overview#configuring-axes).
|
|
48
|
+
* - `label`: the axis label. If not specified, the `field` value will be used.
|
|
49
|
+
*
|
|
50
|
+
* You can also include an `options` field to further configure the axes with the following options:
|
|
51
|
+
* - `groupFieldByColor` (string): specify a field to [apply color](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/bar-chart#colors) to for visual clarity.
|
|
52
|
+
* - `stacking` (boolean): [stack grouped data](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/bar-chart#stacking) instead of always rendering separate bars.
|
|
53
|
+
* - `colors` (object): [specify colors for values](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/bar-chart#specify-colors-per-field-value) in the field specified in `groupFieldByColor`.
|
|
54
|
+
*
|
|
55
|
+
* Learn more about [chart axes](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/overview#configuring-axes).
|
|
56
|
+
*/
|
|
57
|
+
axes: ChartAxisPair;
|
|
58
|
+
}
|
|
8
59
|
/**
|
|
9
60
|
* The props type for {@link !components.LineChart}.
|
|
10
61
|
*
|
|
11
62
|
* @category Component Props
|
|
12
63
|
*/
|
|
13
|
-
export
|
|
14
|
-
export interface ChartProps extends BaseComponentProps {
|
|
64
|
+
export interface LineChartProps extends ChartProps {
|
|
15
65
|
/**
|
|
16
66
|
* An object containing the chart's data in an array.
|
|
67
|
+
* - Data should be formatted as comma-separated objects containing key-value pairs.
|
|
68
|
+
* - Data will be displayed in the order it's provided, so any sorting will need to be done before passing it to the component.
|
|
69
|
+
* - While it's recommended to pre-format your data to be human-readable, you can also provide the `propertyLabels` parameter via this prop's `options` to relabel data values. See example in the [Stacking section](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/line-chart#stacking).
|
|
70
|
+
*
|
|
71
|
+
* Learn more about [formatting data](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/overview#formatting-data).
|
|
17
72
|
*/
|
|
18
73
|
data: ChartDataRow[] | {
|
|
19
74
|
data: ChartDataRow[];
|
|
20
75
|
options?: ChartDataOptions;
|
|
21
76
|
};
|
|
22
77
|
/**
|
|
23
|
-
* Configures the chart's axes.
|
|
78
|
+
* Configures the chart's axes. Using the `x` and `y` fields, you'll configure each axis individually with `field` and `fieldType` parameters, along with an optional `label` parameter:
|
|
79
|
+
* - `field` (Required): the field from your dataset to use. This value will be used as the displayed axis label if no `label` is specified.
|
|
80
|
+
* - `fieldType` (Required): the type of field. Can be `category`, `datetime`, or `linear`. Learn more about [field types](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/overview#configuring-axes).
|
|
81
|
+
* - `label`: the axis label. If not specified, the `field` value will be used.
|
|
82
|
+
*
|
|
83
|
+
* You can also include an `options` field to further configure the axes with the following options:
|
|
84
|
+
* - `groupFieldByColor` (string): specify a field to [apply color](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/line-chart#colors) to for visual clarity.
|
|
85
|
+
* - `stacking` (boolean): [stack grouped data](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/line-chart#stacking) instead of always rendering separated lines.
|
|
86
|
+
* - `colors` (object): [specify colors for values](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/line-chart#specify-colors-per-field-value) in the field specified in `groupFieldByColor`.
|
|
87
|
+
*
|
|
88
|
+
* Learn more about [chart axes](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/standard-components/charts/overview#configuring-axes).
|
|
24
89
|
*/
|
|
25
90
|
axes: ChartAxisPair;
|
|
26
|
-
/**
|
|
27
|
-
* Additional chart configuration options.
|
|
28
|
-
*/
|
|
29
|
-
options?: ChartOptions;
|
|
30
91
|
}
|
|
31
92
|
export type ChartDataRow = {
|
|
32
93
|
[key: string]: number | string;
|
|
@@ -24,6 +24,7 @@ export type * from './loading-spinner.ts';
|
|
|
24
24
|
export type * from './modal.ts';
|
|
25
25
|
export type * from './panel.ts';
|
|
26
26
|
export type * from './progress-bar.ts';
|
|
27
|
+
export type * from './score.ts';
|
|
27
28
|
export type * from './selects.ts';
|
|
28
29
|
export type * from './statistics.ts';
|
|
29
30
|
export type * from './status-tag.ts';
|
|
@@ -272,6 +272,17 @@ export interface DateInputProps extends BaseDateInputForDate {
|
|
|
272
272
|
* @defaultValue `'userTz'`
|
|
273
273
|
*/
|
|
274
274
|
timezone?: 'userTz' | 'portalTz';
|
|
275
|
+
/**
|
|
276
|
+
* The value of the input. Must include the year, month, and day.
|
|
277
|
+
* - `year`: the four-digit year (e.g., `2023`).
|
|
278
|
+
* - `month`: starting at `0`, the number of the month (e.g., `0` = January, `11` = December).
|
|
279
|
+
* - `date`: the number of the day (e.g., `1` = the first day of the month).
|
|
280
|
+
*/
|
|
281
|
+
value?: BaseDate;
|
|
282
|
+
/**
|
|
283
|
+
* The default date value. Uses the same format as the value field.
|
|
284
|
+
*/
|
|
285
|
+
defaultValue?: BaseDate;
|
|
275
286
|
}
|
|
276
287
|
/**
|
|
277
288
|
* Object that represents times.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseComponentProps } from '../shared.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The props type for {@link !components.ScoreCircle}.
|
|
4
|
+
*
|
|
5
|
+
* @category Component Props
|
|
6
|
+
*/
|
|
7
|
+
export type ScoreProps = BaseComponentProps & {
|
|
8
|
+
/**
|
|
9
|
+
* The numerical score to display. Must be a value between 0 and 100.
|
|
10
|
+
* Decimal values will be clamped to the nearest integer.
|
|
11
|
+
*/
|
|
12
|
+
score: number;
|
|
13
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -52,10 +52,12 @@ export interface CrmContext extends BaseContext {
|
|
|
52
52
|
/** @ignore */
|
|
53
53
|
export interface SettingsContext extends BaseContext {
|
|
54
54
|
location: 'settings';
|
|
55
|
+
extension?: AppContext;
|
|
55
56
|
}
|
|
56
57
|
/** @ignore */
|
|
57
58
|
export interface AppHomeContext extends BaseContext {
|
|
58
59
|
location: 'home';
|
|
60
|
+
extension?: AppContext;
|
|
59
61
|
}
|
|
60
62
|
/** @ignore */
|
|
61
63
|
export interface GenericContext extends BaseContext {
|
|
@@ -115,9 +115,10 @@ export interface ExtensionPoints extends _ExtensionPoints {
|
|
|
115
115
|
}
|
|
116
116
|
/** @ignore */
|
|
117
117
|
export interface ExtensionPointApi<TExtensionPointLocation extends ExtensionPointLocation> {
|
|
118
|
-
runServerlessFunction: ServerlessFuncRunner;
|
|
119
118
|
actions: ExtensionPoints[TExtensionPointLocation]['actions'];
|
|
120
119
|
context: ExtensionPoints[TExtensionPointLocation]['context'];
|
|
120
|
+
/** @deprecated */
|
|
121
|
+
runServerlessFunction: ServerlessFuncRunner;
|
|
121
122
|
}
|
|
122
123
|
/**
|
|
123
124
|
* The actions of the extension point API.
|
|
@@ -127,4 +128,8 @@ export type ExtensionPointApiActions<TExtensionPointLocation extends ExtensionPo
|
|
|
127
128
|
* The context of the extension point API.
|
|
128
129
|
*/
|
|
129
130
|
export type ExtensionPointApiContext<TExtensionPointLocation extends ExtensionPointLocation> = ExtensionPointApi<TExtensionPointLocation>['context'];
|
|
131
|
+
/**
|
|
132
|
+
* The full API of the extension point.
|
|
133
|
+
*/
|
|
134
|
+
export type ExtensionPointFullApi<TExtensionPointLocation extends ExtensionPointLocation> = ExtensionPointApi<TExtensionPointLocation>;
|
|
130
135
|
export {};
|
|
@@ -3,6 +3,6 @@ export type * from './actions.ts';
|
|
|
3
3
|
export type * from './context.ts';
|
|
4
4
|
export type * from './crm.ts';
|
|
5
5
|
export type * from './extension-points.ts';
|
|
6
|
-
export
|
|
6
|
+
export * from './http-requests.ts';
|
|
7
7
|
export type * from './reactions.ts';
|
|
8
8
|
export * from './shared.ts';
|
|
@@ -240,5 +240,10 @@ export declare class RemoteEvent<V> {
|
|
|
240
240
|
constructor(value: V, event: Event);
|
|
241
241
|
}
|
|
242
242
|
export interface BaseComponentProps {
|
|
243
|
+
/**
|
|
244
|
+
* Used by `findByTestId()` to locate this component in tests.
|
|
245
|
+
*
|
|
246
|
+
* @see {@link https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/testing/reference#findbytestid | Testing utilities reference}
|
|
247
|
+
*/
|
|
243
248
|
testId?: string;
|
|
244
249
|
}
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { Logger } from './logger.ts';
|
|
2
2
|
import { HubspotExtendFunction } from './extend.ts';
|
|
3
|
-
import type { ExtensionPoints, ExtensionPointApiContext, ExtensionPointApiActions } from './extension-points.ts';
|
|
3
|
+
import type { ExtensionPoints, ExtensionPointApiContext, ExtensionPointApiActions, ExtensionPointFullApi } from './extension-points.ts';
|
|
4
|
+
export interface WorkersApi {
|
|
5
|
+
/**
|
|
6
|
+
* Hook added to worker globals so customer code can access extension context at runtime.
|
|
7
|
+
*/
|
|
8
|
+
useExtensionContext: <ExtensionPoint extends keyof ExtensionPoints>() => ExtensionPointApiContext<ExtensionPoint>;
|
|
9
|
+
/**
|
|
10
|
+
* Hook added to worker globals so customer code can access extension actions at runtime.
|
|
11
|
+
*/
|
|
12
|
+
useExtensionActions: <ExtensionPoint extends keyof ExtensionPoints>() => ExtensionPointApiActions<ExtensionPoint>;
|
|
13
|
+
/**
|
|
14
|
+
* Hook added to worker globals so customer code can access extension API at runtime.
|
|
15
|
+
*/
|
|
16
|
+
useExtensionApi: <ExtensionPoint extends keyof ExtensionPoints>() => ExtensionPointFullApi<ExtensionPoint>;
|
|
17
|
+
}
|
|
4
18
|
export interface WorkerGlobalsInternal {
|
|
5
19
|
/**
|
|
6
20
|
* A marker that the current global is a HubSpot extension worker.
|
|
@@ -19,14 +33,5 @@ export interface WorkerGlobalsInternal {
|
|
|
19
33
|
* Namespace where all HubSpot APIs that go on the worker should live.
|
|
20
34
|
* This avoids polluting the global and reduces the possibility of customer code having collisions with our code.
|
|
21
35
|
*/
|
|
22
|
-
hsWorkerAPI:
|
|
23
|
-
/**
|
|
24
|
-
* Hook added to worker globals so customer code can access extension context at runtime.
|
|
25
|
-
*/
|
|
26
|
-
useExtensionContext: <ExtensionPoint extends keyof ExtensionPoints>() => ExtensionPointApiContext<ExtensionPoint>;
|
|
27
|
-
/**
|
|
28
|
-
* Hook added to worker globals so customer code can access extension actions at runtime.
|
|
29
|
-
*/
|
|
30
|
-
useExtensionActions: <ExtensionPoint extends keyof ExtensionPoints>() => ExtensionPointApiActions<ExtensionPoint>;
|
|
31
|
-
};
|
|
36
|
+
hsWorkerAPI: WorkersApi;
|
|
32
37
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { Button, Text } from "../../index.js";
|
|
4
|
+
import { createRenderer } from "../index.js";
|
|
5
|
+
import { useExtensionApi } from "../../hooks/useExtensionApi.js";
|
|
6
|
+
function MyComponent() {
|
|
7
|
+
const { context } = useExtensionApi();
|
|
8
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { testId: "location-text", children: context.location }), _jsx(Text, { testId: "user-email", children: context.user.email }), _jsx(Text, { testId: "user-firstName", children: context.user.firstName }), _jsx(Text, { testId: "user-lastName", children: context.user.lastName }), _jsx(Text, { testId: "portal-id", children: context.portal.id }), _jsx(Text, { testId: "portal-timezone", children: context.portal.timezone }), 'crm' in context && (_jsxs(_Fragment, { children: [_jsx(Text, { testId: "crm-objectId", children: context.crm.objectId }), _jsx(Text, { testId: "crm-objectTypeId", children: context.crm.objectTypeId })] }))] }));
|
|
9
|
+
}
|
|
10
|
+
function MyActionsCrmComponent() {
|
|
11
|
+
const { actions } = useExtensionApi();
|
|
12
|
+
return (_jsxs(_Fragment, { children: [_jsx(Button, { testId: "reloadButton", onClick: () => actions.reloadPage(), children: "Reload Page" }), _jsx(Button, { testId: "fetchButton", onClick: () => {
|
|
13
|
+
actions.fetchCrmObjectProperties(['firstname', 'lastname']);
|
|
14
|
+
}, children: "Fetch Properties" })] }));
|
|
15
|
+
}
|
|
16
|
+
function MyActionsHomeComponent() {
|
|
17
|
+
const { actions } = useExtensionApi();
|
|
18
|
+
return (_jsx(_Fragment, { children: _jsx(Button, { testId: "copyButton", onClick: () => actions.copyTextToClipboard('Hello world'), children: "Copy Text" }) }));
|
|
19
|
+
}
|
|
20
|
+
describe('mock useExtensionApi', () => {
|
|
21
|
+
it('should provide a CRM mock implementation', () => {
|
|
22
|
+
const { render, findByTestId } = createRenderer('crm.record.tab');
|
|
23
|
+
render(_jsx(MyComponent, {}));
|
|
24
|
+
expect(findByTestId(Text, 'location-text').text).toEqual('crm.record.tab');
|
|
25
|
+
expect(findByTestId(Text, 'user-email').text).toEqual('fake_email@example.com');
|
|
26
|
+
expect(findByTestId(Text, 'user-firstName').text).toEqual('fake_firstName');
|
|
27
|
+
expect(findByTestId(Text, 'user-lastName').text).toEqual('fake_lastName');
|
|
28
|
+
expect(findByTestId(Text, 'portal-id').text).toEqual('123');
|
|
29
|
+
expect(findByTestId(Text, 'portal-timezone').text).toEqual('America/New_York');
|
|
30
|
+
expect(findByTestId(Text, 'crm-objectId').text).toEqual('123');
|
|
31
|
+
expect(findByTestId(Text, 'crm-objectTypeId').text).toEqual('0-1');
|
|
32
|
+
});
|
|
33
|
+
it('should provide a generic mock implementation without CRM properties', () => {
|
|
34
|
+
const { render, findByTestId, maybeFindByTestId } = createRenderer('settings');
|
|
35
|
+
render(_jsx(MyComponent, {}));
|
|
36
|
+
expect(findByTestId(Text, 'location-text').text).toEqual('settings');
|
|
37
|
+
expect(findByTestId(Text, 'user-email').text).toEqual('fake_email@example.com');
|
|
38
|
+
expect(findByTestId(Text, 'user-firstName').text).toEqual('fake_firstName');
|
|
39
|
+
expect(findByTestId(Text, 'user-lastName').text).toEqual('fake_lastName');
|
|
40
|
+
expect(findByTestId(Text, 'portal-id').text).toEqual('123');
|
|
41
|
+
expect(findByTestId(Text, 'portal-timezone').text).toEqual('America/New_York');
|
|
42
|
+
// doesn't have the CRM properties
|
|
43
|
+
expect(maybeFindByTestId(Text, 'crm-objectId')).toBeNull();
|
|
44
|
+
expect(maybeFindByTestId(Text, 'crm-objectTypeId')).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
it('should use the mutated context', () => {
|
|
47
|
+
const newName = 'Gracie';
|
|
48
|
+
const { render, findByTestId, mocks } = createRenderer('settings');
|
|
49
|
+
mocks.context.user.firstName = newName;
|
|
50
|
+
render(_jsx(MyComponent, {}));
|
|
51
|
+
// no longer has the default context value, it has the mutate context
|
|
52
|
+
expect(findByTestId(Text, 'user-firstName').text).toEqual(newName);
|
|
53
|
+
// other properties stay the same
|
|
54
|
+
expect(findByTestId(Text, 'location-text').text).toEqual('settings');
|
|
55
|
+
});
|
|
56
|
+
it('should provide the ability to spy on the actions for the "crm.record.tab" extension point', () => {
|
|
57
|
+
const { mocks, render, findByTestId } = createRenderer('crm.record.tab');
|
|
58
|
+
const { actions } = mocks;
|
|
59
|
+
render(_jsx(MyActionsCrmComponent, {}));
|
|
60
|
+
findByTestId(Button, 'reloadButton').trigger('onClick');
|
|
61
|
+
expect(actions.reloadPage.callCount).toBe(1);
|
|
62
|
+
findByTestId(Button, 'fetchButton').trigger('onClick');
|
|
63
|
+
expect(actions.fetchCrmObjectProperties.callCount).toBe(1);
|
|
64
|
+
});
|
|
65
|
+
it('should provide the ability to spy on the actions for the "home" extension point', () => {
|
|
66
|
+
const { mocks, render, findByTestId } = createRenderer('home');
|
|
67
|
+
const { actions } = mocks;
|
|
68
|
+
render(_jsx(MyActionsHomeComponent, {}));
|
|
69
|
+
findByTestId(Button, 'copyButton').trigger('onClick');
|
|
70
|
+
expect(actions.copyTextToClipboard.callCount).toBe(1);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -69,5 +69,14 @@ export const createMockHooks = () => {
|
|
|
69
69
|
return mocks
|
|
70
70
|
.actions;
|
|
71
71
|
},
|
|
72
|
+
useExtensionApi: () => {
|
|
73
|
+
const mocks = useMocksContext();
|
|
74
|
+
return {
|
|
75
|
+
context: mocks.context,
|
|
76
|
+
actions: mocks
|
|
77
|
+
.actions,
|
|
78
|
+
runServerlessFunction: mocks.runServerlessFunction,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
72
81
|
};
|
|
73
82
|
};
|
|
@@ -4,6 +4,7 @@ import { HubSpotReactComponent, UnknownComponentProps } from '../../shared/types
|
|
|
4
4
|
import { useAssociations, useCrmProperties } from '../../crm/index.ts';
|
|
5
5
|
import { useExtensionContext } from '../../hooks/useExtensionContext.tsx';
|
|
6
6
|
import { useExtensionActions } from '../../hooks/useExtensionActions.tsx';
|
|
7
|
+
import { useExtensionApi } from '../../hooks/useExtensionApi.tsx';
|
|
7
8
|
import type { ElementMatcher, RenderedElementNode, RenderedFragmentNode, RenderedNode, RenderedParentNode, RenderedRootNode, RenderedTextNode } from '../types.ts';
|
|
8
9
|
export type RemoteChildNode = RemoteComponent<any, RemoteRoot> | RemoteText<RemoteRoot>;
|
|
9
10
|
export type RemoteParentNode = RemoteRoot | RemoteComponent<any, RemoteRoot> | RemoteFragment;
|
|
@@ -63,6 +64,7 @@ export interface RendererMockHooksInternal {
|
|
|
63
64
|
useAssociations: typeof useAssociations;
|
|
64
65
|
useExtensionContext: typeof useExtensionContext;
|
|
65
66
|
useExtensionActions: typeof useExtensionActions;
|
|
67
|
+
useExtensionApi: typeof useExtensionApi;
|
|
66
68
|
}
|
|
67
69
|
export type RendererMocksInternal<TExtensionPointLocationName extends ExtensionPointLocation = ExtensionPointLocation> = RendererMockHooksInternal & ExtensionPointApi<TExtensionPointLocationName>;
|
|
68
70
|
export type AnyFunction = (...args: any[]) => any;
|
package/dist/testing/types.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { useAssociations, useCrmProperties } from '../crm/index.ts';
|
|
|
6
6
|
import { useExtensionContext } from '../hooks/useExtensionContext.tsx';
|
|
7
7
|
import { AnyFunction } from './internal/types-internal.ts';
|
|
8
8
|
import { useExtensionActions } from '../hooks/useExtensionActions.tsx';
|
|
9
|
+
import { useExtensionApi } from '../hooks/useExtensionApi.tsx';
|
|
9
10
|
/**
|
|
10
11
|
* The type of a rendered node.
|
|
11
12
|
*/
|
|
@@ -297,6 +298,15 @@ export interface RendererSpies<TExtensionPointLocation extends ExtensionPointLoc
|
|
|
297
298
|
* information about spies and the supported API.
|
|
298
299
|
*/
|
|
299
300
|
useExtensionActions: FunctionSpy<typeof useExtensionActions>;
|
|
301
|
+
/**
|
|
302
|
+
* A spy for the `useExtensionApi` hook function.
|
|
303
|
+
* The spy can be used to track the calls to the hook function and also control the
|
|
304
|
+
* return result of the hook function.
|
|
305
|
+
*
|
|
306
|
+
* See the [tinyspy](https://github.com/tiny-spy/tinyspy) library for more
|
|
307
|
+
* information about spies and the supported API.
|
|
308
|
+
*/
|
|
309
|
+
useExtensionApi: FunctionSpy<typeof useExtensionApi>;
|
|
300
310
|
/**
|
|
301
311
|
* Mock context object that contains fake context data for the extension point API.
|
|
302
312
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/ui-extensions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"./crm": "./dist/crm/index.js",
|
|
29
29
|
"./pages/home": "./dist/pages/home/index.js",
|
|
30
30
|
"./experimental": "./dist/experimental/index.js",
|
|
31
|
-
"./testing": "./dist/testing/index.js"
|
|
31
|
+
"./testing": "./dist/testing/index.js",
|
|
32
|
+
"./hs-internal": "./dist/hs-internal/index.js"
|
|
32
33
|
},
|
|
33
34
|
"license": "MIT",
|
|
34
35
|
"dependencies": {
|
|
@@ -74,5 +75,5 @@
|
|
|
74
75
|
"tsd": {
|
|
75
76
|
"directory": "src/__tests__/test-d"
|
|
76
77
|
},
|
|
77
|
-
"gitHead": "
|
|
78
|
+
"gitHead": "3da4a07183d48528ef431d46a8d327c053c4fe94"
|
|
78
79
|
}
|