app-tutor-ai-consumer 1.8.1 → 1.8.2
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/CHANGELOG.md +2 -0
- package/config/rspack/rspack.config.js +4 -6
- package/config/rspack/utils/plugins.js +2 -2
- package/package.json +1 -1
- package/src/development-bootstrap.tsx +16 -1
- package/src/index.tsx +21 -11
- package/src/lib/contexts/index.ts +1 -0
- package/src/lib/contexts/shared-ref/index.ts +1 -0
- package/src/lib/contexts/shared-ref/shared-ref.tsx +26 -0
- package/src/main/main.spec.tsx +6 -3
- package/src/modules/global-providers/global-providers.tsx +5 -0
- package/src/modules/messages/components/messages-list/messages-list.tsx +12 -12
- package/src/modules/sparkie/service.ts +1 -1
- package/src/modules/widget/components/page-layout/constants.tsx +8 -0
- package/src/modules/widget/components/page-layout/index.ts +1 -0
- package/src/modules/widget/components/page-layout/page-layout.tsx +18 -3
- package/src/modules/widget/components/scroll-to-bottom-button/scroll-to-bottom-button.tsx +3 -4
- package/src/modules/widget/events.ts +31 -6
- package/src/modules/widget/store/widget-tabs.atom.ts +2 -2
- package/src/modules/widget/types.ts +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
## [1.8.2](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.8.1...v1.8.2) (2025-07-16)
|
|
2
|
+
|
|
1
3
|
## [1.8.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.8.0...v1.8.1) (2025-07-15)
|
|
2
4
|
|
|
3
5
|
# [1.8.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.7.0...v1.8.0) (2025-07-14)
|
|
@@ -11,6 +11,7 @@ require('dotenv').config({
|
|
|
11
11
|
})
|
|
12
12
|
|
|
13
13
|
module.exports = async function (env) {
|
|
14
|
+
const standaloneMode = env?.local
|
|
14
15
|
const productionMode = env?.production || env?.staging
|
|
15
16
|
const releaseFullName = `app-tutor-ai-consumer_v${packageJSON.version}`
|
|
16
17
|
const fileVersion = `${productionMode ? 'prod' : 'dev'}-${packageJSON?.version}`
|
|
@@ -23,7 +24,7 @@ module.exports = async function (env) {
|
|
|
23
24
|
*/
|
|
24
25
|
const config = {
|
|
25
26
|
mode: productionMode ? 'production' : 'development',
|
|
26
|
-
entry:
|
|
27
|
+
entry: standaloneMode ? paths.DEV_BOOTSTRAP : paths.INDEX,
|
|
27
28
|
...(productionMode ? {} : require('./utils/devserver.config')),
|
|
28
29
|
optimization: {
|
|
29
30
|
usedExports: true,
|
|
@@ -159,17 +160,14 @@ module.exports = async function (env) {
|
|
|
159
160
|
new rspack.DefinePlugin({
|
|
160
161
|
'process.env.PROJECT_VERSION': JSON.stringify(packageJSON.version),
|
|
161
162
|
'process.env.TARGET_ENV': JSON.stringify(process.env.NODE_ENV),
|
|
162
|
-
'process.env.RELEASE_FULL_NAME': JSON.stringify(releaseFullName)
|
|
163
|
+
'process.env.RELEASE_FULL_NAME': JSON.stringify(releaseFullName),
|
|
164
|
+
'process.env.STANDALONE_MODE': JSON.stringify(standaloneMode)
|
|
163
165
|
}),
|
|
164
166
|
new rspack.ProgressPlugin(),
|
|
165
167
|
new TsCheckerRspackPlugin(),
|
|
166
168
|
new rspack.HtmlRspackPlugin({
|
|
167
169
|
template: path.resolve(paths.PUBLIC, 'index.html'),
|
|
168
170
|
favicon: path.resolve(paths.PUBLIC, 'favicon.ico')
|
|
169
|
-
}),
|
|
170
|
-
new rspack.CssExtractRspackPlugin({
|
|
171
|
-
filename: productionMode ? `app-tutor-ai-consumer.css` : '[name].[contenthash].css',
|
|
172
|
-
chunkFilename: productionMode ? '[id].[contenthash].css' : '[id].[contenthash].css'
|
|
173
171
|
})
|
|
174
172
|
].concat(await getEnvironmentPlugins(!productionMode)),
|
|
175
173
|
watchOptions: {
|
|
@@ -27,8 +27,8 @@ async function getEnvironmentPlugins(isDevelopment) {
|
|
|
27
27
|
return [
|
|
28
28
|
new rspack.EnvironmentPlugin(allEnvs),
|
|
29
29
|
new rspack.CssExtractRspackPlugin({
|
|
30
|
-
filename: '
|
|
31
|
-
chunkFilename: '[id]
|
|
30
|
+
filename: 'app-tutor-ai-consumer.css',
|
|
31
|
+
chunkFilename: '[id].[contenthash].css'
|
|
32
32
|
}),
|
|
33
33
|
new CompressionPlugin({
|
|
34
34
|
algorithm: 'gzip'
|
package/package.json
CHANGED
|
@@ -5,10 +5,25 @@ import { v4 } from 'uuid'
|
|
|
5
5
|
import { LANGUAGES } from './config/i18n'
|
|
6
6
|
import { devMode } from './lib/utils'
|
|
7
7
|
|
|
8
|
+
const rootId = 'app-tutor-ai-widget'
|
|
9
|
+
|
|
8
10
|
if (devMode) {
|
|
9
11
|
window.TOKEN = process.env.TOKEN ?? ''
|
|
10
12
|
void (async () => {
|
|
11
|
-
|
|
13
|
+
// TODO: Remove after sidebar implementation
|
|
14
|
+
// Add Local popup css config
|
|
15
|
+
const root = document.getElementById('c3po-app-widget')
|
|
16
|
+
const container = document.createElement('div')
|
|
17
|
+
|
|
18
|
+
container.setAttribute('id', rootId)
|
|
19
|
+
container.setAttribute(
|
|
20
|
+
'class',
|
|
21
|
+
'bg-ai-dark fixed bottom-5 right-5 w-[27rem] h-[min(43rem,calc(100vh-2.5rem))] max-h-[calc(100vh-2.5rem)] z-10 rounded-[0.625rem] border border-neutral-800 shadow-lg overflow-hidden flex flex-col'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
root?.appendChild(container)
|
|
25
|
+
|
|
26
|
+
await window.startChatWidget(rootId, {
|
|
12
27
|
hotmartToken: window.TOKEN,
|
|
13
28
|
locale: LANGUAGES.PT_BR,
|
|
14
29
|
conversationId: '21506473-a93c-4b38-9c32-68a5ca37ce73', // OWNER
|
package/src/index.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import { createRoot } from 'react-dom/client'
|
|
|
7
7
|
import { initDayjs } from './config/dayjs'
|
|
8
8
|
import { initLanguage } from './config/i18n'
|
|
9
9
|
import { initAxios } from './config/request/api'
|
|
10
|
-
import { productionMode } from './lib/utils'
|
|
10
|
+
import { devMode, productionMode } from './lib/utils'
|
|
11
11
|
import { Main } from './main'
|
|
12
12
|
import { SparkieService } from './modules/sparkie'
|
|
13
13
|
import { TutorWidgetEvents, TutorWidgetEventTypes } from './modules/widget'
|
|
@@ -33,24 +33,34 @@ window.startChatWidget = async (
|
|
|
33
33
|
elementId = 'tutor-chat-app-widget',
|
|
34
34
|
settings: WidgetSettingProps
|
|
35
35
|
) => {
|
|
36
|
-
|
|
36
|
+
if (!devMode) {
|
|
37
|
+
loadMainStyles()
|
|
38
|
+
}
|
|
37
39
|
|
|
38
40
|
const rootElement = document.getElementById(elementId) as HTMLElement
|
|
39
41
|
const root = createRoot(rootElement)
|
|
40
42
|
|
|
41
|
-
await SparkieService.initSparkie({
|
|
42
|
-
token: settings.hotmartToken,
|
|
43
|
-
skipPresenceSetup: true,
|
|
44
|
-
retryOptions: {
|
|
45
|
-
maxRetries: 5,
|
|
46
|
-
retryDelay: 2000,
|
|
47
|
-
backoffMultiplier: 1.5
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
43
|
initAxios(settings.hotmartToken)
|
|
51
44
|
await initLanguage(settings.locale)
|
|
52
45
|
await initDayjs(settings.locale)
|
|
53
46
|
|
|
47
|
+
try {
|
|
48
|
+
await SparkieService.initSparkie({
|
|
49
|
+
token: settings?.hotmartToken,
|
|
50
|
+
skipPresenceSetup: true,
|
|
51
|
+
retryOptions: {
|
|
52
|
+
maxRetries: 5,
|
|
53
|
+
retryDelay: 2000,
|
|
54
|
+
backoffMultiplier: 1.5
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
await SparkieService.ensureInitialized()
|
|
58
|
+
TutorWidgetEvents.get(TutorWidgetEventTypes.LOADED)?.dispatch()
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(error)
|
|
61
|
+
TutorWidgetEvents.get(TutorWidgetEventTypes.LOADED)?.dispatch({ detail: { isSuccess: false } })
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
if (root)
|
|
55
65
|
root.render(
|
|
56
66
|
<StrictMode>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './shared-ref'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as createSharedRefContext } from './shared-ref'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createContext, useContext, useRef } from 'react'
|
|
2
|
+
import type { PropsWithChildren, RefObject } from 'react'
|
|
3
|
+
|
|
4
|
+
function createSharedRefContext<T extends HTMLElement>() {
|
|
5
|
+
const SharedRefContext = createContext<RefObject<T | null> | null>(null)
|
|
6
|
+
|
|
7
|
+
const SharedRefContextProvider = ({ children }: PropsWithChildren) => {
|
|
8
|
+
const sharedRef = useRef<T>(null)
|
|
9
|
+
return <SharedRefContext.Provider value={sharedRef}>{children}</SharedRefContext.Provider>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const useSharedRefContext = () => {
|
|
13
|
+
const ctx = useContext(SharedRefContext)
|
|
14
|
+
|
|
15
|
+
if (!ctx) throw new Error('SharedRefContext must be used inside a SharedRefContextProvider')
|
|
16
|
+
return ctx
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
SharedRefContext,
|
|
21
|
+
SharedRefContextProvider,
|
|
22
|
+
useSharedRefContext
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default createSharedRefContext
|
package/src/main/main.spec.tsx
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { chance, render, screen, waitFor } from '@/config/tests'
|
|
2
|
+
import { useWidgetSettingsAtom } from '@/src/modules/widget/store/widget-settings.atom'
|
|
2
3
|
import WidgetSettingPropsBuilder from '../modules/widget/__tests__/widget-settings-props.builder'
|
|
3
|
-
import { useWidgetSettingsAtom } from '../modules/widget/store/widget-settings.atom'
|
|
4
4
|
import { Main } from '.'
|
|
5
5
|
|
|
6
|
-
vi.mock('
|
|
6
|
+
vi.mock('@/src/modules/widget/store/widget-settings.atom', async (importOriginal) => ({
|
|
7
|
+
...(await importOriginal()),
|
|
8
|
+
useWidgetSettingsAtom: vi.fn()
|
|
9
|
+
}))
|
|
7
10
|
|
|
8
11
|
describe('Main', () => {
|
|
9
12
|
const defaultProps = new WidgetSettingPropsBuilder()
|
|
@@ -20,7 +23,7 @@ describe('Main', () => {
|
|
|
20
23
|
renderComponent({ settings: props })
|
|
21
24
|
|
|
22
25
|
await waitFor(() => {
|
|
23
|
-
expect(screen.getByText(/
|
|
26
|
+
expect(screen.getByText(/send/i)).toBeInTheDocument()
|
|
24
27
|
})
|
|
25
28
|
})
|
|
26
29
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type PropsWithChildren, useEffect } from 'react'
|
|
2
|
+
import { v4 } from 'uuid'
|
|
2
3
|
|
|
3
4
|
import { OptimizelyProvider } from '@/src/config/optimizely'
|
|
4
5
|
import { QueryProvider } from '@/src/config/tanstack'
|
|
@@ -13,6 +14,10 @@ function GlobalProviders({ children, settings }: GlobalProvidersProps) {
|
|
|
13
14
|
useEffect(() => {
|
|
14
15
|
if (!settings || !Object.keys(settings)?.length) return
|
|
15
16
|
|
|
17
|
+
if (!settings?.sessionId) {
|
|
18
|
+
settings.sessionId = v4()
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
setWidgetSettings(settings)
|
|
17
22
|
}, [setWidgetSettings, settings])
|
|
18
23
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { lazy, useCallback, useRef } from 'react'
|
|
2
2
|
import clsx from 'clsx'
|
|
3
|
+
import { createPortal } from 'react-dom'
|
|
3
4
|
|
|
4
|
-
import { useWidgetLoadingAtomValue } from '@/src/modules/widget'
|
|
5
|
+
import { usePageLayoutMainRefContext, useWidgetLoadingAtomValue } from '@/src/modules/widget'
|
|
5
6
|
import { useAllMessages, useManageScroll } from '../../hooks'
|
|
6
7
|
import { useSkeletonRef } from '../../hooks/use-skeleton-ref'
|
|
7
8
|
import { MessageItem } from '../message-item'
|
|
@@ -26,6 +27,7 @@ function MessagesList() {
|
|
|
26
27
|
const widgetIsLoading = useWidgetLoadingAtomValue()
|
|
27
28
|
const skeletonRef = useSkeletonRef()
|
|
28
29
|
const { showScrollButton } = useManageScroll(scrollerRef)
|
|
30
|
+
const mainLayoutRef = usePageLayoutMainRefContext()
|
|
29
31
|
|
|
30
32
|
const scrollToBottom = useCallback(() => {
|
|
31
33
|
const { current: scroller } = scrollerRef
|
|
@@ -39,23 +41,21 @@ function MessagesList() {
|
|
|
39
41
|
}, [])
|
|
40
42
|
|
|
41
43
|
return (
|
|
42
|
-
<div ref={scrollerRef} className='
|
|
44
|
+
<div ref={scrollerRef} className='mx-2 my-4 flex flex-col gap-2 overflow-auto px-4'>
|
|
43
45
|
<MessageItemLoading show={messagesQuery.isFetching} />
|
|
44
46
|
|
|
45
47
|
<MessageItemEndOfScroll
|
|
46
48
|
show={!messagesQuery.isFetching && !messagesQuery.hasNextPage && allMessages.length > 0}
|
|
47
49
|
/>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
{mainLayoutRef.current &&
|
|
51
|
+
createPortal(
|
|
52
|
+
<ScrollToBottomButton
|
|
53
|
+
ref={scrollToButtonRef}
|
|
54
|
+
show={showScrollButton}
|
|
55
|
+
onClick={scrollToBottom}
|
|
56
|
+
/>,
|
|
57
|
+
mainLayoutRef.current
|
|
55
58
|
)}
|
|
56
|
-
show={showScrollButton}
|
|
57
|
-
onClick={scrollToBottom}
|
|
58
|
-
/>
|
|
59
59
|
|
|
60
60
|
{allMessages?.map(([publishingDate, messages], i) => (
|
|
61
61
|
<div key={i} className='flex flex-1 flex-col justify-center gap-6'>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createSharedRefContext } from '@/src/lib/contexts'
|
|
2
|
+
|
|
3
|
+
const { useSharedRefContext, SharedRefContext, SharedRefContextProvider } =
|
|
4
|
+
createSharedRefContext<HTMLDivElement>()
|
|
5
|
+
|
|
6
|
+
export const usePageLayoutMainRefContext = useSharedRefContext
|
|
7
|
+
export const PageLayoutMainRefContext = SharedRefContext
|
|
8
|
+
export const PageLayoutMainRefContextProvider = SharedRefContextProvider
|
|
@@ -1,18 +1,25 @@
|
|
|
1
|
+
import { type PropsWithChildren, type ReactNode } from 'react'
|
|
1
2
|
import clsx from 'clsx'
|
|
2
|
-
|
|
3
|
+
|
|
4
|
+
import { PageLayoutMainRefContextProvider, usePageLayoutMainRefContext } from './constants'
|
|
3
5
|
|
|
4
6
|
export type PageLayoutProps = PropsWithChildren<{
|
|
5
7
|
asideChild?: ReactNode
|
|
6
8
|
className?: string
|
|
7
9
|
}>
|
|
10
|
+
|
|
8
11
|
function PageLayout({ asideChild, children, className }: PageLayoutProps) {
|
|
12
|
+
const mainLayoutRef = usePageLayoutMainRefContext()
|
|
13
|
+
|
|
9
14
|
return (
|
|
10
15
|
<div
|
|
11
16
|
className={clsx(
|
|
12
17
|
'grid-areas-[main_aside] grid h-full min-h-0 w-full grid-cols-1 grid-rows-[1fr_auto]',
|
|
13
18
|
className
|
|
14
19
|
)}>
|
|
15
|
-
<div
|
|
20
|
+
<div
|
|
21
|
+
ref={mainLayoutRef}
|
|
22
|
+
className='grid-area-[main] relative flex min-h-0 flex-col overflow-y-auto overflow-x-hidden'>
|
|
16
23
|
{children}
|
|
17
24
|
</div>
|
|
18
25
|
{asideChild && (
|
|
@@ -24,4 +31,12 @@ function PageLayout({ asideChild, children, className }: PageLayoutProps) {
|
|
|
24
31
|
)
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
|
|
34
|
+
function PageLayoutWrapper(props: PageLayoutProps) {
|
|
35
|
+
return (
|
|
36
|
+
<PageLayoutMainRefContextProvider>
|
|
37
|
+
<PageLayout {...props} />
|
|
38
|
+
</PageLayoutMainRefContextProvider>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default PageLayoutWrapper
|
|
@@ -10,14 +10,13 @@ export interface IScrollToBottomButtonProps
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const ScrollToBottomButton = forwardRef<HTMLButtonElement, IScrollToBottomButtonProps>(
|
|
13
|
-
({ show = false,
|
|
13
|
+
({ show = false, onClick, className, ...props }, ref) => (
|
|
14
14
|
<button
|
|
15
15
|
{...props}
|
|
16
|
-
style={isNaN(Number(top)) ? undefined : { top }}
|
|
17
16
|
ref={ref}
|
|
18
17
|
className={clsx(
|
|
19
|
-
'
|
|
20
|
-
{ 'pointer-events-none opacity-0': !show },
|
|
18
|
+
'absolute bottom-4 left-1/2 flex size-7 cursor-pointer flex-col items-center justify-center rounded-full bg-neutral-600 text-sm text-neutral-50 outline-none transition-colors duration-300 ease-in hover:scale-110 hover:bg-neutral-700 focus:outline-none focus:ring-neutral-500 focus:ring-offset-2 focus-visible:ring-2 active:ring-2',
|
|
19
|
+
{ 'opacity-85': show, 'pointer-events-none opacity-0': !show },
|
|
21
20
|
className
|
|
22
21
|
)}
|
|
23
22
|
onClick={onClick}
|
|
@@ -1,21 +1,46 @@
|
|
|
1
1
|
import type { ITutorWidgetEvent } from './types'
|
|
2
2
|
|
|
3
3
|
export const TutorWidgetEventTypes = {
|
|
4
|
-
OPEN: '
|
|
5
|
-
CLOSE: '
|
|
4
|
+
OPEN: 'c3po-app-widget-open',
|
|
5
|
+
CLOSE: 'c3po-app-widget-close',
|
|
6
|
+
LOADED: 'tutor-app-widget-loaded'
|
|
6
7
|
} as const
|
|
7
8
|
|
|
8
|
-
const TutorWidgetEventsList
|
|
9
|
+
const TutorWidgetEventsList = [
|
|
9
10
|
{
|
|
10
11
|
name: TutorWidgetEventTypes.OPEN,
|
|
11
12
|
handler: () => () => undefined,
|
|
12
|
-
dispatch: () =>
|
|
13
|
+
dispatch: () => {
|
|
14
|
+
window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.OPEN))
|
|
15
|
+
}
|
|
13
16
|
},
|
|
14
17
|
{
|
|
15
18
|
name: TutorWidgetEventTypes.CLOSE,
|
|
16
19
|
handler: () => () => undefined,
|
|
17
|
-
dispatch: () =>
|
|
18
|
-
|
|
20
|
+
dispatch: () => {
|
|
21
|
+
window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.CLOSE))
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: TutorWidgetEventTypes.LOADED,
|
|
26
|
+
handler: (callback) => {
|
|
27
|
+
const listener: EventListener = (e) => {
|
|
28
|
+
const evt = e as CustomEvent<{ isSuccess: boolean }>
|
|
29
|
+
|
|
30
|
+
console.log(evt.detail)
|
|
31
|
+
|
|
32
|
+
callback(evt.detail)
|
|
33
|
+
}
|
|
34
|
+
window.addEventListener(TutorWidgetEventTypes.LOADED, listener)
|
|
35
|
+
|
|
36
|
+
return () => {
|
|
37
|
+
window.removeEventListener(TutorWidgetEventTypes.LOADED, listener)
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
dispatch: (payload = { detail: { isSuccess: true } }) => {
|
|
41
|
+
window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.LOADED, payload))
|
|
42
|
+
}
|
|
43
|
+
} as ITutorWidgetEvent<{ isSuccess: boolean }>
|
|
19
44
|
] as const
|
|
20
45
|
|
|
21
46
|
export const TutorWidgetEvents = new Map(TutorWidgetEventsList.map((e) => [e.name, e]))
|
|
@@ -8,8 +8,8 @@ export type WidgetTabsProps = {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const INITIAL_PROPS: WidgetTabsProps = {
|
|
11
|
-
currentTab: '
|
|
12
|
-
history: new Set(['
|
|
11
|
+
currentTab: 'chat',
|
|
12
|
+
history: new Set(['chat'])
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const widgetTabsAtom = atom<WidgetTabsProps>(INITIAL_PROPS)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TutorWidgetEventTypes } from './events'
|
|
2
2
|
|
|
3
|
-
export type ITutorWidgetEvent = {
|
|
3
|
+
export type ITutorWidgetEvent<T = unknown> = {
|
|
4
4
|
name: (typeof TutorWidgetEventTypes)[keyof typeof TutorWidgetEventTypes]
|
|
5
|
-
handler: (
|
|
6
|
-
dispatch:
|
|
5
|
+
handler: (callback: (payload: T) => void) => () => void
|
|
6
|
+
dispatch: (payload?: CustomEventInit<T>) => void
|
|
7
7
|
}
|