@liveblocks/react
Advanced tools
Comparing version
1897
dist/index.js
@@ -1,23 +0,6 @@ | ||
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client"; | ||
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); | ||
// src/index.ts | ||
var _core = require('@liveblocks/core'); | ||
// src/version.ts | ||
var PKG_NAME = "@liveblocks/react"; | ||
var PKG_VERSION = "1.19.0-test1"; | ||
var PKG_FORMAT = "cjs"; | ||
// src/ClientSideSuspense.tsx | ||
var _react = require('react'); var React = _interopRequireWildcard(_react); var React3 = _interopRequireWildcard(_react); | ||
function ClientSideSuspense(props) { | ||
const [mounted, setMounted] = React.useState(false); | ||
React.useEffect(() => { | ||
setMounted(true); | ||
}, []); | ||
return /* @__PURE__ */ React.createElement(React.Suspense, { fallback: props.fallback }, mounted ? props.children() : props.fallback); | ||
} | ||
// src/factory.tsx | ||
var _client = require('@liveblocks/client'); | ||
@@ -32,7 +15,4 @@ | ||
var _withselectorjs = require('use-sync-external-store/shim/with-selector.js'); | ||
// src/comments/CommentsRoom.tsx | ||
var _nanoid = require('nanoid'); | ||
@@ -46,1850 +26,39 @@ | ||
var _indexjs = require('use-sync-external-store/shim/index.js'); | ||
// src/comments/errors.ts | ||
var CreateThreadError = class extends Error { | ||
constructor(cause, context) { | ||
super("Create thread failed."); | ||
this.cause = cause; | ||
this.context = context; | ||
this.name = "CreateThreadError"; | ||
} | ||
}; | ||
var EditThreadMetadataError = class extends Error { | ||
constructor(cause, context) { | ||
super("Edit thread metadata failed."); | ||
this.cause = cause; | ||
this.context = context; | ||
this.name = "EditThreadMetadataError"; | ||
} | ||
}; | ||
var CreateCommentError = class extends Error { | ||
constructor(cause, context) { | ||
super("Create comment failed."); | ||
this.cause = cause; | ||
this.context = context; | ||
this.name = "CreateCommentError"; | ||
} | ||
}; | ||
var EditCommentError = class extends Error { | ||
constructor(cause, context) { | ||
super("Edit comment failed."); | ||
this.cause = cause; | ||
this.context = context; | ||
this.name = "EditCommentError"; | ||
} | ||
}; | ||
var DeleteCommentError = class extends Error { | ||
constructor(cause, context) { | ||
super("Delete comment failed."); | ||
this.cause = cause; | ||
this.context = context; | ||
this.name = "DeleteCommentError"; | ||
} | ||
}; | ||
var AddReactionError = class extends Error { | ||
constructor(cause, context) { | ||
super("Add reaction failed."); | ||
this.cause = cause; | ||
this.context = context; | ||
this.name = "AddReactionError"; | ||
} | ||
}; | ||
var RemoveReactionError = class extends Error { | ||
constructor(cause, context) { | ||
super("Remove reaction failed."); | ||
this.cause = cause; | ||
this.context = context; | ||
this.name = "RemoveReactionError"; | ||
} | ||
}; | ||
// src/comments/lib/revalidation.ts | ||
// src/comments/lib/use-is-document-visible.ts | ||
function useIsDocumentVisible() { | ||
const isVisible = _indexjs.useSyncExternalStore.call(void 0, subscribe, getSnapshot, getSnapshot); | ||
return isVisible; | ||
} | ||
function subscribe(onStoreChange) { | ||
document.addEventListener("visibilitychange", onStoreChange); | ||
return () => { | ||
document.removeEventListener("visibilitychange", onStoreChange); | ||
}; | ||
} | ||
function getSnapshot() { | ||
const isDocumentDefined = typeof document !== "undefined"; | ||
return isDocumentDefined ? document.visibilityState === "visible" : true; | ||
} | ||
// src/comments/lib/use-is-online.ts | ||
function useIsOnline() { | ||
const isOnlineRef = _react.useRef.call(void 0, true); | ||
const subscribe2 = _react.useCallback.call(void 0, (onStoreChange) => { | ||
function handleIsOnline() { | ||
isOnlineRef.current = true; | ||
onStoreChange(); | ||
} | ||
function handleIsOffline() { | ||
isOnlineRef.current = false; | ||
onStoreChange(); | ||
} | ||
window.addEventListener("online", handleIsOnline); | ||
window.addEventListener("offline", handleIsOffline); | ||
return () => { | ||
window.removeEventListener("online", handleIsOnline); | ||
window.removeEventListener("offline", handleIsOffline); | ||
}; | ||
}, []); | ||
const getSnapshot2 = _react.useCallback.call(void 0, () => { | ||
return isOnlineRef.current; | ||
}, []); | ||
const isOnline = _indexjs.useSyncExternalStore.call(void 0, subscribe2, getSnapshot2, getSnapshot2); | ||
return isOnline; | ||
} | ||
// src/comments/lib/revalidation.ts | ||
var DEFAULT_ERROR_RETRY_INTERVAL = 5e3; | ||
var DEFAULT_MAX_ERROR_RETRY_COUNT = 5; | ||
var DEFAULT_DEDUPING_INTERVAL = 2e3; | ||
var timestamp = 0; | ||
function useRevalidateCache(manager, fetcher, options = {}) { | ||
const isOnlineRef = _react.useRef.call(void 0, true); | ||
const { | ||
dedupingInterval = DEFAULT_DEDUPING_INTERVAL, | ||
errorRetryInterval = DEFAULT_ERROR_RETRY_INTERVAL, | ||
errorRetryCount = DEFAULT_MAX_ERROR_RETRY_COUNT | ||
} = options; | ||
const _revalidateCache = _react.useCallback.call(void 0, | ||
async ({ | ||
shouldDedupe, | ||
retryCount = 0 | ||
}) => { | ||
let startAt; | ||
const shouldStartRequest = !manager.getRequest() || !shouldDedupe; | ||
function deleteActiveRequest() { | ||
const activeRequest = manager.getRequest(); | ||
if (!activeRequest) | ||
return; | ||
if (activeRequest.timestamp !== startAt) | ||
return; | ||
manager.setRequest(void 0); | ||
} | ||
function handleError() { | ||
const timeout = ~~((Math.random() + 0.5) * (1 << (retryCount < 8 ? retryCount : 8))) * errorRetryInterval; | ||
if (retryCount > errorRetryCount) | ||
return; | ||
setTimeout(() => { | ||
void _revalidateCache({ | ||
shouldDedupe: false, | ||
retryCount: retryCount + 1 | ||
}); | ||
}, timeout); | ||
} | ||
if (shouldStartRequest) { | ||
manager.setRequest({ | ||
fetcher: fetcher(), | ||
timestamp: ++timestamp | ||
}); | ||
} | ||
try { | ||
let activeRequest = manager.getRequest(); | ||
if (!activeRequest) | ||
return; | ||
startAt = activeRequest.timestamp; | ||
const newData = await activeRequest.fetcher; | ||
if (shouldStartRequest) { | ||
setTimeout(deleteActiveRequest, dedupingInterval); | ||
} | ||
activeRequest = manager.getRequest(); | ||
if (!activeRequest || activeRequest.timestamp !== startAt) | ||
return; | ||
const activeMutation = manager.getMutation(); | ||
if (activeMutation && (activeMutation.startTime > startAt || activeMutation.endTime > startAt || activeMutation.endTime === 0)) { | ||
return; | ||
} | ||
manager.setCache(newData); | ||
} catch (err) { | ||
deleteActiveRequest(); | ||
const isVisible = document.visibilityState === "visible"; | ||
const isOnline = isOnlineRef.current; | ||
if (shouldStartRequest && isVisible && isOnline) | ||
handleError(); | ||
manager.setError(err); | ||
} | ||
return; | ||
}, | ||
[manager, fetcher, dedupingInterval, errorRetryInterval, errorRetryCount] | ||
); | ||
_react.useEffect.call(void 0, () => { | ||
function handleIsOnline() { | ||
isOnlineRef.current = true; | ||
} | ||
function handleIsOffline() { | ||
isOnlineRef.current = false; | ||
} | ||
window.addEventListener("online", handleIsOnline); | ||
window.addEventListener("offline", handleIsOffline); | ||
return () => { | ||
window.removeEventListener("online", handleIsOnline); | ||
window.removeEventListener("offline", handleIsOffline); | ||
}; | ||
}, []); | ||
const revalidateCache = _react.useCallback.call(void 0, | ||
({ shouldDedupe }) => { | ||
return _revalidateCache({ shouldDedupe, retryCount: 0 }); | ||
}, | ||
[_revalidateCache] | ||
); | ||
return revalidateCache; | ||
} | ||
function useMutate(manager, revalidateCache) { | ||
const mutate = _react.useCallback.call(void 0, | ||
async (data, options) => { | ||
const beforeMutationTimestamp = ++timestamp; | ||
manager.setMutation({ | ||
startTime: beforeMutationTimestamp, | ||
endTime: 0 | ||
}); | ||
const currentCache = manager.getCache(); | ||
manager.setCache(options.optimisticData); | ||
let error; | ||
try { | ||
await data; | ||
} catch (err) { | ||
error = err; | ||
} | ||
const activeMutation = manager.getMutation(); | ||
if (activeMutation && beforeMutationTimestamp !== activeMutation.startTime) { | ||
if (error) | ||
throw error; | ||
return; | ||
} | ||
if (error) { | ||
manager.setCache(currentCache); | ||
} | ||
manager.setMutation({ | ||
startTime: beforeMutationTimestamp, | ||
endTime: ++timestamp | ||
}); | ||
manager.setRequest(void 0); | ||
void revalidateCache({ shouldDedupe: false }); | ||
if (error) | ||
throw error; | ||
}, | ||
[manager, revalidateCache] | ||
); | ||
return mutate; | ||
} | ||
function useAutomaticRevalidation(manager, revalidateCache, options = {}) { | ||
const isOnline = useIsOnline(); | ||
const isDocumentVisible = useIsDocumentVisible(); | ||
const { | ||
revalidateOnFocus = true, | ||
revalidateOnReconnect = true, | ||
refreshInterval = 0 | ||
} = options; | ||
_react.useEffect.call(void 0, () => { | ||
let revalidationTimerId; | ||
function scheduleRevalidation() { | ||
if (refreshInterval === 0) | ||
return; | ||
revalidationTimerId = window.setTimeout(() => { | ||
if (isOnline && isDocumentVisible && !manager.getError()) { | ||
void revalidateCache({ shouldDedupe: true }).then( | ||
scheduleRevalidation | ||
); | ||
return; | ||
} | ||
scheduleRevalidation(); | ||
}, refreshInterval); | ||
} | ||
scheduleRevalidation(); | ||
return () => { | ||
window.clearTimeout(revalidationTimerId); | ||
}; | ||
}, [revalidateCache, refreshInterval, isOnline, isDocumentVisible, manager]); | ||
_react.useEffect.call(void 0, () => { | ||
function handleIsOnline() { | ||
if (revalidateOnReconnect && isDocumentVisible) { | ||
void revalidateCache({ shouldDedupe: true }); | ||
} | ||
} | ||
window.addEventListener("online", handleIsOnline); | ||
return () => { | ||
window.removeEventListener("online", handleIsOnline); | ||
}; | ||
}, [revalidateCache, revalidateOnReconnect, isDocumentVisible]); | ||
_react.useEffect.call(void 0, () => { | ||
function handleVisibilityChange() { | ||
const isVisible = document.visibilityState === "visible"; | ||
if (revalidateOnFocus && isVisible && isOnline) { | ||
void revalidateCache({ shouldDedupe: true }); | ||
} | ||
} | ||
document.addEventListener("visibilitychange", handleVisibilityChange); | ||
return () => { | ||
document.removeEventListener("visibilitychange", handleVisibilityChange); | ||
}; | ||
}, [revalidateCache, revalidateOnFocus, isOnline]); | ||
} | ||
// src/comments/CommentsRoom.tsx | ||
var POLLING_INTERVAL_REALTIME = 3e4; | ||
var POLLING_INTERVAL = 5e3; | ||
var THREAD_ID_PREFIX = "th"; | ||
var COMMENT_ID_PREFIX = "cm"; | ||
function createCommentsRoom(errorEventSource) { | ||
const manager = createThreadsCacheManager(); | ||
const filterOptions = /* @__PURE__ */ new Map(); | ||
const cacheStates = /* @__PURE__ */ new Map(); | ||
const revalidationManagers = /* @__PURE__ */ new Map(); | ||
function createThreadsRevalidationManager(key) { | ||
let request; | ||
let error; | ||
return { | ||
getCache() { | ||
return void 0; | ||
}, | ||
setCache(value) { | ||
const cache = new Map( | ||
(_nullishCoalesce(manager.getCache(), () => ( []))).map((thread) => [thread.id, thread]) | ||
); | ||
for (const thread of value) { | ||
cache.set(thread.id, thread); | ||
} | ||
setCache(key, { | ||
isLoading: false, | ||
data: value | ||
}); | ||
manager.setCache(Array.from(cache.values())); | ||
}, | ||
// Request | ||
getRequest() { | ||
return request; | ||
}, | ||
setRequest(value) { | ||
request = value; | ||
}, | ||
// Error | ||
getError() { | ||
return error; | ||
}, | ||
setError(err) { | ||
error = err; | ||
manager.setError(err); | ||
}, | ||
// Mutation | ||
getMutation() { | ||
return void 0; | ||
}, | ||
setMutation() { | ||
} | ||
}; | ||
} | ||
const eventSource = _core.makeEventSource.call(void 0, ); | ||
const subscribe2 = eventSource.subscribe; | ||
const getCache = (key) => cacheStates.get(key); | ||
const setCache = (key, value) => { | ||
cacheStates.set(key, value); | ||
}; | ||
const FetcherContext = _react.createContext.call(void 0, null); | ||
const CommentsEventSubscriptionContext = _react.createContext.call(void 0, () => { | ||
}); | ||
function getThreads() { | ||
const threads = manager.getCache(); | ||
if (!threads) { | ||
throw new Error( | ||
"Cannot update threads or comments before they are loaded." | ||
); | ||
} | ||
return threads; | ||
} | ||
function CommentsRoomProvider({ | ||
room, | ||
children | ||
}) { | ||
const commentsEventSubscribersCountRef = _react.useRef.call(void 0, 0); | ||
const commentsEventDisposerRef = _react.useRef.call(void 0, ); | ||
const fetcher = _react.useCallback.call(void 0, async () => { | ||
const responses = await Promise.all( | ||
Array.from(filterOptions.values()).map((info) => { | ||
return room.getThreads(info.options); | ||
}) | ||
); | ||
const threads = Array.from( | ||
new Map(responses.flat().map((thread) => [thread.id, thread])).values() | ||
); | ||
return threads; | ||
}, [room]); | ||
const revalidateCache = useRevalidateCache(manager, fetcher); | ||
const subscribeToCommentEvents = _react.useCallback.call(void 0, () => { | ||
const commentsEventSubscribersCount = commentsEventSubscribersCountRef.current; | ||
if (commentsEventSubscribersCount === 0) { | ||
const unsubscribe = room.events.comments.subscribe(() => { | ||
void revalidateCache({ shouldDedupe: true }); | ||
}); | ||
commentsEventDisposerRef.current = unsubscribe; | ||
} | ||
commentsEventSubscribersCountRef.current = commentsEventSubscribersCount + 1; | ||
return () => { | ||
commentsEventSubscribersCountRef.current = commentsEventSubscribersCountRef.current - 1; | ||
if (commentsEventSubscribersCountRef.current > 0) | ||
return; | ||
_optionalChain([commentsEventDisposerRef, 'access', _ => _.current, 'optionalCall', _2 => _2()]); | ||
commentsEventDisposerRef.current = void 0; | ||
}; | ||
}, [revalidateCache, room]); | ||
_react.useEffect.call(void 0, () => { | ||
const unsubscribe = manager.subscribe("cache", (threads) => { | ||
for (const [key, info] of filterOptions.entries()) { | ||
const filtered = threads.filter((thread) => { | ||
const query = info.options.query; | ||
if (!query) | ||
return true; | ||
for (const key2 in query.metadata) { | ||
if (thread.metadata[key2] !== query.metadata[key2]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
}); | ||
setCache(key, { | ||
isLoading: false, | ||
data: filtered | ||
}); | ||
} | ||
for (const [key] of cacheStates.entries()) { | ||
if (filterOptions.has(key)) | ||
continue; | ||
cacheStates.delete(key); | ||
} | ||
eventSource.notify(threads); | ||
}); | ||
return () => { | ||
unsubscribe(); | ||
}; | ||
}, []); | ||
_react.useEffect.call(void 0, () => { | ||
const unsubscribe = manager.subscribe("error", (error) => { | ||
for (const state of cacheStates.values()) { | ||
state.error = error; | ||
} | ||
}); | ||
return () => { | ||
unsubscribe(); | ||
}; | ||
}, []); | ||
return /* @__PURE__ */ React.default.createElement(FetcherContext.Provider, { value: fetcher }, /* @__PURE__ */ React.default.createElement( | ||
CommentsEventSubscriptionContext.Provider, | ||
{ | ||
value: subscribeToCommentEvents | ||
}, | ||
children | ||
)); | ||
} | ||
function useThreadsFetcher() { | ||
const fetcher = _react.useContext.call(void 0, FetcherContext); | ||
if (fetcher === null) { | ||
throw new Error("CommentsRoomProvider is missing from the React tree."); | ||
} | ||
return fetcher; | ||
} | ||
function _useThreads(room, key) { | ||
const fetcher = useThreadsFetcher(); | ||
const revalidateCache = useRevalidateCache(manager, fetcher); | ||
const status = _indexjs.useSyncExternalStore.call(void 0, | ||
room.events.status.subscribe, | ||
room.getStatus, | ||
room.getStatus | ||
); | ||
const isOnline = useIsOnline(); | ||
const isDocumentVisible = useIsDocumentVisible(); | ||
const subscribeToCommentEvents = _react.useContext.call(void 0, | ||
CommentsEventSubscriptionContext | ||
); | ||
const interval = getPollingInterval( | ||
isOnline, | ||
isDocumentVisible, | ||
status === "connected" | ||
); | ||
useAutomaticRevalidation(manager, revalidateCache, { | ||
revalidateOnFocus: true, | ||
revalidateOnReconnect: true, | ||
refreshInterval: interval | ||
}); | ||
_react.useEffect.call(void 0, subscribeToCommentEvents, [subscribeToCommentEvents]); | ||
const cache = _indexjs.useSyncExternalStore.call(void 0, | ||
subscribe2, | ||
() => getCache(key), | ||
() => getCache(key) | ||
); | ||
if (!cache || cache.isLoading) { | ||
return { isLoading: true }; | ||
} | ||
return { | ||
isLoading: cache.isLoading, | ||
threads: cache.data || [], | ||
error: cache.error | ||
}; | ||
} | ||
function useThreads(room, options = { query: { metadata: {} } }) { | ||
const key = _react.useMemo.call(void 0, () => _core.stringify.call(void 0, options), [options]); | ||
let revalidationManager = revalidationManagers.get(key); | ||
if (!revalidationManager) { | ||
revalidationManager = createThreadsRevalidationManager(key); | ||
revalidationManagers.set(key, revalidationManager); | ||
} | ||
const fetcher = _react.useCallback.call(void 0, | ||
() => { | ||
return room.getThreads(options); | ||
}, | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[key] | ||
); | ||
const revalidateCache = useRevalidateCache(revalidationManager, fetcher); | ||
_react.useEffect.call(void 0, | ||
() => { | ||
const info = filterOptions.get(key); | ||
if (info) { | ||
info.count += 1; | ||
} else { | ||
filterOptions.set(key, { | ||
options, | ||
count: 1 | ||
}); | ||
cacheStates.set(key, { isLoading: true }); | ||
} | ||
return () => { | ||
const info2 = filterOptions.get(key); | ||
if (!info2) | ||
return; | ||
info2.count -= 1; | ||
if (info2.count > 0) | ||
return; | ||
filterOptions.delete(key); | ||
}; | ||
}, | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[key] | ||
); | ||
_react.useEffect.call(void 0, () => { | ||
void revalidateCache({ shouldDedupe: true }); | ||
}, [revalidateCache]); | ||
return _useThreads(room, key); | ||
} | ||
function useThreadsSuspense(room, options = {}) { | ||
const key = _react.useMemo.call(void 0, () => _core.stringify.call(void 0, options), [options]); | ||
let revalidationManager = revalidationManagers.get(key); | ||
if (!revalidationManager) { | ||
revalidationManager = createThreadsRevalidationManager(key); | ||
revalidationManagers.set(key, revalidationManager); | ||
} | ||
const fetcher = _react.useCallback.call(void 0, | ||
() => { | ||
return room.getThreads(options); | ||
}, | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[key] | ||
); | ||
const info = filterOptions.get(key); | ||
if (!info) { | ||
filterOptions.set(key, { | ||
options, | ||
count: 0 | ||
}); | ||
cacheStates.set(key, { isLoading: true }); | ||
} | ||
const revalidateCache = useRevalidateCache(revalidationManager, fetcher); | ||
_react.useEffect.call(void 0, | ||
() => { | ||
const info2 = filterOptions.get(key); | ||
if (info2) { | ||
info2.count += 1; | ||
} else { | ||
filterOptions.set(key, { | ||
options, | ||
count: 1 | ||
}); | ||
} | ||
return () => { | ||
const info3 = filterOptions.get(key); | ||
if (!info3) | ||
return; | ||
info3.count -= 1; | ||
if (info3.count > 0) | ||
return; | ||
filterOptions.delete(key); | ||
}; | ||
}, | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[key] | ||
); | ||
const cache = _useThreads(room, key); | ||
if (cache.error) { | ||
throw cache.error; | ||
} | ||
if (cache.isLoading || !cache.threads) { | ||
throw revalidateCache({ shouldDedupe: true }); | ||
} | ||
return { | ||
threads: cache.threads, | ||
isLoading: false | ||
}; | ||
} | ||
function useEditThreadMetadata(room) { | ||
const revalidate = useRevalidateCache(manager, room.getThreads); | ||
const mutate = useMutate(manager, revalidate); | ||
const editThreadMetadata = _react.useCallback.call(void 0, | ||
(options) => { | ||
const threadId = options.threadId; | ||
const metadata = "metadata" in options ? options.metadata : {}; | ||
const threads = getThreads(); | ||
const optimisticData = threads.map( | ||
(thread) => thread.id === threadId ? { | ||
...thread, | ||
metadata: { | ||
...thread.metadata, | ||
...metadata | ||
} | ||
} : thread | ||
); | ||
mutate(room.editThreadMetadata({ metadata, threadId }), { | ||
optimisticData | ||
}).catch((err) => { | ||
if (!(err instanceof _core.CommentsApiError)) { | ||
throw err; | ||
} | ||
const error = handleCommentsApiError(err); | ||
errorEventSource.notify( | ||
new EditThreadMetadataError(error, { | ||
roomId: room.id, | ||
threadId, | ||
metadata | ||
}) | ||
); | ||
}); | ||
}, | ||
[room, mutate] | ||
); | ||
return editThreadMetadata; | ||
} | ||
function useCreateThread(room) { | ||
const fetcher = useThreadsFetcher(); | ||
const revalidate = useRevalidateCache(manager, fetcher); | ||
const mutate = useMutate(manager, revalidate); | ||
const createThread = _react.useCallback.call(void 0, | ||
(options) => { | ||
const body = options.body; | ||
const metadata = "metadata" in options ? options.metadata : {}; | ||
const threads = getThreads(); | ||
const threadId = createOptimisticId(THREAD_ID_PREFIX); | ||
const commentId = createOptimisticId(COMMENT_ID_PREFIX); | ||
const now = /* @__PURE__ */ new Date(); | ||
const newComment = { | ||
id: commentId, | ||
threadId, | ||
roomId: room.id, | ||
createdAt: now, | ||
type: "comment", | ||
userId: getCurrentUserId(room), | ||
body, | ||
reactions: [] | ||
}; | ||
const newThread = { | ||
id: threadId, | ||
type: "thread", | ||
createdAt: now, | ||
roomId: room.id, | ||
metadata, | ||
comments: [newComment] | ||
}; | ||
mutate(room.createThread({ threadId, commentId, body, metadata }), { | ||
optimisticData: [...threads, newThread] | ||
}).catch((err) => { | ||
if (!(err instanceof _core.CommentsApiError)) { | ||
throw err; | ||
} | ||
const error = handleCommentsApiError(err); | ||
errorEventSource.notify( | ||
new CreateThreadError(error, { | ||
roomId: room.id, | ||
threadId, | ||
commentId, | ||
body, | ||
metadata | ||
}) | ||
); | ||
}); | ||
return newThread; | ||
}, | ||
[room, mutate] | ||
); | ||
return createThread; | ||
} | ||
function useCreateComment(room) { | ||
const fetcher = useThreadsFetcher(); | ||
const revalidate = useRevalidateCache(manager, fetcher); | ||
const mutate = useMutate(manager, revalidate); | ||
const createComment = _react.useCallback.call(void 0, | ||
({ threadId, body }) => { | ||
const threads = getThreads(); | ||
const commentId = createOptimisticId(COMMENT_ID_PREFIX); | ||
const now = /* @__PURE__ */ new Date(); | ||
const comment = { | ||
id: commentId, | ||
threadId, | ||
roomId: room.id, | ||
type: "comment", | ||
createdAt: now, | ||
userId: getCurrentUserId(room), | ||
body, | ||
reactions: [] | ||
}; | ||
const optimisticData = threads.map( | ||
(thread) => thread.id === threadId ? { | ||
...thread, | ||
comments: [...thread.comments, comment] | ||
} : thread | ||
); | ||
mutate(room.createComment({ threadId, commentId, body }), { | ||
optimisticData | ||
}).catch((err) => { | ||
if (!(err instanceof _core.CommentsApiError)) { | ||
throw err; | ||
} | ||
const error = handleCommentsApiError(err); | ||
errorEventSource.notify( | ||
new CreateCommentError(error, { | ||
roomId: room.id, | ||
threadId, | ||
commentId, | ||
body | ||
}) | ||
); | ||
}); | ||
return comment; | ||
}, | ||
[room, mutate] | ||
); | ||
return createComment; | ||
} | ||
function useEditComment(room) { | ||
const revalidate = useRevalidateCache(manager, room.getThreads); | ||
const mutate = useMutate(manager, revalidate); | ||
const editComment = _react.useCallback.call(void 0, | ||
({ threadId, commentId, body }) => { | ||
const threads = getThreads(); | ||
const now = /* @__PURE__ */ new Date(); | ||
const optimisticData = threads.map( | ||
(thread) => thread.id === threadId ? { | ||
...thread, | ||
comments: thread.comments.map( | ||
(comment) => comment.id === commentId ? { | ||
...comment, | ||
editedAt: now, | ||
body | ||
} : comment | ||
) | ||
} : thread | ||
); | ||
mutate(room.editComment({ threadId, commentId, body }), { | ||
optimisticData | ||
}).catch((err) => { | ||
if (!(err instanceof _core.CommentsApiError)) { | ||
throw err; | ||
} | ||
const error = handleCommentsApiError(err); | ||
errorEventSource.notify( | ||
new EditCommentError(error, { | ||
roomId: room.id, | ||
threadId, | ||
commentId, | ||
body | ||
}) | ||
); | ||
}); | ||
}, | ||
[room, mutate] | ||
); | ||
return editComment; | ||
} | ||
function useDeleteComment(room) { | ||
const revalidate = useRevalidateCache(manager, room.getThreads); | ||
const mutate = useMutate(manager, revalidate); | ||
const deleteComment = _react.useCallback.call(void 0, | ||
({ threadId, commentId }) => { | ||
const threads = getThreads(); | ||
const now = /* @__PURE__ */ new Date(); | ||
const newThreads = []; | ||
for (const thread of threads) { | ||
if (thread.id === threadId) { | ||
const newThread = { | ||
...thread, | ||
comments: thread.comments.map( | ||
(comment) => comment.id === commentId ? { | ||
...comment, | ||
deletedAt: now, | ||
body: void 0 | ||
} : comment | ||
) | ||
}; | ||
if (newThread.comments.some( | ||
(comment) => comment.deletedAt === void 0 | ||
)) { | ||
newThreads.push(newThread); | ||
} | ||
} else { | ||
newThreads.push(thread); | ||
} | ||
} | ||
mutate(room.deleteComment({ threadId, commentId }), { | ||
optimisticData: newThreads | ||
}).catch((err) => { | ||
if (!(err instanceof _core.CommentsApiError)) { | ||
throw err; | ||
} | ||
const error = handleCommentsApiError(err); | ||
errorEventSource.notify( | ||
new DeleteCommentError(error, { | ||
roomId: room.id, | ||
threadId, | ||
commentId | ||
}) | ||
); | ||
}); | ||
}, | ||
[room, mutate] | ||
); | ||
return deleteComment; | ||
} | ||
function useAddReaction(room) { | ||
const revalidate = useRevalidateCache(manager, room.getThreads); | ||
const mutate = useMutate(manager, revalidate); | ||
const createComment = _react.useCallback.call(void 0, | ||
({ threadId, commentId, emoji }) => { | ||
const threads = getThreads(); | ||
const now = /* @__PURE__ */ new Date(); | ||
const userId = getCurrentUserId(room); | ||
const optimisticData = threads.map( | ||
(thread) => thread.id === threadId ? { | ||
...thread, | ||
comments: thread.comments.map((comment) => { | ||
if (comment.id !== commentId) { | ||
return comment; | ||
} | ||
let reactions; | ||
if (comment.reactions.some( | ||
(reaction) => reaction.emoji === emoji | ||
)) { | ||
reactions = comment.reactions.map( | ||
(reaction) => reaction.emoji === emoji ? { | ||
...reaction, | ||
users: [...reaction.users, { id: userId }] | ||
} : reaction | ||
); | ||
} else { | ||
reactions = [ | ||
...comment.reactions, | ||
{ | ||
emoji, | ||
createdAt: now, | ||
users: [{ id: userId }] | ||
} | ||
]; | ||
} | ||
return { | ||
...comment, | ||
reactions | ||
}; | ||
}) | ||
} : thread | ||
); | ||
mutate(room.addReaction({ threadId, commentId, emoji }), { | ||
optimisticData | ||
}).catch((err) => { | ||
if (!(err instanceof _core.CommentsApiError)) { | ||
throw err; | ||
} | ||
const error = handleCommentsApiError(err); | ||
errorEventSource.notify( | ||
new AddReactionError(error, { | ||
roomId: room.id, | ||
threadId, | ||
commentId, | ||
emoji | ||
}) | ||
); | ||
}); | ||
}, | ||
[room, mutate] | ||
); | ||
return createComment; | ||
} | ||
function useRemoveReaction(room) { | ||
const revalidate = useRevalidateCache(manager, room.getThreads); | ||
const mutate = useMutate(manager, revalidate); | ||
const createComment = _react.useCallback.call(void 0, | ||
({ threadId, commentId, emoji }) => { | ||
const threads = getThreads(); | ||
const userId = getCurrentUserId(room); | ||
const optimisticData = threads.map( | ||
(thread) => thread.id === threadId ? { | ||
...thread, | ||
comments: thread.comments.map((comment) => { | ||
if (comment.id !== commentId) { | ||
return comment; | ||
} | ||
const reactionIndex = comment.reactions.findIndex( | ||
(reaction) => reaction.emoji === emoji | ||
); | ||
let reactions = comment.reactions; | ||
if (reactionIndex >= 0 && comment.reactions[reactionIndex].users.some( | ||
(user) => user.id === userId | ||
)) { | ||
if (comment.reactions[reactionIndex].users.length <= 1) { | ||
reactions = [...comment.reactions]; | ||
reactions.splice(reactionIndex, 1); | ||
} else { | ||
reactions[reactionIndex] = { | ||
...reactions[reactionIndex], | ||
users: reactions[reactionIndex].users.filter( | ||
(user) => user.id !== userId | ||
) | ||
}; | ||
} | ||
} | ||
return { | ||
...comment, | ||
reactions | ||
}; | ||
}) | ||
} : thread | ||
); | ||
mutate(room.removeReaction({ threadId, commentId, emoji }), { | ||
optimisticData | ||
}).catch((err) => { | ||
if (!(err instanceof _core.CommentsApiError)) { | ||
throw err; | ||
} | ||
const error = handleCommentsApiError(err); | ||
errorEventSource.notify( | ||
new RemoveReactionError(error, { | ||
roomId: room.id, | ||
threadId, | ||
commentId, | ||
emoji | ||
}) | ||
); | ||
}); | ||
}, | ||
[room, mutate] | ||
); | ||
return createComment; | ||
} | ||
return { | ||
CommentsRoomProvider, | ||
useThreads, | ||
useThreadsSuspense, | ||
useEditThreadMetadata, | ||
useCreateThread, | ||
useCreateComment, | ||
useEditComment, | ||
useDeleteComment, | ||
useAddReaction, | ||
useRemoveReaction | ||
}; | ||
} | ||
function createOptimisticId(prefix) { | ||
return `${prefix}_${_nanoid.nanoid.call(void 0, )}`; | ||
} | ||
function getCurrentUserId(room) { | ||
const self = room.getSelf(); | ||
if (self === null || self.id === void 0) { | ||
return "anonymous"; | ||
} else { | ||
return self.id; | ||
} | ||
} | ||
function handleCommentsApiError(err) { | ||
const message = `Request failed with status ${err.status}: ${err.message}`; | ||
if (_optionalChain([err, 'access', _3 => _3.details, 'optionalAccess', _4 => _4.error]) === "FORBIDDEN") { | ||
const detailedMessage = [message, err.details.suggestion, err.details.docs].filter(Boolean).join("\n"); | ||
console.error(detailedMessage); | ||
} | ||
return new Error(message); | ||
} | ||
function getPollingInterval(isBrowserOnline, isDocumentVisible, isRoomConnected) { | ||
if (!isBrowserOnline || !isDocumentVisible) | ||
return; | ||
if (isRoomConnected) | ||
return POLLING_INTERVAL_REALTIME; | ||
return POLLING_INTERVAL; | ||
} | ||
function createThreadsCacheManager() { | ||
let cache; | ||
let request; | ||
let error; | ||
let mutation; | ||
const cacheEventSource = _core.makeEventSource.call(void 0, ); | ||
const errorEventSource = _core.makeEventSource.call(void 0, ); | ||
return { | ||
// Cache | ||
getCache() { | ||
return cache; | ||
}, | ||
setCache(value) { | ||
const sorted = value.sort( | ||
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime() | ||
); | ||
cache = sorted; | ||
cacheEventSource.notify(cache); | ||
}, | ||
// Request | ||
getRequest() { | ||
return request; | ||
}, | ||
setRequest(value) { | ||
request = value; | ||
}, | ||
// Error | ||
getError() { | ||
return error; | ||
}, | ||
setError(err) { | ||
error = err; | ||
errorEventSource.notify(err); | ||
}, | ||
// Mutation | ||
getMutation() { | ||
return mutation; | ||
}, | ||
setMutation(info) { | ||
mutation = info; | ||
}, | ||
// Subscription | ||
subscribe(type, callback) { | ||
switch (type) { | ||
case "cache": | ||
return cacheEventSource.subscribe( | ||
callback | ||
); | ||
case "error": | ||
return errorEventSource.subscribe(callback); | ||
} | ||
} | ||
}; | ||
} | ||
// src/comments/lib/use-debounce.ts | ||
var DEFAULT_DELAY = 500; | ||
function useDebounce(value, delay = DEFAULT_DELAY) { | ||
const timeout = _react.useRef.call(void 0, ); | ||
const [debouncedValue, setDebouncedValue] = _react.useState.call(void 0, value); | ||
_react.useEffect.call(void 0, () => { | ||
if (delay === false) { | ||
return; | ||
} | ||
if (timeout.current === void 0) { | ||
setDebouncedValue(value); | ||
} | ||
timeout.current = window.setTimeout(() => { | ||
setDebouncedValue(value); | ||
timeout.current = void 0; | ||
}, delay); | ||
return () => { | ||
window.clearTimeout(timeout.current); | ||
}; | ||
}, [value, delay]); | ||
return debouncedValue; | ||
} | ||
// src/lib/use-async-cache.ts | ||
// src/lib/use-initial.ts | ||
function useInitial(value) { | ||
return _react.useState.call(void 0, value)[0]; | ||
} | ||
// src/lib/use-async-cache.ts | ||
var INITIAL_ASYNC_STATE = { | ||
isLoading: false, | ||
data: void 0, | ||
error: void 0 | ||
}; | ||
var noop = () => { | ||
}; | ||
function useAsyncCache(cache, key, options) { | ||
const frozenOptions = useInitial(options); | ||
const cacheItem = _react.useMemo.call(void 0, () => { | ||
if (key === null || !cache) { | ||
return null; | ||
} | ||
const cacheItem2 = cache.create(key); | ||
void cacheItem2.get(); | ||
return cacheItem2; | ||
}, [cache, key]); | ||
const subscribe2 = _react.useCallback.call(void 0, | ||
(callback) => _nullishCoalesce(_optionalChain([cacheItem, 'optionalAccess', _5 => _5.subscribe, 'call', _6 => _6(callback)]), () => ( noop)), | ||
[cacheItem] | ||
); | ||
const getState = _react.useCallback.call(void 0, | ||
() => _nullishCoalesce(_optionalChain([cacheItem, 'optionalAccess', _7 => _7.getState, 'call', _8 => _8()]), () => ( INITIAL_ASYNC_STATE)), | ||
[cacheItem] | ||
); | ||
const revalidate = _react.useCallback.call(void 0, () => _optionalChain([cacheItem, 'optionalAccess', _9 => _9.revalidate, 'call', _10 => _10()]), [cacheItem]); | ||
const state = _indexjs.useSyncExternalStore.call(void 0, subscribe2, getState, getState); | ||
const previousData = _react.useRef.call(void 0, ); | ||
let data = state.data; | ||
_react.useEffect.call(void 0, () => { | ||
previousData.current = { key, data: state.data }; | ||
}, [key, state.data]); | ||
if (!cacheItem) { | ||
return { | ||
isLoading: false, | ||
data: void 0, | ||
error: void 0, | ||
getState, | ||
revalidate | ||
}; | ||
} | ||
if (_optionalChain([frozenOptions, 'optionalAccess', _11 => _11.suspense])) { | ||
const error = getState().error; | ||
if (error) { | ||
throw error; | ||
} else if (getState().isLoading) { | ||
throw new Promise((resolve) => { | ||
cacheItem.subscribeOnce((state2) => { | ||
if (!state2.isLoading) { | ||
resolve(); | ||
} | ||
}); | ||
}); | ||
} | ||
} | ||
if (state.isLoading && _optionalChain([frozenOptions, 'optionalAccess', _12 => _12.keepPreviousDataWhileLoading]) && typeof state.data === "undefined" && _optionalChain([previousData, 'access', _13 => _13.current, 'optionalAccess', _14 => _14.key]) !== key && typeof _optionalChain([previousData, 'access', _15 => _15.current, 'optionalAccess', _16 => _16.data]) !== "undefined") { | ||
data = previousData.current.data; | ||
} | ||
return { | ||
isLoading: state.isLoading, | ||
data, | ||
error: state.error, | ||
getState, | ||
revalidate | ||
}; | ||
} | ||
// src/lib/use-latest.ts | ||
function useLatest(value) { | ||
const ref = _react.useRef.call(void 0, value); | ||
_react.useEffect.call(void 0, () => { | ||
ref.current = value; | ||
}, [value]); | ||
return ref; | ||
} | ||
// src/lib/use-rerender.ts | ||
function useRerender() { | ||
const [, update] = _react.useReducer.call(void 0, | ||
// This implementation works by incrementing a hidden counter value that is | ||
// never consumed. Simply incrementing the counter changes the component's | ||
// state and, thus, trigger a re-render. | ||
(x) => x + 1, | ||
0 | ||
); | ||
return update; | ||
} | ||
// src/factory.tsx | ||
var noop2 = () => { | ||
}; | ||
var identity = (x) => x; | ||
var missing_unstable_batchedUpdates = (reactVersion, roomId) => `We noticed you\u2019re using React ${reactVersion}. Please pass unstable_batchedUpdates at the RoomProvider level until you\u2019re ready to upgrade to React 18: | ||
import { unstable_batchedUpdates } from "react-dom"; // or "react-native" | ||
<RoomProvider id=${JSON.stringify( | ||
roomId | ||
)} ... unstable_batchedUpdates={unstable_batchedUpdates}> | ||
... | ||
</RoomProvider> | ||
Why? Please see https://liveblocks.io/docs/platform/troubleshooting#stale-props-zombie-child for more information`; | ||
var superfluous_unstable_batchedUpdates = "You don\u2019t need to pass unstable_batchedUpdates to RoomProvider anymore, since you\u2019re on React 18+ already."; | ||
function useSyncExternalStore5(s, gs, gss) { | ||
return _withselectorjs.useSyncExternalStoreWithSelector.call(void 0, s, gs, gss, identity); | ||
} | ||
var STABLE_EMPTY_LIST = Object.freeze([]); | ||
function alwaysEmptyList() { | ||
return STABLE_EMPTY_LIST; | ||
} | ||
function alwaysNull() { | ||
return null; | ||
} | ||
function makeMutationContext(room) { | ||
const errmsg = "This mutation cannot be used until connected to the Liveblocks room"; | ||
return { | ||
get storage() { | ||
const mutableRoot = room.getStorageSnapshot(); | ||
if (mutableRoot === null) { | ||
throw new Error(errmsg); | ||
} | ||
return mutableRoot; | ||
}, | ||
get self() { | ||
const self = room.getSelf(); | ||
if (self === null) { | ||
throw new Error(errmsg); | ||
} | ||
return self; | ||
}, | ||
get others() { | ||
const others = room.getOthers(); | ||
if (room.getSelf() === null) { | ||
throw new Error(errmsg); | ||
} | ||
return others; | ||
}, | ||
setMyPresence: room.updatePresence | ||
}; | ||
} | ||
var hasWarnedIfNoResolveUsers = false; | ||
function warnIfNoResolveUsers(usersCache) { | ||
if (!hasWarnedIfNoResolveUsers && !usersCache && process.env.NODE_ENV !== "production") { | ||
console.warn( | ||
"Set the resolveUsers option in createRoomContext to specify user info." | ||
); | ||
hasWarnedIfNoResolveUsers = true; | ||
} | ||
} | ||
var ContextBundle = React3.createContext(null); | ||
function useRoomContextBundle() { | ||
const bundle = React3.useContext(ContextBundle); | ||
if (bundle === null) { | ||
throw new Error("RoomProvider is missing from the React tree."); | ||
} | ||
return bundle; | ||
} | ||
function createRoomContext(client, options) { | ||
const RoomContext = React3.createContext(null); | ||
const commentsErrorEventSource = _core.makeEventSource.call(void 0, ); | ||
const { CommentsRoomProvider, ...commentsRoom } = createCommentsRoom(commentsErrorEventSource); | ||
function RoomProviderOuter(props) { | ||
const [cache] = React3.useState( | ||
() => /* @__PURE__ */ new Map() | ||
); | ||
const stableEnterRoom = React3.useCallback( | ||
(roomId, options2) => { | ||
const cached = cache.get(roomId); | ||
if (cached) | ||
return cached; | ||
const rv = client.enterRoom( | ||
roomId, | ||
options2 | ||
); | ||
const origLeave = rv.leave; | ||
rv.leave = () => { | ||
origLeave(); | ||
cache.delete(roomId); | ||
}; | ||
cache.set(roomId, rv); | ||
return rv; | ||
}, | ||
[cache] | ||
); | ||
return /* @__PURE__ */ React3.createElement(RoomProviderInner, { ...props, stableEnterRoom }); | ||
} | ||
function RoomProviderInner(props) { | ||
const { id: roomId, stableEnterRoom } = props; | ||
if (process.env.NODE_ENV !== "production") { | ||
if (!roomId) { | ||
throw new Error( | ||
"RoomProvider id property is required. For more information: https://liveblocks.io/docs/errors/liveblocks-react/RoomProvider-id-property-is-required" | ||
); | ||
} | ||
if (typeof roomId !== "string") { | ||
throw new Error("RoomProvider id property should be a string."); | ||
} | ||
const majorReactVersion = parseInt(React3.version) || 1; | ||
const oldReactVersion = majorReactVersion < 18; | ||
_core.errorIf.call(void 0, | ||
oldReactVersion && props.unstable_batchedUpdates === void 0, | ||
missing_unstable_batchedUpdates(majorReactVersion, roomId) | ||
); | ||
_core.deprecateIf.call(void 0, | ||
!oldReactVersion && props.unstable_batchedUpdates !== void 0, | ||
superfluous_unstable_batchedUpdates | ||
); | ||
} | ||
const frozenProps = useInitial({ | ||
initialPresence: props.initialPresence, | ||
initialStorage: props.initialStorage, | ||
unstable_batchedUpdates: props.unstable_batchedUpdates, | ||
autoConnect: _nullishCoalesce(_nullishCoalesce(props.autoConnect, () => ( props.shouldInitiallyConnect)), () => ( typeof window !== "undefined")) | ||
}); | ||
const [{ room }, setRoomLeavePair] = React3.useState( | ||
() => stableEnterRoom(roomId, { | ||
...frozenProps, | ||
autoConnect: false | ||
// Deliberately using false here on the first render, see below | ||
}) | ||
); | ||
React3.useEffect(() => { | ||
const pair = stableEnterRoom(roomId, frozenProps); | ||
setRoomLeavePair(pair); | ||
const { room: room2, leave } = pair; | ||
if (frozenProps.autoConnect) { | ||
room2.connect(); | ||
} | ||
return () => { | ||
leave(); | ||
}; | ||
}, [roomId, frozenProps, stableEnterRoom]); | ||
return /* @__PURE__ */ React3.createElement(RoomContext.Provider, { value: room }, /* @__PURE__ */ React3.createElement(CommentsRoomProvider, { room }, /* @__PURE__ */ React3.createElement( | ||
ContextBundle.Provider, | ||
{ | ||
value: internalBundle | ||
}, | ||
props.children | ||
))); | ||
} | ||
function connectionIdSelector(others) { | ||
return others.map((user) => user.connectionId); | ||
} | ||
function useRoom() { | ||
const room = React3.useContext(RoomContext); | ||
if (room === null) { | ||
throw new Error("RoomProvider is missing from the React tree."); | ||
} | ||
return room; | ||
} | ||
function useStatus() { | ||
const room = useRoom(); | ||
const subscribe2 = room.events.status.subscribe; | ||
const getSnapshot2 = room.getStatus; | ||
const getServerSnapshot = room.getStatus; | ||
return useSyncExternalStore5(subscribe2, getSnapshot2, getServerSnapshot); | ||
} | ||
function useMyPresence() { | ||
const room = useRoom(); | ||
const subscribe2 = room.events.myPresence.subscribe; | ||
const getSnapshot2 = room.getPresence; | ||
const presence = useSyncExternalStore5(subscribe2, getSnapshot2, getSnapshot2); | ||
const setPresence = room.updatePresence; | ||
return [presence, setPresence]; | ||
} | ||
function useUpdateMyPresence() { | ||
return useRoom().updatePresence; | ||
} | ||
function useOthers(selector, isEqual) { | ||
const room = useRoom(); | ||
const subscribe2 = room.events.others.subscribe; | ||
const getSnapshot2 = room.getOthers; | ||
const getServerSnapshot = alwaysEmptyList; | ||
return _withselectorjs.useSyncExternalStoreWithSelector.call(void 0, | ||
subscribe2, | ||
getSnapshot2, | ||
getServerSnapshot, | ||
_nullishCoalesce(selector, () => ( identity)), | ||
isEqual | ||
); | ||
} | ||
function useOthersConnectionIds() { | ||
return useOthers(connectionIdSelector, _client.shallow); | ||
} | ||
function useOthersMapped(itemSelector, itemIsEqual) { | ||
const wrappedSelector = React3.useCallback( | ||
(others) => others.map( | ||
(other) => [other.connectionId, itemSelector(other)] | ||
), | ||
[itemSelector] | ||
); | ||
const wrappedIsEqual = React3.useCallback( | ||
(a, b) => { | ||
const eq = _nullishCoalesce(itemIsEqual, () => ( Object.is)); | ||
return a.length === b.length && a.every((atuple, index) => { | ||
const btuple = b[index]; | ||
return atuple[0] === btuple[0] && eq(atuple[1], btuple[1]); | ||
}); | ||
}, | ||
[itemIsEqual] | ||
); | ||
return useOthers(wrappedSelector, wrappedIsEqual); | ||
} | ||
const NOT_FOUND = Symbol(); | ||
function useOther(connectionId, selector, isEqual) { | ||
const wrappedSelector = React3.useCallback( | ||
(others) => { | ||
const other2 = others.find( | ||
(other3) => other3.connectionId === connectionId | ||
); | ||
return other2 !== void 0 ? selector(other2) : NOT_FOUND; | ||
}, | ||
[connectionId, selector] | ||
); | ||
const wrappedIsEqual = React3.useCallback( | ||
(prev, curr) => { | ||
if (prev === NOT_FOUND || curr === NOT_FOUND) { | ||
return prev === curr; | ||
} | ||
const eq = _nullishCoalesce(isEqual, () => ( Object.is)); | ||
return eq(prev, curr); | ||
}, | ||
[isEqual] | ||
); | ||
const other = useOthers(wrappedSelector, wrappedIsEqual); | ||
if (other === NOT_FOUND) { | ||
throw new Error( | ||
`No such other user with connection id ${connectionId} exists` | ||
); | ||
} | ||
return other; | ||
} | ||
function useBroadcastEvent() { | ||
const room = useRoom(); | ||
return React3.useCallback( | ||
(event, options2 = { shouldQueueEventIfNotReady: false }) => { | ||
room.broadcastEvent(event, options2); | ||
}, | ||
[room] | ||
); | ||
} | ||
function useOthersListener(callback) { | ||
const room = useRoom(); | ||
const savedCallback = useLatest(callback); | ||
React3.useEffect( | ||
() => room.events.others.subscribe((event) => savedCallback.current(event)), | ||
[room, savedCallback] | ||
); | ||
} | ||
function useLostConnectionListener(callback) { | ||
const room = useRoom(); | ||
const savedCallback = useLatest(callback); | ||
React3.useEffect( | ||
() => room.events.lostConnection.subscribe( | ||
(event) => savedCallback.current(event) | ||
), | ||
[room, savedCallback] | ||
); | ||
} | ||
function useErrorListener(callback) { | ||
const room = useRoom(); | ||
const savedCallback = useLatest(callback); | ||
React3.useEffect( | ||
() => room.events.error.subscribe((e) => savedCallback.current(e)), | ||
[room, savedCallback] | ||
); | ||
} | ||
function useEventListener(callback) { | ||
const room = useRoom(); | ||
const savedCallback = useLatest(callback); | ||
React3.useEffect(() => { | ||
const listener = (eventData) => { | ||
savedCallback.current(eventData); | ||
}; | ||
return room.events.customEvent.subscribe(listener); | ||
}, [room, savedCallback]); | ||
} | ||
function useSelf(maybeSelector, isEqual) { | ||
const room = useRoom(); | ||
const subscribe2 = room.events.self.subscribe; | ||
const getSnapshot2 = room.getSelf; | ||
const selector = _nullishCoalesce(maybeSelector, () => ( identity)); | ||
const wrappedSelector = React3.useCallback( | ||
(me) => me !== null ? selector(me) : null, | ||
[selector] | ||
); | ||
const getServerSnapshot = alwaysNull; | ||
return _withselectorjs.useSyncExternalStoreWithSelector.call(void 0, | ||
subscribe2, | ||
getSnapshot2, | ||
getServerSnapshot, | ||
wrappedSelector, | ||
isEqual | ||
); | ||
} | ||
function useMutableStorageRoot() { | ||
const room = useRoom(); | ||
const subscribe2 = room.events.storageDidLoad.subscribeOnce; | ||
const getSnapshot2 = room.getStorageSnapshot; | ||
const getServerSnapshot = alwaysNull; | ||
return useSyncExternalStore5(subscribe2, getSnapshot2, getServerSnapshot); | ||
} | ||
function useStorageRoot() { | ||
return [useMutableStorageRoot()]; | ||
} | ||
function useHistory() { | ||
return useRoom().history; | ||
} | ||
function useUndo() { | ||
return useHistory().undo; | ||
} | ||
function useRedo() { | ||
return useHistory().redo; | ||
} | ||
function useCanUndo() { | ||
const room = useRoom(); | ||
const subscribe2 = room.events.history.subscribe; | ||
const canUndo = room.history.canUndo; | ||
return useSyncExternalStore5(subscribe2, canUndo, canUndo); | ||
} | ||
function useCanRedo() { | ||
const room = useRoom(); | ||
const subscribe2 = room.events.history.subscribe; | ||
const canRedo = room.history.canRedo; | ||
return useSyncExternalStore5(subscribe2, canRedo, canRedo); | ||
} | ||
function useBatch() { | ||
return useRoom().batch; | ||
} | ||
function useLegacyKey(key) { | ||
const room = useRoom(); | ||
const rootOrNull = useMutableStorageRoot(); | ||
const rerender = useRerender(); | ||
React3.useEffect(() => { | ||
if (rootOrNull === null) { | ||
return; | ||
} | ||
const root = rootOrNull; | ||
let unsubCurr; | ||
let curr = root.get(key); | ||
function subscribeToCurr() { | ||
unsubCurr = _core.isLiveNode.call(void 0, curr) ? room.subscribe(curr, rerender) : void 0; | ||
} | ||
function onRootChange() { | ||
const newValue = root.get(key); | ||
if (newValue !== curr) { | ||
_optionalChain([unsubCurr, 'optionalCall', _17 => _17()]); | ||
curr = newValue; | ||
subscribeToCurr(); | ||
rerender(); | ||
} | ||
} | ||
subscribeToCurr(); | ||
rerender(); | ||
const unsubscribeRoot = room.subscribe(root, onRootChange); | ||
return () => { | ||
unsubscribeRoot(); | ||
_optionalChain([unsubCurr, 'optionalCall', _18 => _18()]); | ||
}; | ||
}, [rootOrNull, room, key, rerender]); | ||
if (rootOrNull === null) { | ||
return null; | ||
} else { | ||
return rootOrNull.get(key); | ||
} | ||
} | ||
function useStorage(selector, isEqual) { | ||
const room = useRoom(); | ||
const rootOrNull = useMutableStorageRoot(); | ||
const wrappedSelector = React3.useCallback( | ||
(rootOrNull2) => rootOrNull2 !== null ? selector(rootOrNull2) : null, | ||
[selector] | ||
); | ||
const subscribe2 = React3.useCallback( | ||
(onStoreChange) => rootOrNull !== null ? room.subscribe(rootOrNull, onStoreChange, { isDeep: true }) : noop2, | ||
[room, rootOrNull] | ||
); | ||
const getSnapshot2 = React3.useCallback(() => { | ||
if (rootOrNull === null) { | ||
return null; | ||
} else { | ||
const root = rootOrNull; | ||
const imm = root.toImmutable(); | ||
return imm; | ||
} | ||
}, [rootOrNull]); | ||
const getServerSnapshot = alwaysNull; | ||
return _withselectorjs.useSyncExternalStoreWithSelector.call(void 0, | ||
subscribe2, | ||
getSnapshot2, | ||
getServerSnapshot, | ||
wrappedSelector, | ||
isEqual | ||
); | ||
} | ||
function ensureNotServerSide() { | ||
if (typeof window === "undefined") { | ||
throw new Error( | ||
"You cannot use the Suspense version of this hook on the server side. Make sure to only call them on the client side.\nFor tips, see https://liveblocks.io/docs/api-reference/liveblocks-react#suspense-avoid-ssr" | ||
); | ||
} | ||
} | ||
function useSuspendUntilStorageLoaded() { | ||
const room = useRoom(); | ||
if (room.getStorageSnapshot() !== null) { | ||
return; | ||
} | ||
ensureNotServerSide(); | ||
throw new Promise((res) => { | ||
room.events.storageDidLoad.subscribeOnce(() => res()); | ||
}); | ||
} | ||
function useSuspendUntilPresenceLoaded() { | ||
const room = useRoom(); | ||
if (room.getSelf() !== null) { | ||
return; | ||
} | ||
ensureNotServerSide(); | ||
throw new Promise((res) => { | ||
room.events.self.subscribeOnce(() => res()); | ||
room.events.status.subscribeOnce(() => res()); | ||
}); | ||
} | ||
function useMutation(callback, deps) { | ||
const room = useRoom(); | ||
return React3.useMemo( | ||
() => { | ||
return (...args) => ( | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
room.batch( | ||
() => ( | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
callback( | ||
makeMutationContext(room), | ||
...args | ||
) | ||
) | ||
) | ||
); | ||
}, | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[room, ...deps] | ||
); | ||
} | ||
function useStorageSuspense(selector, isEqual) { | ||
useSuspendUntilStorageLoaded(); | ||
return useStorage( | ||
selector, | ||
isEqual | ||
); | ||
} | ||
function useSelfSuspense(selector, isEqual) { | ||
useSuspendUntilPresenceLoaded(); | ||
return useSelf( | ||
selector, | ||
isEqual | ||
); | ||
} | ||
function useOthersSuspense(selector, isEqual) { | ||
useSuspendUntilPresenceLoaded(); | ||
return useOthers( | ||
selector, | ||
isEqual | ||
); | ||
} | ||
function useOthersConnectionIdsSuspense() { | ||
useSuspendUntilPresenceLoaded(); | ||
return useOthersConnectionIds(); | ||
} | ||
function useOthersMappedSuspense(itemSelector, itemIsEqual) { | ||
useSuspendUntilPresenceLoaded(); | ||
return useOthersMapped(itemSelector, itemIsEqual); | ||
} | ||
function useOtherSuspense(connectionId, selector, isEqual) { | ||
useSuspendUntilPresenceLoaded(); | ||
return useOther(connectionId, selector, isEqual); | ||
} | ||
function useLegacyKeySuspense(key) { | ||
useSuspendUntilStorageLoaded(); | ||
return useLegacyKey(key); | ||
} | ||
function useThreads(options2) { | ||
const room = useRoom(); | ||
return commentsRoom.useThreads(room, options2); | ||
} | ||
function useThreadsSuspense(options2) { | ||
const room = useRoom(); | ||
return commentsRoom.useThreadsSuspense(room, options2); | ||
} | ||
function useCreateThread() { | ||
const room = useRoom(); | ||
return commentsRoom.useCreateThread(room); | ||
} | ||
function useEditThreadMetadata() { | ||
const room = useRoom(); | ||
return commentsRoom.useEditThreadMetadata(room); | ||
} | ||
function useAddReaction() { | ||
const room = useRoom(); | ||
return commentsRoom.useAddReaction(room); | ||
} | ||
function useRemoveReaction() { | ||
const room = useRoom(); | ||
return commentsRoom.useRemoveReaction(room); | ||
} | ||
function useCreateComment() { | ||
const room = useRoom(); | ||
return commentsRoom.useCreateComment(room); | ||
} | ||
function useEditComment() { | ||
const room = useRoom(); | ||
return commentsRoom.useEditComment(room); | ||
} | ||
function useDeleteComment() { | ||
const room = useRoom(); | ||
return commentsRoom.useDeleteComment(room); | ||
} | ||
const { resolveUsers, resolveMentionSuggestions } = _nullishCoalesce(options, () => ( {})); | ||
const usersCache = resolveUsers ? _core.createAsyncCache.call(void 0, async (stringifiedOptions) => { | ||
const users = await resolveUsers( | ||
JSON.parse(stringifiedOptions) | ||
); | ||
return _optionalChain([users, 'optionalAccess', _19 => _19[0]]); | ||
}) : void 0; | ||
function useUser(userId) { | ||
const room = useRoom(); | ||
const resolverKey = React3.useMemo( | ||
() => _core.stringify.call(void 0, { userIds: [userId], roomId: room.id }), | ||
[userId, room.id] | ||
); | ||
const state = useAsyncCache(usersCache, resolverKey); | ||
React3.useEffect(() => warnIfNoResolveUsers(usersCache), []); | ||
if (state.isLoading) { | ||
return { | ||
isLoading: true | ||
}; | ||
} else { | ||
return { | ||
user: state.data, | ||
error: state.error, | ||
isLoading: false | ||
}; | ||
} | ||
} | ||
function useUserSuspense(userId) { | ||
const room = useRoom(); | ||
const resolverKey = React3.useMemo( | ||
() => _core.stringify.call(void 0, { userIds: [userId], roomId: room.id }), | ||
[userId, room.id] | ||
); | ||
const state = useAsyncCache(usersCache, resolverKey, { | ||
suspense: true | ||
}); | ||
React3.useEffect(() => warnIfNoResolveUsers(usersCache), []); | ||
return { | ||
user: state.data, | ||
isLoading: false | ||
}; | ||
} | ||
const mentionSuggestionsCache = _core.createAsyncCache.call(void 0, | ||
resolveMentionSuggestions ? (stringifiedOptions) => { | ||
return resolveMentionSuggestions( | ||
JSON.parse(stringifiedOptions) | ||
); | ||
} : () => Promise.resolve([]) | ||
); | ||
function useMentionSuggestions(search) { | ||
const room = useRoom(); | ||
const debouncedSearch = useDebounce(search, 500); | ||
const resolverKey = React3.useMemo( | ||
() => debouncedSearch !== void 0 ? _core.stringify.call(void 0, { text: debouncedSearch, roomId: room.id }) : null, | ||
[debouncedSearch, room.id] | ||
); | ||
const { data } = useAsyncCache(mentionSuggestionsCache, resolverKey, { | ||
keepPreviousDataWhileLoading: true | ||
}); | ||
return data; | ||
} | ||
const bundle = { | ||
RoomContext, | ||
RoomProvider: RoomProviderOuter, | ||
useRoom, | ||
useStatus, | ||
useBatch, | ||
useBroadcastEvent, | ||
useOthersListener, | ||
useLostConnectionListener, | ||
useErrorListener, | ||
useEventListener, | ||
useHistory, | ||
useUndo, | ||
useRedo, | ||
useCanRedo, | ||
useCanUndo, | ||
// These are just aliases. The passed-in key will define their return values. | ||
useList: useLegacyKey, | ||
useMap: useLegacyKey, | ||
useObject: useLegacyKey, | ||
useStorageRoot, | ||
useStorage, | ||
useSelf, | ||
useMyPresence, | ||
useUpdateMyPresence, | ||
useOthers, | ||
useOthersMapped, | ||
useOthersConnectionIds, | ||
useOther, | ||
useMutation, | ||
useThreads, | ||
useUser, | ||
useCreateThread, | ||
useEditThreadMetadata, | ||
useCreateComment, | ||
useEditComment, | ||
useDeleteComment, | ||
useAddReaction, | ||
useRemoveReaction, | ||
suspense: { | ||
RoomContext, | ||
RoomProvider: RoomProviderOuter, | ||
useRoom, | ||
useStatus, | ||
useBatch, | ||
useBroadcastEvent, | ||
useOthersListener, | ||
useLostConnectionListener, | ||
useErrorListener, | ||
useEventListener, | ||
useHistory, | ||
useUndo, | ||
useRedo, | ||
useCanRedo, | ||
useCanUndo, | ||
// Legacy hooks | ||
useList: useLegacyKeySuspense, | ||
useMap: useLegacyKeySuspense, | ||
useObject: useLegacyKeySuspense, | ||
useStorageRoot, | ||
useStorage: useStorageSuspense, | ||
useSelf: useSelfSuspense, | ||
useMyPresence, | ||
useUpdateMyPresence, | ||
useOthers: useOthersSuspense, | ||
useOthersMapped: useOthersMappedSuspense, | ||
useOthersConnectionIds: useOthersConnectionIdsSuspense, | ||
useOther: useOtherSuspense, | ||
useMutation, | ||
useThreads: useThreadsSuspense, | ||
useUser: useUserSuspense, | ||
useCreateThread, | ||
useEditThreadMetadata, | ||
useCreateComment, | ||
useEditComment, | ||
useDeleteComment, | ||
useAddReaction, | ||
useRemoveReaction | ||
} | ||
}; | ||
const internalBundle = { | ||
...bundle, | ||
hasResolveMentionSuggestions: resolveMentionSuggestions !== void 0, | ||
useMentionSuggestions | ||
}; | ||
return bundle; | ||
} | ||
var _chunkRCQYZVO3js = require('./chunk-RCQYZVO3.js'); | ||
// src/index.ts | ||
var _core = require('@liveblocks/core'); | ||
var _client = require('@liveblocks/client'); | ||
_core.detectDupes.call(void 0, _chunkRCQYZVO3js.PKG_NAME, _chunkRCQYZVO3js.PKG_VERSION, _chunkRCQYZVO3js.PKG_FORMAT); | ||
_core.detectDupes.call(void 0, PKG_NAME, PKG_VERSION, PKG_FORMAT); | ||
@@ -1900,3 +69,49 @@ | ||
exports.ClientSideSuspense = ClientSideSuspense; exports.createRoomContext = createRoomContext; exports.shallow = _client.shallow; exports.useRoomContextBundle = useRoomContextBundle; | ||
exports.ClientContext = _chunkRCQYZVO3js.ClientContext; exports.ClientSideSuspense = _chunkRCQYZVO3js.ClientSideSuspense; exports.LiveblocksProvider = _chunkRCQYZVO3js.LiveblocksProvider; exports.RoomContext = _chunkRCQYZVO3js.RoomContext; exports.RoomProvider = _chunkRCQYZVO3js._RoomProvider; exports.createLiveblocksContext = _chunkRCQYZVO3js.createLiveblocksContext; exports.createRoomContext = _chunkRCQYZVO3js.createRoomContext; exports.shallow = _client.shallow; exports.useAddReaction = _chunkRCQYZVO3js._useAddReaction; exports.useBatch = _chunkRCQYZVO3js.useBatch; exports.useBroadcastEvent = _chunkRCQYZVO3js._useBroadcastEvent; exports.useCanRedo = _chunkRCQYZVO3js.useCanRedo; exports.useCanUndo = _chunkRCQYZVO3js.useCanUndo; exports.useClient = _chunkRCQYZVO3js.useClient; exports.useCreateComment = _chunkRCQYZVO3js.useCreateComment; exports.useCreateThread = _chunkRCQYZVO3js._useCreateThread; exports.useDeleteComment = _chunkRCQYZVO3js.useDeleteComment; exports.useEditComment = _chunkRCQYZVO3js.useEditComment; exports.useEditThreadMetadata = _chunkRCQYZVO3js._useEditThreadMetadata; exports.useErrorListener = _chunkRCQYZVO3js.useErrorListener; exports.useEventListener = _chunkRCQYZVO3js._useEventListener; exports.useHistory = _chunkRCQYZVO3js.useHistory; exports.useInboxNotificationThread = _chunkRCQYZVO3js.__1; exports.useInboxNotifications = _chunkRCQYZVO3js.useInboxNotifications; exports.useLostConnectionListener = _chunkRCQYZVO3js.useLostConnectionListener; exports.useMarkAllInboxNotificationsAsRead = _chunkRCQYZVO3js.useMarkAllInboxNotificationsAsRead; exports.useMarkInboxNotificationAsRead = _chunkRCQYZVO3js.useMarkInboxNotificationAsRead; exports.useMarkThreadAsRead = _chunkRCQYZVO3js.useMarkThreadAsRead; exports.useMutation = _chunkRCQYZVO3js._useMutation; exports.useMyPresence = _chunkRCQYZVO3js._useMyPresence; exports.useOther = _chunkRCQYZVO3js._useOther; exports.useOthers = _chunkRCQYZVO3js._useOthers; exports.useOthersConnectionIds = _chunkRCQYZVO3js.useOthersConnectionIds; exports.useOthersListener = _chunkRCQYZVO3js._useOthersListener; exports.useOthersMapped = _chunkRCQYZVO3js._useOthersMapped; exports.useRedo = _chunkRCQYZVO3js.useRedo; exports.useRemoveReaction = _chunkRCQYZVO3js.useRemoveReaction; exports.useRoom = _chunkRCQYZVO3js._useRoom; exports.useRoomInfo = _chunkRCQYZVO3js.useRoomInfo; exports.useRoomNotificationSettings = _chunkRCQYZVO3js.useRoomNotificationSettings; exports.useSelf = _chunkRCQYZVO3js._useSelf; exports.useStatus = _chunkRCQYZVO3js.useStatus; exports.useStorage = _chunkRCQYZVO3js._useStorage; exports.useStorageRoot = _chunkRCQYZVO3js._useStorageRoot; exports.useThreadSubscription = _chunkRCQYZVO3js.useThreadSubscription; exports.useThreads = _chunkRCQYZVO3js._useThreads; exports.useUndo = _chunkRCQYZVO3js.useUndo; exports.useUnreadInboxNotificationsCount = _chunkRCQYZVO3js.useUnreadInboxNotificationsCount; exports.useUpdateMyPresence = _chunkRCQYZVO3js._useUpdateMyPresence; exports.useUpdateRoomNotificationSettings = _chunkRCQYZVO3js.useUpdateRoomNotificationSettings; exports.useUser = _chunkRCQYZVO3js.__2; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@liveblocks/react", | ||
"version": "1.19.0-test1", | ||
"version": "2.0.0-alpha1", | ||
"description": "A set of React hooks and providers to use Liveblocks declaratively. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", | ||
"license": "Apache-2.0", | ||
"type": "commonjs", | ||
"main": "./dist/index.js", | ||
@@ -19,2 +20,13 @@ "types": "./dist/index.d.ts", | ||
} | ||
}, | ||
"./suspense": { | ||
"import": { | ||
"types": "./dist/suspense.d.mts", | ||
"default": "./dist/suspense.mjs" | ||
}, | ||
"require": { | ||
"types": "./dist/suspense.d.ts", | ||
"module": "./dist/suspense.mjs", | ||
"default": "./dist/suspense.js" | ||
} | ||
} | ||
@@ -24,2 +36,3 @@ }, | ||
"dist/**", | ||
"suspense/**", | ||
"README.md" | ||
@@ -35,8 +48,8 @@ ], | ||
"test": "jest --silent --verbose --color=always", | ||
"test:types": "tsd", | ||
"test:types": "ls test-d/* | xargs -n1 tsd --files", | ||
"test:watch": "jest --silent --verbose --color=always --watch" | ||
}, | ||
"dependencies": { | ||
"@liveblocks/client": "1.19.0-test1", | ||
"@liveblocks/core": "1.19.0-test1", | ||
"@liveblocks/client": "2.0.0-alpha1", | ||
"@liveblocks/core": "2.0.0-alpha1", | ||
"nanoid": "^3", | ||
@@ -51,7 +64,11 @@ "use-sync-external-store": "^1.2.0" | ||
"@liveblocks/jest-config": "*", | ||
"@testing-library/jest-dom": "^5.16.5", | ||
"@testing-library/react": "^13.1.1", | ||
"@liveblocks/query-parser": "^0.0.3", | ||
"@testing-library/jest-dom": "6.1.6", | ||
"@testing-library/react": "14.1.2", | ||
"@types/use-sync-external-store": "^0.0.3", | ||
"date-fns": "^3.0.6", | ||
"eslint-plugin-react-hooks": "^4.6.0", | ||
"msw": "1.2.3" | ||
"itertools": "^2.3.1", | ||
"msw": "1.3.2", | ||
"react-error-boundary": "^4.0.12" | ||
}, | ||
@@ -58,0 +75,0 @@ "sideEffects": false, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
22
175%5963
12.38%2
-50%569135
-2.54%11
57.14%1
Infinity%+ Added
+ Added
- Removed
- Removed