@codeleap/web 3.21.1 → 3.21.3
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/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { onUpdate, useCallback, useDefaultComponentStyle, useState, useValidate } from '@codeleap/common'
|
|
2
3
|
import { BubbleMenu, FloatingMenu, EditorContent } from '@tiptap/react'
|
|
3
|
-
import { FileInput, View } from '../components'
|
|
4
|
+
import { FileInput, Text, View } from '../components'
|
|
4
5
|
import { TextEditorProps } from './types'
|
|
5
6
|
import { TextEditorPresets } from './styles'
|
|
6
|
-
|
|
7
7
|
export * from './styles'
|
|
8
8
|
export * from './types'
|
|
9
9
|
|
|
@@ -19,13 +19,43 @@ export const TextEditor = (props: TextEditorProps) => {
|
|
|
19
19
|
floatingMenuProps,
|
|
20
20
|
toolbarComponent,
|
|
21
21
|
fileInputRef,
|
|
22
|
+
_error,
|
|
23
|
+
validate,
|
|
22
24
|
} = props
|
|
25
|
+
|
|
26
|
+
const [_isFocused, setIsFocused] = useState(false)
|
|
27
|
+
const validation = useValidate(editor?.getText() ?? '', validate)
|
|
28
|
+
|
|
29
|
+
const hasError = !validation.isValid || _error
|
|
30
|
+
const errorMessage = validation.message || _error
|
|
31
|
+
const isDisabled = !editor?.isEditable ?? null
|
|
32
|
+
|
|
23
33
|
const variantStyles = useDefaultComponentStyle<'u:TextEditor', typeof TextEditorPresets>('u:TextEditor', {
|
|
24
34
|
variants,
|
|
25
35
|
styles,
|
|
26
36
|
responsiveVariants,
|
|
27
37
|
})
|
|
28
38
|
|
|
39
|
+
const handleBlur = React.useCallback(() => {
|
|
40
|
+
validation?.onInputBlurred()
|
|
41
|
+
setIsFocused(false)
|
|
42
|
+
}, [validation?.onInputBlurred])
|
|
43
|
+
|
|
44
|
+
const handleFocus = React.useCallback(() => {
|
|
45
|
+
validation?.onInputFocused()
|
|
46
|
+
setIsFocused(true)
|
|
47
|
+
}, [validation?.onInputFocused])
|
|
48
|
+
|
|
49
|
+
onUpdate(() => {
|
|
50
|
+
editor?.on('blur', handleBlur)
|
|
51
|
+
editor?.on('focus', handleFocus)
|
|
52
|
+
|
|
53
|
+
return () => {
|
|
54
|
+
editor?.off('blur', handleBlur)
|
|
55
|
+
editor?.off('focus', handleFocus)
|
|
56
|
+
}
|
|
57
|
+
}, [editor, handleBlur, handleFocus])
|
|
58
|
+
|
|
29
59
|
const _BubbleMenu = useCallback(() => {
|
|
30
60
|
return (
|
|
31
61
|
<BubbleMenu css={[variantStyles.bubbleMenu]} {...bubbleMenuProps} editor={editor}>
|
|
@@ -46,14 +76,30 @@ export const TextEditor = (props: TextEditorProps) => {
|
|
|
46
76
|
)
|
|
47
77
|
}, [editor])
|
|
48
78
|
|
|
79
|
+
const editorStyles = [
|
|
80
|
+
variantStyles.editor,
|
|
81
|
+
hasError && variantStyles['editor:error'],
|
|
82
|
+
isDisabled && variantStyles['editor:disabled'],
|
|
83
|
+
]
|
|
84
|
+
|
|
49
85
|
if (!editor) return null
|
|
50
86
|
return (
|
|
51
|
-
<View
|
|
87
|
+
<View
|
|
88
|
+
css={[
|
|
89
|
+
variantStyles.wrapper,
|
|
90
|
+
hasError && variantStyles['wrapper:error'],
|
|
91
|
+
{
|
|
92
|
+
'.tiptap': editorStyles,
|
|
93
|
+
},
|
|
94
|
+
style,
|
|
95
|
+
]}
|
|
96
|
+
>
|
|
52
97
|
{toolbarComponent}
|
|
53
98
|
{children}
|
|
54
|
-
<_BubbleMenu/>
|
|
55
|
-
<_FloatingMenu/>
|
|
56
|
-
<EditorContent editor={editor}/>
|
|
99
|
+
<_BubbleMenu />
|
|
100
|
+
<_FloatingMenu />
|
|
101
|
+
<EditorContent editor={editor} />
|
|
102
|
+
{hasError ? <Text text={errorMessage} css={variantStyles['errorMessage:error']} /> : null}
|
|
57
103
|
<FileInput
|
|
58
104
|
ref={fileInputRef}
|
|
59
105
|
/>
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { createDefaultVariantFactory, includePresets } from '@codeleap/common'
|
|
2
2
|
|
|
3
|
-
export type
|
|
3
|
+
export type TextEditorStates = 'error' | 'disabled'
|
|
4
|
+
export type TextEditorParts = 'wrapper' | 'editor' | 'floatingMenu' | 'bubbleMenu' | 'bubbleMenuInnerWrapper' | 'floatingMenuInnerWrapper' | 'errorMessage'
|
|
5
|
+
|
|
6
|
+
export type TextEditorComposition = `${TextEditorParts}:${TextEditorStates}` | TextEditorParts
|
|
4
7
|
|
|
5
8
|
const createTextEditorStyle = createDefaultVariantFactory<TextEditorComposition>()
|
|
6
9
|
|
|
7
|
-
export const TextEditorPresets = includePresets((styles) => createTextEditorStyle(() => ({
|
|
10
|
+
export const TextEditorPresets = includePresets((styles) => createTextEditorStyle(() => ({
|
|
11
|
+
wrapper: styles,
|
|
12
|
+
})))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RefObject } from 'react'
|
|
2
|
-
import { ComponentVariants, StylesOf } from '@codeleap/common'
|
|
2
|
+
import { ComponentVariants, FormTypes, StylesOf, yup } from '@codeleap/common'
|
|
3
3
|
import { BubbleMenuProps, Editor, FloatingMenuProps } from '@tiptap/react'
|
|
4
4
|
import { FileInputRef } from '../FileInput'
|
|
5
5
|
import { TextEditorComposition, TextEditorPresets } from './styles'
|
|
@@ -8,8 +8,10 @@ export type TextEditorProps = React.PropsWithChildren<{
|
|
|
8
8
|
editor: Editor
|
|
9
9
|
styles?: StylesOf<TextEditorComposition>
|
|
10
10
|
style?: React.CSSProperties
|
|
11
|
-
bubbleMenuProps?: BubbleMenuProps & {renderContent: React.ReactNode}
|
|
12
|
-
floatingMenuProps?: FloatingMenuProps & {renderContent: React.ReactNode}
|
|
11
|
+
bubbleMenuProps?: BubbleMenuProps & { renderContent: React.ReactNode }
|
|
12
|
+
floatingMenuProps?: FloatingMenuProps & { renderContent: React.ReactNode }
|
|
13
13
|
toolbarComponent?: JSX.Element
|
|
14
14
|
fileInputRef?: RefObject<FileInputRef>
|
|
15
|
+
_error?: boolean
|
|
16
|
+
validate?: FormTypes.ValidatorWithoutForm<string> | yup.SchemaOf<string>
|
|
15
17
|
} & ComponentVariants<typeof TextEditorPresets>>
|
package/src/lib/hooks.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { AnyFunction, onMount, onUpdate, range, useUncontrolled } from '@codeleap/common'
|
|
2
|
-
import { useCallback, useMemo, useRef, useState } from 'react'
|
|
1
|
+
import { AnyFunction, onMount, onUpdate, range, TypeGuards, useUncontrolled } from '@codeleap/common'
|
|
2
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
|
3
3
|
import { v4 } from 'uuid'
|
|
4
4
|
import { easeInOut, EasingFunction, AnimationProps, useAnimate, useAnimation, animate } from 'framer-motion'
|
|
5
|
+
import { globalHistory } from '@reach/router'
|
|
5
6
|
|
|
6
7
|
export function useWindowSize() {
|
|
7
8
|
const [size, setSize] = useState([])
|
|
@@ -352,3 +353,76 @@ export function useAnimatedVariantStyles<T extends Record<string|number|symbol,
|
|
|
352
353
|
|
|
353
354
|
return animated
|
|
354
355
|
}
|
|
356
|
+
|
|
357
|
+
type UseWindowFocusOptions = {
|
|
358
|
+
onFocus?: AnyFunction
|
|
359
|
+
onBlur?: AnyFunction
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export const useWindowFocus = (options: UseWindowFocusOptions = {}, deps: Array<any> = []): boolean => {
|
|
363
|
+
const [focused, setFocused] = useState(true)
|
|
364
|
+
|
|
365
|
+
const onFocus = () => {
|
|
366
|
+
setFocused(true)
|
|
367
|
+
if (TypeGuards.isFunction(options?.onFocus)) options?.onFocus()
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const onBlur = () => {
|
|
371
|
+
setFocused(false)
|
|
372
|
+
if (TypeGuards.isFunction(options?.onBlur)) options?.onBlur()
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
window.addEventListener('focus', onFocus)
|
|
377
|
+
window.addEventListener('blur', onBlur)
|
|
378
|
+
|
|
379
|
+
return () => {
|
|
380
|
+
window.removeEventListener('focus', onFocus)
|
|
381
|
+
window.removeEventListener('blur', onBlur)
|
|
382
|
+
}
|
|
383
|
+
}, deps)
|
|
384
|
+
|
|
385
|
+
return focused
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export const usePageExitBlocker = (
|
|
389
|
+
handler: (willLeavePage: boolean) => void,
|
|
390
|
+
deps: Array<any> = [],
|
|
391
|
+
message: string = 'Are you sure you want to leave?'
|
|
392
|
+
) => {
|
|
393
|
+
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
|
394
|
+
if (!event) return null
|
|
395
|
+
|
|
396
|
+
event?.preventDefault()
|
|
397
|
+
event.returnValue = ''
|
|
398
|
+
return
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
React.useEffect(() => {
|
|
402
|
+
if (!window) return null
|
|
403
|
+
|
|
404
|
+
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
405
|
+
|
|
406
|
+
return () => {
|
|
407
|
+
window.removeEventListener('beforeunload', handleBeforeUnload)
|
|
408
|
+
}
|
|
409
|
+
}, deps)
|
|
410
|
+
|
|
411
|
+
React.useEffect(() => {
|
|
412
|
+
return globalHistory.listen((args) => {
|
|
413
|
+
if (!window) return null
|
|
414
|
+
|
|
415
|
+
const historyPathname = args?.location?.pathname
|
|
416
|
+
const windowPathname = window?.location?.pathname
|
|
417
|
+
|
|
418
|
+
const isPopAction = args?.action === 'POP'
|
|
419
|
+
const isLeaveAction = args?.action === 'PUSH' && !historyPathname?.includes(windowPathname)
|
|
420
|
+
|
|
421
|
+
if (isLeaveAction || isPopAction) {
|
|
422
|
+
const willLeavePage = window.confirm(message)
|
|
423
|
+
|
|
424
|
+
handler?.(willLeavePage)
|
|
425
|
+
}
|
|
426
|
+
})
|
|
427
|
+
}, deps)
|
|
428
|
+
}
|