mirror of https://github.com/buster-so/buster.git
Fix tests
This commit is contained in:
parent
4023985bd6
commit
20d7c179b0
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { markdownToPlatejs } from './markdown-to-platejs';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('markdownToPlatejs', () => {
|
||||
it('should convert elaborate markdown to platejs', async () => {
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
import { MarkdownPlugin, remarkMdx, remarkMention } from '@platejs/markdown';
|
||||
import { createSlateEditor } from 'platejs';
|
||||
import { AutoformatPlugin } from '@platejs/autoformat';
|
||||
import { ReportElementsSchema } from '@buster/server-shared/reports';
|
||||
import { AutoformatPlugin } from '@platejs/autoformat';
|
||||
import {
|
||||
BaseBasicBlocksPlugin,
|
||||
BaseBasicMarksPlugin,
|
||||
BaseBlockquotePlugin,
|
||||
BaseBoldPlugin,
|
||||
BaseItalicPlugin,
|
||||
BaseCodePlugin,
|
||||
BaseH1Plugin,
|
||||
BaseH2Plugin,
|
||||
BaseH3Plugin,
|
||||
BaseH4Plugin,
|
||||
BaseH5Plugin,
|
||||
BaseH6Plugin,
|
||||
BaseBasicBlocksPlugin,
|
||||
BaseBasicMarksPlugin,
|
||||
BaseBlockquotePlugin,
|
||||
BaseCodePlugin,
|
||||
BaseHeadingPlugin,
|
||||
BaseHighlightPlugin,
|
||||
BaseHorizontalRulePlugin,
|
||||
BaseItalicPlugin,
|
||||
BaseKbdPlugin,
|
||||
BaseStrikethroughPlugin,
|
||||
BaseSubscriptPlugin,
|
||||
BaseSuperscriptPlugin,
|
||||
BaseUnderlinePlugin
|
||||
BaseUnderlinePlugin,
|
||||
} from '@platejs/basic-nodes';
|
||||
import { z } from 'zod';
|
||||
import { MarkdownPlugin, remarkMdx, remarkMention } from '@platejs/markdown';
|
||||
import { createSlateEditor } from 'platejs';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { z } from 'zod';
|
||||
|
||||
const serverNode = [
|
||||
BaseBoldPlugin,
|
||||
|
@ -51,13 +51,13 @@ const serverNode = [
|
|||
AutoformatPlugin,
|
||||
MarkdownPlugin.configure({
|
||||
options: {
|
||||
remarkPlugins: [remarkGfm, remarkMdx, remarkMention]
|
||||
}
|
||||
})
|
||||
remarkPlugins: [remarkGfm, remarkMdx, remarkMention],
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const SERVER_EDITOR = createSlateEditor({
|
||||
plugins: serverNode
|
||||
plugins: serverNode,
|
||||
});
|
||||
|
||||
export const markdownToPlatejs = async (markdown: string) => {
|
||||
|
|
|
@ -53,7 +53,7 @@ export const BlockDiscussion: RenderNodeWrapper<AnyPluginConfig> = (props) => {
|
|||
return;
|
||||
}
|
||||
|
||||
return (props) => (
|
||||
const Component = (props: PlateElementProps) => (
|
||||
<BlockCommentContent
|
||||
blockPath={blockPath}
|
||||
commentNodes={commentNodes}
|
||||
|
@ -62,6 +62,10 @@ export const BlockDiscussion: RenderNodeWrapper<AnyPluginConfig> = (props) => {
|
|||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
Component.displayName = 'BlockDiscussion';
|
||||
|
||||
return Component;
|
||||
};
|
||||
|
||||
const BlockCommentContent = ({
|
||||
|
|
|
@ -61,7 +61,10 @@ export const BlockDraggable: RenderNodeWrapper = ({ editor, element, path }) =>
|
|||
if (!enabled) return;
|
||||
|
||||
// Return a function that renders the Draggable component
|
||||
return (props) => <Draggable {...props} />;
|
||||
const Component = (props: PlateElementProps) => <Draggable {...props} />;
|
||||
Component.displayName = 'BlockDraggable';
|
||||
|
||||
return Component;
|
||||
};
|
||||
|
||||
function Draggable(props: PlateElementProps) {
|
||||
|
|
|
@ -27,7 +27,10 @@ const config: Record<
|
|||
export const BlockList: RenderNodeWrapper = (props) => {
|
||||
if (!props.element.listStyleType) return;
|
||||
|
||||
return (props) => <List {...props} />;
|
||||
const Component = (props: PlateElementProps) => <List {...props} />;
|
||||
Component.displayName = 'BlockList';
|
||||
|
||||
return Component;
|
||||
};
|
||||
|
||||
function List(props: PlateElementProps) {
|
||||
|
|
|
@ -339,8 +339,8 @@ export const useResolveSuggestion = (
|
|||
|
||||
let newText = '';
|
||||
let text = '';
|
||||
let properties: any = {};
|
||||
let newProperties: any = {};
|
||||
let properties = {};
|
||||
let newProperties = {};
|
||||
|
||||
// overlapping suggestion
|
||||
entries.forEach(([node]) => {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { getCommentKey, getDraftCommentKey } from '@platejs/comment';
|
|||
import { CommentPlugin, useCommentId } from '@platejs/comment/react';
|
||||
import dayjs from 'dayjs';
|
||||
import { ArrowUp, Check, Dots, Pencil, Trash, Xmark } from '@/components/ui/icons';
|
||||
import { type Value, KEYS, nanoid, NodeApi } from 'platejs';
|
||||
import { type TElement, type Value, KEYS, nanoid, NodeApi } from 'platejs';
|
||||
import {
|
||||
Plate,
|
||||
useEditorPlugin,
|
||||
|
@ -363,7 +363,7 @@ function CommentMoreDropdown(props: {
|
|||
|
||||
const useCommentEditor = (
|
||||
options: Omit<CreatePlateEditorOptions, 'plugins'> = {},
|
||||
deps: any[] = []
|
||||
deps: Value[] = []
|
||||
) => {
|
||||
const commentEditor = usePlateEditor(
|
||||
{
|
||||
|
|
|
@ -18,7 +18,7 @@ export function LinkElement(props: PlateElementProps<TLinkElement>) {
|
|||
className="text-primary decoration-primary font-medium underline underline-offset-4"
|
||||
attributes={{
|
||||
...props.attributes,
|
||||
...(linkProps as any)
|
||||
...linkProps
|
||||
}}>
|
||||
{props.children}
|
||||
</PlateElement>
|
||||
|
|
|
@ -64,7 +64,7 @@ export const PlaceholderElement = withHOC(
|
|||
const { openFilePicker } = useFilePicker({
|
||||
accept: currentContent.accept,
|
||||
multiple: true,
|
||||
onFilesSelected: (data: { plainFiles?: File[]; errors?: any[] }) => {
|
||||
onFilesSelected: (data: { plainFiles?: File[]; errors?: [] }) => {
|
||||
if (!data.plainFiles || data.plainFiles.length === 0) return;
|
||||
|
||||
const updatedFiles = data.plainFiles;
|
||||
|
@ -204,6 +204,7 @@ export function ImageProgress({
|
|||
|
||||
return (
|
||||
<div className={cn('relative', className)} contentEditable={false}>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
ref={imageRef as React.RefObject<HTMLImageElement>}
|
||||
className="h-auto w-full rounded-sm object-cover"
|
||||
|
|
|
@ -419,13 +419,13 @@ export function TableRowElement(props: PlateElementProps<TTableRowElement>) {
|
|||
);
|
||||
}
|
||||
|
||||
function RowDragHandle({ dragRef }: { dragRef: React.Ref<any> }) {
|
||||
function RowDragHandle({ dragRef }: { dragRef: React.Ref<HTMLElement | HTMLButtonElement> }) {
|
||||
const editor = useEditorRef();
|
||||
const element = useElement();
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={dragRef}
|
||||
ref={dragRef as React.Ref<HTMLButtonElement>}
|
||||
variant="outlined"
|
||||
className={cn(
|
||||
'absolute top-1/2 left-0 z-51 h-6 w-4 -translate-y-1/2 p-0 focus-visible:ring-0 focus-visible:ring-offset-0',
|
||||
|
@ -539,7 +539,7 @@ export function TableCellElement({
|
|||
className={cn(
|
||||
'bg-ring absolute top-0 z-30 hidden h-full w-1',
|
||||
'right-[-1.5px]',
|
||||
columnResizeVariants({ colIndex: colIndex as any })
|
||||
columnResizeVariants({ colIndex: colIndex as 1 })
|
||||
)}
|
||||
/>
|
||||
{colIndex === 0 && (
|
||||
|
|
|
@ -270,7 +270,7 @@ type TooltipProps<T extends React.ElementType> = {
|
|||
} & React.ComponentProps<T>;
|
||||
|
||||
function withTooltip<T extends React.ElementType>(Component: T) {
|
||||
const ExtendComponent = React.forwardRef<any, TooltipProps<T>>(
|
||||
const ExtendComponent = React.forwardRef<HTMLElement, TooltipProps<T>>(
|
||||
({ tooltip, tooltipContentProps, tooltipProps, tooltipTriggerProps, ...props }, ref) => {
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ export const VideoElement = withHOC(
|
|||
src={unsafeUrl as string}
|
||||
width="100%"
|
||||
controls
|
||||
onError={(error: any) => console.error(error)}
|
||||
onError={(error) => console.error(error)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -10,9 +10,9 @@ export const AlignKit = [
|
|||
defaultNodeValue: 'start',
|
||||
nodeKey: 'align',
|
||||
styleKey: 'textAlign',
|
||||
validNodeValues: ['start', 'left', 'center', 'right', 'end', 'justify'],
|
||||
validNodeValues: ['start', 'left', 'center', 'right', 'end', 'justify']
|
||||
},
|
||||
targetPlugins: [...KEYS.heading, KEYS.p, KEYS.img, KEYS.mediaEmbed],
|
||||
},
|
||||
}),
|
||||
targetPlugins: [...KEYS.heading, KEYS.p, KEYS.img, KEYS.mediaEmbed]
|
||||
}
|
||||
})
|
||||
];
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
autoformatMath,
|
||||
AutoformatPlugin,
|
||||
autoformatPunctuation,
|
||||
autoformatSmartQuotes,
|
||||
autoformatSmartQuotes
|
||||
} from '@platejs/autoformat';
|
||||
import { insertEmptyCodeBlock } from '@platejs/code-block';
|
||||
import { toggleList } from '@platejs/list';
|
||||
|
@ -19,110 +19,110 @@ const autoformatMarks: AutoformatRule[] = [
|
|||
{
|
||||
match: '***',
|
||||
mode: 'mark',
|
||||
type: [KEYS.bold, KEYS.italic],
|
||||
type: [KEYS.bold, KEYS.italic]
|
||||
},
|
||||
{
|
||||
match: '__*',
|
||||
mode: 'mark',
|
||||
type: [KEYS.underline, KEYS.italic],
|
||||
type: [KEYS.underline, KEYS.italic]
|
||||
},
|
||||
{
|
||||
match: '__**',
|
||||
mode: 'mark',
|
||||
type: [KEYS.underline, KEYS.bold],
|
||||
type: [KEYS.underline, KEYS.bold]
|
||||
},
|
||||
{
|
||||
match: '___***',
|
||||
mode: 'mark',
|
||||
type: [KEYS.underline, KEYS.bold, KEYS.italic],
|
||||
type: [KEYS.underline, KEYS.bold, KEYS.italic]
|
||||
},
|
||||
{
|
||||
match: '**',
|
||||
mode: 'mark',
|
||||
type: KEYS.bold,
|
||||
type: KEYS.bold
|
||||
},
|
||||
{
|
||||
match: '__',
|
||||
mode: 'mark',
|
||||
type: KEYS.underline,
|
||||
type: KEYS.underline
|
||||
},
|
||||
{
|
||||
match: '*',
|
||||
mode: 'mark',
|
||||
type: KEYS.italic,
|
||||
type: KEYS.italic
|
||||
},
|
||||
{
|
||||
match: '_',
|
||||
mode: 'mark',
|
||||
type: KEYS.italic,
|
||||
type: KEYS.italic
|
||||
},
|
||||
{
|
||||
match: '~~',
|
||||
mode: 'mark',
|
||||
type: KEYS.strikethrough,
|
||||
type: KEYS.strikethrough
|
||||
},
|
||||
{
|
||||
match: '^',
|
||||
mode: 'mark',
|
||||
type: KEYS.sup,
|
||||
type: KEYS.sup
|
||||
},
|
||||
{
|
||||
match: '~',
|
||||
mode: 'mark',
|
||||
type: KEYS.sub,
|
||||
type: KEYS.sub
|
||||
},
|
||||
{
|
||||
match: '==',
|
||||
mode: 'mark',
|
||||
type: KEYS.highlight,
|
||||
type: KEYS.highlight
|
||||
},
|
||||
{
|
||||
match: '≡',
|
||||
mode: 'mark',
|
||||
type: KEYS.highlight,
|
||||
type: KEYS.highlight
|
||||
},
|
||||
{
|
||||
match: '`',
|
||||
mode: 'mark',
|
||||
type: KEYS.code,
|
||||
},
|
||||
type: KEYS.code
|
||||
}
|
||||
];
|
||||
|
||||
const autoformatBlocks: AutoformatRule[] = [
|
||||
{
|
||||
match: '# ',
|
||||
mode: 'block',
|
||||
type: KEYS.h1,
|
||||
type: KEYS.h1
|
||||
},
|
||||
{
|
||||
match: '## ',
|
||||
mode: 'block',
|
||||
type: KEYS.h2,
|
||||
type: KEYS.h2
|
||||
},
|
||||
{
|
||||
match: '### ',
|
||||
mode: 'block',
|
||||
type: KEYS.h3,
|
||||
type: KEYS.h3
|
||||
},
|
||||
{
|
||||
match: '#### ',
|
||||
mode: 'block',
|
||||
type: KEYS.h4,
|
||||
type: KEYS.h4
|
||||
},
|
||||
{
|
||||
match: '##### ',
|
||||
mode: 'block',
|
||||
type: KEYS.h5,
|
||||
type: KEYS.h5
|
||||
},
|
||||
{
|
||||
match: '###### ',
|
||||
mode: 'block',
|
||||
type: KEYS.h6,
|
||||
type: KEYS.h6
|
||||
},
|
||||
{
|
||||
match: '> ',
|
||||
mode: 'block',
|
||||
type: KEYS.blockquote,
|
||||
type: KEYS.blockquote
|
||||
},
|
||||
{
|
||||
match: '```',
|
||||
|
@ -131,9 +131,9 @@ const autoformatBlocks: AutoformatRule[] = [
|
|||
format: (editor) => {
|
||||
insertEmptyCodeBlock(editor, {
|
||||
defaultType: KEYS.p,
|
||||
insertNodesOptions: { select: true },
|
||||
insertNodesOptions: { select: true }
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
// {
|
||||
// match: '+ ',
|
||||
|
@ -149,10 +149,10 @@ const autoformatBlocks: AutoformatRule[] = [
|
|||
editor.tf.setNodes({ type: KEYS.hr });
|
||||
editor.tf.insertNodes({
|
||||
children: [{ text: '' }],
|
||||
type: KEYS.p,
|
||||
type: KEYS.p
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const autoformatLists: AutoformatRule[] = [
|
||||
|
@ -162,9 +162,9 @@ const autoformatLists: AutoformatRule[] = [
|
|||
type: 'list',
|
||||
format: (editor) => {
|
||||
toggleList(editor, {
|
||||
listStyleType: KEYS.ul,
|
||||
listStyleType: KEYS.ul
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
match: [String.raw`^\d+\.$ `, String.raw`^\d+\)$ `],
|
||||
|
@ -174,9 +174,9 @@ const autoformatLists: AutoformatRule[] = [
|
|||
format: (editor, { matchString }) => {
|
||||
toggleList(editor, {
|
||||
listRestartPolite: Number(matchString) || 1,
|
||||
listStyleType: KEYS.ol,
|
||||
listStyleType: KEYS.ol
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
match: ['[] '],
|
||||
|
@ -184,13 +184,13 @@ const autoformatLists: AutoformatRule[] = [
|
|||
type: 'list',
|
||||
format: (editor) => {
|
||||
toggleList(editor, {
|
||||
listStyleType: KEYS.listTodo,
|
||||
listStyleType: KEYS.listTodo
|
||||
});
|
||||
editor.tf.setNodes({
|
||||
checked: false,
|
||||
listStyleType: KEYS.listTodo,
|
||||
listStyleType: KEYS.listTodo
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
match: ['[x] '],
|
||||
|
@ -198,14 +198,14 @@ const autoformatLists: AutoformatRule[] = [
|
|||
type: 'list',
|
||||
format: (editor) => {
|
||||
toggleList(editor, {
|
||||
listStyleType: KEYS.listTodo,
|
||||
listStyleType: KEYS.listTodo
|
||||
});
|
||||
editor.tf.setNodes({
|
||||
checked: true,
|
||||
listStyleType: KEYS.listTodo,
|
||||
listStyleType: KEYS.listTodo
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export const AutoformatKit = [
|
||||
|
@ -221,16 +221,16 @@ export const AutoformatKit = [
|
|||
...autoformatLegalHtml,
|
||||
...autoformatArrow,
|
||||
...autoformatMath,
|
||||
...autoformatLists,
|
||||
...autoformatLists
|
||||
].map(
|
||||
(rule): AutoformatRule => ({
|
||||
...rule,
|
||||
query: (editor) =>
|
||||
!editor.api.some({
|
||||
match: { type: editor.getType(KEYS.codeBlock) },
|
||||
}),
|
||||
match: { type: editor.getType(KEYS.codeBlock) }
|
||||
})
|
||||
})
|
||||
),
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
];
|
||||
|
|
|
@ -9,11 +9,11 @@ export const BlockPlaceholderKit = [
|
|||
className:
|
||||
'before:absolute before:cursor-text before:opacity-30 before:content-[attr(placeholder)]',
|
||||
placeholders: {
|
||||
[KEYS.p]: 'Type something...',
|
||||
[KEYS.p]: 'Type something...'
|
||||
},
|
||||
query: ({ path }) => {
|
||||
return path.length === 1;
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
})
|
||||
];
|
||||
|
|
|
@ -6,16 +6,10 @@ import { KEYS } from 'platejs';
|
|||
export const IndentKit = [
|
||||
IndentPlugin.configure({
|
||||
inject: {
|
||||
targetPlugins: [
|
||||
...KEYS.heading,
|
||||
KEYS.p,
|
||||
KEYS.blockquote,
|
||||
KEYS.codeBlock,
|
||||
KEYS.toggle,
|
||||
],
|
||||
targetPlugins: [...KEYS.heading, KEYS.p, KEYS.blockquote, KEYS.codeBlock, KEYS.toggle]
|
||||
},
|
||||
options: {
|
||||
offset: 24,
|
||||
},
|
||||
}),
|
||||
offset: 24
|
||||
}
|
||||
})
|
||||
];
|
||||
|
|
|
@ -88,7 +88,7 @@ export const suggestionPlugin = toTPlatePlugin<SuggestionConfig>(
|
|||
|
||||
const suggestionLineBreakPlugin = createPlatePlugin({
|
||||
key: 'suggestionLineBreak',
|
||||
render: { belowNodes: SuggestionLineBreak as any }
|
||||
render: { belowNodes: SuggestionLineBreak }
|
||||
});
|
||||
|
||||
export const SuggestionKit = [suggestionPlugin, suggestionLineBreakPlugin];
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
import { renderHook } from '@testing-library/react';
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { useIsTouchDevice } from './useIsTouchDevice';
|
||||
|
||||
// Mock window.matchMedia
|
||||
const mockMatchMedia = vi.fn();
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: mockMatchMedia
|
||||
});
|
||||
|
||||
describe('useIsTouchDevice', () => {
|
||||
beforeEach(() => {
|
||||
// Reset all mocks before each test
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Reset navigator properties
|
||||
Object.defineProperty(navigator, 'maxTouchPoints', {
|
||||
writable: true,
|
||||
value: 0
|
||||
});
|
||||
|
||||
// Reset window properties
|
||||
delete (window as any).ontouchstart;
|
||||
});
|
||||
|
||||
it('should return true when device has touch support via ontouchstart', () => {
|
||||
// Test case: Device supports touch via ontouchstart property
|
||||
// Expected output: true
|
||||
Object.defineProperty(window, 'ontouchstart', {
|
||||
value: null,
|
||||
writable: true
|
||||
});
|
||||
|
||||
mockMatchMedia.mockReturnValue({ matches: false });
|
||||
|
||||
const { result } = renderHook(() => useIsTouchDevice());
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when device has maxTouchPoints > 0', () => {
|
||||
// Test case: Device supports touch via navigator.maxTouchPoints
|
||||
// Expected output: true
|
||||
Object.defineProperty(navigator, 'maxTouchPoints', {
|
||||
writable: true,
|
||||
value: 1
|
||||
});
|
||||
|
||||
mockMatchMedia.mockReturnValue({ matches: false });
|
||||
|
||||
const { result } = renderHook(() => useIsTouchDevice());
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when media query matches coarse pointer', () => {
|
||||
// Test case: Device supports touch via pointer: coarse media query
|
||||
// Expected output: true
|
||||
mockMatchMedia.mockReturnValue({ matches: true });
|
||||
|
||||
const { result } = renderHook(() => useIsTouchDevice());
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
expect(mockMatchMedia).toHaveBeenCalledWith('(pointer: coarse)');
|
||||
});
|
||||
|
||||
it('should return false when no touch support is detected', () => {
|
||||
// Test case: Device has no touch support detected by any method
|
||||
// Expected output: false
|
||||
mockMatchMedia.mockReturnValue({ matches: false });
|
||||
|
||||
const { result } = renderHook(() => useIsTouchDevice());
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle missing matchMedia gracefully', () => {
|
||||
// Test case: Browser doesn't support matchMedia (older browsers)
|
||||
// Expected output: false (no touch support detected)
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: undefined
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useIsTouchDevice());
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
});
|
||||
});
|
|
@ -15,8 +15,7 @@ export const useIsTouchDevice = (): boolean => {
|
|||
// Multiple detection methods for better accuracy
|
||||
const hasTouchStart = 'ontouchstart' in window;
|
||||
const hasMaxTouchPoints = navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
|
||||
const hasMsMaxTouchPoints =
|
||||
(navigator as any).msMaxTouchPoints && (navigator as any).msMaxTouchPoints > 0;
|
||||
const hasMsMaxTouchPoints = navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
|
||||
|
||||
// Match media query for touch devices
|
||||
const hasCoarsePointer = window.matchMedia && window.matchMedia('(pointer: coarse)').matches;
|
||||
|
|
Loading…
Reference in New Issue