@tldraw/editor
Version:
tldraw infinite canvas SDK (editor).
103 lines (92 loc) • 3.93 kB
text/typescript
import { atom } from '@tldraw/state'
import { getGlobalWindow } from '../utils/dom'
/**
* An object that contains information about the current device and environment.
* This object is not reactive and will not update automatically when the environment changes,
* so only include values that are fixed, such as the user's browser and operating system.
*
* @public
*/
const tlenv = {
isSafari: false,
isIos: false,
isChromeForIos: false,
isFirefox: false,
isAndroid: false,
isWebview: false,
isDarwin: false,
hasCanvasSupport: false,
}
let isForcedFinePointer = false
if (typeof window !== 'undefined') {
if ('navigator' in window) {
tlenv.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
tlenv.isIos = !!navigator.userAgent.match(/iPad/i) || !!navigator.userAgent.match(/iPhone/i)
tlenv.isChromeForIos = /crios.*safari/i.test(navigator.userAgent)
tlenv.isFirefox = /firefox/i.test(navigator.userAgent)
tlenv.isAndroid = /android/i.test(navigator.userAgent)
tlenv.isDarwin = getGlobalWindow().navigator.userAgent.toLowerCase().indexOf('mac') > -1
}
tlenv.hasCanvasSupport = 'Promise' in window && 'HTMLCanvasElement' in window
isForcedFinePointer = tlenv.isFirefox && !tlenv.isAndroid && !tlenv.isIos
}
/**
* An atom that contains information about the current device and environment.
* This object is reactive and will update automatically when the environment changes.
* Use it for values that may change over time, such as the pointer type.
*
* @public
*/
const tlenvReactive = atom('tlenvReactive', {
// Whether the user's device has a coarse pointer. This is dynamic on many systems, especially
// on touch-screen laptops, which will become "coarse" if the user touches the screen.
// See https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/pointer#coarse
isCoarsePointer: false,
// Whether the user's display supports P3 color space. This is dynamic because a window can
// move between displays with different color gamut support.
supportsP3ColorSpace: false,
})
if (typeof window !== 'undefined') {
const canRenderP3 = typeof CSS !== 'undefined' && CSS.supports('color', 'color(display-p3 1 1 1)')
if (canRenderP3) {
const p3mql = window.matchMedia('(color-gamut: p3)')
const updateSupportsP3 = () => {
const supportsP3 = p3mql.matches
if (supportsP3 !== tlenvReactive.__unsafe__getWithoutCapture().supportsP3ColorSpace) {
tlenvReactive.update((prev) => ({ ...prev, supportsP3ColorSpace: supportsP3 }))
}
}
updateSupportsP3()
p3mql.addEventListener('change', updateSupportsP3)
}
}
if (typeof window !== 'undefined' && !isForcedFinePointer) {
const mql = getGlobalWindow().matchMedia && getGlobalWindow().matchMedia('(any-pointer: coarse)')
const isCurrentCoarsePointer = () => tlenvReactive.__unsafe__getWithoutCapture().isCoarsePointer
if (mql) {
// 1. Update the coarse pointer automatically when the media query changes
const updateIsCoarsePointer = () => {
const isCoarsePointer = mql.matches
if (isCoarsePointer !== isCurrentCoarsePointer()) {
tlenvReactive.update((prev) => ({ ...prev, isCoarsePointer: isCoarsePointer }))
}
}
updateIsCoarsePointer()
mql.addEventListener('change', updateIsCoarsePointer)
}
// 2. Also update the coarse pointer state when a pointer down event occurs. We need `capture: true`
// here because the tldraw component itself stops propagation on pointer events it receives.
getGlobalWindow().addEventListener(
'pointerdown',
(e: PointerEvent) => {
// when the user interacts with a mouse, we assume they have a fine pointer.
// otherwise, we assume they have a coarse pointer.
const isCoarseEvent = e.pointerType !== 'mouse'
if (isCoarseEvent !== isCurrentCoarsePointer()) {
tlenvReactive.update((prev) => ({ ...prev, isCoarsePointer: isCoarseEvent }))
}
},
{ capture: true }
)
}
export { tlenv, tlenvReactive }