@take-out/helpers 0.2.2 → 0.2.4

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.
Files changed (92) hide show
  1. package/package.json +2 -1
  2. package/types/array/getRandomItem.d.ts.map +2 -2
  3. package/types/array/takeLast.d.ts.map +2 -2
  4. package/types/array/uniqBy.d.ts.map +2 -2
  5. package/types/assert.d.ts.map +2 -2
  6. package/types/async/abortable.d.ts.map +2 -2
  7. package/types/async/asyncContext.d.ts.map +4 -4
  8. package/types/async/asyncContext.native.d.ts.map +2 -2
  9. package/types/async/idle.d.ts.map +2 -2
  10. package/types/async/interval.d.ts.map +2 -2
  11. package/types/async/isAborted.d.ts.map +2 -2
  12. package/types/async/sleep.d.ts.map +2 -2
  13. package/types/async/useAsync.d.ts.map +2 -2
  14. package/types/async/useAsyncEffect.d.ts.map +4 -4
  15. package/types/async/useLazyMount.d.ts +1 -1
  16. package/types/async/useLazyMount.d.ts.map +3 -3
  17. package/types/async/useLazyValue.d.ts +1 -1
  18. package/types/async/useLazyValue.d.ts.map +3 -3
  19. package/types/browser/clearIndexedDB.d.ts.map +2 -2
  20. package/types/browser/isActiveElementFormField.d.ts.map +2 -2
  21. package/types/browser/openPopup.d.ts.map +2 -2
  22. package/types/client-only.d.ts.map +2 -2
  23. package/types/clipboard/clipboard.d.ts.map +2 -2
  24. package/types/clipboard/clipboard.native.d.ts.map +2 -2
  25. package/types/color/extractOpacityFromColor.d.ts.map +2 -2
  26. package/types/color/generateColors.d.ts.map +2 -2
  27. package/types/color/lum.d.ts.map +2 -2
  28. package/types/color/toHex.d.ts.map +2 -2
  29. package/types/constants.d.ts +1 -1
  30. package/types/constants.d.ts.map +5 -11
  31. package/types/debug/debugLog.d.ts.map +2 -2
  32. package/types/debug/debugUseState.d.ts +1 -1
  33. package/types/debug/debugUseState.d.ts.map +3 -3
  34. package/types/emitter.d.ts.map +4 -4
  35. package/types/ensure/ensure.d.ts.map +2 -2
  36. package/types/ensure/ensureOne.d.ts.map +2 -2
  37. package/types/error/errors.d.ts +16 -0
  38. package/types/error/errors.d.ts.map +4 -4
  39. package/types/function/emptyFn.d.ts.map +2 -2
  40. package/types/function/identityFn.d.ts.map +2 -2
  41. package/types/function/throttle.d.ts.map +2 -2
  42. package/types/global/globalEffect.d.ts.map +2 -2
  43. package/types/global/globalValue.d.ts.map +2 -2
  44. package/types/index.d.ts +1 -0
  45. package/types/index.d.ts.map +4 -4
  46. package/types/number/formatNumber.d.ts.map +2 -2
  47. package/types/object/decorateObject.d.ts.map +2 -2
  48. package/types/object/isEqualDeep.d.ts.map +4 -7
  49. package/types/object/isEqualIdentity.d.ts.map +2 -2
  50. package/types/object/isEqualJSON.d.ts.map +2 -2
  51. package/types/object/isEqualNever.d.ts.map +2 -2
  52. package/types/object/mapObject.d.ts.map +2 -2
  53. package/types/object/object.d.ts.map +2 -2
  54. package/types/object/objectUniqueKey.d.ts.map +2 -2
  55. package/types/react/createGlobalContext.d.ts.map +2 -2
  56. package/types/react/getCurrentComponentStack.d.ts.map +3 -3
  57. package/types/server/ensureEnv.d.ts.map +2 -2
  58. package/types/server/getHeaders.d.ts.map +2 -2
  59. package/types/server/isServerRuntime.d.ts +3 -0
  60. package/types/server/isServerRuntime.d.ts.map +11 -0
  61. package/types/server/prettyPrintRequest.d.ts.map +2 -2
  62. package/types/server/prettyPrintResponse.d.ts.map +2 -2
  63. package/types/server/streamToString.d.ts.map +2 -2
  64. package/types/server-only.d.ts.map +2 -2
  65. package/types/storage/createStorage.d.ts +2 -2
  66. package/types/storage/createStorage.d.ts.map +4 -4
  67. package/types/storage/driver.d.ts.map +2 -2
  68. package/types/storage/storage.test.d.ts.map +2 -2
  69. package/types/storage/types.d.ts.map +2 -2
  70. package/types/string/dedent.d.ts.map +2 -2
  71. package/types/string/ellipsis.d.ts.map +2 -2
  72. package/types/string/hash.d.ts.map +2 -2
  73. package/types/string/insertAtIndex.d.ts.map +2 -2
  74. package/types/string/nbspLastWord.d.ts.map +2 -2
  75. package/types/string/pickLast.d.ts.map +2 -2
  76. package/types/string/pluralize.d.ts.map +2 -2
  77. package/types/string/pluralize.native.d.ts.map +4 -6
  78. package/types/string/randomId.d.ts.map +2 -2
  79. package/types/string/slugify.d.ts.map +2 -2
  80. package/types/string/truncateList.d.ts.map +2 -2
  81. package/types/time/formatDate.d.ts.map +2 -2
  82. package/types/time/formatDateRelative.d.ts.map +2 -2
  83. package/types/time/formatDistanceToNow.d.ts.map +2 -2
  84. package/types/time/time.d.ts.map +4 -6
  85. package/types/time/useTimer.d.ts.map +2 -2
  86. package/types/types/NullToOptional.d.ts.map +2 -2
  87. package/types/types/object.d.ts.map +2 -2
  88. package/types/types/react.d.ts.map +2 -2
  89. package/types/types/timer.d.ts.map +2 -2
  90. package/types/types/tuple.d.ts.map +2 -2
  91. package/types/url/urlSanitize.d.ts.map +2 -2
  92. package/types/url/urlValidate.d.ts.map +2 -2
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@take-out/helpers",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
6
  "source": "src/index.ts",
7
+ "types": "./types/index.d.ts",
7
8
  "main": "dist/cjs",
8
9
  "module": "dist/esm",
9
10
  "files": [
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/array/getRandomItem.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "export const getRandomItem = <T>(items: T[]): T | undefined => {\n if (items.length === 0) return\n const randomIndex = Math.floor(Math.random() * items.length)\n return items[randomIndex]\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/array/takeLast.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "export const takeLast = <T>(arr: T[], n: number): T[] => {\n return arr.slice(-n)\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/array/uniqBy.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "/**\n * Returns a new array with duplicate elements removed based on a key selector function.\n * @param array The input array to remove duplicates from\n * @param keyFn Function that returns the key to compare elements by\n * @returns A new array with duplicates removed\n */\n\nexport function uniqBy<T, K>(array: T[], keyFn: (item: T) => K): T[] {\n const seen = new Map<K, T>()\n\n for (const item of array) {\n const key = keyFn(item)\n if (!seen.has(key)) {\n seen.set(key, item)\n }\n }\n\n return Array.from(seen.values())\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/assert.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "import { AbortError } from './error/errors'\n\nexport function assertExists<T>(value: T | undefined | null, msg = ''): T {\n if (value === undefined || value === null) {\n throw new AbortError(`Invalid nullish value (${value}): ${msg}`)\n }\n return value\n}\n\nexport function assert<T>(value: T, msg = ''): Exclude<T, null | undefined | false> {\n if (!value) {\n throw new AbortError(`Invalid falsy value (${value}): ${msg}`)\n }\n return value as Exclude<T, null | undefined | false>\n}\n\nexport function assertError(val: unknown): Error {\n if (!(val instanceof Error)) {\n throw val\n }\n return val\n}\n\nexport function assertString(val: unknown, name = '(unnamed)'): string {\n if (typeof val !== 'string') {\n throw new AbortError(`No string ${name}`)\n }\n return val\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/async/abortable.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "import { AbortError, EnsureError } from '../error/errors'\n\ntype AbortableOptions = { message?: string }\n\nexport function abortable<T>(cb: () => T, options?: AbortableOptions): T | undefined\nexport function abortable<T>(\n cb: () => Promise<T>,\n options?: AbortableOptions\n): Promise<T | undefined>\nexport function abortable<T>(\n cb: () => T | Promise<T>,\n options?: AbortableOptions\n): T | undefined | Promise<T | undefined> {\n try {\n const value = cb()\n\n if (value instanceof Promise) {\n return value.catch((err) => {\n if (didAbort(err, options)) {\n return undefined\n }\n throw err\n })\n }\n\n return value\n } catch (err) {\n if (didAbort(err, options)) {\n return undefined\n }\n throw err\n }\n}\n\nexport function didAbort(err: unknown, options?: AbortableOptions): boolean {\n if (err instanceof AbortError || err instanceof EnsureError) {\n if (options?.message) {\n console.warn(`Aborted: ${options.message}`)\n }\n return true\n }\n return false\n}\n\nexport function handleAbortError(error: any, debug?: boolean): void {\n if (error instanceof AbortError || error instanceof EnsureError) {\n if (debug || process.env.DEBUG) {\n console.info(`🐛 useAsyncEffect aborted: ${error.message}`)\n }\n return\n }\n\n // js handles aborting a promise as an error. ignore them since they're\n // a normal part of the expected async workflow\n if (typeof error === 'object' && error.name === 'AbortError') {\n return\n }\n\n throw error\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -1,11 +1,11 @@
1
1
  {
2
- "mappings": "UAAU,aAAa,GAAG;CACxB,OAAO;CACP,IAAI,GAAG,OAAO,GAAG,UAAU,IAAI,QAAQ,KAAK,QAAQ;;AA8BtD,OAAO,iBAAS,mBAAmB,MAAM,aAAa",
2
+ "mappings": "UAEU,aAAa,GAAG;CACxB,OAAO;CACP,IAAI,GAAG,OAAO,GAAG,UAAU,IAAI,QAAQ,KAAK,QAAQ;;AA8BtD,OAAO,iBAAS,mBAAmB,MAAM,aAAa",
3
3
  "names": [],
4
4
  "sources": [
5
5
  "src/async/asyncContext.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
- "interface AsyncContext<T> {\n get(): T | undefined\n run<R>(value: T, fn: () => R | Promise<R>): Promise<R>\n}\n\ninterface NodeAsyncLocalStorage<T> {\n getStore(): T | undefined\n run<R>(store: T, callback: () => R): R\n}\n\ninterface AsyncLocalStorageConstructor {\n new <T>(): NodeAsyncLocalStorage<T>\n}\n\nlet nodeAsyncLocalStorageCache: AsyncLocalStorageConstructor | null = null\n\n// hide from vite/esbuild static analysis to avoid browser compat warning\nconst nodeModuleId = ['node', 'async_hooks'].join(':')\n\nasync function getNodeAsyncLocalStorage(): Promise<AsyncLocalStorageConstructor | null> {\n if (!nodeAsyncLocalStorageCache) {\n try {\n const module = await import(/* @vite-ignore */ nodeModuleId)\n nodeAsyncLocalStorageCache =\n module.AsyncLocalStorage as AsyncLocalStorageConstructor\n } catch {\n return null\n }\n }\n return nodeAsyncLocalStorageCache\n}\n\nexport function createAsyncContext<T>(): AsyncContext<T> {\n if (process.env.VITE_ENVIRONMENT === 'ssr') {\n let storage: NodeAsyncLocalStorage<T> | null = null\n\n getNodeAsyncLocalStorage().then((AsyncLocalStorage) => {\n if (AsyncLocalStorage && !storage) {\n storage = new AsyncLocalStorage<T>()\n }\n })\n\n return {\n get(): T | undefined {\n if (!storage) {\n console.warn(`⚠️ called AsyncContext before load!`)\n return\n }\n\n return storage.getStore()\n },\n\n async run<R>(value: T, fn: () => R | Promise<R>): Promise<R> {\n if (!storage) {\n throw new Error(`⚠️ called AsyncContext before load!`)\n }\n return storage.run(value, fn)\n },\n }\n } else {\n // browser implementation using promise patching\n return createBrowserAsyncContext<T>()\n }\n}\n\nfunction createBrowserAsyncContext<T>(): AsyncContext<T> {\n let currentContext: T | undefined\n const contextStack: (T | undefined)[] = []\n\n return {\n get(): T | undefined {\n return currentContext\n },\n async run<R>(value: T, fn: () => R | Promise<R>): Promise<R> {\n const prevContext = currentContext\n currentContext = value\n contextStack.push(prevContext)\n\n // store original Promise methods\n const OriginalPromise = Promise\n const OriginalThen = OriginalPromise.prototype.then\n const OriginalCatch = OriginalPromise.prototype.catch\n const OriginalFinally = OriginalPromise.prototype.finally\n\n function wrapCallback(\n callback: Function | undefined | null,\n context: T | undefined\n ): Function | undefined | null {\n if (!callback) return callback\n return (...args: any[]) => {\n const prevContext = currentContext\n currentContext = context\n try {\n return callback(...args)\n } finally {\n currentContext = prevContext\n }\n }\n }\n\n // patch Promise methods to capture and restore context\n // eslint-disable-next-line no-then-property -- intentional patching for context propagation\n OriginalPromise.prototype.then = function (\n this: Promise<any>,\n onFulfilled?: any,\n onRejected?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalThen.call(\n this,\n wrapCallback(onFulfilled, context) as any,\n wrapCallback(onRejected, context) as any\n )\n }\n\n OriginalPromise.prototype.catch = function (\n this: Promise<any>,\n onRejected?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalCatch.call(this, wrapCallback(onRejected, context) as any)\n }\n\n OriginalPromise.prototype.finally = function (\n this: Promise<any>,\n onFinally?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalFinally.call(this, wrapCallback(onFinally, context) as any)\n }\n\n try {\n const result = await fn()\n return result\n } finally {\n // restore original Promise methods\n // eslint-disable-next-line no-then-property -- restoring original methods\n OriginalPromise.prototype.then = OriginalThen\n OriginalPromise.prototype.catch = OriginalCatch\n OriginalPromise.prototype.finally = OriginalFinally\n\n contextStack.pop()\n currentContext = prevContext\n }\n },\n }\n}\n"
9
- ],
10
- "version": 3
9
+ "import { isServerRuntime } from '../server/isServerRuntime'\n\ninterface AsyncContext<T> {\n get(): T | undefined\n run<R>(value: T, fn: () => R | Promise<R>): Promise<R>\n}\n\ninterface NodeAsyncLocalStorage<T> {\n getStore(): T | undefined\n run<R>(store: T, callback: () => R): R\n}\n\ninterface AsyncLocalStorageConstructor {\n new <T>(): NodeAsyncLocalStorage<T>\n}\n\nlet nodeAsyncLocalStorageCache: AsyncLocalStorageConstructor | null = null\n\n// hide from vite/esbuild static analysis to avoid browser compat warning\nconst nodeModuleId = ['node', 'async_hooks'].join(':')\n\nasync function getNodeAsyncLocalStorage(): Promise<AsyncLocalStorageConstructor | null> {\n if (!nodeAsyncLocalStorageCache) {\n try {\n const module = await import(/* @vite-ignore */ nodeModuleId)\n nodeAsyncLocalStorageCache =\n module.AsyncLocalStorage as AsyncLocalStorageConstructor\n } catch {\n return null\n }\n }\n return nodeAsyncLocalStorageCache\n}\n\nexport function createAsyncContext<T>(): AsyncContext<T> {\n if (isServerRuntime()) {\n let storage: NodeAsyncLocalStorage<T> | null = null\n\n getNodeAsyncLocalStorage().then((AsyncLocalStorage) => {\n if (AsyncLocalStorage && !storage) {\n storage = new AsyncLocalStorage<T>()\n }\n })\n\n return {\n get(): T | undefined {\n if (!storage) {\n console.warn(`⚠️ called AsyncContext before load!`)\n return\n }\n\n return storage.getStore()\n },\n\n async run<R>(value: T, fn: () => R | Promise<R>): Promise<R> {\n if (!storage) {\n throw new Error(`⚠️ called AsyncContext before load!`)\n }\n return storage.run(value, fn)\n },\n }\n } else {\n // browser implementation using promise patching\n return createBrowserAsyncContext<T>()\n }\n}\n\nfunction createBrowserAsyncContext<T>(): AsyncContext<T> {\n let currentContext: T | undefined\n const contextStack: (T | undefined)[] = []\n\n return {\n get(): T | undefined {\n return currentContext\n },\n async run<R>(value: T, fn: () => R | Promise<R>): Promise<R> {\n const prevContext = currentContext\n currentContext = value\n contextStack.push(prevContext)\n\n // store original Promise methods\n const OriginalPromise = Promise\n const OriginalThen = OriginalPromise.prototype.then\n const OriginalCatch = OriginalPromise.prototype.catch\n const OriginalFinally = OriginalPromise.prototype.finally\n\n function wrapCallback(\n callback: Function | undefined | null,\n context: T | undefined\n ): Function | undefined | null {\n if (!callback) return callback\n return (...args: any[]) => {\n const prevContext = currentContext\n currentContext = context\n try {\n return callback(...args)\n } finally {\n currentContext = prevContext\n }\n }\n }\n\n // patch Promise methods to capture and restore context\n // eslint-disable-next-line no-then-property -- intentional patching for context propagation\n OriginalPromise.prototype.then = function (\n this: Promise<any>,\n onFulfilled?: any,\n onRejected?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalThen.call(\n this,\n wrapCallback(onFulfilled, context) as any,\n wrapCallback(onRejected, context) as any\n )\n }\n\n OriginalPromise.prototype.catch = function (\n this: Promise<any>,\n onRejected?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalCatch.call(this, wrapCallback(onRejected, context) as any)\n }\n\n OriginalPromise.prototype.finally = function (\n this: Promise<any>,\n onFinally?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalFinally.call(this, wrapCallback(onFinally, context) as any)\n }\n\n try {\n const result = await fn()\n return result\n } finally {\n // restore original Promise methods\n // eslint-disable-next-line no-then-property -- restoring original methods\n OriginalPromise.prototype.then = OriginalThen\n OriginalPromise.prototype.catch = OriginalCatch\n OriginalPromise.prototype.finally = OriginalFinally\n\n contextStack.pop()\n currentContext = prevContext\n }\n },\n }\n}\n"
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/async/asyncContext.native.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "interface AsyncContext<T> {\n get(): T | undefined\n run<R>(value: T, fn: () => R | Promise<R>): Promise<R>\n}\n\n// react native implementation - no node:async_hooks available\nexport function createAsyncContext<T>(): AsyncContext<T> {\n let currentContext: T | undefined\n const contextStack: (T | undefined)[] = []\n\n return {\n get(): T | undefined {\n return currentContext\n },\n async run<R>(value: T, fn: () => R | Promise<R>): Promise<R> {\n const prevContext = currentContext\n currentContext = value\n contextStack.push(prevContext)\n\n // store original Promise methods\n const OriginalPromise = Promise\n const OriginalThen = OriginalPromise.prototype.then\n const OriginalCatch = OriginalPromise.prototype.catch\n const OriginalFinally = OriginalPromise.prototype.finally\n\n function wrapCallback(\n callback: Function | undefined | null,\n context: T | undefined\n ): Function | undefined | null {\n if (!callback) return callback\n return (...args: any[]) => {\n const prevContext = currentContext\n currentContext = context\n try {\n return callback(...args)\n } finally {\n currentContext = prevContext\n }\n }\n }\n\n // patch Promise methods to capture and restore context\n // eslint-disable-next-line no-then-property -- intentional patching for context propagation\n OriginalPromise.prototype.then = function (\n this: Promise<any>,\n onFulfilled?: any,\n onRejected?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalThen.call(\n this,\n wrapCallback(onFulfilled, context) as any,\n wrapCallback(onRejected, context) as any\n )\n }\n\n OriginalPromise.prototype.catch = function (\n this: Promise<any>,\n onRejected?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalCatch.call(this, wrapCallback(onRejected, context) as any)\n }\n\n OriginalPromise.prototype.finally = function (\n this: Promise<any>,\n onFinally?: any\n ): Promise<any> {\n const context = currentContext\n return OriginalFinally.call(this, wrapCallback(onFinally, context) as any)\n }\n\n try {\n const result = await fn()\n return result\n } finally {\n // restore original Promise methods\n // eslint-disable-next-line no-then-property -- restoring original methods\n OriginalPromise.prototype.then = OriginalThen\n OriginalPromise.prototype.catch = OriginalCatch\n OriginalPromise.prototype.finally = OriginalFinally\n\n contextStack.pop()\n currentContext = prevContext\n }\n },\n }\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/async/idle.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "import { AbortError } from '../error/errors'\nimport { sleep } from './sleep'\n\n/**\n * Two helpers wrapping requestIdleCallback in async, abortable.\n */\n\ntype CallbackFn = (...args: any[]) => any | (() => any)\ntype CallbackerFn = (cb: CallbackFn) => void\n\nconst idleCb: CallbackerFn =\n typeof requestIdleCallback === 'undefined'\n ? (cb: CallbackFn) => setTimeout(cb, 1)\n : requestIdleCallback\n\nconst idleAsync = () => {\n return new Promise((res) => {\n idleCb(res)\n })\n}\n\nexport type IdleOptions = {\n max?: number\n min?: number\n fully?: boolean\n}\n\nexport const idle = async (\n options?: IdleOptions,\n signal?: AbortSignal\n): Promise<void> => {\n const { max, min, fully } = options || {}\n\n const idleFn = fully ? fullyIdle : idleAsync\n\n if (max && min && min < max) {\n await Promise.race([Promise.all([idleFn(), sleep(min)]), sleep(max)])\n } else if (max) {\n await Promise.race([idleFn(), sleep(max)])\n } else if (min) {\n await Promise.all([idleFn(), sleep(min)])\n } else {\n await idleFn()\n }\n\n if (signal?.aborted) {\n throw new AbortError()\n }\n}\n\nconst fullyIdle = async (signal?: AbortSignal): Promise<void> => {\n while (true) {\n const startTime = Date.now()\n await idle({}, signal)\n const endTime = Date.now()\n const duration = endTime - startTime\n\n // If idle callback took less than 15ms, consider it truly idle\n if (duration < 15) {\n break\n }\n\n // Check for abort signal after each iteration\n if (signal?.aborted) {\n throw new AbortError()\n }\n }\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/async/interval.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "import { sleep } from './sleep'\n\nexport const interval = async (\n callback: () => void,\n ms: number,\n signal?: AbortSignal\n): Promise<never> => {\n while (true) {\n callback()\n await sleep(ms, signal)\n }\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/async/isAborted.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "export const isAborted = (controller?: AbortController): boolean =>\n !!controller?.signal.aborted\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/async/sleep.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "import { AbortError } from '../error/errors'\n\nexport const sleep = async (ms: number, signal?: AbortSignal): Promise<void> => {\n await new Promise((res) => setTimeout(res, ms))\n if (signal?.aborted) {\n throw new AbortError()\n }\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/async/useAsync.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "import { useState, useEffect } from 'react'\n\nexport function useAsync<T>(\n promiseFn: () => Promise<T>,\n args: any[]\n): [T, 'loading' | 'idle' | 'error', Error | null] {\n const [data, setData] = useState<T | null>(null)\n const [error, setError] = useState<Error | null>(null)\n const [loading, setLoading] = useState<boolean>(true)\n\n useEffect(() => {\n let isMounted = true\n\n const fetchData = async () => {\n try {\n const result = await promiseFn()\n if (isMounted) {\n setData(result)\n setLoading(false)\n }\n } catch (err) {\n if (isMounted) {\n setError(err as Error)\n setLoading(false)\n }\n }\n }\n\n fetchData()\n\n return () => {\n isMounted = false\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, args)\n\n return [data as T, error ? 'error' : loading ? 'loading' : 'idle', error]\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -1,11 +1,11 @@
1
1
  {
2
- "mappings": "KAOK;KAEA,uBACH,QAAQ,aACR,GAAG,gBACA,QAAQ;KAER,qBAAqB;CACxB;CACA;CACA;;AAGF,OAAO,iBAAS,eACd,IAAI,qBACJ,cACA,UAAU;AAKZ,OAAO,iBAAS,qBACd,IAAI,qBACJ,cACA,UAAU",
2
+ "mappings": "KAQK;KAEA,uBACH,QAAQ,aACR,GAAG,gBACA,QAAQ;KAER,qBAAqB;CACxB;CACA;CACA;;AAGF,OAAO,iBAAS,eACd,IAAI,qBACJ,cACA,UAAU;AAKZ,OAAO,iBAAS,qBACd,IAAI,qBACJ,cACA,UAAU",
3
3
  "names": [],
4
4
  "sources": [
5
5
  "src/async/useAsyncEffect.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
- "// adopted from https://github.com/franciscop/use-async/blob/master/src/index.js\n\nimport { useEffect, useId, useLayoutEffect } from 'react'\n\nimport { EMPTY_OBJECT } from '../constants'\nimport { handleAbortError } from './abortable'\n\ntype Cleanup = () => void\n\ntype AsyncEffectCallback = (\n signal: AbortSignal,\n ...deps: any[]\n) => Promise<Cleanup | void> | void\n\ntype AsyncEffectOptions = {\n circuitBreakAfter?: number\n circuitBreakPeriod?: number\n debug?: boolean\n}\n\nexport function useAsyncEffect(\n cb: AsyncEffectCallback,\n deps: any[] = [],\n options?: AsyncEffectOptions\n): void {\n useAsyncEffectImpl(false, cb, deps, options)\n}\n\nexport function useAsyncLayoutEffect(\n cb: AsyncEffectCallback,\n deps: any[] = [],\n options?: AsyncEffectOptions\n): void {\n useAsyncEffectImpl(true, cb, deps, options)\n}\n\nfunction useAsyncEffectImpl(\n isLayoutEffect: boolean,\n cb: AsyncEffectCallback,\n deps: any[] = [],\n options: AsyncEffectOptions = EMPTY_OBJECT\n): void {\n const effectHook = isLayoutEffect ? useLayoutEffect : useEffect\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const effectId = process.env.NODE_ENV === 'development' ? useId() : ''\n\n effectHook(() => {\n // Generate a unique ID for this effect instance for loop detection\n checkEffectLoop(effectId, options.circuitBreakAfter, options.circuitBreakPeriod)\n const controller = new AbortController()\n const signal = controller.signal\n\n // wrap in try in case its not async (for simple use cases)\n try {\n const value = cb(signal, ...deps)\n\n Promise.resolve(value)\n .then(async (res) => {\n if (res && typeof res === 'function') {\n if (signal.aborted) return res()\n signal.addEventListener('abort', res)\n }\n })\n .catch(handleAbortError)\n } catch (error) {\n handleAbortError(error, options.debug)\n }\n\n return () => {\n if (signal.aborted) return\n controller.abort()\n }\n }, deps)\n}\n\n// loop detection in dev mode\nlet effectRunCounts: Map<string, number[]>\nlet checkEffectLoop: (\n effectId: string,\n circuitBreakAfter?: number,\n circuitBreakPeriod?: number\n) => void\n\nif (process.env.NODE_ENV === 'development') {\n effectRunCounts = new Map<string, number[]>()\n\n checkEffectLoop = (\n effectId: string,\n circuitBreakAfter: number = 20,\n circuitBreakPeriod: number = 1000\n ) => {\n const now = Date.now()\n const runs = effectRunCounts.get(effectId) || []\n\n runs.push(now)\n\n // keep only runs from the specified period\n const recentRuns = runs.filter((time) => now - time < circuitBreakPeriod)\n effectRunCounts.set(effectId, recentRuns)\n\n const runCount = recentRuns.length\n\n if (runCount > circuitBreakAfter) {\n const message = `🚨 useAsyncEffect infinite loop detected! Effect ran ${runCount} times in <${circuitBreakPeriod}ms`\n if (process.env.NODE_ENV === 'development') {\n console.error(message)\n // eslint-disable-next-line no-debugger\n debugger\n } else {\n alert(message)\n throw new Error(message)\n }\n } else if (runCount > circuitBreakAfter / 2) {\n console.warn(\n `⚠️ useAsyncEffect potential loop: Effect ran ${runCount} times in <${circuitBreakPeriod}ms`\n )\n }\n }\n} else {\n checkEffectLoop = () => {}\n}\n"
9
- ],
10
- "version": 3
9
+ "// adopted from https://github.com/franciscop/use-async/blob/master/src/index.js\n\nimport { useEffect, useId, useLayoutEffect } from 'react'\n\nimport { EMPTY_OBJECT } from '../constants'\nimport { getCurrentComponentStack } from '../react/getCurrentComponentStack'\nimport { handleAbortError } from './abortable'\n\ntype Cleanup = () => void\n\ntype AsyncEffectCallback = (\n signal: AbortSignal,\n ...deps: any[]\n) => Promise<Cleanup | void> | void\n\ntype AsyncEffectOptions = {\n circuitBreakAfter?: number\n circuitBreakPeriod?: number\n debug?: boolean\n}\n\nexport function useAsyncEffect(\n cb: AsyncEffectCallback,\n deps: any[] = [],\n options?: AsyncEffectOptions\n): void {\n useAsyncEffectImpl(false, cb, deps, options)\n}\n\nexport function useAsyncLayoutEffect(\n cb: AsyncEffectCallback,\n deps: any[] = [],\n options?: AsyncEffectOptions\n): void {\n useAsyncEffectImpl(true, cb, deps, options)\n}\n\nfunction useAsyncEffectImpl(\n isLayoutEffect: boolean,\n cb: AsyncEffectCallback,\n deps: any[] = [],\n options: AsyncEffectOptions = EMPTY_OBJECT\n): void {\n const effectHook = isLayoutEffect ? useLayoutEffect : useEffect\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const effectId = process.env.NODE_ENV === 'development' ? useId() : ''\n\n effectHook(() => {\n // Generate a unique ID for this effect instance for loop detection\n checkEffectLoop(\n effectId,\n cb,\n deps,\n options.circuitBreakAfter,\n options.circuitBreakPeriod\n )\n const controller = new AbortController()\n const signal = controller.signal\n\n // wrap in try in case its not async (for simple use cases)\n try {\n const value = cb(signal, ...deps)\n\n Promise.resolve(value)\n .then(async (res) => {\n if (res && typeof res === 'function') {\n if (signal.aborted) return res()\n signal.addEventListener('abort', res)\n }\n })\n .catch(handleAbortError)\n } catch (error) {\n handleAbortError(error, options.debug)\n }\n\n return () => {\n if (signal.aborted) return\n controller.abort()\n }\n }, deps)\n}\n\n// loop detection in dev mode\nlet effectRunCounts: Map<string, number[]>\nlet checkEffectLoop: (\n effectId: string,\n cb: AsyncEffectCallback,\n deps: any[],\n circuitBreakAfter?: number,\n circuitBreakPeriod?: number\n) => void\n\nfunction formatDeps(deps: any[]): string {\n try {\n return JSON.stringify(\n deps,\n (_, v) => {\n if (typeof v === 'function') return `[Function: ${v.name || 'anonymous'}]`\n if (typeof v === 'symbol') return v.toString()\n if (v instanceof Error) return `[Error: ${v.message}]`\n return v\n },\n 2\n )\n } catch {\n return `[${deps.length} deps - not serializable]`\n }\n}\n\nif (process.env.NODE_ENV === 'development') {\n effectRunCounts = new Map<string, number[]>()\n\n checkEffectLoop = (\n effectId: string,\n cb: AsyncEffectCallback,\n deps: any[],\n circuitBreakAfter: number = 20,\n circuitBreakPeriod: number = 1000\n ) => {\n const now = Date.now()\n const runs = effectRunCounts.get(effectId) || []\n\n runs.push(now)\n\n // keep only runs from the specified period\n const recentRuns = runs.filter((time) => now - time < circuitBreakPeriod)\n effectRunCounts.set(effectId, recentRuns)\n\n const runCount = recentRuns.length\n\n if (runCount > circuitBreakAfter) {\n const message = `🚨 useAsyncEffect infinite loop detected! Effect ran ${runCount} times in <${circuitBreakPeriod}ms`\n if (process.env.NODE_ENV === 'development') {\n console.error(message)\n console.error('Effect function:', cb.toString().slice(0, 500))\n console.error('Dependencies:', formatDeps(deps))\n console.error('Stack:', getCurrentComponentStack())\n // eslint-disable-next-line no-debugger\n debugger\n } else {\n alert(message)\n throw new Error(message)\n }\n } else if (runCount > circuitBreakAfter / 2) {\n console.warn(\n `⚠️ useAsyncEffect potential loop: Effect ran ${runCount} times in <${circuitBreakPeriod}ms`\n )\n console.warn('Effect function:', cb.toString().slice(0, 500))\n console.warn('Dependencies:', formatDeps(deps))\n console.warn('Stack:', getCurrentComponentStack())\n }\n }\n} else {\n checkEffectLoop = (_id, _cb, _deps, _after, _period) => {}\n}\n"
10
+ ]
11
11
  }
@@ -2,7 +2,7 @@ import { type IdleOptions } from "./idle";
2
2
  import type React from "react";
3
3
  export type LazyMountProps = IdleOptions;
4
4
  export declare const useLazyMount: (props?: LazyMountProps) => boolean;
5
- export declare const LazyMount: ({ children,...idleProps }: LazyMountProps & {
5
+ export declare const LazyMount: ({ children, ...idleProps }: LazyMountProps & {
6
6
  children: any;
7
7
  }) => React.ReactNode;
8
8
 
@@ -1,11 +1,11 @@
1
1
  {
2
- "mappings": "AAEA,cAAoB,mBAAmB;AAGvC,YAAY,WAAW;AAEvB,YAAY,iBAAiB;AAE7B,OAAO,cAAM,eAAgB,QAAO;AAkBpC,OAAO,cAAM,YAAa,EACxB,SACA,GAAG,aACF,iBAAiB;CAAE;MAAkB,MAAM",
2
+ "mappings": "AAEA,cAAoB,mBAAmB;AAGvC,YAAY,WAAW;AAEvB,YAAY,iBAAiB;AAE7B,OAAO,cAAM,eAAgB,QAAO;AAkBpC,OAAO,cAAM,YAAa,EACxB,UACA,GAAG,aACF,iBAAiB;CAAE;MAAkB,MAAM",
3
3
  "names": [],
4
4
  "sources": [
5
5
  "src/async/useLazyMount.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "import { startTransition, useState } from 'react'\n\nimport { idle, type IdleOptions } from './idle'\nimport { useAsyncEffect } from './useAsyncEffect'\n\nimport type React from 'react'\n\nexport type LazyMountProps = IdleOptions\n\nexport const useLazyMount = (props: LazyMountProps = { max: 100 }): boolean => {\n const [mounted, setMounted] = useState(false)\n\n useAsyncEffect(\n async (signal) => {\n await idle(props, signal)\n startTransition(() => {\n setMounted(true)\n })\n },\n [\n // no need for deps it only ever mounts once\n ]\n )\n\n return mounted\n}\n\nexport const LazyMount = ({\n children,\n ...idleProps\n}: LazyMountProps & { children: any }): React.ReactNode => {\n const mounted = useLazyMount(idleProps)\n return mounted ? children : null\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -1,5 +1,5 @@
1
1
  import { type IdleOptions } from "./idle";
2
- export declare const useLazyValue: <T>(value: T, { immediateFirstUpdate,...idleOptions }?: IdleOptions & {
2
+ export declare const useLazyValue: <T>(value: T, { immediateFirstUpdate, ...idleOptions }?: IdleOptions & {
3
3
  immediateFirstUpdate?: boolean;
4
4
  }) => T;
5
5
 
@@ -1,11 +1,11 @@
1
1
  {
2
- "mappings": "AAEA,cAAoB,mBAAmB;AAGvC,OAAO,cAAM,eAAgB,GAC3B,OAAO,GACP,EACE,qBACA,GAAG,gBACF,cAAc;CAAE;MAClB",
2
+ "mappings": "AAEA,cAAoB,mBAAmB;AAGvC,OAAO,cAAM,eAAgB,GAC3B,OAAO,GACP,EACE,sBACA,GAAG,gBACF,cAAc;CAAE;MAClB",
3
3
  "names": [],
4
4
  "sources": [
5
5
  "src/async/useLazyValue.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "import { startTransition, useState } from 'react'\n\nimport { idle, type IdleOptions } from './idle'\nimport { useAsyncEffect } from './useAsyncEffect'\n\nexport const useLazyValue = <T>(\n value: T,\n {\n immediateFirstUpdate,\n ...idleOptions\n }: IdleOptions & { immediateFirstUpdate?: boolean } = {}\n): T => {\n const [lazyValue, setLazyValue] = useState(value)\n\n // first update to a real value immediate\n if (value && lazyValue === undefined && lazyValue !== value && immediateFirstUpdate) {\n setLazyValue(value)\n }\n\n useAsyncEffect(\n async (signal) => {\n await idle(idleOptions, signal)\n startTransition(() => {\n setLazyValue(value)\n })\n },\n [value]\n )\n\n return lazyValue\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/browser/clearIndexedDB.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "export async function clearIndexedDB(): Promise<void> {\n if ('indexedDB' in window) {\n const databases = await indexedDB.databases()\n await Promise.all(\n databases.map((db) => {\n if (db.name) {\n return new Promise<void>((resolve, reject) => {\n const deleteReq = indexedDB.deleteDatabase(db.name!)\n deleteReq.onsuccess = () => resolve()\n deleteReq.onerror = () => reject(deleteReq.error)\n deleteReq.onblocked = () => reject(new Error('database deletion blocked'))\n })\n }\n return Promise.resolve()\n })\n )\n }\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/browser/isActiveElementFormField.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "export function isActiveElementFormField(): boolean {\n return (\n typeof document !== 'undefined' &&\n document.activeElement !== null &&\n (document.activeElement.tagName === 'INPUT' ||\n document.activeElement.tagName === 'TEXTAREA' ||\n document.activeElement.tagName === 'SELECT' ||\n document.activeElement.tagName === 'FORM')\n )\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/browser/openPopup.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "// note safari enhanced privacy will break this as it reports screen height as smaller\n\nexport function openCenteredPopup(\n url: string,\n title: string,\n width: number,\n height: number\n): Window | null {\n // availWidth/availHeight accounts for taskbars, docks, and other UI elements\n const screenWidth = screen.availWidth || screen.width\n const screenHeight = screen.availHeight || screen.height\n\n const shouldGuess = screenHeight < height\n\n // if we are guessing, at least make it a bit more likely to be centered\n // and not just touching the top which looks broken\n const left = Math.max(0, (screenWidth - width) / 2) + (shouldGuess ? 100 : 0)\n const top = Math.max(150, shouldGuess ? 150 : Math.max(0, (screenHeight - height) / 2))\n\n const windowFeatures = `\n width=${width},\n height=${height},\n left=${left},\n top=${top},\n scrollbars=yes,\n resizable=yes\n `\n\n return window.open(url, title, windowFeatures)\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/client-only.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "if (process.env.VITE_ENVIRONMENT !== 'client') {\n console.error(`This file should only be imported on the client!`)\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/clipboard/clipboard.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "export const getClipboardText = async (): Promise<string | null> => {\n try {\n const text = await navigator.clipboard.readText()\n return text\n } catch {\n return null\n }\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/clipboard/clipboard.native.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "import * as Clipboard from 'expo-clipboard'\n\nexport const getClipboardText = async (): Promise<string | null> => {\n try {\n const text = await Clipboard.getStringAsync()\n return text\n } catch {\n return null\n }\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/color/extractOpacityFromColor.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "export function extractOpacityFromColor(color: string): number {\n if (color === 'transparent') return 0\n\n // Match hex codes like #RRGGBBAA or #RRGGBB\n const hexMatch = color.match(/^#([0-9a-fA-F]{6})([0-9a-fA-F]{2})?$/)\n if (hexMatch) {\n const [, _rgb, alphaHex] = hexMatch\n if (alphaHex) {\n const alpha = parseInt(alphaHex, 16)\n return alpha / 255\n }\n return 1 // No alpha specified → fully opaque\n }\n\n // Could expand this to support rgba(), hsl(), etc. if needed\n console.warn(`Unsupported color format: ${color}`)\n return 1\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/color/generateColors.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "import { toHex } from './toHex'\n\ntype ColorGenOptions = {\n numColors?: number\n minSaturation?: number\n maxSaturation?: number\n minLightness?: number\n maxLightness?: number\n}\n\n// convert HSL to hex\nfunction hslToHex(h: number, s: number, l: number): string {\n // normalize values\n h = h / 360\n s = s / 100\n l = l / 100\n\n const a = s * Math.min(l, 1 - l)\n const f = (n: number) => {\n const k = (n + h * 12) % 12\n const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)\n return Math.round(255 * color)\n }\n\n const r = f(0)\n const g = f(8)\n const b = f(4)\n\n return toHex((r << 16) | (g << 8) | b)\n}\n\nexport const generateColors = ({\n numColors = 32,\n minSaturation = 45,\n maxSaturation = 85,\n minLightness = 45,\n maxLightness = 65,\n}: ColorGenOptions = {}): string[] => {\n const colors: string[] = []\n\n // Define hue ranges for color groups\n const hueRanges = [\n [0, 30], // reds\n [30, 60], // oranges\n [60, 90], // yellows\n [90, 150], // greens\n [150, 180], // teals\n [180, 240], // blues\n [240, 270], // purples\n [270, 330], // magentas\n [330, 360], // pink-reds\n ]\n\n // Calculate colors per group\n const colorsPerGroup = Math.ceil(numColors / hueRanges.length)\n\n hueRanges.forEach(([start, end]) => {\n const hueStep = (end! - start!) / colorsPerGroup\n\n for (let i = 0; i < colorsPerGroup; i++) {\n if (colors.length >= numColors) break\n\n const hue = start! + hueStep * i\n const saturation = minSaturation + Math.random() * (maxSaturation - minSaturation)\n const lightness = minLightness + Math.random() * (maxLightness - minLightness)\n\n colors.push(hslToHex(hue, saturation, lightness))\n }\n })\n\n return colors\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/color/lum.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "// Helper to ensure color string is valid (simple fallback implementation)\nconst validColor = (color: string) => color\n\nexport function lum(color: string, luminance = 0.5): string {\n // handle hsl/hsla\n if (color.startsWith('hsl')) {\n const match = color.match(/hsla?\\((\\d+),\\s*(\\d+)%,\\s*(\\d+)%(?:,\\s*([\\d.]+))?\\)/)\n if (match) {\n const [, h, s, , a] = match\n const newL = Math.round(luminance * 100)\n if (a) {\n return validColor(`hsla(${h}, ${s}%, ${newL}%, ${a})`)\n }\n return validColor(`hsl(${h}, ${s}%, ${newL}%)`)\n }\n }\n\n // handle hex - convert to hsl and adjust\n if (color.startsWith('#')) {\n let hex = color.slice(1)\n\n // expand shorthand hex\n if (hex.length === 3) {\n hex = hex\n .split('')\n .map((c) => c + c)\n .join('')\n }\n\n // convert hex to rgb\n const r = parseInt(hex.slice(0, 2), 16) / 255\n const g = parseInt(hex.slice(2, 4), 16) / 255\n const b = parseInt(hex.slice(4, 6), 16) / 255\n\n // convert rgb to hsl\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n const l = (max + min) / 2\n\n if (max === min) {\n // achromatic\n const newL = Math.round(luminance * 100)\n return validColor(`hsl(0, 0%, ${newL}%)`)\n }\n\n const d = max - min\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n\n let h: number\n switch (max) {\n case r:\n h = ((g - b) / d + (g < b ? 6 : 0)) / 6\n break\n case g:\n h = ((b - r) / d + 2) / 6\n break\n case b:\n h = ((r - g) / d + 4) / 6\n break\n default:\n h = 0\n }\n\n const newH = Math.round(h * 360)\n const newS = Math.round(s * 100)\n const newL = Math.round(luminance * 100)\n\n // preserve alpha if present\n if (hex.length === 8) {\n const alpha = parseInt(hex.slice(6, 8), 16) / 255\n return validColor(`hsla(${newH}, ${newS}%, ${newL}%, ${alpha.toFixed(2)})`)\n }\n\n return validColor(`hsl(${newH}, ${newS}%, ${newL}%)`)\n }\n\n return validColor(color)\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/color/toHex.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "// simple helper to ensure we always get a valid 6-digit hex color from a number\nexport function toHex(value: number): string {\n // ensure we get a 6-digit hex string with leading zeros if needed\n return '#' + value.toString(16).padStart(6, '0')\n}\n\n// generate a random hex color\nexport function randomHex(): string {\n return toHex(Math.floor(Math.random() * 0xffffff))\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -2,7 +2,7 @@ export declare const isTauri: boolean;
2
2
  export declare const isNative: boolean;
3
3
  export declare const IS_MAC_DESKTOP: boolean;
4
4
  export declare const IS_SAFARI: boolean;
5
- export { isAndroid, isClient, isIos, isServer, isWeb } from "@tamagui/constants";
5
+ export { isAndroid, isBrowser, isIos, isServer, isWeb } from "@tamagui/constants";
6
6
  export declare const EMPTY_ARRAY: never;
7
7
  export declare const EMPTY_OBJECT: never;
8
8
  export declare const DEBUG_LEVEL: number;
@@ -1,17 +1,11 @@
1
1
  {
2
- "mappings": "AAEA,OAAO,cAAMA;AAGb,OAAO,cAAMC;AAIb,OAAO,cAAMC;AAGb,OAAO,cAAMC;AAMb,SAAS,WAAW,UAAU,OAAO,UAAU,aAAa;AAE5D,OAAO,cAAM;AACb,OAAO,cAAM;AAQb,OAAO,cAAMC",
3
- "names": [
4
- "isTauri: boolean",
5
- "isNative: boolean",
6
- "IS_MAC_DESKTOP: boolean",
7
- "IS_SAFARI: boolean",
8
- "DEBUG_LEVEL: number"
9
- ],
2
+ "mappings": "AAEA,OAAO,cAAM;AAEb,OAAO,cAAM;AAIb,OAAO,cAAM;AAGb,OAAO,cAAM;AAMb,SAAS,WAAW,WAAW,OAAO,UAAU,aAAa;AAE7D,OAAO,cAAM;AACb,OAAO,cAAM;AAQb,OAAO,cAAM",
3
+ "names": [],
10
4
  "sources": [
11
5
  "src/constants.ts"
12
6
  ],
7
+ "version": 3,
13
8
  "sourcesContent": [
14
- "import { isWeb } from '@tamagui/constants'\n\nexport const isTauri: boolean =\n typeof window !== 'undefined' && window['__TAURI__'] !== undefined\n\nexport const isNative: boolean = !isWeb && !isTauri\n\n// TODO move to probably ~/interface/constants\n\nexport const IS_MAC_DESKTOP: boolean =\n typeof navigator !== 'undefined' && /Macintosh|MacIntel/.test(navigator.platform)\n\nexport const IS_SAFARI: boolean =\n isTauri ||\n (typeof navigator !== 'undefined' &&\n /Version\\/[\\d.]+.*Safari/.test(navigator.userAgent) &&\n /Apple Computer/.test(navigator.vendor))\n\nexport { isAndroid, isClient, isIos, isServer, isWeb } from '@tamagui/constants'\n\nexport const EMPTY_ARRAY = [] as never\nexport const EMPTY_OBJECT = {} as never\n\nconst getDebugLevelFromUrl = (): number | null => {\n if (typeof window === 'undefined') return null\n const match = window.location?.search?.match(/debug=(\\d+)/)\n return match?.[1] ? parseInt(match[1], 10) : null\n}\n\nexport const DEBUG_LEVEL: number = process.env.DEBUG_LEVEL\n ? +process.env.DEBUG_LEVEL\n : (getDebugLevelFromUrl() ?? (process.env.NODE_ENV === 'development' ? 1 : 0))\n"
15
- ],
16
- "version": 3
9
+ "import { isWeb } from '@tamagui/constants'\n\nexport const isTauri: boolean = typeof window !== 'undefined' && '__TAURI__' in window\n\nexport const isNative: boolean = !isWeb && !isTauri\n\n// TODO move to probably ~/interface/constants\n\nexport const IS_MAC_DESKTOP: boolean =\n typeof navigator !== 'undefined' && /Macintosh|MacIntel/.test(navigator.platform)\n\nexport const IS_SAFARI: boolean =\n isTauri ||\n (typeof navigator !== 'undefined' &&\n /Version\\/[\\d.]+.*Safari/.test(navigator.userAgent) &&\n /Apple Computer/.test(navigator.vendor))\n\nexport { isAndroid, isBrowser, isIos, isServer, isWeb } from '@tamagui/constants'\n\nexport const EMPTY_ARRAY = [] as never\nexport const EMPTY_OBJECT = {} as never\n\nconst getDebugLevelFromUrl = (): number | null => {\n if (typeof window === 'undefined') return null\n const match = window.location?.search?.match(/debug=(\\d+)/)\n return match?.[1] ? parseInt(match[1], 10) : null\n}\n\nexport const DEBUG_LEVEL: number = process.env.DEBUG_LEVEL\n ? +process.env.DEBUG_LEVEL\n : (getDebugLevelFromUrl() ?? (process.env.NODE_ENV === 'development' ? 1 : 0))\n"
10
+ ]
17
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/debug/debugLog.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "/**\n * Debug utility that logs a value and passes it through unchanged.\n * Useful for debugging chains of operations without breaking the flow.\n *\n * @param value - The value to log and pass through\n * @param label - Optional label to prefix the log output\n * @returns The original value unchanged\n */\nexport function debugLog<T>(value: T, label?: string): T {\n const output = label ? `[${label}]` : '[DEBUG]'\n console.info(output, value)\n return value\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -1,3 +1,3 @@
1
- export declare function debugUseState<T>(useState: [T, React.Dispatch<React.SetStateAction<T>>]): [T, React.Dispatch<React.SetStateAction<T>>];
1
+ export declare function useDebugState<T>(useState: [T, React.Dispatch<React.SetStateAction<T>>]): [T, React.Dispatch<React.SetStateAction<T>>];
2
2
 
3
3
  //# sourceMappingURL=debugUseState.d.ts.map
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/debug/debugUseState.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
- "import { useCallback } from 'react'\n\nexport function debugUseState<T>(\n useState: [T, React.Dispatch<React.SetStateAction<T>>]\n): [T, React.Dispatch<React.SetStateAction<T>>] {\n const [state, setState] = useState\n\n const wrappedSetState = useCallback(\n (newValue: T | ((prevState: T) => T)) => {\n if (typeof newValue === 'function') {\n // Handle callback style\n setState((prevState: T) => {\n const nextState = (newValue as (prevState: T) => T)(prevState)\n console.trace('State update (callback):', {\n prevState,\n nextState,\n stack: new Error().stack,\n })\n return nextState\n })\n } else {\n // Handle direct value style\n console.trace('State update (direct):', {\n prevState: state,\n nextState: newValue,\n stack: new Error().stack,\n })\n setState(newValue)\n }\n },\n [setState, state]\n )\n\n return [state, wrappedSetState]\n}\n"
9
- ],
10
- "version": 3
9
+ "import { useCallback } from 'react'\n\nexport function useDebugState<T>(\n useState: [T, React.Dispatch<React.SetStateAction<T>>]\n): [T, React.Dispatch<React.SetStateAction<T>>] {\n const [state, setState] = useState\n\n const wrappedSetState = useCallback(\n (newValue: T | ((prevState: T) => T)) => {\n if (typeof newValue === 'function') {\n // Handle callback style\n setState((prevState: T) => {\n const nextState = (newValue as (prevState: T) => T)(prevState)\n console.trace('State update (callback):', {\n prevState,\n nextState,\n stack: new Error().stack,\n })\n return nextState\n })\n } else {\n // Handle direct value style\n console.trace('State update (direct):', {\n prevState: state,\n nextState: newValue,\n stack: new Error().stack,\n })\n setState(newValue)\n }\n },\n [setState, state]\n )\n\n return [state, wrappedSetState]\n}\n"
10
+ ]
11
11
  }
@@ -1,11 +1,11 @@
1
1
  {
2
- "mappings": "AAUA,cAAc,KAAK,yBAAyB;KAwBvC,eAAe,KAAK,kBAAkB,KAAK;CAC9C;;KAGG,kBAAkB,KAAK;CAC1B;CACA,cAAc,GAAG,GAAG,GAAG;;AAGzB,OAAO,cAAM,cAAc,GAAG;CAC5B,QAAQ;CACR,OAAO;CACP,UAAU,eAAe;CAEzB,YAAY,OAAO,GAAG,UAAU,eAAe;CAK/C,SAAU,aAAa,IAAI;CAO3B,OAAQ,MAAM;CAqCd,iBAAgB,QAAQ;;AAW1B,OAAO,iBAAS,oBAAoB,GAClC,cACA,cAAc,GACd,UAAU,kBAAkB,KAC3B,QAAQ;AAIX,OAAO,iBAAS,cAAc,GAC5B,cACA,cAAc,GACd,UAAU,kBAAkB,KAC3B,QAAQ;AAKX,YAAY,YAAY,UAAU,gBAChC,UAAU,cAAc,OAAO;AAEjC,OAAO,cAAM,aAAc,UAAU,cACnC,SAAS,GACT,KAAK,IAAI,YAAY,aACrB;AAkBF,OAAO,cAAM,kBAAmB,UAAU,cACxC,SAAS,GACT,UAAU;CAAE;MACX,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDf,OAAO,cAAM;CAAsB,UAAU;CAAc,UAAU,YAAY;CAAI;EACnF,SAAS,GACT,WAAW,OAAO,MAAM,GACxB,UAAU;CACR;CACA;GAEF,iBACC;AAgCH,OAAO,cAAM;OAA6B,mBAAmB;CAAgB;EAC3E,UAAU,GACV,WAAW,WAAW,WAAW,KAAI,YAAY,EAAE,UAAU,GAC7D,UAAU;CAAE;CAAmB,WAAW,GAAG,GAAG,GAAG;MAClD;AAgDH,OAAO,cAAM,mBAAoB,UAAU,cACzC,SAAS,QACN,KAAK,KAAK,YAAY,aAAa;AAKxC,OAAO,cAAM,oBAAqB,UAAU,cAC1C,SAAS,QACN,GACH,WAAW,OAAO,YAAY,OAAO,GACrC,UAAU;CAAE;CAAmB;GAC/B,iBACG;AAUL,OAAO,iBAAS,wBAAwB,GACtC,cACA,cAAc,GACd,iBAAiB,KAAK,eAAe,IAAI,0BAEnC,QAAQ,KACb,OAAO,kBAAkB;CAAE,QAAQ;CAAG;OAAwB,IAAI",
2
+ "mappings": "AAUA,cAAc,KAAK,yBAAyB;KAyBvC,eAAe,KAAK,kBAAkB,KAAK;CAC9C;;KAGG,kBAAkB,KAAK;CAC1B;CACA,cAAc,GAAG,GAAG,GAAG;;AAGzB,OAAO,cAAM,cAAc,GAAG;CAC5B,QAAQ;CACR,OAAO;CACP,UAAU,eAAe;CAEzB,YAAY,OAAO,GAAG,UAAU,eAAe;CAK/C,SAAU,aAAa,IAAI;CAO3B,OAAQ,MAAM;CAqCd,iBAAgB,QAAQ;;AAW1B,OAAO,iBAAS,oBAAoB,GAClC,cACA,cAAc,GACd,UAAU,kBAAkB,KAC3B,QAAQ;AAIX,OAAO,iBAAS,cAAc,GAC5B,cACA,cAAc,GACd,UAAU,kBAAkB,KAC3B,QAAQ;AAKX,YAAY,YAAY,UAAU,gBAChC,UAAU,cAAc,OAAO;AAEjC,OAAO,cAAM,aAAc,UAAU,cACnC,SAAS,GACT,KAAK,IAAI,YAAY,aACrB;AAkBF,OAAO,cAAM,kBAAmB,UAAU,cACxC,SAAS,GACT,UAAU;CAAE;MACX,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8Cf,OAAO,cAAM;CAAsB,UAAU;CAAc,UAAU,YAAY;CAAI;EACnF,SAAS,GACT,WAAW,OAAO,MAAM,GACxB,UAAU;CACR;CACA;GAEF,iBACC;AAgCH,OAAO,cAAM;OAA6B,mBAAmB;CAAgB;EAC3E,UAAU,GACV,WAAW,WAAW,WAAW,KAAI,YAAY,EAAE,UAAU,GAC7D,UAAU;CAAE;CAAmB,WAAW,GAAG,GAAG,GAAG;MAClD;AAgDH,OAAO,cAAM,mBAAoB,UAAU,cACzC,SAAS,QACN,KAAK,KAAK,YAAY,aAAa;AAKxC,OAAO,cAAM,oBAAqB,UAAU,cAC1C,SAAS,QACN,GACH,WAAW,OAAO,YAAY,OAAO,GACrC,UAAU;CAAE;CAAmB;GAC/B,iBACG;AAUL,OAAO,iBAAS,wBAAwB,GACtC,cACA,cAAc,GACd,iBAAiB,KAAK,eAAe,IAAI,0BAEnC,QAAQ,KACb,OAAO,kBAAkB;CAAE,QAAQ;CAAG;OAAwB,IAAI",
3
3
  "names": [],
4
4
  "sources": [
5
5
  "src/emitter.tsx"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
- "import { dequal } from 'dequal'\nimport * as React from 'react'\nimport { use, useLayoutEffect, useState } from 'react'\n\nimport { handleAbortError } from './async/abortable'\nimport { DEBUG_LEVEL, EMPTY_ARRAY } from './constants'\nimport { AbortError } from './error/errors'\nimport { globalValue } from './global/globalValue'\nimport { createGlobalContext } from './react/createGlobalContext'\n\nimport type { JSX, PropsWithChildren } from 'react'\n\n// keeps a reference to the current value easily\n\n// TODO can replace with useEffectEvent\nfunction useGet<A>(\n currentValue: A,\n initialValue?: any,\n forwardToFunction?: boolean\n): () => A {\n const curRef = React.useRef<any>(initialValue ?? currentValue)\n\n useLayoutEffect(() => {\n curRef.current = currentValue\n })\n\n return React.useCallback(\n forwardToFunction\n ? (...args) => curRef.current?.apply(null, args)\n : () => curRef.current,\n []\n )\n}\n\ntype EmitterOptions<T> = CreateEmitterOpts<T> & {\n name: string\n}\n\ntype CreateEmitterOpts<T> = {\n silent?: boolean\n comparator?: (a: T, b: T) => boolean\n}\n\nexport class Emitter<const T> {\n private disposables = new Set<(cb: any) => void>()\n value: T\n options?: EmitterOptions<T>\n\n constructor(value: T, options?: EmitterOptions<T>) {\n this.value = value\n this.options = options\n }\n\n listen = (disposable: (cb: T) => void): (() => void) => {\n this.disposables.add(disposable)\n return (): void => {\n this.disposables.delete(disposable)\n }\n }\n\n emit = (next: T): void => {\n if (process.env.NODE_ENV === 'development') {\n setCache(this, next)\n }\n const compare = this.options?.comparator\n if (compare) {\n if (this.value && compare(this.value, next)) {\n return\n }\n } else {\n if (this.value === next) {\n if (process.env.NODE_ENV === 'development') {\n console.warn(\n `[emitter] ${this.options?.name} no comparator option but received same value!\n \nthis will emit the same value again, which can be desirable, but we warn to ensure it's not unintended:\n\n- if you want this behavior, add { comparator: isEqualNever }\n- if you want only non-equal values: { comparator: isEqualIdentity }\n- if you want only deeply non-equal values: { comparator: isEqualDeep }`\n )\n }\n }\n }\n this.value = next\n if (DEBUG_LEVEL > 1) {\n if (!this.options?.silent) {\n const name = this.options?.name\n console.groupCollapsed(`📣 ${name}`)\n console.info(next)\n console.trace(`trace >`)\n console.groupEnd()\n }\n }\n this.disposables.forEach((cb) => cb(next))\n }\n\n nextValue = (): Promise<T> => {\n return new Promise<T>((res) => {\n const dispose = this.listen((val) => {\n dispose()\n res(val)\n })\n })\n }\n}\n\n// just createEmitter but ensures it doesn't mess up on HMR\nexport function createGlobalEmitter<T>(\n name: string,\n defaultValue: T,\n options?: CreateEmitterOpts<T>\n): Emitter<T> {\n return globalValue(name, () => createEmitter(name, defaultValue, options))\n}\n\nexport function createEmitter<T>(\n name: string,\n defaultValue: T,\n options?: CreateEmitterOpts<T>\n): Emitter<T> {\n const existing = createOrUpdateCache(name, defaultValue) as T\n return new Emitter<T>(existing || defaultValue, { name, ...options })\n}\n\nexport type EmitterType<E extends Emitter<any>> =\n E extends Emitter<infer Val> ? Val : never\n\nexport const useEmitter = <E extends Emitter<any>>(\n emitter: E,\n cb: (cb: EmitterType<E>) => void,\n args?: any[]\n): void => {\n const getCallback = useGet(cb)\n\n useLayoutEffect(() => {\n return emitter.listen((val) => {\n try {\n getCallback()(val)\n } catch (err) {\n handleAbortError(err)\n }\n })\n }, [emitter, getCallback])\n}\n\n// i think this was useSyncExternalStore but removed for concurrent rendering improvements\n// wondering if we could just always return a deferred value? or default to it?\n\nexport const useEmitterValue = <E extends Emitter<any>>(\n emitter: E,\n options?: { disable?: boolean }\n): EmitterType<E> => {\n const disabled = options?.disable\n\n // use a function initializer to get current emitter value\n const [state, setState] = useState<EmitterType<E>>(() => emitter.value)\n\n useLayoutEffect(() => {\n if (disabled) return\n\n // sync immediately in case emitter changed between render and effect\n if (emitter.value !== state) {\n setState(emitter.value)\n }\n\n return emitter.listen(setState)\n // intentionally omit state from deps - we only want to re-subscribe when emitter changes\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [disabled, emitter])\n\n return state\n}\n\n/**\n * By default selectors run every render, as well as when emitters update. This is a change\n * from the previous behavior where they only ran when emitters changed value.\n *\n * The reason for this is because emitters capture the variables in scope of the component\n * each render already by using \"useGet\" by default, which makes them easier to use - you\n * don't need to pass an args[] array except for edge cases.\n *\n * Before explaining why we switched to the default, understand the different uses:\n *\n * - Default behavior - selector is updated every render, and ran every render, as well as\n * when emitter value changes, so you basically are always up to date.\n *\n * - Set an args[] array as the fourth argument - this will stop the automatic capturing\n * and instead update selector only when you change args[] yourself. This is good for when you\n * want explicit control over re-selection and rendering.\n *\n * - With { lazy: true }, the selector only runs when the emitter value changes. If used with\n * args[], you capture the context of the selector based on args[], if not, it's based on the\n * current render.\n *\n * I made this change when we had 16 usages of useEmitterSelector and 100% of them are doing very\n * cheap calculations, so this feels like the right pattern. For the rare case of a heavy selector,\n * you have the option to control it.\n *\n */\nexport const useEmitterSelector = <E extends Emitter<any>, T extends EmitterType<E>, R>(\n emitter: E,\n selector: (value: T) => R,\n options?: {\n disable?: boolean\n lazy?: boolean\n },\n args: any[] = EMPTY_ARRAY\n): R => {\n const [state, setState] = useState<R>(() => selector(emitter.value))\n const disabled = options?.disable\n const getSelector = useGet(selector)\n\n if (options?.lazy !== true) {\n const next = selector(emitter.value)\n if (next !== state) {\n setState(next)\n }\n }\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(() => {\n if (disabled) return\n return emitter.listen((val) => {\n try {\n const selectorFn = args !== EMPTY_ARRAY ? selector : getSelector()\n const next = selectorFn(val)\n setState(next)\n } catch (error) {\n if (error instanceof AbortError) {\n return\n }\n throw error\n }\n })\n }, [disabled, emitter, getSelector, ...args])\n\n return state\n}\n\nexport const useEmittersSelector = <const E extends readonly Emitter<any>[], R>(\n emitters: E,\n selector: (values: { [K in keyof E]: EmitterType<E[K]> }) => R,\n options?: { disable?: boolean; isEqual?: (a: R, b: R) => boolean }\n): R => {\n const getSelector = useGet(selector)\n const disabled = options?.disable\n\n const [state, setState] = useState<R>(() => {\n const values = emitters.map((e) => e.value) as { [K in keyof E]: EmitterType<E[K]> }\n return getSelector()(values)\n })\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(() => {\n if (disabled) {\n return\n }\n\n const handler = () => {\n const values = emitters.map((e) => e.value) as {\n [K in keyof E]: EmitterType<E[K]>\n }\n try {\n const next = getSelector()(values)\n setState((prev) => {\n if (options?.isEqual?.(prev, next)) {\n return prev\n }\n if (dequal(prev, next)) {\n return prev\n }\n return next\n })\n } catch (error) {\n if (error instanceof AbortError) {\n return\n }\n throw error\n }\n }\n\n const disposals = emitters.map((emitter) => emitter.listen(handler))\n\n return () => {\n disposals.forEach((d) => d())\n }\n }, [disabled, getSelector, ...emitters])\n\n return state\n}\n\nexport const createUseEmitter = <E extends Emitter<any>>(\n emitter: E\n): ((cb: (val: EmitterType<E>) => void, args?: any[]) => void) => {\n return (cb: (val: EmitterType<E>) => void, args?: any[]) =>\n useEmitter(emitter, cb, args)\n}\n\nexport const createUseSelector = <E extends Emitter<any>>(\n emitter: E\n): (<R>(\n selector: (value: EmitterType<E>) => R,\n options?: { disable?: boolean; lazy?: boolean },\n args?: any[]\n) => R) => {\n return <R,>(\n selector: (value: EmitterType<E>) => R,\n options?: { disable?: boolean; lazy?: boolean },\n args?: any[]\n ): R => {\n return useEmitterSelector(emitter, selector, options, args)\n }\n}\n\nexport function createContextualEmitter<T>(\n name: string,\n defaultValue: T,\n defaultOptions?: Omit<EmitterOptions<T>, 'name'>\n): readonly [\n () => Emitter<T>,\n (props: PropsWithChildren<{ value?: T; silent?: boolean }>) => JSX.Element,\n] {\n const id = Math.random().toString(36)\n const EmitterContext = createGlobalContext<Emitter<T> | null>(\n `contextual-emitter/${id}`,\n null\n )\n\n const useContextEmitter = () => {\n const emitter = use(EmitterContext)\n if (!emitter) {\n throw new Error('useContextEmitter must be used within an EmitterProvider')\n }\n return emitter\n }\n\n type ProvideEmitterProps = PropsWithChildren<{\n value?: T\n silent?: boolean\n }>\n\n const ProvideEmitter = (props: ProvideEmitterProps) => {\n const { children, value, silent } = props\n const [emitter] = useState(\n () => new Emitter<T>(value ?? defaultValue, { name, silent, ...defaultOptions })\n )\n\n useLayoutEffect(() => {\n if (value !== undefined && value !== emitter.value) {\n emitter.emit(value)\n }\n }, [value, emitter])\n\n return <EmitterContext.Provider value={emitter}>{children}</EmitterContext.Provider>\n }\n\n return [useContextEmitter, ProvideEmitter] as const\n}\n\nconst HMRCache =\n process.env.NODE_ENV === 'development'\n ? new Map<string, { originalDefaultValue: unknown; currentValue: unknown }>()\n : null\n\nfunction setCache(emitter: Emitter<any>, value: unknown) {\n const name = emitter.options?.name\n if (!name) return\n const cache = HMRCache?.get(name)\n if (!cache) return\n cache.currentValue = value\n}\n\nfunction createOrUpdateCache(name: string, defaultValueProp: unknown) {\n const existing = HMRCache?.get(name)\n const defaultValue = dequal(existing?.originalDefaultValue, defaultValueProp)\n ? existing?.currentValue\n : defaultValueProp\n\n if (!existing) {\n HMRCache?.set(name, {\n originalDefaultValue: defaultValueProp,\n currentValue: defaultValue,\n })\n }\n\n return defaultValue\n}\n"
9
- ],
10
- "version": 3
9
+ "import { dequal } from 'dequal'\nimport * as React from 'react'\nimport { use, useLayoutEffect, useState } from 'react'\n\nimport { handleAbortError } from './async/abortable'\nimport { DEBUG_LEVEL, EMPTY_ARRAY } from './constants'\nimport { AbortError } from './error/errors'\nimport { globalValue } from './global/globalValue'\nimport { createGlobalContext } from './react/createGlobalContext'\n\nimport type { JSX, PropsWithChildren } from 'react'\n\n// keeps a reference to the current value easily\n\n// TODO can replace with useEffectEvent\nfunction useGet<A>(\n currentValue: A,\n initialValue?: any,\n forwardToFunction?: boolean\n): () => A {\n const curRef = React.useRef<any>(initialValue ?? currentValue)\n\n useLayoutEffect(() => {\n curRef.current = currentValue\n })\n\n // oxlint-disable-next-line exhaustive-deps\n return React.useCallback(\n forwardToFunction\n ? (...args) => curRef.current?.apply(null, args)\n : () => curRef.current,\n [curRef, forwardToFunction]\n )\n}\n\ntype EmitterOptions<T> = CreateEmitterOpts<T> & {\n name: string\n}\n\ntype CreateEmitterOpts<T> = {\n silent?: boolean\n comparator?: (a: T, b: T) => boolean\n}\n\nexport class Emitter<const T> {\n private disposables = new Set<(cb: any) => void>()\n value: T\n options?: EmitterOptions<T>\n\n constructor(value: T, options?: EmitterOptions<T>) {\n this.value = value\n this.options = options\n }\n\n listen = (disposable: (cb: T) => void): (() => void) => {\n this.disposables.add(disposable)\n return (): void => {\n this.disposables.delete(disposable)\n }\n }\n\n emit = (next: T): void => {\n if (process.env.NODE_ENV === 'development') {\n setCache(this, next)\n }\n const compare = this.options?.comparator\n if (compare) {\n if (this.value && compare(this.value, next)) {\n return\n }\n } else {\n if (this.value === next) {\n if (process.env.NODE_ENV === 'development') {\n console.warn(\n `[emitter] ${this.options?.name} no comparator option but received same value!\n \nthis will emit the same value again, which can be desirable, but we warn to ensure it's not unintended:\n\n- if you want this behavior, add { comparator: isEqualNever }\n- if you want only non-equal values: { comparator: isEqualIdentity }\n- if you want only deeply non-equal values: { comparator: isEqualDeep }`\n )\n }\n }\n }\n this.value = next\n if (DEBUG_LEVEL > 1) {\n if (!this.options?.silent) {\n const name = this.options?.name\n console.groupCollapsed(`📣 ${name}`)\n console.info(next)\n console.trace(`trace >`)\n console.groupEnd()\n }\n }\n this.disposables.forEach((cb) => cb(next))\n }\n\n nextValue = (): Promise<T> => {\n return new Promise<T>((res) => {\n const dispose = this.listen((val) => {\n dispose()\n res(val)\n })\n })\n }\n}\n\n// just createEmitter but ensures it doesn't mess up on HMR\nexport function createGlobalEmitter<T>(\n name: string,\n defaultValue: T,\n options?: CreateEmitterOpts<T>\n): Emitter<T> {\n return globalValue(name, () => createEmitter(name, defaultValue, options))\n}\n\nexport function createEmitter<T>(\n name: string,\n defaultValue: T,\n options?: CreateEmitterOpts<T>\n): Emitter<T> {\n const existing = createOrUpdateCache(name, defaultValue) as T\n return new Emitter<T>(existing || defaultValue, { name, ...options })\n}\n\nexport type EmitterType<E extends Emitter<any>> =\n E extends Emitter<infer Val> ? Val : never\n\nexport const useEmitter = <E extends Emitter<any>>(\n emitter: E,\n cb: (cb: EmitterType<E>) => void,\n args?: any[]\n): void => {\n const getCallback = useGet(cb)\n\n useLayoutEffect(() => {\n return emitter.listen((val) => {\n try {\n getCallback()(val)\n } catch (err) {\n handleAbortError(err)\n }\n })\n }, [emitter, getCallback])\n}\n\n// i think this was useSyncExternalStore but removed for concurrent rendering improvements\n// wondering if we could just always return a deferred value? or default to it?\n\nexport const useEmitterValue = <E extends Emitter<any>>(\n emitter: E,\n options?: { disable?: boolean }\n): EmitterType<E> => {\n const disabled = options?.disable\n\n // use a function initializer to get current emitter value\n const [state, setState] = useState<EmitterType<E>>(() => emitter.value)\n\n useLayoutEffect(() => {\n if (disabled) return\n\n // sync immediately in case emitter changed between render and effect\n if (emitter.value !== state) {\n setState(emitter.value)\n }\n\n return emitter.listen(setState)\n }, [state, disabled, emitter])\n\n return state\n}\n\n/**\n * By default selectors run every render, as well as when emitters update. This is a change\n * from the previous behavior where they only ran when emitters changed value.\n *\n * The reason for this is because emitters capture the variables in scope of the component\n * each render already by using \"useGet\" by default, which makes them easier to use - you\n * don't need to pass an args[] array except for edge cases.\n *\n * Before explaining why we switched to the default, understand the different uses:\n *\n * - Default behavior - selector is updated every render, and ran every render, as well as\n * when emitter value changes, so you basically are always up to date.\n *\n * - Set an args[] array as the fourth argument - this will stop the automatic capturing\n * and instead update selector only when you change args[] yourself. This is good for when you\n * want explicit control over re-selection and rendering.\n *\n * - With { lazy: true }, the selector only runs when the emitter value changes. If used with\n * args[], you capture the context of the selector based on args[], if not, it's based on the\n * current render.\n *\n * I made this change when we had 16 usages of useEmitterSelector and 100% of them are doing very\n * cheap calculations, so this feels like the right pattern. For the rare case of a heavy selector,\n * you have the option to control it.\n *\n */\nexport const useEmitterSelector = <E extends Emitter<any>, T extends EmitterType<E>, R>(\n emitter: E,\n selector: (value: T) => R,\n options?: {\n disable?: boolean\n lazy?: boolean\n },\n args: any[] = EMPTY_ARRAY\n): R => {\n const [state, setState] = useState<R>(() => selector(emitter.value))\n const disabled = options?.disable\n const getSelector = useGet(selector)\n\n if (options?.lazy !== true) {\n const next = selector(emitter.value)\n if (next !== state) {\n setState(next)\n }\n }\n\n useLayoutEffect(() => {\n if (disabled) return\n return emitter.listen((val) => {\n try {\n const selectorFn = args !== EMPTY_ARRAY ? selector : getSelector()\n const next = selectorFn(val)\n setState(next)\n } catch (error) {\n if (error instanceof AbortError) {\n return\n }\n throw error\n }\n })\n // oxlint-disable-next-line exhaustive-deps\n }, [disabled, emitter, getSelector, ...args])\n\n return state\n}\n\nexport const useEmittersSelector = <const E extends readonly Emitter<any>[], R>(\n emitters: E,\n selector: (values: { [K in keyof E]: EmitterType<E[K]> }) => R,\n options?: { disable?: boolean; isEqual?: (a: R, b: R) => boolean }\n): R => {\n const getSelector = useGet(selector)\n const disabled = options?.disable\n\n const [state, setState] = useState<R>(() => {\n const values = emitters.map((e) => e.value) as { [K in keyof E]: EmitterType<E[K]> }\n return getSelector()(values)\n })\n\n useLayoutEffect(() => {\n if (disabled) {\n return\n }\n\n const handler = () => {\n const values = emitters.map((e) => e.value) as {\n [K in keyof E]: EmitterType<E[K]>\n }\n try {\n const next = getSelector()(values)\n setState((prev) => {\n if (options?.isEqual?.(prev, next)) {\n return prev\n }\n if (dequal(prev, next)) {\n return prev\n }\n return next\n })\n } catch (error) {\n if (error instanceof AbortError) {\n return\n }\n throw error\n }\n }\n\n const disposals = emitters.map((emitter) => emitter.listen(handler))\n\n return () => {\n disposals.forEach((d) => d())\n }\n // oxlint-disable-next-line exhaustive-deps\n }, [disabled, getSelector, ...emitters])\n\n return state\n}\n\nexport const createUseEmitter = <E extends Emitter<any>>(\n emitter: E\n): ((cb: (val: EmitterType<E>) => void, args?: any[]) => void) => {\n return (cb: (val: EmitterType<E>) => void, args?: any[]) =>\n useEmitter(emitter, cb, args)\n}\n\nexport const createUseSelector = <E extends Emitter<any>>(\n emitter: E\n): (<R>(\n selector: (value: EmitterType<E>) => R,\n options?: { disable?: boolean; lazy?: boolean },\n args?: any[]\n) => R) => {\n return <R,>(\n selector: (value: EmitterType<E>) => R,\n options?: { disable?: boolean; lazy?: boolean },\n args?: any[]\n ): R => {\n return useEmitterSelector(emitter, selector, options, args)\n }\n}\n\nexport function createContextualEmitter<T>(\n name: string,\n defaultValue: T,\n defaultOptions?: Omit<EmitterOptions<T>, 'name'>\n): readonly [\n () => Emitter<T>,\n (props: PropsWithChildren<{ value?: T; silent?: boolean }>) => JSX.Element,\n] {\n const id = Math.random().toString(36)\n const EmitterContext = createGlobalContext<Emitter<T> | null>(\n `contextual-emitter/${id}`,\n null\n )\n\n const useContextEmitter = () => {\n const emitter = use(EmitterContext)\n if (!emitter) {\n throw new Error('useContextEmitter must be used within an EmitterProvider')\n }\n return emitter\n }\n\n type ProvideEmitterProps = PropsWithChildren<{\n value?: T\n silent?: boolean\n }>\n\n const ProvideEmitter = (props: ProvideEmitterProps) => {\n const { children, value, silent } = props\n const [emitter] = useState(\n () => new Emitter<T>(value ?? defaultValue, { name, silent, ...defaultOptions })\n )\n\n useLayoutEffect(() => {\n if (value !== undefined && value !== emitter.value) {\n emitter.emit(value)\n }\n }, [value, emitter])\n\n return <EmitterContext.Provider value={emitter}>{children}</EmitterContext.Provider>\n }\n\n return [useContextEmitter, ProvideEmitter] as const\n}\n\nconst HMRCache =\n process.env.NODE_ENV === 'development'\n ? new Map<string, { originalDefaultValue: unknown; currentValue: unknown }>()\n : null\n\nfunction setCache(emitter: Emitter<any>, value: unknown) {\n const name = emitter.options?.name\n if (!name) return\n const cache = HMRCache?.get(name)\n if (!cache) return\n cache.currentValue = value\n}\n\nfunction createOrUpdateCache(name: string, defaultValueProp: unknown) {\n const existing = HMRCache?.get(name)\n const defaultValue = dequal(existing?.originalDefaultValue, defaultValueProp)\n ? existing?.currentValue\n : defaultValueProp\n\n if (!existing) {\n HMRCache?.set(name, {\n originalDefaultValue: defaultValueProp,\n currentValue: defaultValue,\n })\n }\n\n return defaultValue\n}\n"
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/ensure/ensure.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "import { EnsureError } from '../error/errors'\n\nexport function ensureExists<T>(\n value: T | undefined | null,\n msg = ''\n): asserts value is T {\n if (value === undefined || value === null) {\n throw new EnsureError(`Invalid nullish value (${value}): ${msg}`)\n }\n}\n\nexport function ensure<T>(\n value: T,\n msg = ''\n): asserts value is Exclude<T, null | undefined | false> {\n if (!value) {\n throw new EnsureError(`ensure() invalid: (${value}): ${msg} ${new Error().stack}`)\n }\n}\n\nexport function ensureError(val: unknown): asserts val is Error {\n if (!(val instanceof Error)) {\n throw val\n }\n}\n\nexport function ensureString(val: unknown, name = '(unnamed)'): asserts val is string {\n if (typeof val !== 'string') {\n throw new EnsureError(`No string ${name}`)\n }\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/ensure/ensureOne.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "// Ensures only one instance ever exists, but replaces it every time it's called\n\nconst Controllers = new WeakMap<symbol, AbortController>()\n\nexport function ensureOne<T>(key: string, factory: (signal: AbortSignal) => T): T {\n const symbolKey = Symbol.for(key)\n const g = globalThis as Record<symbol, unknown>\n\n if (g[symbolKey]) {\n Controllers.get(symbolKey)?.abort()\n }\n\n const controller = new AbortController()\n g[symbolKey] = factory(controller.signal)\n\n return g[symbolKey] as T\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }