@tldraw/utils 4.5.0-next.dc46682213a8 → 4.5.1
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/dist-cjs/index.d.ts +1 -0
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/media/media.js +18 -8
- package/dist-cjs/lib/media/media.js.map +2 -2
- package/dist-cjs/lib/version.js +13 -0
- package/dist-cjs/lib/version.js.map +2 -2
- package/dist-esm/index.d.mts +1 -0
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/media/media.mjs +18 -8
- package/dist-esm/lib/media/media.mjs.map +2 -2
- package/dist-esm/lib/version.mjs +13 -0
- package/dist-esm/lib/version.mjs.map +2 -2
- package/package.json +1 -1
- package/src/lib/media/media.ts +20 -12
- package/src/lib/version.test.ts +20 -4
- package/src/lib/version.ts +19 -0
package/dist-cjs/index.d.ts
CHANGED
package/dist-cjs/index.js
CHANGED
|
@@ -169,7 +169,7 @@ var import_version2 = require("./lib/version");
|
|
|
169
169
|
var import_warn = require("./lib/warn");
|
|
170
170
|
(0, import_version.registerTldrawLibraryVersion)(
|
|
171
171
|
"@tldraw/utils",
|
|
172
|
-
"4.5.
|
|
172
|
+
"4.5.1",
|
|
173
173
|
"cjs"
|
|
174
174
|
);
|
|
175
175
|
//# sourceMappingURL=index.js.map
|
|
@@ -237,21 +237,31 @@ class MediaHelpers {
|
|
|
237
237
|
if (physChunk) {
|
|
238
238
|
const physData = import_png.PngHelpers.parsePhys(view, physChunk.dataOffset);
|
|
239
239
|
if (physData.unit === 1 && physData.ppux === physData.ppuy) {
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
240
|
+
const dpi = Math.round(physData.ppux * 0.0254);
|
|
241
|
+
const r96 = dpi / 96;
|
|
242
|
+
const r72 = dpi / 72;
|
|
243
|
+
let pixelRatio = 1;
|
|
244
|
+
if (Number.isInteger(r96) && r96 > 1) {
|
|
245
|
+
pixelRatio = r96;
|
|
246
|
+
} else if (Number.isInteger(r72) && r72 > 1) {
|
|
247
|
+
pixelRatio = r72;
|
|
248
|
+
}
|
|
249
|
+
if (pixelRatio > 1) {
|
|
250
|
+
return {
|
|
251
|
+
w: Math.ceil(w / pixelRatio),
|
|
252
|
+
h: Math.ceil(h / pixelRatio),
|
|
253
|
+
pixelRatio
|
|
254
|
+
};
|
|
255
|
+
}
|
|
246
256
|
}
|
|
247
257
|
}
|
|
248
258
|
}
|
|
249
259
|
}
|
|
250
260
|
} catch (err) {
|
|
251
261
|
console.error(err);
|
|
252
|
-
return { w, h };
|
|
262
|
+
return { w, h, pixelRatio: 1 };
|
|
253
263
|
}
|
|
254
|
-
return { w, h };
|
|
264
|
+
return { w, h, pixelRatio: 1 };
|
|
255
265
|
}
|
|
256
266
|
/**
|
|
257
267
|
* Check if a media file blob contains animation data.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/media/media.ts"],
|
|
4
|
-
"sourcesContent": ["import { promiseWithResolve } from '../control'\nimport { Image } from '../network'\nimport { isApngAnimated } from './apng'\nimport { isAvifAnimated } from './avif'\nimport { isGifAnimated } from './gif'\nimport { PngHelpers } from './png'\nimport { isWebpAnimated } from './webp'\n\n/**\n * Array of supported vector image MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isSvg = DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES.includes('image/svg+xml')\n * console.log(isSvg) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES = Object.freeze(['image/svg+xml' as const])\n/**\n * Array of supported static (non-animated) image MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isStatic = DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES.includes('image/jpeg')\n * console.log(isStatic) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES = Object.freeze([\n\t'image/jpeg' as const,\n\t'image/png' as const,\n\t'image/webp' as const,\n])\n/**\n * Array of supported animated image MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isAnimated = DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES.includes('image/gif')\n * console.log(isAnimated) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES = Object.freeze([\n\t'image/gif' as const,\n\t'image/apng' as const,\n\t'image/avif' as const,\n])\n/**\n * Array of all supported image MIME types, combining static, vector, and animated types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isSupported = DEFAULT_SUPPORTED_IMAGE_TYPES.includes('image/png')\n * console.log(isSupported) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_IMAGE_TYPES = Object.freeze([\n\t...DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES,\n\t...DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES,\n\t...DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES,\n])\n/**\n * Array of supported video MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORT_VIDEO_TYPES } from '@tldraw/utils'\n *\n * const isVideo = DEFAULT_SUPPORT_VIDEO_TYPES.includes('video/mp4')\n * console.log(isVideo) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORT_VIDEO_TYPES = Object.freeze([\n\t'video/mp4' as const,\n\t'video/webm' as const,\n\t'video/quicktime' as const,\n])\n/**\n * Array of all supported media MIME types, combining images and videos.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_MEDIA_TYPES } from '@tldraw/utils'\n *\n * const isMediaFile = DEFAULT_SUPPORTED_MEDIA_TYPES.includes('video/mp4')\n * console.log(isMediaFile) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_MEDIA_TYPES = Object.freeze([\n\t...DEFAULT_SUPPORTED_IMAGE_TYPES,\n\t...DEFAULT_SUPPORT_VIDEO_TYPES,\n])\n/**\n * Comma-separated string of all supported media MIME types, useful for HTML file input accept attributes.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_MEDIA_TYPE_LIST } from '@tldraw/utils'\n *\n * // Use in HTML file input for media uploads\n * const input = document.createElement('input')\n * input.type = 'file'\n * input.accept = DEFAULT_SUPPORTED_MEDIA_TYPE_LIST\n * input.addEventListener('change', (e) => {\n * const files = (e.target as HTMLInputElement).files\n * if (files) console.log(`Selected ${files.length} file(s)`)\n * })\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_MEDIA_TYPE_LIST = DEFAULT_SUPPORTED_MEDIA_TYPES.join(',')\n\n/**\n * Helpers for media\n *\n * @public\n */\nexport class MediaHelpers {\n\t/**\n\t * Load a video element from a URL with cross-origin support.\n\t *\n\t * @param src - The URL of the video to load\n\t * @returns Promise that resolves to the loaded HTMLVideoElement\n\t * @example\n\t * ```ts\n\t * const video = await MediaHelpers.loadVideo('https://example.com/video.mp4')\n\t * console.log(`Video dimensions: ${video.videoWidth}x${video.videoHeight}`)\n\t * ```\n\t * @public\n\t */\n\tstatic loadVideo(src: string): Promise<HTMLVideoElement> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst video = document.createElement('video')\n\t\t\tvideo.onloadeddata = () => resolve(video)\n\t\t\tvideo.onerror = (e) => {\n\t\t\t\tconsole.error(e)\n\t\t\t\treject(new Error('Could not load video'))\n\t\t\t}\n\t\t\tvideo.crossOrigin = 'anonymous'\n\t\t\tvideo.src = src\n\t\t})\n\t}\n\n\t/**\n\t * Extract a frame from a video element as a data URL.\n\t *\n\t * @param video - The HTMLVideoElement to extract frame from\n\t * @param time - The time in seconds to extract the frame from (default: 0)\n\t * @returns Promise that resolves to a data URL of the video frame\n\t * @example\n\t * ```ts\n\t * const video = await MediaHelpers.loadVideo('https://example.com/video.mp4')\n\t * const frameDataUrl = await MediaHelpers.getVideoFrameAsDataUrl(video, 5.0)\n\t * // Use frameDataUrl as image thumbnail\n\t * const img = document.createElement('img')\n\t * img.src = frameDataUrl\n\t * ```\n\t * @public\n\t */\n\tstatic async getVideoFrameAsDataUrl(video: HTMLVideoElement, time = 0): Promise<string> {\n\t\tconst promise = promiseWithResolve<string>()\n\t\tlet didSetTime = false\n\n\t\tconst onReadyStateChanged = () => {\n\t\t\tif (!didSetTime) {\n\t\t\t\tif (video.readyState >= video.HAVE_METADATA) {\n\t\t\t\t\tdidSetTime = true\n\t\t\t\t\tvideo.currentTime = time\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (video.readyState >= video.HAVE_CURRENT_DATA) {\n\t\t\t\tconst canvas = document.createElement('canvas')\n\t\t\t\tcanvas.width = video.videoWidth\n\t\t\t\tcanvas.height = video.videoHeight\n\t\t\t\tconst ctx = canvas.getContext('2d')\n\t\t\t\tif (!ctx) {\n\t\t\t\t\tthrow new Error('Could not get 2d context')\n\t\t\t\t}\n\t\t\t\tctx.drawImage(video, 0, 0)\n\t\t\t\tpromise.resolve(canvas.toDataURL())\n\t\t\t}\n\t\t}\n\t\tconst onError = (e: Event) => {\n\t\t\tconsole.error(e)\n\t\t\tpromise.reject(new Error('Could not get video frame'))\n\t\t}\n\n\t\tvideo.addEventListener('loadedmetadata', onReadyStateChanged)\n\t\tvideo.addEventListener('loadeddata', onReadyStateChanged)\n\t\tvideo.addEventListener('canplay', onReadyStateChanged)\n\t\tvideo.addEventListener('seeked', onReadyStateChanged)\n\n\t\tvideo.addEventListener('error', onError)\n\t\tvideo.addEventListener('stalled', onError)\n\n\t\tonReadyStateChanged()\n\n\t\ttry {\n\t\t\treturn await promise\n\t\t} finally {\n\t\t\tvideo.removeEventListener('loadedmetadata', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('loadeddata', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('canplay', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('seeked', onReadyStateChanged)\n\n\t\t\tvideo.removeEventListener('error', onError)\n\t\t\tvideo.removeEventListener('stalled', onError)\n\t\t}\n\t}\n\n\t/**\n\t * Load an image from a URL and get its dimensions along with the image element.\n\t *\n\t * @param src - The URL of the image to load\n\t * @returns Promise that resolves to an object with width, height, and the image element\n\t * @example\n\t * ```ts\n\t * const { w, h, image } = await MediaHelpers.getImageAndDimensions('https://example.com/image.png')\n\t * console.log(`Image size: ${w}x${h}`)\n\t * // Image is ready to use\n\t * document.body.appendChild(image)\n\t * ```\n\t * @public\n\t */\n\tstatic getImageAndDimensions(\n\t\tsrc: string\n\t): Promise<{ w: number; h: number; image: HTMLImageElement }> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst img = Image()\n\t\t\timg.onload = () => {\n\t\t\t\tlet dimensions\n\t\t\t\tif (img.naturalWidth) {\n\t\t\t\t\tdimensions = {\n\t\t\t\t\t\tw: img.naturalWidth,\n\t\t\t\t\t\th: img.naturalHeight,\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Sigh, Firefox doesn't have naturalWidth or naturalHeight for SVGs. :-/\n\t\t\t\t\t// We have to attach to dom and use clientWidth/clientHeight.\n\t\t\t\t\tdocument.body.appendChild(img)\n\t\t\t\t\tdimensions = {\n\t\t\t\t\t\tw: img.clientWidth,\n\t\t\t\t\t\th: img.clientHeight,\n\t\t\t\t\t}\n\t\t\t\t\tdocument.body.removeChild(img)\n\t\t\t\t}\n\t\t\t\tresolve({ ...dimensions, image: img })\n\t\t\t}\n\t\t\timg.onerror = (e) => {\n\t\t\t\tconsole.error(e)\n\t\t\t\treject(new Error('Could not load image'))\n\t\t\t}\n\t\t\timg.crossOrigin = 'anonymous'\n\t\t\timg.referrerPolicy = 'strict-origin-when-cross-origin'\n\t\t\timg.style.visibility = 'hidden'\n\t\t\timg.style.position = 'absolute'\n\t\t\timg.style.opacity = '0'\n\t\t\timg.style.zIndex = '-9999'\n\t\t\timg.src = src\n\t\t})\n\t}\n\n\t/**\n\t * Get the size of a video blob\n\t *\n\t * @param blob - A Blob containing the video\n\t * @returns Promise that resolves to an object with width and height properties\n\t * @example\n\t * ```ts\n\t * const file = new File([...], 'video.mp4', { type: 'video/mp4' })\n\t * const { w, h } = await MediaHelpers.getVideoSize(file)\n\t * console.log(`Video dimensions: ${w}x${h}`)\n\t * ```\n\t * @public\n\t */\n\tstatic async getVideoSize(blob: Blob): Promise<{ w: number; h: number }> {\n\t\treturn MediaHelpers.usingObjectURL(blob, async (url) => {\n\t\t\tconst video = await MediaHelpers.loadVideo(url)\n\t\t\treturn { w: video.videoWidth, h: video.videoHeight }\n\t\t})\n\t}\n\n\t/**\n\t * Get the size of an image blob\n\t *\n\t * @param blob - A Blob containing the image\n\t * @returns Promise that resolves to an object with width and height properties\n\t * @example\n\t * ```ts\n\t * const file = new File([...], 'image.png', { type: 'image/png' })\n\t * const { w, h } = await MediaHelpers.getImageSize(file)\n\t * console.log(`Image dimensions: ${w}x${h}`)\n\t * ```\n\t * @public\n\t */\n\tstatic async getImageSize(blob: Blob): Promise<{ w: number; h: number }> {\n\t\tconst { w, h } = await MediaHelpers.usingObjectURL(blob, MediaHelpers.getImageAndDimensions)\n\n\t\ttry {\n\t\t\tif (blob.type === 'image/png') {\n\t\t\t\tconst view = new DataView(await blob.arrayBuffer())\n\t\t\t\tif (PngHelpers.isPng(view, 0)) {\n\t\t\t\t\tconst physChunk = PngHelpers.findChunk(view, 'pHYs')\n\t\t\t\t\tif (physChunk) {\n\t\t\t\t\t\tconst physData = PngHelpers.parsePhys(view, physChunk.dataOffset)\n\t\t\t\t\t\tif (physData.unit === 1 && physData.ppux === physData.ppuy) {\n\t\t\t\t\t\t\t// Calculate pixels per meter:\n\t\t\t\t\t\t\t// - 1 inch = 0.0254 meters\n\t\t\t\t\t\t\t// - 72 DPI is 72 dots per inch\n\t\t\t\t\t\t\t// - pixels per meter = 72 / 0.0254\n\t\t\t\t\t\t\tconst pixelsPerMeter = 72 / 0.0254\n\t\t\t\t\t\t\tconst pixelRatio = Math.max(physData.ppux / pixelsPerMeter, 1)\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tw: Math.round(w / pixelRatio),\n\t\t\t\t\t\t\t\th: Math.round(h / pixelRatio),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconsole.error(err)\n\t\t\treturn { w, h }\n\t\t}\n\t\treturn { w, h }\n\t}\n\n\t/**\n\t * Check if a media file blob contains animation data.\n\t *\n\t * @param file - The Blob to check for animation\n\t * @returns Promise that resolves to true if the file is animated, false otherwise\n\t * @example\n\t * ```ts\n\t * const file = new File([...], 'animation.gif', { type: 'image/gif' })\n\t * const animated = await MediaHelpers.isAnimated(file)\n\t * console.log(animated ? 'Animated' : 'Static')\n\t * ```\n\t * @public\n\t */\n\tstatic async isAnimated(file: Blob): Promise<boolean> {\n\t\tif (file.type === 'image/gif') {\n\t\t\treturn isGifAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/avif') {\n\t\t\treturn isAvifAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/webp') {\n\t\t\treturn isWebpAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/apng') {\n\t\t\treturn isApngAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\treturn false\n\t}\n\n\t/**\n\t * Check if a MIME type represents an animated image format.\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is an animated image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isAnimated = MediaHelpers.isAnimatedImageType('image/gif')\n\t * console.log(isAnimated) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isAnimatedImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Check if a MIME type represents a static (non-animated) image format.\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is a static image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isStatic = MediaHelpers.isStaticImageType('image/jpeg')\n\t * console.log(isStatic) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isStaticImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Check if a MIME type represents a vector image format.\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is a vector image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isVector = MediaHelpers.isVectorImageType('image/svg+xml')\n\t * console.log(isVector) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isVectorImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Check if a MIME type represents any supported image format (static, animated, or vector).\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is a supported image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isImage = MediaHelpers.isImageType('image/png')\n\t * console.log(isImage) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isImageType(mimeType: string): boolean {\n\t\treturn DEFAULT_SUPPORTED_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Utility function to create an object URL from a blob, execute a function with it, and automatically clean it up.\n\t *\n\t * @param blob - The Blob to create an object URL for\n\t * @param fn - Function to execute with the object URL\n\t * @returns Promise that resolves to the result of the function\n\t * @example\n\t * ```ts\n\t * const result = await MediaHelpers.usingObjectURL(imageBlob, async (url) => {\n\t * const { w, h } = await MediaHelpers.getImageAndDimensions(url)\n\t * return { width: w, height: h }\n\t * })\n\t * // Object URL is automatically revoked after function completes\n\t * console.log(`Image dimensions: ${result.width}x${result.height}`)\n\t * ```\n\t * @public\n\t */\n\tstatic async usingObjectURL<T>(blob: Blob, fn: (url: string) => Promise<T>): Promise<T> {\n\t\tconst url = URL.createObjectURL(blob)\n\t\ttry {\n\t\t\treturn await fn(url)\n\t\t} finally {\n\t\t\tURL.revokeObjectURL(url)\n\t\t}\n\t}\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { promiseWithResolve } from '../control'\nimport { Image } from '../network'\nimport { isApngAnimated } from './apng'\nimport { isAvifAnimated } from './avif'\nimport { isGifAnimated } from './gif'\nimport { PngHelpers } from './png'\nimport { isWebpAnimated } from './webp'\n\n/**\n * Array of supported vector image MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isSvg = DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES.includes('image/svg+xml')\n * console.log(isSvg) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES = Object.freeze(['image/svg+xml' as const])\n/**\n * Array of supported static (non-animated) image MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isStatic = DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES.includes('image/jpeg')\n * console.log(isStatic) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES = Object.freeze([\n\t'image/jpeg' as const,\n\t'image/png' as const,\n\t'image/webp' as const,\n])\n/**\n * Array of supported animated image MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isAnimated = DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES.includes('image/gif')\n * console.log(isAnimated) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES = Object.freeze([\n\t'image/gif' as const,\n\t'image/apng' as const,\n\t'image/avif' as const,\n])\n/**\n * Array of all supported image MIME types, combining static, vector, and animated types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isSupported = DEFAULT_SUPPORTED_IMAGE_TYPES.includes('image/png')\n * console.log(isSupported) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_IMAGE_TYPES = Object.freeze([\n\t...DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES,\n\t...DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES,\n\t...DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES,\n])\n/**\n * Array of supported video MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORT_VIDEO_TYPES } from '@tldraw/utils'\n *\n * const isVideo = DEFAULT_SUPPORT_VIDEO_TYPES.includes('video/mp4')\n * console.log(isVideo) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORT_VIDEO_TYPES = Object.freeze([\n\t'video/mp4' as const,\n\t'video/webm' as const,\n\t'video/quicktime' as const,\n])\n/**\n * Array of all supported media MIME types, combining images and videos.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_MEDIA_TYPES } from '@tldraw/utils'\n *\n * const isMediaFile = DEFAULT_SUPPORTED_MEDIA_TYPES.includes('video/mp4')\n * console.log(isMediaFile) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_MEDIA_TYPES = Object.freeze([\n\t...DEFAULT_SUPPORTED_IMAGE_TYPES,\n\t...DEFAULT_SUPPORT_VIDEO_TYPES,\n])\n/**\n * Comma-separated string of all supported media MIME types, useful for HTML file input accept attributes.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_MEDIA_TYPE_LIST } from '@tldraw/utils'\n *\n * // Use in HTML file input for media uploads\n * const input = document.createElement('input')\n * input.type = 'file'\n * input.accept = DEFAULT_SUPPORTED_MEDIA_TYPE_LIST\n * input.addEventListener('change', (e) => {\n * const files = (e.target as HTMLInputElement).files\n * if (files) console.log(`Selected ${files.length} file(s)`)\n * })\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_MEDIA_TYPE_LIST = DEFAULT_SUPPORTED_MEDIA_TYPES.join(',')\n\n/**\n * Helpers for media\n *\n * @public\n */\nexport class MediaHelpers {\n\t/**\n\t * Load a video element from a URL with cross-origin support.\n\t *\n\t * @param src - The URL of the video to load\n\t * @returns Promise that resolves to the loaded HTMLVideoElement\n\t * @example\n\t * ```ts\n\t * const video = await MediaHelpers.loadVideo('https://example.com/video.mp4')\n\t * console.log(`Video dimensions: ${video.videoWidth}x${video.videoHeight}`)\n\t * ```\n\t * @public\n\t */\n\tstatic loadVideo(src: string): Promise<HTMLVideoElement> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst video = document.createElement('video')\n\t\t\tvideo.onloadeddata = () => resolve(video)\n\t\t\tvideo.onerror = (e) => {\n\t\t\t\tconsole.error(e)\n\t\t\t\treject(new Error('Could not load video'))\n\t\t\t}\n\t\t\tvideo.crossOrigin = 'anonymous'\n\t\t\tvideo.src = src\n\t\t})\n\t}\n\n\t/**\n\t * Extract a frame from a video element as a data URL.\n\t *\n\t * @param video - The HTMLVideoElement to extract frame from\n\t * @param time - The time in seconds to extract the frame from (default: 0)\n\t * @returns Promise that resolves to a data URL of the video frame\n\t * @example\n\t * ```ts\n\t * const video = await MediaHelpers.loadVideo('https://example.com/video.mp4')\n\t * const frameDataUrl = await MediaHelpers.getVideoFrameAsDataUrl(video, 5.0)\n\t * // Use frameDataUrl as image thumbnail\n\t * const img = document.createElement('img')\n\t * img.src = frameDataUrl\n\t * ```\n\t * @public\n\t */\n\tstatic async getVideoFrameAsDataUrl(video: HTMLVideoElement, time = 0): Promise<string> {\n\t\tconst promise = promiseWithResolve<string>()\n\t\tlet didSetTime = false\n\n\t\tconst onReadyStateChanged = () => {\n\t\t\tif (!didSetTime) {\n\t\t\t\tif (video.readyState >= video.HAVE_METADATA) {\n\t\t\t\t\tdidSetTime = true\n\t\t\t\t\tvideo.currentTime = time\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (video.readyState >= video.HAVE_CURRENT_DATA) {\n\t\t\t\tconst canvas = document.createElement('canvas')\n\t\t\t\tcanvas.width = video.videoWidth\n\t\t\t\tcanvas.height = video.videoHeight\n\t\t\t\tconst ctx = canvas.getContext('2d')\n\t\t\t\tif (!ctx) {\n\t\t\t\t\tthrow new Error('Could not get 2d context')\n\t\t\t\t}\n\t\t\t\tctx.drawImage(video, 0, 0)\n\t\t\t\tpromise.resolve(canvas.toDataURL())\n\t\t\t}\n\t\t}\n\t\tconst onError = (e: Event) => {\n\t\t\tconsole.error(e)\n\t\t\tpromise.reject(new Error('Could not get video frame'))\n\t\t}\n\n\t\tvideo.addEventListener('loadedmetadata', onReadyStateChanged)\n\t\tvideo.addEventListener('loadeddata', onReadyStateChanged)\n\t\tvideo.addEventListener('canplay', onReadyStateChanged)\n\t\tvideo.addEventListener('seeked', onReadyStateChanged)\n\n\t\tvideo.addEventListener('error', onError)\n\t\tvideo.addEventListener('stalled', onError)\n\n\t\tonReadyStateChanged()\n\n\t\ttry {\n\t\t\treturn await promise\n\t\t} finally {\n\t\t\tvideo.removeEventListener('loadedmetadata', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('loadeddata', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('canplay', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('seeked', onReadyStateChanged)\n\n\t\t\tvideo.removeEventListener('error', onError)\n\t\t\tvideo.removeEventListener('stalled', onError)\n\t\t}\n\t}\n\n\t/**\n\t * Load an image from a URL and get its dimensions along with the image element.\n\t *\n\t * @param src - The URL of the image to load\n\t * @returns Promise that resolves to an object with width, height, and the image element\n\t * @example\n\t * ```ts\n\t * const { w, h, image } = await MediaHelpers.getImageAndDimensions('https://example.com/image.png')\n\t * console.log(`Image size: ${w}x${h}`)\n\t * // Image is ready to use\n\t * document.body.appendChild(image)\n\t * ```\n\t * @public\n\t */\n\tstatic getImageAndDimensions(\n\t\tsrc: string\n\t): Promise<{ w: number; h: number; image: HTMLImageElement }> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst img = Image()\n\t\t\timg.onload = () => {\n\t\t\t\tlet dimensions\n\t\t\t\tif (img.naturalWidth) {\n\t\t\t\t\tdimensions = {\n\t\t\t\t\t\tw: img.naturalWidth,\n\t\t\t\t\t\th: img.naturalHeight,\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Sigh, Firefox doesn't have naturalWidth or naturalHeight for SVGs. :-/\n\t\t\t\t\t// We have to attach to dom and use clientWidth/clientHeight.\n\t\t\t\t\tdocument.body.appendChild(img)\n\t\t\t\t\tdimensions = {\n\t\t\t\t\t\tw: img.clientWidth,\n\t\t\t\t\t\th: img.clientHeight,\n\t\t\t\t\t}\n\t\t\t\t\tdocument.body.removeChild(img)\n\t\t\t\t}\n\t\t\t\tresolve({ ...dimensions, image: img })\n\t\t\t}\n\t\t\timg.onerror = (e) => {\n\t\t\t\tconsole.error(e)\n\t\t\t\treject(new Error('Could not load image'))\n\t\t\t}\n\t\t\timg.crossOrigin = 'anonymous'\n\t\t\timg.referrerPolicy = 'strict-origin-when-cross-origin'\n\t\t\timg.style.visibility = 'hidden'\n\t\t\timg.style.position = 'absolute'\n\t\t\timg.style.opacity = '0'\n\t\t\timg.style.zIndex = '-9999'\n\t\t\timg.src = src\n\t\t})\n\t}\n\n\t/**\n\t * Get the size of a video blob\n\t *\n\t * @param blob - A Blob containing the video\n\t * @returns Promise that resolves to an object with width and height properties\n\t * @example\n\t * ```ts\n\t * const file = new File([...], 'video.mp4', { type: 'video/mp4' })\n\t * const { w, h } = await MediaHelpers.getVideoSize(file)\n\t * console.log(`Video dimensions: ${w}x${h}`)\n\t * ```\n\t * @public\n\t */\n\tstatic async getVideoSize(blob: Blob): Promise<{ w: number; h: number }> {\n\t\treturn MediaHelpers.usingObjectURL(blob, async (url) => {\n\t\t\tconst video = await MediaHelpers.loadVideo(url)\n\t\t\treturn { w: video.videoWidth, h: video.videoHeight }\n\t\t})\n\t}\n\n\t/**\n\t * Get the size of an image blob\n\t *\n\t * @param blob - A Blob containing the image\n\t * @returns Promise that resolves to an object with width and height properties\n\t * @example\n\t * ```ts\n\t * const file = new File([...], 'image.png', { type: 'image/png' })\n\t * const { w, h } = await MediaHelpers.getImageSize(file)\n\t * console.log(`Image dimensions: ${w}x${h}`)\n\t * ```\n\t * @public\n\t */\n\tstatic async getImageSize(blob: Blob): Promise<{ w: number; h: number; pixelRatio: number }> {\n\t\tconst { w, h } = await MediaHelpers.usingObjectURL(blob, MediaHelpers.getImageAndDimensions)\n\n\t\ttry {\n\t\t\tif (blob.type === 'image/png') {\n\t\t\t\tconst view = new DataView(await blob.arrayBuffer())\n\t\t\t\tif (PngHelpers.isPng(view, 0)) {\n\t\t\t\t\tconst physChunk = PngHelpers.findChunk(view, 'pHYs')\n\t\t\t\t\tif (physChunk) {\n\t\t\t\t\t\tconst physData = PngHelpers.parsePhys(view, physChunk.dataOffset)\n\t\t\t\t\t\tif (physData.unit === 1 && physData.ppux === physData.ppuy) {\n\t\t\t\t\t\t\tconst dpi = Math.round(physData.ppux * 0.0254)\n\t\t\t\t\t\t\t// Try both standard baselines: Windows/web = 96, macOS = 72.\n\t\t\t\t\t\t\t// Pick whichever yields a clean integer ratio > 1.\n\t\t\t\t\t\t\tconst r96 = dpi / 96\n\t\t\t\t\t\t\tconst r72 = dpi / 72\n\t\t\t\t\t\t\tlet pixelRatio = 1\n\t\t\t\t\t\t\tif (Number.isInteger(r96) && r96 > 1) {\n\t\t\t\t\t\t\t\tpixelRatio = r96\n\t\t\t\t\t\t\t} else if (Number.isInteger(r72) && r72 > 1) {\n\t\t\t\t\t\t\t\tpixelRatio = r72\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (pixelRatio > 1) {\n\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\tw: Math.ceil(w / pixelRatio),\n\t\t\t\t\t\t\t\t\th: Math.ceil(h / pixelRatio),\n\t\t\t\t\t\t\t\t\tpixelRatio,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconsole.error(err)\n\t\t\treturn { w, h, pixelRatio: 1 }\n\t\t}\n\t\treturn { w, h, pixelRatio: 1 }\n\t}\n\n\t/**\n\t * Check if a media file blob contains animation data.\n\t *\n\t * @param file - The Blob to check for animation\n\t * @returns Promise that resolves to true if the file is animated, false otherwise\n\t * @example\n\t * ```ts\n\t * const file = new File([...], 'animation.gif', { type: 'image/gif' })\n\t * const animated = await MediaHelpers.isAnimated(file)\n\t * console.log(animated ? 'Animated' : 'Static')\n\t * ```\n\t * @public\n\t */\n\tstatic async isAnimated(file: Blob): Promise<boolean> {\n\t\tif (file.type === 'image/gif') {\n\t\t\treturn isGifAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/avif') {\n\t\t\treturn isAvifAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/webp') {\n\t\t\treturn isWebpAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/apng') {\n\t\t\treturn isApngAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\treturn false\n\t}\n\n\t/**\n\t * Check if a MIME type represents an animated image format.\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is an animated image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isAnimated = MediaHelpers.isAnimatedImageType('image/gif')\n\t * console.log(isAnimated) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isAnimatedImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Check if a MIME type represents a static (non-animated) image format.\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is a static image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isStatic = MediaHelpers.isStaticImageType('image/jpeg')\n\t * console.log(isStatic) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isStaticImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Check if a MIME type represents a vector image format.\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is a vector image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isVector = MediaHelpers.isVectorImageType('image/svg+xml')\n\t * console.log(isVector) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isVectorImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Check if a MIME type represents any supported image format (static, animated, or vector).\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is a supported image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isImage = MediaHelpers.isImageType('image/png')\n\t * console.log(isImage) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isImageType(mimeType: string): boolean {\n\t\treturn DEFAULT_SUPPORTED_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Utility function to create an object URL from a blob, execute a function with it, and automatically clean it up.\n\t *\n\t * @param blob - The Blob to create an object URL for\n\t * @param fn - Function to execute with the object URL\n\t * @returns Promise that resolves to the result of the function\n\t * @example\n\t * ```ts\n\t * const result = await MediaHelpers.usingObjectURL(imageBlob, async (url) => {\n\t * const { w, h } = await MediaHelpers.getImageAndDimensions(url)\n\t * return { width: w, height: h }\n\t * })\n\t * // Object URL is automatically revoked after function completes\n\t * console.log(`Image dimensions: ${result.width}x${result.height}`)\n\t * ```\n\t * @public\n\t */\n\tstatic async usingObjectURL<T>(blob: Blob, fn: (url: string) => Promise<T>): Promise<T> {\n\t\tconst url = URL.createObjectURL(blob)\n\t\ttry {\n\t\t\treturn await fn(url)\n\t\t} finally {\n\t\t\tURL.revokeObjectURL(url)\n\t\t}\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-cjs/lib/version.js
CHANGED
|
@@ -57,6 +57,12 @@ function registerTldrawLibraryVersion(name, version, modules) {
|
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
59
|
const info = getLibraryVersions();
|
|
60
|
+
if (isNextjsDev()) {
|
|
61
|
+
const isDuplicate = info.versions.some(
|
|
62
|
+
(v) => v.name === name && v.version === version && v.modules === modules
|
|
63
|
+
);
|
|
64
|
+
if (isDuplicate) return;
|
|
65
|
+
}
|
|
60
66
|
info.versions.push({ name, version, modules });
|
|
61
67
|
if (!info.scheduledNotice) {
|
|
62
68
|
try {
|
|
@@ -159,6 +165,13 @@ const formats = {
|
|
|
159
165
|
function format(value, formatters = []) {
|
|
160
166
|
return `\x1B[${formatters.map((f) => formats[f]).join(";")}m${value}\x1B[m`;
|
|
161
167
|
}
|
|
168
|
+
function isNextjsDev() {
|
|
169
|
+
try {
|
|
170
|
+
return process.env.NODE_ENV === "development" && "__NEXT_DATA__" in globalThis;
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
162
175
|
function entry(map, key, defaultValue) {
|
|
163
176
|
if (map.has(key)) {
|
|
164
177
|
return map.get(key);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/version.ts"],
|
|
4
|
-
"sourcesContent": ["interface TldrawLibraryVersion {\n\tname: string\n\tversion: string\n\tmodules: string\n}\n\ninterface TldrawLibraryVersionInfo {\n\tversions: TldrawLibraryVersion[]\n\tdidWarn: boolean\n\tscheduledNotice: number | NodeJS.Timeout | null\n}\n\nconst TLDRAW_LIBRARY_VERSION_KEY = '__TLDRAW_LIBRARY_VERSIONS__' as const\n\n// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword, @typescript-eslint/no-namespace\ndeclare module globalThis {\n\texport const __TLDRAW_LIBRARY_VERSIONS__: TldrawLibraryVersionInfo\n}\n\nfunction getLibraryVersions(): TldrawLibraryVersionInfo {\n\tif (globalThis[TLDRAW_LIBRARY_VERSION_KEY]) {\n\t\treturn globalThis[TLDRAW_LIBRARY_VERSION_KEY]\n\t}\n\n\tconst info: TldrawLibraryVersionInfo = {\n\t\tversions: [],\n\t\tdidWarn: false,\n\t\tscheduledNotice: null,\n\t}\n\n\tObject.defineProperty(globalThis, TLDRAW_LIBRARY_VERSION_KEY, {\n\t\tvalue: info,\n\t\twritable: false,\n\t\tconfigurable: false,\n\t\tenumerable: false,\n\t})\n\n\treturn info\n}\n\n/**\n * Clears all registered library versions and resets warning state.\n * This function is intended for testing purposes only to reset the global version tracking state.\n * @returns void\n * @example\n * ```ts\n * // In a test setup\n * beforeEach(() => {\n * clearRegisteredVersionsForTests()\n * })\n *\n * // Now version tracking starts fresh for each test\n * registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')\n * ```\n * @internal\n */\nexport function clearRegisteredVersionsForTests() {\n\tconst info = getLibraryVersions()\n\tinfo.versions = []\n\tinfo.didWarn = false\n\tif (info.scheduledNotice) {\n\t\tclearTimeout(info.scheduledNotice)\n\t\tinfo.scheduledNotice = null\n\t}\n}\n\n/**\n * Registers a tldraw library version for conflict detection.\n * This function tracks different tldraw library versions to warn about potential conflicts\n * when multiple versions are loaded simultaneously.\n * @param name - The name of the tldraw library package (e.g., '\\@tldraw/editor').\n * @param version - The semantic version string (e.g., '2.0.0').\n * @param modules - The module system being used ('esm' or 'cjs').\n * @returns void\n * @example\n * ```ts\n * // Register a library version during package initialization\n * registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')\n * registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')\n *\n * // If conflicting versions are detected, warnings will be logged:\n * registerTldrawLibraryVersion('@tldraw/editor', '1.9.0', 'cjs')\n * // Console warning about version mismatch will appear\n * ```\n * @internal\n */\nexport function registerTldrawLibraryVersion(name?: string, version?: string, modules?: string) {\n\tif (!name || !version || !modules) {\n\t\tif ((globalThis as any).TLDRAW_LIBRARY_IS_BUILD) {\n\t\t\tthrow new Error('Missing name/version/module system in built version of tldraw library')\n\t\t}\n\t\treturn\n\t}\n\n\tconst info = getLibraryVersions()\n\tinfo.versions.push({ name, version, modules })\n\n\tif (!info.scheduledNotice) {\n\t\ttry {\n\t\t\t// eslint-disable-next-line no-restricted-globals\n\t\t\tinfo.scheduledNotice = setTimeout(() => {\n\t\t\t\tinfo.scheduledNotice = null\n\t\t\t\tcheckLibraryVersions(info)\n\t\t\t}, 100)\n\t\t} catch {\n\t\t\t// some environments (e.g. cloudflare workers) don't support setTimeout immediately, only in a handler.\n\t\t\t// in this case, we'll just check immediately.\n\t\t\tcheckLibraryVersions(info)\n\t\t}\n\t}\n}\n\nfunction checkLibraryVersions(info: TldrawLibraryVersionInfo) {\n\tif (!info.versions.length) return\n\tif (info.didWarn) return\n\n\tconst sorted = info.versions.sort((a, b) => compareVersions(a.version, b.version))\n\tconst latestVersion = sorted[sorted.length - 1].version\n\n\tconst matchingVersions = new Set<string>()\n\tconst nonMatchingVersions = new Map<string, Set<string>>()\n\tfor (const lib of sorted) {\n\t\tif (nonMatchingVersions.has(lib.name)) {\n\t\t\tmatchingVersions.delete(lib.name)\n\t\t\tentry(nonMatchingVersions, lib.name, new Set()).add(lib.version)\n\t\t\tcontinue\n\t\t}\n\n\t\tif (lib.version === latestVersion) {\n\t\t\tmatchingVersions.add(lib.name)\n\t\t} else {\n\t\t\tmatchingVersions.delete(lib.name)\n\t\t\tentry(nonMatchingVersions, lib.name, new Set()).add(lib.version)\n\t\t}\n\t}\n\n\tif (nonMatchingVersions.size > 0) {\n\t\tconst message = [\n\t\t\t`${format('[tldraw]', ['bold', 'bgRed', 'textWhite'])} ${format('You have multiple versions of tldraw libraries installed. This can lead to bugs and unexpected behavior.', ['textRed', 'bold'])}`,\n\t\t\t'',\n\t\t\t`The latest version you have installed is ${format(`v${latestVersion}`, ['bold', 'textBlue'])}. The following libraries are on the latest version:`,\n\t\t\t...Array.from(matchingVersions, (name) => ` \u2022 \u2705 ${format(name, ['bold'])}`),\n\t\t\t'',\n\t\t\t`The following libraries are not on the latest version, or have multiple versions installed:`,\n\t\t\t...Array.from(nonMatchingVersions, ([name, versions]) => {\n\t\t\t\tconst sortedVersions = Array.from(versions)\n\t\t\t\t\t.sort(compareVersions)\n\t\t\t\t\t.map((v) => format(`v${v}`, v === latestVersion ? ['textGreen'] : ['textRed']))\n\t\t\t\treturn ` \u2022 \u274C ${format(name, ['bold'])} (${sortedVersions.join(', ')})`\n\t\t\t}),\n\t\t]\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(message.join('\\n'))\n\t\tinfo.didWarn = true\n\t\treturn\n\t}\n\n\t// at this point, we know that everything has the same version. there may still be duplicates though!\n\tconst potentialDuplicates = new Map<string, { version: string; modules: string[] }>()\n\tfor (const lib of sorted) {\n\t\tentry(potentialDuplicates, lib.name, { version: lib.version, modules: [] }).modules.push(\n\t\t\tlib.modules\n\t\t)\n\t}\n\n\tconst duplicates = new Map<string, { version: string; modules: string[] }>()\n\tfor (const [name, lib] of potentialDuplicates) {\n\t\tif (lib.modules.length > 1) duplicates.set(name, lib)\n\t}\n\n\tif (duplicates.size > 0) {\n\t\tconst message = [\n\t\t\t`${format('[tldraw]', ['bold', 'bgRed', 'textWhite'])} ${format('You have multiple instances of some tldraw libraries active. This can lead to bugs and unexpected behavior. ', ['textRed', 'bold'])}`,\n\t\t\t'',\n\t\t\t'This usually means that your bundler is misconfigured, and is importing the same library multiple times - usually once as an ES Module, and once as a CommonJS module.',\n\t\t\t'',\n\t\t\t'The following libraries have been imported multiple times:',\n\t\t\t...Array.from(duplicates, ([name, lib]) => {\n\t\t\t\tconst modules = lib.modules\n\t\t\t\t\t.map((m, i) => (m === 'esm' ? ` ${i + 1}. ES Modules` : ` ${i + 1}. CommonJS`))\n\t\t\t\t\t.join('\\n')\n\t\t\t\treturn ` \u2022 \u274C ${format(name, ['bold'])} v${lib.version}: \\n${modules}`\n\t\t\t}),\n\t\t\t'',\n\t\t\t'You should configure your bundler to only import one version of each library.',\n\t\t]\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(message.join('\\n'))\n\t\tinfo.didWarn = true\n\t\treturn\n\t}\n}\n\nfunction compareVersions(a: string, b: string) {\n\tconst aMatch = a.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\w+))?$/)\n\tconst bMatch = b.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\w+))?$/)\n\n\tif (!aMatch || !bMatch) return a.localeCompare(b)\n\tif (aMatch[1] !== bMatch[1]) return Number(aMatch[1]) - Number(bMatch[1])\n\tif (aMatch[2] !== bMatch[2]) return Number(aMatch[2]) - Number(bMatch[2])\n\tif (aMatch[3] !== bMatch[3]) return Number(aMatch[3]) - Number(bMatch[3])\n\tif (aMatch[4] && bMatch[4]) return aMatch[4].localeCompare(bMatch[4])\n\tif (aMatch[4]) return 1\n\tif (bMatch[4]) return -1\n\treturn 0\n}\n\nconst formats = {\n\tbold: '1',\n\ttextBlue: '94',\n\ttextRed: '31',\n\ttextGreen: '32',\n\tbgRed: '41',\n\ttextWhite: '97',\n} as const\nfunction format(value: string, formatters: (keyof typeof formats)[] = []) {\n\treturn `\\x1B[${formatters.map((f) => formats[f]).join(';')}m${value}\\x1B[m`\n}\n\nfunction entry<K, V>(map: Map<K, V>, key: K, defaultValue: V): V {\n\tif (map.has(key)) {\n\t\treturn map.get(key)!\n\t}\n\tmap.set(key, defaultValue)\n\treturn defaultValue\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYA,MAAM,6BAA6B;AAOnC,SAAS,qBAA+C;AACvD,MAAI,WAAW,0BAA0B,GAAG;AAC3C,WAAO,WAAW,0BAA0B;AAAA,EAC7C;AAEA,QAAM,OAAiC;AAAA,IACtC,UAAU,CAAC;AAAA,IACX,SAAS;AAAA,IACT,iBAAiB;AAAA,EAClB;AAEA,SAAO,eAAe,YAAY,4BAA4B;AAAA,IAC7D,OAAO;AAAA,IACP,UAAU;AAAA,IACV,cAAc;AAAA,IACd,YAAY;AAAA,EACb,CAAC;AAED,SAAO;AACR;AAkBO,SAAS,kCAAkC;AACjD,QAAM,OAAO,mBAAmB;AAChC,OAAK,WAAW,CAAC;AACjB,OAAK,UAAU;AACf,MAAI,KAAK,iBAAiB;AACzB,iBAAa,KAAK,eAAe;AACjC,SAAK,kBAAkB;AAAA,EACxB;AACD;AAsBO,SAAS,6BAA6B,MAAe,SAAkB,SAAkB;AAC/F,MAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,SAAS;AAClC,QAAK,MAA4C;AAChD,YAAM,IAAI,MAAM,uEAAuE;AAAA,IACxF;AACA;AAAA,EACD;AAEA,QAAM,OAAO,mBAAmB;
|
|
4
|
+
"sourcesContent": ["interface TldrawLibraryVersion {\n\tname: string\n\tversion: string\n\tmodules: string\n}\n\ninterface TldrawLibraryVersionInfo {\n\tversions: TldrawLibraryVersion[]\n\tdidWarn: boolean\n\tscheduledNotice: number | NodeJS.Timeout | null\n}\n\nconst TLDRAW_LIBRARY_VERSION_KEY = '__TLDRAW_LIBRARY_VERSIONS__' as const\n\n// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword, @typescript-eslint/no-namespace\ndeclare module globalThis {\n\texport const __TLDRAW_LIBRARY_VERSIONS__: TldrawLibraryVersionInfo\n}\n\nfunction getLibraryVersions(): TldrawLibraryVersionInfo {\n\tif (globalThis[TLDRAW_LIBRARY_VERSION_KEY]) {\n\t\treturn globalThis[TLDRAW_LIBRARY_VERSION_KEY]\n\t}\n\n\tconst info: TldrawLibraryVersionInfo = {\n\t\tversions: [],\n\t\tdidWarn: false,\n\t\tscheduledNotice: null,\n\t}\n\n\tObject.defineProperty(globalThis, TLDRAW_LIBRARY_VERSION_KEY, {\n\t\tvalue: info,\n\t\twritable: false,\n\t\tconfigurable: false,\n\t\tenumerable: false,\n\t})\n\n\treturn info\n}\n\n/**\n * Clears all registered library versions and resets warning state.\n * This function is intended for testing purposes only to reset the global version tracking state.\n * @returns void\n * @example\n * ```ts\n * // In a test setup\n * beforeEach(() => {\n * clearRegisteredVersionsForTests()\n * })\n *\n * // Now version tracking starts fresh for each test\n * registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')\n * ```\n * @internal\n */\nexport function clearRegisteredVersionsForTests() {\n\tconst info = getLibraryVersions()\n\tinfo.versions = []\n\tinfo.didWarn = false\n\tif (info.scheduledNotice) {\n\t\tclearTimeout(info.scheduledNotice)\n\t\tinfo.scheduledNotice = null\n\t}\n}\n\n/**\n * Registers a tldraw library version for conflict detection.\n * This function tracks different tldraw library versions to warn about potential conflicts\n * when multiple versions are loaded simultaneously.\n * @param name - The name of the tldraw library package (e.g., '\\@tldraw/editor').\n * @param version - The semantic version string (e.g., '2.0.0').\n * @param modules - The module system being used ('esm' or 'cjs').\n * @returns void\n * @example\n * ```ts\n * // Register a library version during package initialization\n * registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')\n * registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')\n *\n * // If conflicting versions are detected, warnings will be logged:\n * registerTldrawLibraryVersion('@tldraw/editor', '1.9.0', 'cjs')\n * // Console warning about version mismatch will appear\n * ```\n * @internal\n */\nexport function registerTldrawLibraryVersion(name?: string, version?: string, modules?: string) {\n\tif (!name || !version || !modules) {\n\t\tif ((globalThis as any).TLDRAW_LIBRARY_IS_BUILD) {\n\t\t\tthrow new Error('Missing name/version/module system in built version of tldraw library')\n\t\t}\n\t\treturn\n\t}\n\n\tconst info = getLibraryVersions()\n\n\t// In Next.js dev mode, Fast Refresh re-executes module-level code which causes\n\t// the same library to register multiple times, producing a false positive warning.\n\t// We skip exact duplicates only in Next.js dev to avoid hiding genuine issues.\n\tif (isNextjsDev()) {\n\t\tconst isDuplicate = info.versions.some(\n\t\t\t(v) => v.name === name && v.version === version && v.modules === modules\n\t\t)\n\t\tif (isDuplicate) return\n\t}\n\n\tinfo.versions.push({ name, version, modules })\n\n\tif (!info.scheduledNotice) {\n\t\ttry {\n\t\t\t// eslint-disable-next-line no-restricted-globals\n\t\t\tinfo.scheduledNotice = setTimeout(() => {\n\t\t\t\tinfo.scheduledNotice = null\n\t\t\t\tcheckLibraryVersions(info)\n\t\t\t}, 100)\n\t\t} catch {\n\t\t\t// some environments (e.g. cloudflare workers) don't support setTimeout immediately, only in a handler.\n\t\t\t// in this case, we'll just check immediately.\n\t\t\tcheckLibraryVersions(info)\n\t\t}\n\t}\n}\n\nfunction checkLibraryVersions(info: TldrawLibraryVersionInfo) {\n\tif (!info.versions.length) return\n\tif (info.didWarn) return\n\n\tconst sorted = info.versions.sort((a, b) => compareVersions(a.version, b.version))\n\tconst latestVersion = sorted[sorted.length - 1].version\n\n\tconst matchingVersions = new Set<string>()\n\tconst nonMatchingVersions = new Map<string, Set<string>>()\n\tfor (const lib of sorted) {\n\t\tif (nonMatchingVersions.has(lib.name)) {\n\t\t\tmatchingVersions.delete(lib.name)\n\t\t\tentry(nonMatchingVersions, lib.name, new Set()).add(lib.version)\n\t\t\tcontinue\n\t\t}\n\n\t\tif (lib.version === latestVersion) {\n\t\t\tmatchingVersions.add(lib.name)\n\t\t} else {\n\t\t\tmatchingVersions.delete(lib.name)\n\t\t\tentry(nonMatchingVersions, lib.name, new Set()).add(lib.version)\n\t\t}\n\t}\n\n\tif (nonMatchingVersions.size > 0) {\n\t\tconst message = [\n\t\t\t`${format('[tldraw]', ['bold', 'bgRed', 'textWhite'])} ${format('You have multiple versions of tldraw libraries installed. This can lead to bugs and unexpected behavior.', ['textRed', 'bold'])}`,\n\t\t\t'',\n\t\t\t`The latest version you have installed is ${format(`v${latestVersion}`, ['bold', 'textBlue'])}. The following libraries are on the latest version:`,\n\t\t\t...Array.from(matchingVersions, (name) => ` \u2022 \u2705 ${format(name, ['bold'])}`),\n\t\t\t'',\n\t\t\t`The following libraries are not on the latest version, or have multiple versions installed:`,\n\t\t\t...Array.from(nonMatchingVersions, ([name, versions]) => {\n\t\t\t\tconst sortedVersions = Array.from(versions)\n\t\t\t\t\t.sort(compareVersions)\n\t\t\t\t\t.map((v) => format(`v${v}`, v === latestVersion ? ['textGreen'] : ['textRed']))\n\t\t\t\treturn ` \u2022 \u274C ${format(name, ['bold'])} (${sortedVersions.join(', ')})`\n\t\t\t}),\n\t\t]\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(message.join('\\n'))\n\t\tinfo.didWarn = true\n\t\treturn\n\t}\n\n\t// at this point, we know that everything has the same version. there may still be duplicates though!\n\tconst potentialDuplicates = new Map<string, { version: string; modules: string[] }>()\n\tfor (const lib of sorted) {\n\t\tentry(potentialDuplicates, lib.name, { version: lib.version, modules: [] }).modules.push(\n\t\t\tlib.modules\n\t\t)\n\t}\n\n\tconst duplicates = new Map<string, { version: string; modules: string[] }>()\n\tfor (const [name, lib] of potentialDuplicates) {\n\t\tif (lib.modules.length > 1) duplicates.set(name, lib)\n\t}\n\n\tif (duplicates.size > 0) {\n\t\tconst message = [\n\t\t\t`${format('[tldraw]', ['bold', 'bgRed', 'textWhite'])} ${format('You have multiple instances of some tldraw libraries active. This can lead to bugs and unexpected behavior. ', ['textRed', 'bold'])}`,\n\t\t\t'',\n\t\t\t'This usually means that your bundler is misconfigured, and is importing the same library multiple times - usually once as an ES Module, and once as a CommonJS module.',\n\t\t\t'',\n\t\t\t'The following libraries have been imported multiple times:',\n\t\t\t...Array.from(duplicates, ([name, lib]) => {\n\t\t\t\tconst modules = lib.modules\n\t\t\t\t\t.map((m, i) => (m === 'esm' ? ` ${i + 1}. ES Modules` : ` ${i + 1}. CommonJS`))\n\t\t\t\t\t.join('\\n')\n\t\t\t\treturn ` \u2022 \u274C ${format(name, ['bold'])} v${lib.version}: \\n${modules}`\n\t\t\t}),\n\t\t\t'',\n\t\t\t'You should configure your bundler to only import one version of each library.',\n\t\t]\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(message.join('\\n'))\n\t\tinfo.didWarn = true\n\t\treturn\n\t}\n}\n\nfunction compareVersions(a: string, b: string) {\n\tconst aMatch = a.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\w+))?$/)\n\tconst bMatch = b.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\w+))?$/)\n\n\tif (!aMatch || !bMatch) return a.localeCompare(b)\n\tif (aMatch[1] !== bMatch[1]) return Number(aMatch[1]) - Number(bMatch[1])\n\tif (aMatch[2] !== bMatch[2]) return Number(aMatch[2]) - Number(bMatch[2])\n\tif (aMatch[3] !== bMatch[3]) return Number(aMatch[3]) - Number(bMatch[3])\n\tif (aMatch[4] && bMatch[4]) return aMatch[4].localeCompare(bMatch[4])\n\tif (aMatch[4]) return 1\n\tif (bMatch[4]) return -1\n\treturn 0\n}\n\nconst formats = {\n\tbold: '1',\n\ttextBlue: '94',\n\ttextRed: '31',\n\ttextGreen: '32',\n\tbgRed: '41',\n\ttextWhite: '97',\n} as const\nfunction format(value: string, formatters: (keyof typeof formats)[] = []) {\n\treturn `\\x1B[${formatters.map((f) => formats[f]).join(';')}m${value}\\x1B[m`\n}\n\nfunction isNextjsDev(): boolean {\n\ttry {\n\t\treturn process.env.NODE_ENV === 'development' && '__NEXT_DATA__' in globalThis\n\t} catch {\n\t\treturn false\n\t}\n}\n\nfunction entry<K, V>(map: Map<K, V>, key: K, defaultValue: V): V {\n\tif (map.has(key)) {\n\t\treturn map.get(key)!\n\t}\n\tmap.set(key, defaultValue)\n\treturn defaultValue\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYA,MAAM,6BAA6B;AAOnC,SAAS,qBAA+C;AACvD,MAAI,WAAW,0BAA0B,GAAG;AAC3C,WAAO,WAAW,0BAA0B;AAAA,EAC7C;AAEA,QAAM,OAAiC;AAAA,IACtC,UAAU,CAAC;AAAA,IACX,SAAS;AAAA,IACT,iBAAiB;AAAA,EAClB;AAEA,SAAO,eAAe,YAAY,4BAA4B;AAAA,IAC7D,OAAO;AAAA,IACP,UAAU;AAAA,IACV,cAAc;AAAA,IACd,YAAY;AAAA,EACb,CAAC;AAED,SAAO;AACR;AAkBO,SAAS,kCAAkC;AACjD,QAAM,OAAO,mBAAmB;AAChC,OAAK,WAAW,CAAC;AACjB,OAAK,UAAU;AACf,MAAI,KAAK,iBAAiB;AACzB,iBAAa,KAAK,eAAe;AACjC,SAAK,kBAAkB;AAAA,EACxB;AACD;AAsBO,SAAS,6BAA6B,MAAe,SAAkB,SAAkB;AAC/F,MAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,SAAS;AAClC,QAAK,MAA4C;AAChD,YAAM,IAAI,MAAM,uEAAuE;AAAA,IACxF;AACA;AAAA,EACD;AAEA,QAAM,OAAO,mBAAmB;AAKhC,MAAI,YAAY,GAAG;AAClB,UAAM,cAAc,KAAK,SAAS;AAAA,MACjC,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,YAAY,WAAW,EAAE,YAAY;AAAA,IAClE;AACA,QAAI,YAAa;AAAA,EAClB;AAEA,OAAK,SAAS,KAAK,EAAE,MAAM,SAAS,QAAQ,CAAC;AAE7C,MAAI,CAAC,KAAK,iBAAiB;AAC1B,QAAI;AAEH,WAAK,kBAAkB,WAAW,MAAM;AACvC,aAAK,kBAAkB;AACvB,6BAAqB,IAAI;AAAA,MAC1B,GAAG,GAAG;AAAA,IACP,QAAQ;AAGP,2BAAqB,IAAI;AAAA,IAC1B;AAAA,EACD;AACD;AAEA,SAAS,qBAAqB,MAAgC;AAC7D,MAAI,CAAC,KAAK,SAAS,OAAQ;AAC3B,MAAI,KAAK,QAAS;AAElB,QAAM,SAAS,KAAK,SAAS,KAAK,CAAC,GAAG,MAAM,gBAAgB,EAAE,SAAS,EAAE,OAAO,CAAC;AACjF,QAAM,gBAAgB,OAAO,OAAO,SAAS,CAAC,EAAE;AAEhD,QAAM,mBAAmB,oBAAI,IAAY;AACzC,QAAM,sBAAsB,oBAAI,IAAyB;AACzD,aAAW,OAAO,QAAQ;AACzB,QAAI,oBAAoB,IAAI,IAAI,IAAI,GAAG;AACtC,uBAAiB,OAAO,IAAI,IAAI;AAChC,YAAM,qBAAqB,IAAI,MAAM,oBAAI,IAAI,CAAC,EAAE,IAAI,IAAI,OAAO;AAC/D;AAAA,IACD;AAEA,QAAI,IAAI,YAAY,eAAe;AAClC,uBAAiB,IAAI,IAAI,IAAI;AAAA,IAC9B,OAAO;AACN,uBAAiB,OAAO,IAAI,IAAI;AAChC,YAAM,qBAAqB,IAAI,MAAM,oBAAI,IAAI,CAAC,EAAE,IAAI,IAAI,OAAO;AAAA,IAChE;AAAA,EACD;AAEA,MAAI,oBAAoB,OAAO,GAAG;AACjC,UAAM,UAAU;AAAA,MACf,GAAG,OAAO,YAAY,CAAC,QAAQ,SAAS,WAAW,CAAC,CAAC,IAAI,OAAO,4GAA4G,CAAC,WAAW,MAAM,CAAC,CAAC;AAAA,MAChM;AAAA,MACA,4CAA4C,OAAO,IAAI,aAAa,IAAI,CAAC,QAAQ,UAAU,CAAC,CAAC;AAAA,MAC7F,GAAG,MAAM,KAAK,kBAAkB,CAAC,SAAS,mBAAS,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE;AAAA,MAC3E;AAAA,MACA;AAAA,MACA,GAAG,MAAM,KAAK,qBAAqB,CAAC,CAAC,MAAM,QAAQ,MAAM;AACxD,cAAM,iBAAiB,MAAM,KAAK,QAAQ,EACxC,KAAK,eAAe,EACpB,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,IAAI,MAAM,gBAAgB,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,CAAC;AAC/E,eAAO,mBAAS,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,eAAe,KAAK,IAAI,CAAC;AAAA,MACrE,CAAC;AAAA,IACF;AAGA,YAAQ,IAAI,QAAQ,KAAK,IAAI,CAAC;AAC9B,SAAK,UAAU;AACf;AAAA,EACD;AAGA,QAAM,sBAAsB,oBAAI,IAAoD;AACpF,aAAW,OAAO,QAAQ;AACzB,UAAM,qBAAqB,IAAI,MAAM,EAAE,SAAS,IAAI,SAAS,SAAS,CAAC,EAAE,CAAC,EAAE,QAAQ;AAAA,MACnF,IAAI;AAAA,IACL;AAAA,EACD;AAEA,QAAM,aAAa,oBAAI,IAAoD;AAC3E,aAAW,CAAC,MAAM,GAAG,KAAK,qBAAqB;AAC9C,QAAI,IAAI,QAAQ,SAAS,EAAG,YAAW,IAAI,MAAM,GAAG;AAAA,EACrD;AAEA,MAAI,WAAW,OAAO,GAAG;AACxB,UAAM,UAAU;AAAA,MACf,GAAG,OAAO,YAAY,CAAC,QAAQ,SAAS,WAAW,CAAC,CAAC,IAAI,OAAO,gHAAgH,CAAC,WAAW,MAAM,CAAC,CAAC;AAAA,MACpM;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG,MAAM,KAAK,YAAY,CAAC,CAAC,MAAM,GAAG,MAAM;AAC1C,cAAM,UAAU,IAAI,QAClB,IAAI,CAAC,GAAG,MAAO,MAAM,QAAQ,SAAS,IAAI,CAAC,iBAAiB,SAAS,IAAI,CAAC,YAAa,EACvF,KAAK,IAAI;AACX,eAAO,mBAAS,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,OAAO;AAAA,EAAO,OAAO;AAAA,MACrE,CAAC;AAAA,MACD;AAAA,MACA;AAAA,IACD;AAGA,YAAQ,IAAI,QAAQ,KAAK,IAAI,CAAC;AAC9B,SAAK,UAAU;AACf;AAAA,EACD;AACD;AAEA,SAAS,gBAAgB,GAAW,GAAW;AAC9C,QAAM,SAAS,EAAE,MAAM,kCAAkC;AACzD,QAAM,SAAS,EAAE,MAAM,kCAAkC;AAEzD,MAAI,CAAC,UAAU,CAAC,OAAQ,QAAO,EAAE,cAAc,CAAC;AAChD,MAAI,OAAO,CAAC,MAAM,OAAO,CAAC,EAAG,QAAO,OAAO,OAAO,CAAC,CAAC,IAAI,OAAO,OAAO,CAAC,CAAC;AACxE,MAAI,OAAO,CAAC,MAAM,OAAO,CAAC,EAAG,QAAO,OAAO,OAAO,CAAC,CAAC,IAAI,OAAO,OAAO,CAAC,CAAC;AACxE,MAAI,OAAO,CAAC,MAAM,OAAO,CAAC,EAAG,QAAO,OAAO,OAAO,CAAC,CAAC,IAAI,OAAO,OAAO,CAAC,CAAC;AACxE,MAAI,OAAO,CAAC,KAAK,OAAO,CAAC,EAAG,QAAO,OAAO,CAAC,EAAE,cAAc,OAAO,CAAC,CAAC;AACpE,MAAI,OAAO,CAAC,EAAG,QAAO;AACtB,MAAI,OAAO,CAAC,EAAG,QAAO;AACtB,SAAO;AACR;AAEA,MAAM,UAAU;AAAA,EACf,MAAM;AAAA,EACN,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AAAA,EACP,WAAW;AACZ;AACA,SAAS,OAAO,OAAe,aAAuC,CAAC,GAAG;AACzE,SAAO,QAAQ,WAAW,IAAI,CAAC,MAAM,QAAQ,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,IAAI,KAAK;AACpE;AAEA,SAAS,cAAuB;AAC/B,MAAI;AACH,WAAO,QAAQ,IAAI,aAAa,iBAAiB,mBAAmB;AAAA,EACrE,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,SAAS,MAAY,KAAgB,KAAQ,cAAoB;AAChE,MAAI,IAAI,IAAI,GAAG,GAAG;AACjB,WAAO,IAAI,IAAI,GAAG;AAAA,EACnB;AACA,MAAI,IAAI,KAAK,YAAY;AACzB,SAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/index.d.mts
CHANGED
package/dist-esm/index.mjs
CHANGED
|
@@ -101,7 +101,7 @@ import { registerTldrawLibraryVersion as registerTldrawLibraryVersion2 } from ".
|
|
|
101
101
|
import { warnDeprecatedGetter, warnOnce } from "./lib/warn.mjs";
|
|
102
102
|
registerTldrawLibraryVersion(
|
|
103
103
|
"@tldraw/utils",
|
|
104
|
-
"4.5.
|
|
104
|
+
"4.5.1",
|
|
105
105
|
"esm"
|
|
106
106
|
);
|
|
107
107
|
export {
|
|
@@ -207,21 +207,31 @@ class MediaHelpers {
|
|
|
207
207
|
if (physChunk) {
|
|
208
208
|
const physData = PngHelpers.parsePhys(view, physChunk.dataOffset);
|
|
209
209
|
if (physData.unit === 1 && physData.ppux === physData.ppuy) {
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
210
|
+
const dpi = Math.round(physData.ppux * 0.0254);
|
|
211
|
+
const r96 = dpi / 96;
|
|
212
|
+
const r72 = dpi / 72;
|
|
213
|
+
let pixelRatio = 1;
|
|
214
|
+
if (Number.isInteger(r96) && r96 > 1) {
|
|
215
|
+
pixelRatio = r96;
|
|
216
|
+
} else if (Number.isInteger(r72) && r72 > 1) {
|
|
217
|
+
pixelRatio = r72;
|
|
218
|
+
}
|
|
219
|
+
if (pixelRatio > 1) {
|
|
220
|
+
return {
|
|
221
|
+
w: Math.ceil(w / pixelRatio),
|
|
222
|
+
h: Math.ceil(h / pixelRatio),
|
|
223
|
+
pixelRatio
|
|
224
|
+
};
|
|
225
|
+
}
|
|
216
226
|
}
|
|
217
227
|
}
|
|
218
228
|
}
|
|
219
229
|
}
|
|
220
230
|
} catch (err) {
|
|
221
231
|
console.error(err);
|
|
222
|
-
return { w, h };
|
|
232
|
+
return { w, h, pixelRatio: 1 };
|
|
223
233
|
}
|
|
224
|
-
return { w, h };
|
|
234
|
+
return { w, h, pixelRatio: 1 };
|
|
225
235
|
}
|
|
226
236
|
/**
|
|
227
237
|
* Check if a media file blob contains animation data.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/media/media.ts"],
|
|
4
|
-
"sourcesContent": ["import { promiseWithResolve } from '../control'\nimport { Image } from '../network'\nimport { isApngAnimated } from './apng'\nimport { isAvifAnimated } from './avif'\nimport { isGifAnimated } from './gif'\nimport { PngHelpers } from './png'\nimport { isWebpAnimated } from './webp'\n\n/**\n * Array of supported vector image MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isSvg = DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES.includes('image/svg+xml')\n * console.log(isSvg) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES = Object.freeze(['image/svg+xml' as const])\n/**\n * Array of supported static (non-animated) image MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isStatic = DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES.includes('image/jpeg')\n * console.log(isStatic) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES = Object.freeze([\n\t'image/jpeg' as const,\n\t'image/png' as const,\n\t'image/webp' as const,\n])\n/**\n * Array of supported animated image MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isAnimated = DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES.includes('image/gif')\n * console.log(isAnimated) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES = Object.freeze([\n\t'image/gif' as const,\n\t'image/apng' as const,\n\t'image/avif' as const,\n])\n/**\n * Array of all supported image MIME types, combining static, vector, and animated types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isSupported = DEFAULT_SUPPORTED_IMAGE_TYPES.includes('image/png')\n * console.log(isSupported) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_IMAGE_TYPES = Object.freeze([\n\t...DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES,\n\t...DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES,\n\t...DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES,\n])\n/**\n * Array of supported video MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORT_VIDEO_TYPES } from '@tldraw/utils'\n *\n * const isVideo = DEFAULT_SUPPORT_VIDEO_TYPES.includes('video/mp4')\n * console.log(isVideo) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORT_VIDEO_TYPES = Object.freeze([\n\t'video/mp4' as const,\n\t'video/webm' as const,\n\t'video/quicktime' as const,\n])\n/**\n * Array of all supported media MIME types, combining images and videos.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_MEDIA_TYPES } from '@tldraw/utils'\n *\n * const isMediaFile = DEFAULT_SUPPORTED_MEDIA_TYPES.includes('video/mp4')\n * console.log(isMediaFile) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_MEDIA_TYPES = Object.freeze([\n\t...DEFAULT_SUPPORTED_IMAGE_TYPES,\n\t...DEFAULT_SUPPORT_VIDEO_TYPES,\n])\n/**\n * Comma-separated string of all supported media MIME types, useful for HTML file input accept attributes.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_MEDIA_TYPE_LIST } from '@tldraw/utils'\n *\n * // Use in HTML file input for media uploads\n * const input = document.createElement('input')\n * input.type = 'file'\n * input.accept = DEFAULT_SUPPORTED_MEDIA_TYPE_LIST\n * input.addEventListener('change', (e) => {\n * const files = (e.target as HTMLInputElement).files\n * if (files) console.log(`Selected ${files.length} file(s)`)\n * })\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_MEDIA_TYPE_LIST = DEFAULT_SUPPORTED_MEDIA_TYPES.join(',')\n\n/**\n * Helpers for media\n *\n * @public\n */\nexport class MediaHelpers {\n\t/**\n\t * Load a video element from a URL with cross-origin support.\n\t *\n\t * @param src - The URL of the video to load\n\t * @returns Promise that resolves to the loaded HTMLVideoElement\n\t * @example\n\t * ```ts\n\t * const video = await MediaHelpers.loadVideo('https://example.com/video.mp4')\n\t * console.log(`Video dimensions: ${video.videoWidth}x${video.videoHeight}`)\n\t * ```\n\t * @public\n\t */\n\tstatic loadVideo(src: string): Promise<HTMLVideoElement> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst video = document.createElement('video')\n\t\t\tvideo.onloadeddata = () => resolve(video)\n\t\t\tvideo.onerror = (e) => {\n\t\t\t\tconsole.error(e)\n\t\t\t\treject(new Error('Could not load video'))\n\t\t\t}\n\t\t\tvideo.crossOrigin = 'anonymous'\n\t\t\tvideo.src = src\n\t\t})\n\t}\n\n\t/**\n\t * Extract a frame from a video element as a data URL.\n\t *\n\t * @param video - The HTMLVideoElement to extract frame from\n\t * @param time - The time in seconds to extract the frame from (default: 0)\n\t * @returns Promise that resolves to a data URL of the video frame\n\t * @example\n\t * ```ts\n\t * const video = await MediaHelpers.loadVideo('https://example.com/video.mp4')\n\t * const frameDataUrl = await MediaHelpers.getVideoFrameAsDataUrl(video, 5.0)\n\t * // Use frameDataUrl as image thumbnail\n\t * const img = document.createElement('img')\n\t * img.src = frameDataUrl\n\t * ```\n\t * @public\n\t */\n\tstatic async getVideoFrameAsDataUrl(video: HTMLVideoElement, time = 0): Promise<string> {\n\t\tconst promise = promiseWithResolve<string>()\n\t\tlet didSetTime = false\n\n\t\tconst onReadyStateChanged = () => {\n\t\t\tif (!didSetTime) {\n\t\t\t\tif (video.readyState >= video.HAVE_METADATA) {\n\t\t\t\t\tdidSetTime = true\n\t\t\t\t\tvideo.currentTime = time\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (video.readyState >= video.HAVE_CURRENT_DATA) {\n\t\t\t\tconst canvas = document.createElement('canvas')\n\t\t\t\tcanvas.width = video.videoWidth\n\t\t\t\tcanvas.height = video.videoHeight\n\t\t\t\tconst ctx = canvas.getContext('2d')\n\t\t\t\tif (!ctx) {\n\t\t\t\t\tthrow new Error('Could not get 2d context')\n\t\t\t\t}\n\t\t\t\tctx.drawImage(video, 0, 0)\n\t\t\t\tpromise.resolve(canvas.toDataURL())\n\t\t\t}\n\t\t}\n\t\tconst onError = (e: Event) => {\n\t\t\tconsole.error(e)\n\t\t\tpromise.reject(new Error('Could not get video frame'))\n\t\t}\n\n\t\tvideo.addEventListener('loadedmetadata', onReadyStateChanged)\n\t\tvideo.addEventListener('loadeddata', onReadyStateChanged)\n\t\tvideo.addEventListener('canplay', onReadyStateChanged)\n\t\tvideo.addEventListener('seeked', onReadyStateChanged)\n\n\t\tvideo.addEventListener('error', onError)\n\t\tvideo.addEventListener('stalled', onError)\n\n\t\tonReadyStateChanged()\n\n\t\ttry {\n\t\t\treturn await promise\n\t\t} finally {\n\t\t\tvideo.removeEventListener('loadedmetadata', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('loadeddata', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('canplay', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('seeked', onReadyStateChanged)\n\n\t\t\tvideo.removeEventListener('error', onError)\n\t\t\tvideo.removeEventListener('stalled', onError)\n\t\t}\n\t}\n\n\t/**\n\t * Load an image from a URL and get its dimensions along with the image element.\n\t *\n\t * @param src - The URL of the image to load\n\t * @returns Promise that resolves to an object with width, height, and the image element\n\t * @example\n\t * ```ts\n\t * const { w, h, image } = await MediaHelpers.getImageAndDimensions('https://example.com/image.png')\n\t * console.log(`Image size: ${w}x${h}`)\n\t * // Image is ready to use\n\t * document.body.appendChild(image)\n\t * ```\n\t * @public\n\t */\n\tstatic getImageAndDimensions(\n\t\tsrc: string\n\t): Promise<{ w: number; h: number; image: HTMLImageElement }> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst img = Image()\n\t\t\timg.onload = () => {\n\t\t\t\tlet dimensions\n\t\t\t\tif (img.naturalWidth) {\n\t\t\t\t\tdimensions = {\n\t\t\t\t\t\tw: img.naturalWidth,\n\t\t\t\t\t\th: img.naturalHeight,\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Sigh, Firefox doesn't have naturalWidth or naturalHeight for SVGs. :-/\n\t\t\t\t\t// We have to attach to dom and use clientWidth/clientHeight.\n\t\t\t\t\tdocument.body.appendChild(img)\n\t\t\t\t\tdimensions = {\n\t\t\t\t\t\tw: img.clientWidth,\n\t\t\t\t\t\th: img.clientHeight,\n\t\t\t\t\t}\n\t\t\t\t\tdocument.body.removeChild(img)\n\t\t\t\t}\n\t\t\t\tresolve({ ...dimensions, image: img })\n\t\t\t}\n\t\t\timg.onerror = (e) => {\n\t\t\t\tconsole.error(e)\n\t\t\t\treject(new Error('Could not load image'))\n\t\t\t}\n\t\t\timg.crossOrigin = 'anonymous'\n\t\t\timg.referrerPolicy = 'strict-origin-when-cross-origin'\n\t\t\timg.style.visibility = 'hidden'\n\t\t\timg.style.position = 'absolute'\n\t\t\timg.style.opacity = '0'\n\t\t\timg.style.zIndex = '-9999'\n\t\t\timg.src = src\n\t\t})\n\t}\n\n\t/**\n\t * Get the size of a video blob\n\t *\n\t * @param blob - A Blob containing the video\n\t * @returns Promise that resolves to an object with width and height properties\n\t * @example\n\t * ```ts\n\t * const file = new File([...], 'video.mp4', { type: 'video/mp4' })\n\t * const { w, h } = await MediaHelpers.getVideoSize(file)\n\t * console.log(`Video dimensions: ${w}x${h}`)\n\t * ```\n\t * @public\n\t */\n\tstatic async getVideoSize(blob: Blob): Promise<{ w: number; h: number }> {\n\t\treturn MediaHelpers.usingObjectURL(blob, async (url) => {\n\t\t\tconst video = await MediaHelpers.loadVideo(url)\n\t\t\treturn { w: video.videoWidth, h: video.videoHeight }\n\t\t})\n\t}\n\n\t/**\n\t * Get the size of an image blob\n\t *\n\t * @param blob - A Blob containing the image\n\t * @returns Promise that resolves to an object with width and height properties\n\t * @example\n\t * ```ts\n\t * const file = new File([...], 'image.png', { type: 'image/png' })\n\t * const { w, h } = await MediaHelpers.getImageSize(file)\n\t * console.log(`Image dimensions: ${w}x${h}`)\n\t * ```\n\t * @public\n\t */\n\tstatic async getImageSize(blob: Blob): Promise<{ w: number; h: number }> {\n\t\tconst { w, h } = await MediaHelpers.usingObjectURL(blob, MediaHelpers.getImageAndDimensions)\n\n\t\ttry {\n\t\t\tif (blob.type === 'image/png') {\n\t\t\t\tconst view = new DataView(await blob.arrayBuffer())\n\t\t\t\tif (PngHelpers.isPng(view, 0)) {\n\t\t\t\t\tconst physChunk = PngHelpers.findChunk(view, 'pHYs')\n\t\t\t\t\tif (physChunk) {\n\t\t\t\t\t\tconst physData = PngHelpers.parsePhys(view, physChunk.dataOffset)\n\t\t\t\t\t\tif (physData.unit === 1 && physData.ppux === physData.ppuy) {\n\t\t\t\t\t\t\t// Calculate pixels per meter:\n\t\t\t\t\t\t\t// - 1 inch = 0.0254 meters\n\t\t\t\t\t\t\t// - 72 DPI is 72 dots per inch\n\t\t\t\t\t\t\t// - pixels per meter = 72 / 0.0254\n\t\t\t\t\t\t\tconst pixelsPerMeter = 72 / 0.0254\n\t\t\t\t\t\t\tconst pixelRatio = Math.max(physData.ppux / pixelsPerMeter, 1)\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tw: Math.round(w / pixelRatio),\n\t\t\t\t\t\t\t\th: Math.round(h / pixelRatio),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconsole.error(err)\n\t\t\treturn { w, h }\n\t\t}\n\t\treturn { w, h }\n\t}\n\n\t/**\n\t * Check if a media file blob contains animation data.\n\t *\n\t * @param file - The Blob to check for animation\n\t * @returns Promise that resolves to true if the file is animated, false otherwise\n\t * @example\n\t * ```ts\n\t * const file = new File([...], 'animation.gif', { type: 'image/gif' })\n\t * const animated = await MediaHelpers.isAnimated(file)\n\t * console.log(animated ? 'Animated' : 'Static')\n\t * ```\n\t * @public\n\t */\n\tstatic async isAnimated(file: Blob): Promise<boolean> {\n\t\tif (file.type === 'image/gif') {\n\t\t\treturn isGifAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/avif') {\n\t\t\treturn isAvifAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/webp') {\n\t\t\treturn isWebpAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/apng') {\n\t\t\treturn isApngAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\treturn false\n\t}\n\n\t/**\n\t * Check if a MIME type represents an animated image format.\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is an animated image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isAnimated = MediaHelpers.isAnimatedImageType('image/gif')\n\t * console.log(isAnimated) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isAnimatedImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Check if a MIME type represents a static (non-animated) image format.\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is a static image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isStatic = MediaHelpers.isStaticImageType('image/jpeg')\n\t * console.log(isStatic) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isStaticImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Check if a MIME type represents a vector image format.\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is a vector image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isVector = MediaHelpers.isVectorImageType('image/svg+xml')\n\t * console.log(isVector) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isVectorImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Check if a MIME type represents any supported image format (static, animated, or vector).\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is a supported image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isImage = MediaHelpers.isImageType('image/png')\n\t * console.log(isImage) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isImageType(mimeType: string): boolean {\n\t\treturn DEFAULT_SUPPORTED_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Utility function to create an object URL from a blob, execute a function with it, and automatically clean it up.\n\t *\n\t * @param blob - The Blob to create an object URL for\n\t * @param fn - Function to execute with the object URL\n\t * @returns Promise that resolves to the result of the function\n\t * @example\n\t * ```ts\n\t * const result = await MediaHelpers.usingObjectURL(imageBlob, async (url) => {\n\t * const { w, h } = await MediaHelpers.getImageAndDimensions(url)\n\t * return { width: w, height: h }\n\t * })\n\t * // Object URL is automatically revoked after function completes\n\t * console.log(`Image dimensions: ${result.width}x${result.height}`)\n\t * ```\n\t * @public\n\t */\n\tstatic async usingObjectURL<T>(blob: Blob, fn: (url: string) => Promise<T>): Promise<T> {\n\t\tconst url = URL.createObjectURL(blob)\n\t\ttry {\n\t\t\treturn await fn(url)\n\t\t} finally {\n\t\t\tURL.revokeObjectURL(url)\n\t\t}\n\t}\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { promiseWithResolve } from '../control'\nimport { Image } from '../network'\nimport { isApngAnimated } from './apng'\nimport { isAvifAnimated } from './avif'\nimport { isGifAnimated } from './gif'\nimport { PngHelpers } from './png'\nimport { isWebpAnimated } from './webp'\n\n/**\n * Array of supported vector image MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isSvg = DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES.includes('image/svg+xml')\n * console.log(isSvg) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES = Object.freeze(['image/svg+xml' as const])\n/**\n * Array of supported static (non-animated) image MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isStatic = DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES.includes('image/jpeg')\n * console.log(isStatic) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES = Object.freeze([\n\t'image/jpeg' as const,\n\t'image/png' as const,\n\t'image/webp' as const,\n])\n/**\n * Array of supported animated image MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isAnimated = DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES.includes('image/gif')\n * console.log(isAnimated) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES = Object.freeze([\n\t'image/gif' as const,\n\t'image/apng' as const,\n\t'image/avif' as const,\n])\n/**\n * Array of all supported image MIME types, combining static, vector, and animated types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_IMAGE_TYPES } from '@tldraw/utils'\n *\n * const isSupported = DEFAULT_SUPPORTED_IMAGE_TYPES.includes('image/png')\n * console.log(isSupported) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_IMAGE_TYPES = Object.freeze([\n\t...DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES,\n\t...DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES,\n\t...DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES,\n])\n/**\n * Array of supported video MIME types.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORT_VIDEO_TYPES } from '@tldraw/utils'\n *\n * const isVideo = DEFAULT_SUPPORT_VIDEO_TYPES.includes('video/mp4')\n * console.log(isVideo) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORT_VIDEO_TYPES = Object.freeze([\n\t'video/mp4' as const,\n\t'video/webm' as const,\n\t'video/quicktime' as const,\n])\n/**\n * Array of all supported media MIME types, combining images and videos.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_MEDIA_TYPES } from '@tldraw/utils'\n *\n * const isMediaFile = DEFAULT_SUPPORTED_MEDIA_TYPES.includes('video/mp4')\n * console.log(isMediaFile) // true\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_MEDIA_TYPES = Object.freeze([\n\t...DEFAULT_SUPPORTED_IMAGE_TYPES,\n\t...DEFAULT_SUPPORT_VIDEO_TYPES,\n])\n/**\n * Comma-separated string of all supported media MIME types, useful for HTML file input accept attributes.\n *\n * @example\n * ```ts\n * import { DEFAULT_SUPPORTED_MEDIA_TYPE_LIST } from '@tldraw/utils'\n *\n * // Use in HTML file input for media uploads\n * const input = document.createElement('input')\n * input.type = 'file'\n * input.accept = DEFAULT_SUPPORTED_MEDIA_TYPE_LIST\n * input.addEventListener('change', (e) => {\n * const files = (e.target as HTMLInputElement).files\n * if (files) console.log(`Selected ${files.length} file(s)`)\n * })\n * ```\n * @public\n */\nexport const DEFAULT_SUPPORTED_MEDIA_TYPE_LIST = DEFAULT_SUPPORTED_MEDIA_TYPES.join(',')\n\n/**\n * Helpers for media\n *\n * @public\n */\nexport class MediaHelpers {\n\t/**\n\t * Load a video element from a URL with cross-origin support.\n\t *\n\t * @param src - The URL of the video to load\n\t * @returns Promise that resolves to the loaded HTMLVideoElement\n\t * @example\n\t * ```ts\n\t * const video = await MediaHelpers.loadVideo('https://example.com/video.mp4')\n\t * console.log(`Video dimensions: ${video.videoWidth}x${video.videoHeight}`)\n\t * ```\n\t * @public\n\t */\n\tstatic loadVideo(src: string): Promise<HTMLVideoElement> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst video = document.createElement('video')\n\t\t\tvideo.onloadeddata = () => resolve(video)\n\t\t\tvideo.onerror = (e) => {\n\t\t\t\tconsole.error(e)\n\t\t\t\treject(new Error('Could not load video'))\n\t\t\t}\n\t\t\tvideo.crossOrigin = 'anonymous'\n\t\t\tvideo.src = src\n\t\t})\n\t}\n\n\t/**\n\t * Extract a frame from a video element as a data URL.\n\t *\n\t * @param video - The HTMLVideoElement to extract frame from\n\t * @param time - The time in seconds to extract the frame from (default: 0)\n\t * @returns Promise that resolves to a data URL of the video frame\n\t * @example\n\t * ```ts\n\t * const video = await MediaHelpers.loadVideo('https://example.com/video.mp4')\n\t * const frameDataUrl = await MediaHelpers.getVideoFrameAsDataUrl(video, 5.0)\n\t * // Use frameDataUrl as image thumbnail\n\t * const img = document.createElement('img')\n\t * img.src = frameDataUrl\n\t * ```\n\t * @public\n\t */\n\tstatic async getVideoFrameAsDataUrl(video: HTMLVideoElement, time = 0): Promise<string> {\n\t\tconst promise = promiseWithResolve<string>()\n\t\tlet didSetTime = false\n\n\t\tconst onReadyStateChanged = () => {\n\t\t\tif (!didSetTime) {\n\t\t\t\tif (video.readyState >= video.HAVE_METADATA) {\n\t\t\t\t\tdidSetTime = true\n\t\t\t\t\tvideo.currentTime = time\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (video.readyState >= video.HAVE_CURRENT_DATA) {\n\t\t\t\tconst canvas = document.createElement('canvas')\n\t\t\t\tcanvas.width = video.videoWidth\n\t\t\t\tcanvas.height = video.videoHeight\n\t\t\t\tconst ctx = canvas.getContext('2d')\n\t\t\t\tif (!ctx) {\n\t\t\t\t\tthrow new Error('Could not get 2d context')\n\t\t\t\t}\n\t\t\t\tctx.drawImage(video, 0, 0)\n\t\t\t\tpromise.resolve(canvas.toDataURL())\n\t\t\t}\n\t\t}\n\t\tconst onError = (e: Event) => {\n\t\t\tconsole.error(e)\n\t\t\tpromise.reject(new Error('Could not get video frame'))\n\t\t}\n\n\t\tvideo.addEventListener('loadedmetadata', onReadyStateChanged)\n\t\tvideo.addEventListener('loadeddata', onReadyStateChanged)\n\t\tvideo.addEventListener('canplay', onReadyStateChanged)\n\t\tvideo.addEventListener('seeked', onReadyStateChanged)\n\n\t\tvideo.addEventListener('error', onError)\n\t\tvideo.addEventListener('stalled', onError)\n\n\t\tonReadyStateChanged()\n\n\t\ttry {\n\t\t\treturn await promise\n\t\t} finally {\n\t\t\tvideo.removeEventListener('loadedmetadata', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('loadeddata', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('canplay', onReadyStateChanged)\n\t\t\tvideo.removeEventListener('seeked', onReadyStateChanged)\n\n\t\t\tvideo.removeEventListener('error', onError)\n\t\t\tvideo.removeEventListener('stalled', onError)\n\t\t}\n\t}\n\n\t/**\n\t * Load an image from a URL and get its dimensions along with the image element.\n\t *\n\t * @param src - The URL of the image to load\n\t * @returns Promise that resolves to an object with width, height, and the image element\n\t * @example\n\t * ```ts\n\t * const { w, h, image } = await MediaHelpers.getImageAndDimensions('https://example.com/image.png')\n\t * console.log(`Image size: ${w}x${h}`)\n\t * // Image is ready to use\n\t * document.body.appendChild(image)\n\t * ```\n\t * @public\n\t */\n\tstatic getImageAndDimensions(\n\t\tsrc: string\n\t): Promise<{ w: number; h: number; image: HTMLImageElement }> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst img = Image()\n\t\t\timg.onload = () => {\n\t\t\t\tlet dimensions\n\t\t\t\tif (img.naturalWidth) {\n\t\t\t\t\tdimensions = {\n\t\t\t\t\t\tw: img.naturalWidth,\n\t\t\t\t\t\th: img.naturalHeight,\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Sigh, Firefox doesn't have naturalWidth or naturalHeight for SVGs. :-/\n\t\t\t\t\t// We have to attach to dom and use clientWidth/clientHeight.\n\t\t\t\t\tdocument.body.appendChild(img)\n\t\t\t\t\tdimensions = {\n\t\t\t\t\t\tw: img.clientWidth,\n\t\t\t\t\t\th: img.clientHeight,\n\t\t\t\t\t}\n\t\t\t\t\tdocument.body.removeChild(img)\n\t\t\t\t}\n\t\t\t\tresolve({ ...dimensions, image: img })\n\t\t\t}\n\t\t\timg.onerror = (e) => {\n\t\t\t\tconsole.error(e)\n\t\t\t\treject(new Error('Could not load image'))\n\t\t\t}\n\t\t\timg.crossOrigin = 'anonymous'\n\t\t\timg.referrerPolicy = 'strict-origin-when-cross-origin'\n\t\t\timg.style.visibility = 'hidden'\n\t\t\timg.style.position = 'absolute'\n\t\t\timg.style.opacity = '0'\n\t\t\timg.style.zIndex = '-9999'\n\t\t\timg.src = src\n\t\t})\n\t}\n\n\t/**\n\t * Get the size of a video blob\n\t *\n\t * @param blob - A Blob containing the video\n\t * @returns Promise that resolves to an object with width and height properties\n\t * @example\n\t * ```ts\n\t * const file = new File([...], 'video.mp4', { type: 'video/mp4' })\n\t * const { w, h } = await MediaHelpers.getVideoSize(file)\n\t * console.log(`Video dimensions: ${w}x${h}`)\n\t * ```\n\t * @public\n\t */\n\tstatic async getVideoSize(blob: Blob): Promise<{ w: number; h: number }> {\n\t\treturn MediaHelpers.usingObjectURL(blob, async (url) => {\n\t\t\tconst video = await MediaHelpers.loadVideo(url)\n\t\t\treturn { w: video.videoWidth, h: video.videoHeight }\n\t\t})\n\t}\n\n\t/**\n\t * Get the size of an image blob\n\t *\n\t * @param blob - A Blob containing the image\n\t * @returns Promise that resolves to an object with width and height properties\n\t * @example\n\t * ```ts\n\t * const file = new File([...], 'image.png', { type: 'image/png' })\n\t * const { w, h } = await MediaHelpers.getImageSize(file)\n\t * console.log(`Image dimensions: ${w}x${h}`)\n\t * ```\n\t * @public\n\t */\n\tstatic async getImageSize(blob: Blob): Promise<{ w: number; h: number; pixelRatio: number }> {\n\t\tconst { w, h } = await MediaHelpers.usingObjectURL(blob, MediaHelpers.getImageAndDimensions)\n\n\t\ttry {\n\t\t\tif (blob.type === 'image/png') {\n\t\t\t\tconst view = new DataView(await blob.arrayBuffer())\n\t\t\t\tif (PngHelpers.isPng(view, 0)) {\n\t\t\t\t\tconst physChunk = PngHelpers.findChunk(view, 'pHYs')\n\t\t\t\t\tif (physChunk) {\n\t\t\t\t\t\tconst physData = PngHelpers.parsePhys(view, physChunk.dataOffset)\n\t\t\t\t\t\tif (physData.unit === 1 && physData.ppux === physData.ppuy) {\n\t\t\t\t\t\t\tconst dpi = Math.round(physData.ppux * 0.0254)\n\t\t\t\t\t\t\t// Try both standard baselines: Windows/web = 96, macOS = 72.\n\t\t\t\t\t\t\t// Pick whichever yields a clean integer ratio > 1.\n\t\t\t\t\t\t\tconst r96 = dpi / 96\n\t\t\t\t\t\t\tconst r72 = dpi / 72\n\t\t\t\t\t\t\tlet pixelRatio = 1\n\t\t\t\t\t\t\tif (Number.isInteger(r96) && r96 > 1) {\n\t\t\t\t\t\t\t\tpixelRatio = r96\n\t\t\t\t\t\t\t} else if (Number.isInteger(r72) && r72 > 1) {\n\t\t\t\t\t\t\t\tpixelRatio = r72\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (pixelRatio > 1) {\n\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\tw: Math.ceil(w / pixelRatio),\n\t\t\t\t\t\t\t\t\th: Math.ceil(h / pixelRatio),\n\t\t\t\t\t\t\t\t\tpixelRatio,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconsole.error(err)\n\t\t\treturn { w, h, pixelRatio: 1 }\n\t\t}\n\t\treturn { w, h, pixelRatio: 1 }\n\t}\n\n\t/**\n\t * Check if a media file blob contains animation data.\n\t *\n\t * @param file - The Blob to check for animation\n\t * @returns Promise that resolves to true if the file is animated, false otherwise\n\t * @example\n\t * ```ts\n\t * const file = new File([...], 'animation.gif', { type: 'image/gif' })\n\t * const animated = await MediaHelpers.isAnimated(file)\n\t * console.log(animated ? 'Animated' : 'Static')\n\t * ```\n\t * @public\n\t */\n\tstatic async isAnimated(file: Blob): Promise<boolean> {\n\t\tif (file.type === 'image/gif') {\n\t\t\treturn isGifAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/avif') {\n\t\t\treturn isAvifAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/webp') {\n\t\t\treturn isWebpAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\tif (file.type === 'image/apng') {\n\t\t\treturn isApngAnimated(await file.arrayBuffer())\n\t\t}\n\n\t\treturn false\n\t}\n\n\t/**\n\t * Check if a MIME type represents an animated image format.\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is an animated image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isAnimated = MediaHelpers.isAnimatedImageType('image/gif')\n\t * console.log(isAnimated) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isAnimatedImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Check if a MIME type represents a static (non-animated) image format.\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is a static image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isStatic = MediaHelpers.isStaticImageType('image/jpeg')\n\t * console.log(isStatic) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isStaticImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Check if a MIME type represents a vector image format.\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is a vector image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isVector = MediaHelpers.isVectorImageType('image/svg+xml')\n\t * console.log(isVector) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isVectorImageType(mimeType: string | null): boolean {\n\t\treturn DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Check if a MIME type represents any supported image format (static, animated, or vector).\n\t *\n\t * @param mimeType - The MIME type to check\n\t * @returns True if the MIME type is a supported image format, false otherwise\n\t * @example\n\t * ```ts\n\t * const isImage = MediaHelpers.isImageType('image/png')\n\t * console.log(isImage) // true\n\t * ```\n\t * @public\n\t */\n\tstatic isImageType(mimeType: string): boolean {\n\t\treturn DEFAULT_SUPPORTED_IMAGE_TYPES.includes((mimeType as any) || '')\n\t}\n\n\t/**\n\t * Utility function to create an object URL from a blob, execute a function with it, and automatically clean it up.\n\t *\n\t * @param blob - The Blob to create an object URL for\n\t * @param fn - Function to execute with the object URL\n\t * @returns Promise that resolves to the result of the function\n\t * @example\n\t * ```ts\n\t * const result = await MediaHelpers.usingObjectURL(imageBlob, async (url) => {\n\t * const { w, h } = await MediaHelpers.getImageAndDimensions(url)\n\t * return { width: w, height: h }\n\t * })\n\t * // Object URL is automatically revoked after function completes\n\t * console.log(`Image dimensions: ${result.width}x${result.height}`)\n\t * ```\n\t * @public\n\t */\n\tstatic async usingObjectURL<T>(blob: Blob, fn: (url: string) => Promise<T>): Promise<T> {\n\t\tconst url = URL.createObjectURL(blob)\n\t\ttry {\n\t\t\treturn await fn(url)\n\t\t} finally {\n\t\t\tURL.revokeObjectURL(url)\n\t\t}\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/lib/version.mjs
CHANGED
|
@@ -33,6 +33,12 @@ function registerTldrawLibraryVersion(name, version, modules) {
|
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
const info = getLibraryVersions();
|
|
36
|
+
if (isNextjsDev()) {
|
|
37
|
+
const isDuplicate = info.versions.some(
|
|
38
|
+
(v) => v.name === name && v.version === version && v.modules === modules
|
|
39
|
+
);
|
|
40
|
+
if (isDuplicate) return;
|
|
41
|
+
}
|
|
36
42
|
info.versions.push({ name, version, modules });
|
|
37
43
|
if (!info.scheduledNotice) {
|
|
38
44
|
try {
|
|
@@ -135,6 +141,13 @@ const formats = {
|
|
|
135
141
|
function format(value, formatters = []) {
|
|
136
142
|
return `\x1B[${formatters.map((f) => formats[f]).join(";")}m${value}\x1B[m`;
|
|
137
143
|
}
|
|
144
|
+
function isNextjsDev() {
|
|
145
|
+
try {
|
|
146
|
+
return process.env.NODE_ENV === "development" && "__NEXT_DATA__" in globalThis;
|
|
147
|
+
} catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
138
151
|
function entry(map, key, defaultValue) {
|
|
139
152
|
if (map.has(key)) {
|
|
140
153
|
return map.get(key);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/version.ts"],
|
|
4
|
-
"sourcesContent": ["interface TldrawLibraryVersion {\n\tname: string\n\tversion: string\n\tmodules: string\n}\n\ninterface TldrawLibraryVersionInfo {\n\tversions: TldrawLibraryVersion[]\n\tdidWarn: boolean\n\tscheduledNotice: number | NodeJS.Timeout | null\n}\n\nconst TLDRAW_LIBRARY_VERSION_KEY = '__TLDRAW_LIBRARY_VERSIONS__' as const\n\n// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword, @typescript-eslint/no-namespace\ndeclare module globalThis {\n\texport const __TLDRAW_LIBRARY_VERSIONS__: TldrawLibraryVersionInfo\n}\n\nfunction getLibraryVersions(): TldrawLibraryVersionInfo {\n\tif (globalThis[TLDRAW_LIBRARY_VERSION_KEY]) {\n\t\treturn globalThis[TLDRAW_LIBRARY_VERSION_KEY]\n\t}\n\n\tconst info: TldrawLibraryVersionInfo = {\n\t\tversions: [],\n\t\tdidWarn: false,\n\t\tscheduledNotice: null,\n\t}\n\n\tObject.defineProperty(globalThis, TLDRAW_LIBRARY_VERSION_KEY, {\n\t\tvalue: info,\n\t\twritable: false,\n\t\tconfigurable: false,\n\t\tenumerable: false,\n\t})\n\n\treturn info\n}\n\n/**\n * Clears all registered library versions and resets warning state.\n * This function is intended for testing purposes only to reset the global version tracking state.\n * @returns void\n * @example\n * ```ts\n * // In a test setup\n * beforeEach(() => {\n * clearRegisteredVersionsForTests()\n * })\n *\n * // Now version tracking starts fresh for each test\n * registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')\n * ```\n * @internal\n */\nexport function clearRegisteredVersionsForTests() {\n\tconst info = getLibraryVersions()\n\tinfo.versions = []\n\tinfo.didWarn = false\n\tif (info.scheduledNotice) {\n\t\tclearTimeout(info.scheduledNotice)\n\t\tinfo.scheduledNotice = null\n\t}\n}\n\n/**\n * Registers a tldraw library version for conflict detection.\n * This function tracks different tldraw library versions to warn about potential conflicts\n * when multiple versions are loaded simultaneously.\n * @param name - The name of the tldraw library package (e.g., '\\@tldraw/editor').\n * @param version - The semantic version string (e.g., '2.0.0').\n * @param modules - The module system being used ('esm' or 'cjs').\n * @returns void\n * @example\n * ```ts\n * // Register a library version during package initialization\n * registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')\n * registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')\n *\n * // If conflicting versions are detected, warnings will be logged:\n * registerTldrawLibraryVersion('@tldraw/editor', '1.9.0', 'cjs')\n * // Console warning about version mismatch will appear\n * ```\n * @internal\n */\nexport function registerTldrawLibraryVersion(name?: string, version?: string, modules?: string) {\n\tif (!name || !version || !modules) {\n\t\tif ((globalThis as any).TLDRAW_LIBRARY_IS_BUILD) {\n\t\t\tthrow new Error('Missing name/version/module system in built version of tldraw library')\n\t\t}\n\t\treturn\n\t}\n\n\tconst info = getLibraryVersions()\n\tinfo.versions.push({ name, version, modules })\n\n\tif (!info.scheduledNotice) {\n\t\ttry {\n\t\t\t// eslint-disable-next-line no-restricted-globals\n\t\t\tinfo.scheduledNotice = setTimeout(() => {\n\t\t\t\tinfo.scheduledNotice = null\n\t\t\t\tcheckLibraryVersions(info)\n\t\t\t}, 100)\n\t\t} catch {\n\t\t\t// some environments (e.g. cloudflare workers) don't support setTimeout immediately, only in a handler.\n\t\t\t// in this case, we'll just check immediately.\n\t\t\tcheckLibraryVersions(info)\n\t\t}\n\t}\n}\n\nfunction checkLibraryVersions(info: TldrawLibraryVersionInfo) {\n\tif (!info.versions.length) return\n\tif (info.didWarn) return\n\n\tconst sorted = info.versions.sort((a, b) => compareVersions(a.version, b.version))\n\tconst latestVersion = sorted[sorted.length - 1].version\n\n\tconst matchingVersions = new Set<string>()\n\tconst nonMatchingVersions = new Map<string, Set<string>>()\n\tfor (const lib of sorted) {\n\t\tif (nonMatchingVersions.has(lib.name)) {\n\t\t\tmatchingVersions.delete(lib.name)\n\t\t\tentry(nonMatchingVersions, lib.name, new Set()).add(lib.version)\n\t\t\tcontinue\n\t\t}\n\n\t\tif (lib.version === latestVersion) {\n\t\t\tmatchingVersions.add(lib.name)\n\t\t} else {\n\t\t\tmatchingVersions.delete(lib.name)\n\t\t\tentry(nonMatchingVersions, lib.name, new Set()).add(lib.version)\n\t\t}\n\t}\n\n\tif (nonMatchingVersions.size > 0) {\n\t\tconst message = [\n\t\t\t`${format('[tldraw]', ['bold', 'bgRed', 'textWhite'])} ${format('You have multiple versions of tldraw libraries installed. This can lead to bugs and unexpected behavior.', ['textRed', 'bold'])}`,\n\t\t\t'',\n\t\t\t`The latest version you have installed is ${format(`v${latestVersion}`, ['bold', 'textBlue'])}. The following libraries are on the latest version:`,\n\t\t\t...Array.from(matchingVersions, (name) => ` \u2022 \u2705 ${format(name, ['bold'])}`),\n\t\t\t'',\n\t\t\t`The following libraries are not on the latest version, or have multiple versions installed:`,\n\t\t\t...Array.from(nonMatchingVersions, ([name, versions]) => {\n\t\t\t\tconst sortedVersions = Array.from(versions)\n\t\t\t\t\t.sort(compareVersions)\n\t\t\t\t\t.map((v) => format(`v${v}`, v === latestVersion ? ['textGreen'] : ['textRed']))\n\t\t\t\treturn ` \u2022 \u274C ${format(name, ['bold'])} (${sortedVersions.join(', ')})`\n\t\t\t}),\n\t\t]\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(message.join('\\n'))\n\t\tinfo.didWarn = true\n\t\treturn\n\t}\n\n\t// at this point, we know that everything has the same version. there may still be duplicates though!\n\tconst potentialDuplicates = new Map<string, { version: string; modules: string[] }>()\n\tfor (const lib of sorted) {\n\t\tentry(potentialDuplicates, lib.name, { version: lib.version, modules: [] }).modules.push(\n\t\t\tlib.modules\n\t\t)\n\t}\n\n\tconst duplicates = new Map<string, { version: string; modules: string[] }>()\n\tfor (const [name, lib] of potentialDuplicates) {\n\t\tif (lib.modules.length > 1) duplicates.set(name, lib)\n\t}\n\n\tif (duplicates.size > 0) {\n\t\tconst message = [\n\t\t\t`${format('[tldraw]', ['bold', 'bgRed', 'textWhite'])} ${format('You have multiple instances of some tldraw libraries active. This can lead to bugs and unexpected behavior. ', ['textRed', 'bold'])}`,\n\t\t\t'',\n\t\t\t'This usually means that your bundler is misconfigured, and is importing the same library multiple times - usually once as an ES Module, and once as a CommonJS module.',\n\t\t\t'',\n\t\t\t'The following libraries have been imported multiple times:',\n\t\t\t...Array.from(duplicates, ([name, lib]) => {\n\t\t\t\tconst modules = lib.modules\n\t\t\t\t\t.map((m, i) => (m === 'esm' ? ` ${i + 1}. ES Modules` : ` ${i + 1}. CommonJS`))\n\t\t\t\t\t.join('\\n')\n\t\t\t\treturn ` \u2022 \u274C ${format(name, ['bold'])} v${lib.version}: \\n${modules}`\n\t\t\t}),\n\t\t\t'',\n\t\t\t'You should configure your bundler to only import one version of each library.',\n\t\t]\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(message.join('\\n'))\n\t\tinfo.didWarn = true\n\t\treturn\n\t}\n}\n\nfunction compareVersions(a: string, b: string) {\n\tconst aMatch = a.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\w+))?$/)\n\tconst bMatch = b.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\w+))?$/)\n\n\tif (!aMatch || !bMatch) return a.localeCompare(b)\n\tif (aMatch[1] !== bMatch[1]) return Number(aMatch[1]) - Number(bMatch[1])\n\tif (aMatch[2] !== bMatch[2]) return Number(aMatch[2]) - Number(bMatch[2])\n\tif (aMatch[3] !== bMatch[3]) return Number(aMatch[3]) - Number(bMatch[3])\n\tif (aMatch[4] && bMatch[4]) return aMatch[4].localeCompare(bMatch[4])\n\tif (aMatch[4]) return 1\n\tif (bMatch[4]) return -1\n\treturn 0\n}\n\nconst formats = {\n\tbold: '1',\n\ttextBlue: '94',\n\ttextRed: '31',\n\ttextGreen: '32',\n\tbgRed: '41',\n\ttextWhite: '97',\n} as const\nfunction format(value: string, formatters: (keyof typeof formats)[] = []) {\n\treturn `\\x1B[${formatters.map((f) => formats[f]).join(';')}m${value}\\x1B[m`\n}\n\nfunction entry<K, V>(map: Map<K, V>, key: K, defaultValue: V): V {\n\tif (map.has(key)) {\n\t\treturn map.get(key)!\n\t}\n\tmap.set(key, defaultValue)\n\treturn defaultValue\n}\n"],
|
|
5
|
-
"mappings": "AAYA,MAAM,6BAA6B;AAOnC,SAAS,qBAA+C;AACvD,MAAI,WAAW,0BAA0B,GAAG;AAC3C,WAAO,WAAW,0BAA0B;AAAA,EAC7C;AAEA,QAAM,OAAiC;AAAA,IACtC,UAAU,CAAC;AAAA,IACX,SAAS;AAAA,IACT,iBAAiB;AAAA,EAClB;AAEA,SAAO,eAAe,YAAY,4BAA4B;AAAA,IAC7D,OAAO;AAAA,IACP,UAAU;AAAA,IACV,cAAc;AAAA,IACd,YAAY;AAAA,EACb,CAAC;AAED,SAAO;AACR;AAkBO,SAAS,kCAAkC;AACjD,QAAM,OAAO,mBAAmB;AAChC,OAAK,WAAW,CAAC;AACjB,OAAK,UAAU;AACf,MAAI,KAAK,iBAAiB;AACzB,iBAAa,KAAK,eAAe;AACjC,SAAK,kBAAkB;AAAA,EACxB;AACD;AAsBO,SAAS,6BAA6B,MAAe,SAAkB,SAAkB;AAC/F,MAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,SAAS;AAClC,QAAK,MAA4C;AAChD,YAAM,IAAI,MAAM,uEAAuE;AAAA,IACxF;AACA;AAAA,EACD;AAEA,QAAM,OAAO,mBAAmB;
|
|
4
|
+
"sourcesContent": ["interface TldrawLibraryVersion {\n\tname: string\n\tversion: string\n\tmodules: string\n}\n\ninterface TldrawLibraryVersionInfo {\n\tversions: TldrawLibraryVersion[]\n\tdidWarn: boolean\n\tscheduledNotice: number | NodeJS.Timeout | null\n}\n\nconst TLDRAW_LIBRARY_VERSION_KEY = '__TLDRAW_LIBRARY_VERSIONS__' as const\n\n// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword, @typescript-eslint/no-namespace\ndeclare module globalThis {\n\texport const __TLDRAW_LIBRARY_VERSIONS__: TldrawLibraryVersionInfo\n}\n\nfunction getLibraryVersions(): TldrawLibraryVersionInfo {\n\tif (globalThis[TLDRAW_LIBRARY_VERSION_KEY]) {\n\t\treturn globalThis[TLDRAW_LIBRARY_VERSION_KEY]\n\t}\n\n\tconst info: TldrawLibraryVersionInfo = {\n\t\tversions: [],\n\t\tdidWarn: false,\n\t\tscheduledNotice: null,\n\t}\n\n\tObject.defineProperty(globalThis, TLDRAW_LIBRARY_VERSION_KEY, {\n\t\tvalue: info,\n\t\twritable: false,\n\t\tconfigurable: false,\n\t\tenumerable: false,\n\t})\n\n\treturn info\n}\n\n/**\n * Clears all registered library versions and resets warning state.\n * This function is intended for testing purposes only to reset the global version tracking state.\n * @returns void\n * @example\n * ```ts\n * // In a test setup\n * beforeEach(() => {\n * clearRegisteredVersionsForTests()\n * })\n *\n * // Now version tracking starts fresh for each test\n * registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')\n * ```\n * @internal\n */\nexport function clearRegisteredVersionsForTests() {\n\tconst info = getLibraryVersions()\n\tinfo.versions = []\n\tinfo.didWarn = false\n\tif (info.scheduledNotice) {\n\t\tclearTimeout(info.scheduledNotice)\n\t\tinfo.scheduledNotice = null\n\t}\n}\n\n/**\n * Registers a tldraw library version for conflict detection.\n * This function tracks different tldraw library versions to warn about potential conflicts\n * when multiple versions are loaded simultaneously.\n * @param name - The name of the tldraw library package (e.g., '\\@tldraw/editor').\n * @param version - The semantic version string (e.g., '2.0.0').\n * @param modules - The module system being used ('esm' or 'cjs').\n * @returns void\n * @example\n * ```ts\n * // Register a library version during package initialization\n * registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')\n * registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')\n *\n * // If conflicting versions are detected, warnings will be logged:\n * registerTldrawLibraryVersion('@tldraw/editor', '1.9.0', 'cjs')\n * // Console warning about version mismatch will appear\n * ```\n * @internal\n */\nexport function registerTldrawLibraryVersion(name?: string, version?: string, modules?: string) {\n\tif (!name || !version || !modules) {\n\t\tif ((globalThis as any).TLDRAW_LIBRARY_IS_BUILD) {\n\t\t\tthrow new Error('Missing name/version/module system in built version of tldraw library')\n\t\t}\n\t\treturn\n\t}\n\n\tconst info = getLibraryVersions()\n\n\t// In Next.js dev mode, Fast Refresh re-executes module-level code which causes\n\t// the same library to register multiple times, producing a false positive warning.\n\t// We skip exact duplicates only in Next.js dev to avoid hiding genuine issues.\n\tif (isNextjsDev()) {\n\t\tconst isDuplicate = info.versions.some(\n\t\t\t(v) => v.name === name && v.version === version && v.modules === modules\n\t\t)\n\t\tif (isDuplicate) return\n\t}\n\n\tinfo.versions.push({ name, version, modules })\n\n\tif (!info.scheduledNotice) {\n\t\ttry {\n\t\t\t// eslint-disable-next-line no-restricted-globals\n\t\t\tinfo.scheduledNotice = setTimeout(() => {\n\t\t\t\tinfo.scheduledNotice = null\n\t\t\t\tcheckLibraryVersions(info)\n\t\t\t}, 100)\n\t\t} catch {\n\t\t\t// some environments (e.g. cloudflare workers) don't support setTimeout immediately, only in a handler.\n\t\t\t// in this case, we'll just check immediately.\n\t\t\tcheckLibraryVersions(info)\n\t\t}\n\t}\n}\n\nfunction checkLibraryVersions(info: TldrawLibraryVersionInfo) {\n\tif (!info.versions.length) return\n\tif (info.didWarn) return\n\n\tconst sorted = info.versions.sort((a, b) => compareVersions(a.version, b.version))\n\tconst latestVersion = sorted[sorted.length - 1].version\n\n\tconst matchingVersions = new Set<string>()\n\tconst nonMatchingVersions = new Map<string, Set<string>>()\n\tfor (const lib of sorted) {\n\t\tif (nonMatchingVersions.has(lib.name)) {\n\t\t\tmatchingVersions.delete(lib.name)\n\t\t\tentry(nonMatchingVersions, lib.name, new Set()).add(lib.version)\n\t\t\tcontinue\n\t\t}\n\n\t\tif (lib.version === latestVersion) {\n\t\t\tmatchingVersions.add(lib.name)\n\t\t} else {\n\t\t\tmatchingVersions.delete(lib.name)\n\t\t\tentry(nonMatchingVersions, lib.name, new Set()).add(lib.version)\n\t\t}\n\t}\n\n\tif (nonMatchingVersions.size > 0) {\n\t\tconst message = [\n\t\t\t`${format('[tldraw]', ['bold', 'bgRed', 'textWhite'])} ${format('You have multiple versions of tldraw libraries installed. This can lead to bugs and unexpected behavior.', ['textRed', 'bold'])}`,\n\t\t\t'',\n\t\t\t`The latest version you have installed is ${format(`v${latestVersion}`, ['bold', 'textBlue'])}. The following libraries are on the latest version:`,\n\t\t\t...Array.from(matchingVersions, (name) => ` \u2022 \u2705 ${format(name, ['bold'])}`),\n\t\t\t'',\n\t\t\t`The following libraries are not on the latest version, or have multiple versions installed:`,\n\t\t\t...Array.from(nonMatchingVersions, ([name, versions]) => {\n\t\t\t\tconst sortedVersions = Array.from(versions)\n\t\t\t\t\t.sort(compareVersions)\n\t\t\t\t\t.map((v) => format(`v${v}`, v === latestVersion ? ['textGreen'] : ['textRed']))\n\t\t\t\treturn ` \u2022 \u274C ${format(name, ['bold'])} (${sortedVersions.join(', ')})`\n\t\t\t}),\n\t\t]\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(message.join('\\n'))\n\t\tinfo.didWarn = true\n\t\treturn\n\t}\n\n\t// at this point, we know that everything has the same version. there may still be duplicates though!\n\tconst potentialDuplicates = new Map<string, { version: string; modules: string[] }>()\n\tfor (const lib of sorted) {\n\t\tentry(potentialDuplicates, lib.name, { version: lib.version, modules: [] }).modules.push(\n\t\t\tlib.modules\n\t\t)\n\t}\n\n\tconst duplicates = new Map<string, { version: string; modules: string[] }>()\n\tfor (const [name, lib] of potentialDuplicates) {\n\t\tif (lib.modules.length > 1) duplicates.set(name, lib)\n\t}\n\n\tif (duplicates.size > 0) {\n\t\tconst message = [\n\t\t\t`${format('[tldraw]', ['bold', 'bgRed', 'textWhite'])} ${format('You have multiple instances of some tldraw libraries active. This can lead to bugs and unexpected behavior. ', ['textRed', 'bold'])}`,\n\t\t\t'',\n\t\t\t'This usually means that your bundler is misconfigured, and is importing the same library multiple times - usually once as an ES Module, and once as a CommonJS module.',\n\t\t\t'',\n\t\t\t'The following libraries have been imported multiple times:',\n\t\t\t...Array.from(duplicates, ([name, lib]) => {\n\t\t\t\tconst modules = lib.modules\n\t\t\t\t\t.map((m, i) => (m === 'esm' ? ` ${i + 1}. ES Modules` : ` ${i + 1}. CommonJS`))\n\t\t\t\t\t.join('\\n')\n\t\t\t\treturn ` \u2022 \u274C ${format(name, ['bold'])} v${lib.version}: \\n${modules}`\n\t\t\t}),\n\t\t\t'',\n\t\t\t'You should configure your bundler to only import one version of each library.',\n\t\t]\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(message.join('\\n'))\n\t\tinfo.didWarn = true\n\t\treturn\n\t}\n}\n\nfunction compareVersions(a: string, b: string) {\n\tconst aMatch = a.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\w+))?$/)\n\tconst bMatch = b.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\w+))?$/)\n\n\tif (!aMatch || !bMatch) return a.localeCompare(b)\n\tif (aMatch[1] !== bMatch[1]) return Number(aMatch[1]) - Number(bMatch[1])\n\tif (aMatch[2] !== bMatch[2]) return Number(aMatch[2]) - Number(bMatch[2])\n\tif (aMatch[3] !== bMatch[3]) return Number(aMatch[3]) - Number(bMatch[3])\n\tif (aMatch[4] && bMatch[4]) return aMatch[4].localeCompare(bMatch[4])\n\tif (aMatch[4]) return 1\n\tif (bMatch[4]) return -1\n\treturn 0\n}\n\nconst formats = {\n\tbold: '1',\n\ttextBlue: '94',\n\ttextRed: '31',\n\ttextGreen: '32',\n\tbgRed: '41',\n\ttextWhite: '97',\n} as const\nfunction format(value: string, formatters: (keyof typeof formats)[] = []) {\n\treturn `\\x1B[${formatters.map((f) => formats[f]).join(';')}m${value}\\x1B[m`\n}\n\nfunction isNextjsDev(): boolean {\n\ttry {\n\t\treturn process.env.NODE_ENV === 'development' && '__NEXT_DATA__' in globalThis\n\t} catch {\n\t\treturn false\n\t}\n}\n\nfunction entry<K, V>(map: Map<K, V>, key: K, defaultValue: V): V {\n\tif (map.has(key)) {\n\t\treturn map.get(key)!\n\t}\n\tmap.set(key, defaultValue)\n\treturn defaultValue\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
package/src/lib/media/media.ts
CHANGED
|
@@ -309,7 +309,7 @@ export class MediaHelpers {
|
|
|
309
309
|
* ```
|
|
310
310
|
* @public
|
|
311
311
|
*/
|
|
312
|
-
static async getImageSize(blob: Blob): Promise<{ w: number; h: number }> {
|
|
312
|
+
static async getImageSize(blob: Blob): Promise<{ w: number; h: number; pixelRatio: number }> {
|
|
313
313
|
const { w, h } = await MediaHelpers.usingObjectURL(blob, MediaHelpers.getImageAndDimensions)
|
|
314
314
|
|
|
315
315
|
try {
|
|
@@ -320,15 +320,23 @@ export class MediaHelpers {
|
|
|
320
320
|
if (physChunk) {
|
|
321
321
|
const physData = PngHelpers.parsePhys(view, physChunk.dataOffset)
|
|
322
322
|
if (physData.unit === 1 && physData.ppux === physData.ppuy) {
|
|
323
|
-
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
323
|
+
const dpi = Math.round(physData.ppux * 0.0254)
|
|
324
|
+
// Try both standard baselines: Windows/web = 96, macOS = 72.
|
|
325
|
+
// Pick whichever yields a clean integer ratio > 1.
|
|
326
|
+
const r96 = dpi / 96
|
|
327
|
+
const r72 = dpi / 72
|
|
328
|
+
let pixelRatio = 1
|
|
329
|
+
if (Number.isInteger(r96) && r96 > 1) {
|
|
330
|
+
pixelRatio = r96
|
|
331
|
+
} else if (Number.isInteger(r72) && r72 > 1) {
|
|
332
|
+
pixelRatio = r72
|
|
333
|
+
}
|
|
334
|
+
if (pixelRatio > 1) {
|
|
335
|
+
return {
|
|
336
|
+
w: Math.ceil(w / pixelRatio),
|
|
337
|
+
h: Math.ceil(h / pixelRatio),
|
|
338
|
+
pixelRatio,
|
|
339
|
+
}
|
|
332
340
|
}
|
|
333
341
|
}
|
|
334
342
|
}
|
|
@@ -336,9 +344,9 @@ export class MediaHelpers {
|
|
|
336
344
|
}
|
|
337
345
|
} catch (err) {
|
|
338
346
|
console.error(err)
|
|
339
|
-
return { w, h }
|
|
347
|
+
return { w, h, pixelRatio: 1 }
|
|
340
348
|
}
|
|
341
|
-
return { w, h }
|
|
349
|
+
return { w, h, pixelRatio: 1 }
|
|
342
350
|
}
|
|
343
351
|
|
|
344
352
|
/**
|
package/src/lib/version.test.ts
CHANGED
|
@@ -294,16 +294,32 @@ describe('version utilities', () => {
|
|
|
294
294
|
expect(mockConsoleLog).not.toHaveBeenCalled()
|
|
295
295
|
})
|
|
296
296
|
|
|
297
|
-
it('should
|
|
298
|
-
//
|
|
299
|
-
//
|
|
297
|
+
it('should ignore duplicate registrations in Next.js dev mode', () => {
|
|
298
|
+
// In Next.js dev mode, Fast Refresh re-executes module-level code which causes
|
|
299
|
+
// the same library to register multiple times, producing a false positive warning.
|
|
300
|
+
vi.stubGlobal('__NEXT_DATA__', {})
|
|
301
|
+
const originalNodeEnv = process.env.NODE_ENV
|
|
302
|
+
process.env.NODE_ENV = 'development'
|
|
303
|
+
|
|
304
|
+
registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
|
|
305
|
+
registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
|
|
300
306
|
registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
|
|
307
|
+
|
|
308
|
+
vi.runAllTimers()
|
|
309
|
+
|
|
310
|
+
expect(mockConsoleLog).not.toHaveBeenCalled()
|
|
311
|
+
|
|
312
|
+
process.env.NODE_ENV = originalNodeEnv
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('should detect duplicate registrations outside Next.js dev mode', () => {
|
|
316
|
+
// Outside Next.js dev, duplicate exact registrations should still be
|
|
317
|
+
// tracked and trigger the multiple instances warning
|
|
301
318
|
registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
|
|
302
319
|
registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
|
|
303
320
|
|
|
304
321
|
vi.runAllTimers()
|
|
305
322
|
|
|
306
|
-
// Multiple registrations of same version are detected as module duplicates
|
|
307
323
|
expect(mockConsoleLog).toHaveBeenCalled()
|
|
308
324
|
const logMessage = mockConsoleLog.mock.calls[0][0]
|
|
309
325
|
expect(logMessage).toContain('multiple instances')
|
package/src/lib/version.ts
CHANGED
|
@@ -93,6 +93,17 @@ export function registerTldrawLibraryVersion(name?: string, version?: string, mo
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
const info = getLibraryVersions()
|
|
96
|
+
|
|
97
|
+
// In Next.js dev mode, Fast Refresh re-executes module-level code which causes
|
|
98
|
+
// the same library to register multiple times, producing a false positive warning.
|
|
99
|
+
// We skip exact duplicates only in Next.js dev to avoid hiding genuine issues.
|
|
100
|
+
if (isNextjsDev()) {
|
|
101
|
+
const isDuplicate = info.versions.some(
|
|
102
|
+
(v) => v.name === name && v.version === version && v.modules === modules
|
|
103
|
+
)
|
|
104
|
+
if (isDuplicate) return
|
|
105
|
+
}
|
|
106
|
+
|
|
96
107
|
info.versions.push({ name, version, modules })
|
|
97
108
|
|
|
98
109
|
if (!info.scheduledNotice) {
|
|
@@ -219,6 +230,14 @@ function format(value: string, formatters: (keyof typeof formats)[] = []) {
|
|
|
219
230
|
return `\x1B[${formatters.map((f) => formats[f]).join(';')}m${value}\x1B[m`
|
|
220
231
|
}
|
|
221
232
|
|
|
233
|
+
function isNextjsDev(): boolean {
|
|
234
|
+
try {
|
|
235
|
+
return process.env.NODE_ENV === 'development' && '__NEXT_DATA__' in globalThis
|
|
236
|
+
} catch {
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
222
241
|
function entry<K, V>(map: Map<K, V>, key: K, defaultValue: V): V {
|
|
223
242
|
if (map.has(key)) {
|
|
224
243
|
return map.get(key)!
|