Skip to content

Commit

Permalink
feat: add null to several ref types
Browse files Browse the repository at this point in the history
  • Loading branch information
yf-yang committed Dec 8, 2024
1 parent d649052 commit 976fad9
Show file tree
Hide file tree
Showing 32 changed files with 70 additions and 50 deletions.
15 changes: 15 additions & 0 deletions .changeset/old-beds-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@udecode/react-utils': minor
'@udecode/plate-selection': minor
'@udecode/plate-combobox': minor
'@udecode/plate-floating': minor
'@udecode/plate-caption': minor
'@udecode/plate-heading': minor
'@udecode/plate-cursor': minor
'@udecode/plate-emoji': minor
'@udecode/cmdk': minor
'@udecode/plate-core': minor
'@udecode/plate-dnd': minor
---

feat: Add null to React.Ref related types to be compatible with react@19
2 changes: 1 addition & 1 deletion apps/www/content/docs/api/core/store.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ A unique ID used as a provider scope. Use it if you have multiple `Plate` in the
- **Default:** random id
</APIItem>

<APIItem name="containerRef" type="React.RefObject<HTMLDivElement>">
<APIItem name="containerRef" type="React.RefObject<HTMLDivElement | null>">
A reference to the editor container element.
</APIItem>

Expand Down
2 changes: 1 addition & 1 deletion apps/www/content/docs/api/resizable.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Provides resize behavior props and handlers for resizable elements.
Style props for the wrapper element:
- position: 'relative'
</APIItem>
<APIItem name="wrapperRef" type="React.RefObject<HTMLDivElement>">
<APIItem name="wrapperRef" type="React.RefObject<HTMLDivElement | null>">
Reference to the wrapper element.
</APIItem>
</APIReturns>
Expand Down
4 changes: 2 additions & 2 deletions apps/www/content/docs/combobox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ Hook for managing combobox input behavior and keyboard interactions.
<APIParameters>
<APIItem name="options" type="UseComboboxInputOptions">
<APISubList>
<APISubListItem parent="options" name="ref" type="RefObject<HTMLElement>">
<APISubListItem parent="options" name="ref" type="RefObject<HTMLElement | null>">
Reference to the input element.
</APISubListItem>
<APISubListItem parent="options" name="autoFocus" type="boolean" optional>
Expand Down Expand Up @@ -200,7 +200,7 @@ const MyCombobox = () => {
Hook for tracking cursor position in an HTML input element.

<APIParameters>
<APIItem name="ref" type="RefObject<HTMLInputElement>">
<APIItem name="ref" type="RefObject<HTMLInputElement | null>">
Reference to the input element to track.
</APIItem>
</APIParameters>
Expand Down
2 changes: 1 addition & 1 deletion apps/www/content/docs/font.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ A behavior hook for the color dropdown menu component.
A behavior hook for the color input component.

<APIReturns>
<APIItem name="inputRef" type="React.Ref<HTMLInputElement>">
<APIItem name="inputRef" type="React.Ref<HTMLInputElement | null>">
A ref object that should be assigned to the color input element.
</APIItem>
<APIItem name="childProps" type="function">
Expand Down
2 changes: 1 addition & 1 deletion apps/www/content/docs/media.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ A behavior hook for resizable elements.
</APIState>

<APIReturns>
<APIItem name="wrapperRef" type="React.RefObject<HTMLDivElement>">
<APIItem name="wrapperRef" type="React.RefObject<HTMLDivElement | null>">
A React reference to the outermost div wrapping the resizable element.
</APIItem>
<APIItem name="wrapperProps" type="React.HTMLAttributes<HTMLDivElement>">
Expand Down
6 changes: 3 additions & 3 deletions apps/www/content/docs/toc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ This hook manages the state for the TOC sidebar.
<APIItem name="setMouseInToc" type="React.Dispatch<React.SetStateAction<boolean>>">
Function to set whether the mouse is over the TOC.
</APIItem>
<APIItem name="tocRef" type="React.RefObject<HTMLElement>">
<APIItem name="tocRef" type="React.RefObject<HTMLElement | null>">
Ref for the TOC element.
</APIItem>
<APIItem name="onContentScroll" type="(options: { id: string; behavior?: ScrollBehavior; el: HTMLElement }) => void">
Expand Down Expand Up @@ -240,7 +240,7 @@ This hook provides props and handlers for the TOC sidebar component.
<APIItem name="setMouseInToc" type="React.Dispatch<React.SetStateAction<boolean>>">
Function to set whether the mouse is over the TOC.
</APIItem>
<APIItem name="tocRef" type="React.RefObject<HTMLElement>">
<APIItem name="tocRef" type="React.RefObject<HTMLElement | null>">
Ref for the TOC element.
</APIItem>
<APIItem name="onContentScroll" type="(options: { id: string; behavior?: ScrollBehavior; el: HTMLElement }) => void">
Expand All @@ -252,7 +252,7 @@ This hook provides props and handlers for the TOC sidebar component.
<APIItem name="navProps" type="object">
Props for the TOC navigation element.
<APISubList>
<APISubListItem parent="navProps" name="ref" type="React.RefObject<HTMLElement>">
<APISubListItem parent="navProps" name="ref" type="React.RefObject<HTMLElement | null>">
Ref for the TOC element.
</APISubListItem>
<APISubListItem parent="navProps" name="onMouseEnter" type="() => void">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},
"files": [
{
"content": "'use client';\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { TPlaceholderElement } from '@udecode/plate-media';\n\nimport { cn } from '@udecode/cn';\nimport {\n insertNodes,\n removeNodes,\n withoutSavingHistory,\n} from '@udecode/plate-common';\nimport {\n findNodePath,\n useEditorPlugin,\n withHOC,\n withRef,\n} from '@udecode/plate-common/react';\nimport {\n AudioPlugin,\n FilePlugin,\n ImagePlugin,\n PlaceholderPlugin,\n PlaceholderProvider,\n VideoPlugin,\n updateUploadHistory,\n} from '@udecode/plate-media/react';\nimport { AudioLines, FileUp, Film, ImageIcon } from 'lucide-react';\nimport { useFilePicker } from 'use-file-picker';\n\nimport { useUploadFile } from '@/lib/uploadthing';\n\nimport { PlateElement } from './plate-element';\nimport { Spinner } from './spinner';\n\nconst CONTENT: Record<\n string,\n {\n accept: string[];\n content: ReactNode;\n icon: ReactNode;\n }\n> = {\n [AudioPlugin.key]: {\n accept: ['audio/*'],\n content: 'Add an audio file',\n icon: <AudioLines />,\n },\n [FilePlugin.key]: {\n accept: ['*'],\n content: 'Add a file',\n icon: <FileUp />,\n },\n [ImagePlugin.key]: {\n accept: ['image/*'],\n content: 'Add an image',\n icon: <ImageIcon />,\n },\n [VideoPlugin.key]: {\n accept: ['video/*'],\n content: 'Add a video',\n icon: <Film />,\n },\n};\n\nexport const MediaPlaceholderElement = withHOC(\n PlaceholderProvider,\n withRef<typeof PlateElement>(\n ({ children, className, editor, nodeProps, ...props }, ref) => {\n const element = props.element as TPlaceholderElement;\n\n const { api } = useEditorPlugin(PlaceholderPlugin);\n\n const { isUploading, progress, uploadFile, uploadedFile, uploadingFile } =\n useUploadFile();\n\n const loading = isUploading && uploadingFile;\n\n const currentContent = CONTENT[element.mediaType];\n\n const isImage = element.mediaType === ImagePlugin.key;\n\n const imageRef = useRef<HTMLImageElement>(null);\n\n const { openFilePicker } = useFilePicker({\n accept: currentContent.accept,\n multiple: true,\n onFilesSelected: ({ plainFiles: updatedFiles }) => {\n const firstFile = updatedFiles[0];\n const restFiles = updatedFiles.slice(1);\n\n replaceCurrentPlaceholder(firstFile);\n\n restFiles.length > 0 && (editor as any).tf.insert.media(restFiles);\n },\n });\n\n const replaceCurrentPlaceholder = useCallback(\n (file: File) => {\n void uploadFile(file);\n api.placeholder.addUploadingFile(element.id as string, file);\n },\n [api.placeholder, element.id, uploadFile]\n );\n\n useEffect(() => {\n if (!uploadedFile) return;\n\n const path = findNodePath(editor, element);\n\n withoutSavingHistory(editor, () => {\n removeNodes(editor, { at: path });\n\n const node = {\n children: [{ text: '' }],\n initialHeight: imageRef.current?.height,\n initialWidth: imageRef.current?.width,\n isUpload: true,\n name: element.mediaType === FilePlugin.key ? uploadedFile.name : '',\n placeholderId: element.id as string,\n type: element.mediaType!,\n url: uploadedFile.url,\n };\n\n insertNodes(editor, node, { at: path });\n\n updateUploadHistory(editor, node);\n });\n\n api.placeholder.removeUploadingFile(element.id as string);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [uploadedFile, element.id]);\n\n // React dev mode will call useEffect twice\n const isReplaced = useRef(false);\n\n /** Paste and drop */\n useEffect(() => {\n if (isReplaced.current) return;\n\n isReplaced.current = true;\n const currentFiles = api.placeholder.getUploadingFile(\n element.id as string\n );\n\n if (!currentFiles) return;\n\n replaceCurrentPlaceholder(currentFiles);\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isReplaced]);\n\n return (\n <PlateElement\n ref={ref}\n className={cn('relative my-1', className)}\n editor={editor}\n {...props}\n >\n {(!loading || !isImage) && (\n <div\n className={cn(\n 'flex cursor-pointer select-none items-center rounded-sm bg-muted p-3 pr-9 hover:bg-primary/10'\n )}\n onClick={() => !loading && openFilePicker()}\n contentEditable={false}\n >\n <div className=\"relative mr-3 flex text-muted-foreground/80 [&_svg]:size-6\">\n {currentContent.icon}\n </div>\n <div className=\"whitespace-nowrap text-sm text-muted-foreground\">\n <div>\n {loading ? uploadingFile?.name : currentContent.content}\n </div>\n\n {loading && !isImage && (\n <div className=\"mt-1 flex items-center gap-1.5\">\n <div>{formatBytes(uploadingFile?.size ?? 0)}</div>\n <div>–</div>\n <div className=\"flex items-center\">\n <Spinner className=\"mr-1 size-3.5\" />\n {progress ?? 0}%\n </div>\n </div>\n )}\n </div>\n </div>\n )}\n\n {isImage && loading && (\n <ImageProgress\n file={uploadingFile}\n imageRef={imageRef}\n progress={progress}\n />\n )}\n\n {children}\n </PlateElement>\n );\n }\n )\n);\n\nexport function ImageProgress({\n className,\n file,\n imageRef,\n progress = 0,\n}: {\n file: File;\n className?: string;\n imageRef?: React.RefObject<HTMLImageElement>;\n progress?: number;\n}) {\n const [objectUrl, setObjectUrl] = useState<string | null>(null);\n\n useEffect(() => {\n const url = URL.createObjectURL(file);\n setObjectUrl(url);\n\n return () => {\n URL.revokeObjectURL(url);\n };\n }, [file]);\n\n if (!objectUrl) {\n return null;\n }\n\n return (\n <div className={cn('relative', className)} contentEditable={false}>\n <img\n ref={imageRef}\n className=\"h-auto w-full rounded-sm object-cover\"\n alt={file.name}\n src={objectUrl}\n />\n {progress < 100 && (\n <div className=\"absolute bottom-1 right-1 flex items-center space-x-2 rounded-full bg-black/50 px-1 py-0.5\">\n <Spinner />\n <span className=\"text-xs font-medium text-white\">\n {Math.round(progress)}%\n </span>\n </div>\n )}\n </div>\n );\n}\n\nexport function formatBytes(\n bytes: number,\n opts: {\n decimals?: number;\n sizeType?: 'accurate' | 'normal';\n } = {}\n) {\n const { decimals = 0, sizeType = 'normal' } = opts;\n\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];\n const accurateSizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB'];\n\n if (bytes === 0) return '0 Byte';\n\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\n\n return `${(bytes / Math.pow(1024, i)).toFixed(decimals)} ${\n sizeType === 'accurate'\n ? (accurateSizes[i] ?? 'Bytest')\n : (sizes[i] ?? 'Bytes')\n }`;\n}\n",
"content": "'use client';\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { TPlaceholderElement } from '@udecode/plate-media';\n\nimport { cn } from '@udecode/cn';\nimport {\n insertNodes,\n removeNodes,\n withoutSavingHistory,\n} from '@udecode/plate-common';\nimport {\n findNodePath,\n useEditorPlugin,\n withHOC,\n withRef,\n} from '@udecode/plate-common/react';\nimport {\n AudioPlugin,\n FilePlugin,\n ImagePlugin,\n PlaceholderPlugin,\n PlaceholderProvider,\n VideoPlugin,\n updateUploadHistory,\n} from '@udecode/plate-media/react';\nimport { AudioLines, FileUp, Film, ImageIcon } from 'lucide-react';\nimport { useFilePicker } from 'use-file-picker';\n\nimport { useUploadFile } from '@/lib/uploadthing';\n\nimport { PlateElement } from './plate-element';\nimport { Spinner } from './spinner';\n\nconst CONTENT: Record<\n string,\n {\n accept: string[];\n content: ReactNode;\n icon: ReactNode;\n }\n> = {\n [AudioPlugin.key]: {\n accept: ['audio/*'],\n content: 'Add an audio file',\n icon: <AudioLines />,\n },\n [FilePlugin.key]: {\n accept: ['*'],\n content: 'Add a file',\n icon: <FileUp />,\n },\n [ImagePlugin.key]: {\n accept: ['image/*'],\n content: 'Add an image',\n icon: <ImageIcon />,\n },\n [VideoPlugin.key]: {\n accept: ['video/*'],\n content: 'Add a video',\n icon: <Film />,\n },\n};\n\nexport const MediaPlaceholderElement = withHOC(\n PlaceholderProvider,\n withRef<typeof PlateElement>(\n ({ children, className, editor, nodeProps, ...props }, ref) => {\n const element = props.element as TPlaceholderElement;\n\n const { api } = useEditorPlugin(PlaceholderPlugin);\n\n const { isUploading, progress, uploadFile, uploadedFile, uploadingFile } =\n useUploadFile();\n\n const loading = isUploading && uploadingFile;\n\n const currentContent = CONTENT[element.mediaType];\n\n const isImage = element.mediaType === ImagePlugin.key;\n\n const imageRef = useRef<HTMLImageElement>(null);\n\n const { openFilePicker } = useFilePicker({\n accept: currentContent.accept,\n multiple: true,\n onFilesSelected: ({ plainFiles: updatedFiles }) => {\n const firstFile = updatedFiles[0];\n const restFiles = updatedFiles.slice(1);\n\n replaceCurrentPlaceholder(firstFile);\n\n restFiles.length > 0 && (editor as any).tf.insert.media(restFiles);\n },\n });\n\n const replaceCurrentPlaceholder = useCallback(\n (file: File) => {\n void uploadFile(file);\n api.placeholder.addUploadingFile(element.id as string, file);\n },\n [api.placeholder, element.id, uploadFile]\n );\n\n useEffect(() => {\n if (!uploadedFile) return;\n\n const path = findNodePath(editor, element);\n\n withoutSavingHistory(editor, () => {\n removeNodes(editor, { at: path });\n\n const node = {\n children: [{ text: '' }],\n initialHeight: imageRef.current?.height,\n initialWidth: imageRef.current?.width,\n isUpload: true,\n name: element.mediaType === FilePlugin.key ? uploadedFile.name : '',\n placeholderId: element.id as string,\n type: element.mediaType!,\n url: uploadedFile.url,\n };\n\n insertNodes(editor, node, { at: path });\n\n updateUploadHistory(editor, node);\n });\n\n api.placeholder.removeUploadingFile(element.id as string);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [uploadedFile, element.id]);\n\n // React dev mode will call useEffect twice\n const isReplaced = useRef(false);\n\n /** Paste and drop */\n useEffect(() => {\n if (isReplaced.current) return;\n\n isReplaced.current = true;\n const currentFiles = api.placeholder.getUploadingFile(\n element.id as string\n );\n\n if (!currentFiles) return;\n\n replaceCurrentPlaceholder(currentFiles);\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isReplaced]);\n\n return (\n <PlateElement\n ref={ref}\n className={cn('relative my-1', className)}\n editor={editor}\n {...props}\n >\n {(!loading || !isImage) && (\n <div\n className={cn(\n 'flex cursor-pointer select-none items-center rounded-sm bg-muted p-3 pr-9 hover:bg-primary/10'\n )}\n onClick={() => !loading && openFilePicker()}\n contentEditable={false}\n >\n <div className=\"relative mr-3 flex text-muted-foreground/80 [&_svg]:size-6\">\n {currentContent.icon}\n </div>\n <div className=\"whitespace-nowrap text-sm text-muted-foreground\">\n <div>\n {loading ? uploadingFile?.name : currentContent.content}\n </div>\n\n {loading && !isImage && (\n <div className=\"mt-1 flex items-center gap-1.5\">\n <div>{formatBytes(uploadingFile?.size ?? 0)}</div>\n <div>–</div>\n <div className=\"flex items-center\">\n <Spinner className=\"mr-1 size-3.5\" />\n {progress ?? 0}%\n </div>\n </div>\n )}\n </div>\n </div>\n )}\n\n {isImage && loading && (\n <ImageProgress\n file={uploadingFile}\n imageRef={imageRef}\n progress={progress}\n />\n )}\n\n {children}\n </PlateElement>\n );\n }\n )\n);\n\nexport function ImageProgress({\n className,\n file,\n imageRef,\n progress = 0,\n}: {\n file: File;\n className?: string;\n imageRef?: React.RefObject<HTMLImageElement | null>;\n progress?: number;\n}) {\n const [objectUrl, setObjectUrl] = useState<string | null>(null);\n\n useEffect(() => {\n const url = URL.createObjectURL(file);\n setObjectUrl(url);\n\n return () => {\n URL.revokeObjectURL(url);\n };\n }, [file]);\n\n if (!objectUrl) {\n return null;\n }\n\n return (\n <div className={cn('relative', className)} contentEditable={false}>\n <img\n ref={imageRef}\n className=\"h-auto w-full rounded-sm object-cover\"\n alt={file.name}\n src={objectUrl}\n />\n {progress < 100 && (\n <div className=\"absolute bottom-1 right-1 flex items-center space-x-2 rounded-full bg-black/50 px-1 py-0.5\">\n <Spinner />\n <span className=\"text-xs font-medium text-white\">\n {Math.round(progress)}%\n </span>\n </div>\n )}\n </div>\n );\n}\n\nexport function formatBytes(\n bytes: number,\n opts: {\n decimals?: number;\n sizeType?: 'accurate' | 'normal';\n } = {}\n) {\n const { decimals = 0, sizeType = 'normal' } = opts;\n\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];\n const accurateSizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB'];\n\n if (bytes === 0) return '0 Byte';\n\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\n\n return `${(bytes / Math.pow(1024, i)).toFixed(decimals)} ${\n sizeType === 'accurate'\n ? (accurateSizes[i] ?? 'Bytest')\n : (sizes[i] ?? 'Bytes')\n }`;\n}\n",
"path": "plate-ui/media-placeholder-element.tsx",
"target": "components/plate-ui/media-placeholder-element.tsx",
"type": "registry:ui"
Expand Down
2 changes: 1 addition & 1 deletion apps/www/src/components/block-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ type BlockViewerContext = {
dependencies: string[];
isLoading: boolean;
item: z.infer<typeof registryItemSchema> & { src?: string };
resizablePanelRef: React.RefObject<ImperativePanelHandle> | null;
resizablePanelRef: React.RefObject<ImperativePanelHandle | null> | null;
setActiveFile: (file: string) => void;
setView: (view: 'code' | 'preview') => void;
tree: ReturnType<typeof createFileTreeForRegistryItemFiles> | null;
Expand Down
Loading

0 comments on commit 976fad9

Please sign in to comment.