All files / react/src binding.ts

100% Statements 78/78
100% Branches 27/27
100% Functions 12/12
100% Lines 78/78

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 2451x                 1x           1x 1x 1x   1x 1x               1x 3x 3x 3x 1x                                                                             1x 3x 3x             1x             1x               1x 19x 19x                 1x 1x 1x 1x   1x 1x 1x                 1x 8x 8x 8x 8x 1x                   1x 45x 45x                   1x 5x 5x                                                           1x 8x 3x 3x             1x   1x                             1x 9x 9x 9x 9x 9x 9x 9x 2x 9x 9x 9x 9x   9x 7x 7x 9x   9x 9x 4x 4x 9x 3x 3x 9x 1x 1x 1x 1x 9x   9x 9x 9x  
import { createObserver, isMutableRef, mutable, type MutableRef, type StateOptions } from '@anchorlib/core';
import type { Bindable, Linked } from './types.js';
 
/**
 * A reference that links to a computed value through a getter function.
 * Used for creating one-way data flow from derived values.
 *
 * @template T - The value type
 */
export class LinkingRef<T> {
  /**
   * Reads the current value of the linked reference.
   *
   * @returns The current value
   */
  get value(): T | undefined {
    if (typeof this.read === 'function') return this.read() as T;
    if (typeof this.read === 'object' && this.read !== null) return this.read[this.key as never] as T;
 
    return undefined;
  }
 
  /**
   * Creates a new linking reference.
   *
   * @param read - A function that returns the current value, or an object with a property
   * @param key - The key to read from the object (optional)
   */
  constructor(
    private read: () => T | Record<string, unknown>,
    private key: keyof T = 'value' as keyof T
  ) { }
}
 
/**
 * Creates a linking reference from a function.
 *
 * @template T - The function type
 * @param source - The function to link to
 * @returns The return type of the function
 */
export function link<T extends (...args: unknown[]) => unknown>(source: T): Linked<ReturnType<T>>;
 
/**
 * Creates a linking reference from a MutableRef.
 *
 * @template T - The MutableRef type
 * @param source - The MutableRef to link to
 * @returns The value type of the MutableRef
 */
export function link<T extends MutableRef<unknown>>(source: T): Linked<T['value']>;
 
/**
 * Creates a linking reference from an object property.
 *
 * @template T - The object type
 * @template K - The key type
 * @param source - The object to link to
 * @param key - The property key to link to
 * @returns The value type at the specified key
 */
export function link<T, K extends keyof T>(source: T, key: K): Linked<T[K]>;
 
/**
 * Creates a linking reference that computes values reactively.
 *
 * @template T - The value type
 * @param reader - A function that returns the current value, or an object with a property
 * @param key - The key to read from the object (optional)
 * @returns A linked value of type T
 */
export function link<T>(reader: () => T | Record<string, unknown>, key?: keyof T): T {
  return new LinkingRef(reader, key) as never as T;
}
 
/**
 * Alias for the link function.
 *
 * @see {@link link}
 */
export const $link = link;
 
/**
 * Alias for the link function.
 *
 * @see {@link link}
 */
export const $use = link;
 
/**
 * Type guard to check if a value is a LinkingRef.
 *
 * @param value - The value to check
 * @returns True if the value is a LinkingRef, false otherwise
 */
export function isLinkingRef(value: unknown): value is LinkingRef<unknown> {
  return value instanceof LinkingRef;
}
 
/**
 * A reference that binds a value to a property of an object or another reference.
 * Used for creating two-way data binding between components.
 *
 * @template S - The source object or reference type
 * @template V - The value type
 */
export class BindingRef<S, V> {
  public get value(): V {
    return (this.source as Record<string, unknown>)[this.key as never] as V;
  }
 
  public set value(value: V) {
    (this.source as Record<string, unknown>)[this.key as never] = value;
  }
 
  /**
   * Creates a new binding reference.
   *
   * @param source - The source object or reference to bind to
   * @param key - The property key to bind to (default: 'value')
   * @param type - The value type (optional)
   */
  constructor(
    public source: S,
    public key: keyof S | string = 'value',
    public type?: V
  ) { }
}
 
/**
 * Type guard to check if a value is a BindingRef.
 *
 * @template S - The source object or reference type
 * @template V - The value type
 * @param value - The value to check
 * @returns True if the value is a BindingRef, false otherwise
 */
export function isBinding<S, V>(value: unknown): value is BindingRef<S, V> {
  return value instanceof BindingRef;
}
 
/**
 * Type guard to check if a value is a BindableRef.
 *
 * @template S - The source value type
 * @template T - The target object or reference type
 * @param value - The value to check
 * @returns True if the value is a BindableRef, false otherwise
 */
export function isBindable<T>(value: unknown): value is MutableRef<T> {
  return BINDABLE_REGISTRY.has(value as MutableRef<unknown>);
}
 
/**
 * Creates two-way data binding to MutableRef source.
 *
 * @template T - The MutableRef type
 * @param source - The MutableRef source to bind to
 * @returns The value type of the MutableRef
 */
export function bind<T extends MutableRef<unknown>>(source: T): Bindable<T['value']>;
 
/**
 * Creates two-way data binding to object property.
 *
 * @template T - The Record type
 * @template K - The key type
 * @param source - The Record source to bind to
 * @param key - The property key to bind to
 * @returns The value type at the specified key
 */
export function bind<T, K extends keyof T>(source: T, key: K): Bindable<T[K]>;
 
/**
 * Creates a binding reference for two-way data binding.
 *
 * @template T - The source type
 * @param source - The source object or reference to bind to
 * @param key - The property key to bind to (optional)
 * @returns A BindingRef instance
 */
export function bind<T>(source: T, key?: keyof T) {
  if (isMutableRef(source)) return source;
  return new BindingRef(source, key);
}
 
/**
 * Alias for the bind function.
 *
 * @see {@link bind}
 */
export const $bind = bind;
 
const BINDABLE_REGISTRY = new WeakSet<MutableRef<unknown>>();
 
/**
 * @deprecated Will be removed in the next major release.
 * Creates a bindable reference that synchronizes values between sources.
 *
 * @template T - The value type
 * @template S - The target object or reference type
 * @template K - The key type of the target
 * @param init - The initial value
 * @param source - The target object or reference to bind to
 * @param key - The property key to bind to (optional)
 * @param options - Optional state options for the underlying mutable reference
 * @returns A MutableRef that synchronizes with the target
 */
export function bindable<T, S, K extends keyof S>(
  init: T,
  source: S,
  key?: S[K] extends T ? K : never,
  options?: StateOptions
): MutableRef<T> {
  const state = mutable(init as never, options) as MutableRef<T>;
  const observer = createObserver(() => {
    state.value = key ? (source[key] as T) : (source as MutableRef<T>).value;
  });
  observer.name = `Binding(${(key as string) ?? 'value'})`;
  observer.run(() => {
    const value = key ? (source[key] as T) : (source as MutableRef<T>).value;
 
    if (typeof value !== 'undefined') {
      state.value = value;
    }
  });
 
  const ref = {
    get value() {
      return state.value;
    },
    set value(value) {
      (source as MutableRef<T>)[(key ?? 'value') as 'value'] = value as T;
    },
    destroy() {
      observer.destroy();
      state.destroy();
      BINDABLE_REGISTRY.delete(ref);
    },
  } as MutableRef<T>;
 
  BINDABLE_REGISTRY.add(ref);
  return ref;
}