diff --git a/packages/core/package.json b/packages/core/package.json index c43b6043..02a058f3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -50,6 +50,10 @@ "types": "./dist/index.react.d.ts", "default": "./dist/index.react.js" }, + "./react-native": { + "types": "./dist/index.react-native.d.ts", + "default": "./dist/index.react-native.js" + }, "./vue": { "types": "./dist/index.vue.d.ts", "default": "./dist/index.vue.js" diff --git a/packages/core/src/framework/index.react-native.ts b/packages/core/src/framework/index.react-native.ts new file mode 100644 index 00000000..a4bd69b1 --- /dev/null +++ b/packages/core/src/framework/index.react-native.ts @@ -0,0 +1,131 @@ +import type { Signal } from '../types/signal/index.ts'; +import type { Framework } from './index.ts'; + +/** + * The current framework being used. + */ +export const framework: Framework = 'react-native'; + +/** + * Creates a unique identifier string. + * + * @returns The unique identifier. + */ +// @__NO_SIDE_EFFECTS__ +export function createId(): string { + return Math.random().toString(36).slice(2); +} + +/** + * Listener tuple. + * + * Hint: The first element is the execute function, which notifies the listener + * about updates. The second element is the subscription set, which keeps track + * of where the listener is subscribed and is used to clean up subscriptions if + * they are no longer needed. + */ +export type Listener = [() => void, Set>]; + +/** + * The current listener being tracked. + */ +let listener: Listener | undefined; + +/** + * Sets the current listener being tracked. + * + * @param newListener The new listener to set. + */ +export function setListener(newListener: Listener | undefined): void { + listener = newListener; +} + +/** + * Subscribers collected during a batch. + */ +let batchSubscribers: Set | undefined; + +/** + * Creates a reactive signal with an initial value. + * + * @param value The initial value. + * + * @returns The created signal. + */ +// @__NO_SIDE_EFFECTS__ +export function createSignal(value: T): Signal { + const subscribers = new Set(); + return { + get value() { + if (listener) { + subscribers.add(listener); + listener[1].add(subscribers); + } + return value; + }, + set value(newValue: T) { + if (newValue !== value) { + value = newValue; + const localSubscribers: Listener[] = []; + for (const subscriber of subscribers) { + if (batchSubscribers) { + batchSubscribers.add(subscriber); + } else { + localSubscribers.push(subscriber); + } + subscriber[1].delete(subscribers); + } + subscribers.clear(); + for (const subscriber of localSubscribers) { + subscriber[0](); + } + } + }, + }; +} + +// Global batch depth counter +let batchDepth = 0; + +/** + * Batches multiple signal updates into a single update cycle. + * + * @param fn The function to execute in batch. + * + * @returns The return value of the function. + */ +export function batch(fn: () => T): T { + batchDepth++; + if (!batchSubscribers) { + batchSubscribers = new Set(); + } + try { + return fn(); + } finally { + batchDepth--; + if (batchDepth === 0) { + const subscribers = batchSubscribers; + batchSubscribers = undefined; + for (const subscriber of subscribers) { + subscriber[0](); + } + } + } +} + +/** + * Executes a function without tracking reactive dependencies. + * + * @param fn The function to execute without tracking. + * + * @returns The return value of the function. + */ +export function untrack(fn: () => T): T { + const prev = listener; + listener = undefined; + try { + return fn(); + } finally { + listener = prev; + } +} diff --git a/packages/core/src/framework/index.ts b/packages/core/src/framework/index.ts index 05a119c1..62a7291d 100644 --- a/packages/core/src/framework/index.ts +++ b/packages/core/src/framework/index.ts @@ -10,6 +10,7 @@ export type Framework = | 'preact' | 'qwik' | 'react' + | 'react-native' | 'solid' | 'svelte' | 'vue'; diff --git a/packages/core/tsdown.config.ts b/packages/core/tsdown.config.ts index 113b6da9..d34f7839 100644 --- a/packages/core/tsdown.config.ts +++ b/packages/core/tsdown.config.ts @@ -4,7 +4,15 @@ import { join } from 'node:path'; import type { RolldownPluginOption } from 'rolldown'; import { defineConfig, type UserConfig, type UserConfigFn } from 'tsdown'; -type Framework = 'angular' | 'preact' | 'qwik' | 'react' | 'solid' | 'svelte' | 'vue'; +type Framework = + | 'angular' + | 'preact' + | 'qwik' + | 'react' + | 'react-native' + | 'solid' + | 'svelte' + | 'vue'; /** * Rolldown plugin to rewrite framework-specific imports. @@ -120,6 +128,7 @@ const config: (UserConfig | UserConfigFn)[] = [ defineFrameworkConfig('preact'), defineFrameworkConfig('qwik'), defineFrameworkConfig('react'), + defineFrameworkConfig('react-native'), defineFrameworkConfig('solid'), defineFrameworkConfig('svelte'), defineFrameworkConfig('vue'), diff --git a/packages/methods/package.json b/packages/methods/package.json index 22bf7c54..0219aa59 100644 --- a/packages/methods/package.json +++ b/packages/methods/package.json @@ -32,6 +32,10 @@ "types": "./dist/index.react.d.ts", "default": "./dist/index.react.js" }, + "./react-native": { + "types": "./dist/index.react-native.d.ts", + "default": "./dist/index.react-native.js" + }, "./solid": { "types": "./dist/index.solid.d.ts", "default": "./dist/index.solid.js" diff --git a/packages/methods/tsdown.config.ts b/packages/methods/tsdown.config.ts index 864907a3..41563405 100644 --- a/packages/methods/tsdown.config.ts +++ b/packages/methods/tsdown.config.ts @@ -4,7 +4,14 @@ import { join } from 'node:path'; import type { RolldownPluginOption } from 'rolldown'; import { defineConfig, type UserConfig, type UserConfigFn } from 'tsdown'; -type Framework = 'preact' | 'qwik' | 'react' | 'solid' | 'svelte' | 'vue'; +type Framework = + | 'preact' + | 'qwik' + | 'react' + | 'react-native' + | 'solid' + | 'svelte' + | 'vue'; /** * Rolldown plugin to rewrite framework-specific imports. @@ -116,6 +123,7 @@ const config: (UserConfig | UserConfigFn)[] = [ defineFrameworkConfig('preact'), defineFrameworkConfig('qwik'), defineFrameworkConfig('react'), + defineFrameworkConfig('react-native'), defineFrameworkConfig('solid'), defineFrameworkConfig('svelte'), defineFrameworkConfig('vue'),