All files / state controllable.ts

100% Statements 67/67
100% Branches 36/36
100% Functions 6/6
100% Lines 56/56

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                                                    353x 353x 353x 353x     353x 353x 6820x 353x 353x 87x 86x 86x 86x 53x   53x 87x 53x   353x 353x 353x 466x 463x 2x 2x   461x 461x 461x       461x 461x 463x 463x 463x 462x 364x 462x 462x 462x   462x   461x 461x 409x 409x 409x     462x   460x   1x 1x 1x   461x 461x     353x         353x 352x 352x 352x 352x        
import type { ChangeDetails, ControllableValueOptions, Unsubscribe } from '../core/types';
 
/** Shared controlled/uncontrolled state cell with atomic, re-entrant-safe updates. @internal */
export interface ControllableValue<TValue, TReason extends string> {
  /** Reads the consumer-owned value in controlled mode or the internal value otherwise. */
  get(): TValue;
  /** Requests a value change and returns whether the observable value changed. */
  set(value: TValue, details: ChangeDetails<TReason>): boolean;
  /** Releases an optional external subscription. */
  destroy(): void;
  /** Reports whether the value is owned by the consumer. */
  readonly controlled: boolean;
}
 
/**
 * Creates the single state ownership primitive used by every component controller.
 *
 * @param options - Value reader, default, callback, and optional external subscription.
 * @param onExternalChange - Invalidates the owning controller when external state changes.
 * @returns An idempotently disposable value cell.
 * @internal
 */
export function createControllableValue<TValue, TReason extends string>(
  options: ControllableValueOptions<TValue, TReason>,
  onExternalChange: (value: TValue, details?: ChangeDetails<TReason>) => void,
): ControllableValue<TValue, TReason> {
  let internal = options.defaultValue;
  let destroyed = false;
  let notifying = false;
  let requesting = false;
  let queued: { value: TValue; details: ChangeDetails<TReason> } | undefined;
  let pendingRequest: { value: TValue; details: ChangeDetails<TReason> } | undefined;
  const externalReader = options.getValue;
  const controlled = externalReader !== undefined;
  const get = (): TValue => (externalReader ? externalReader() : internal);
  let lastObserved = get();
  const notifyExternal = (): void => {
    if (destroyed) return;
    const value = get();
    if (pendingRequest && !Object.is(pendingRequest.value, value)) pendingRequest = undefined;
    if (Object.is(lastObserved, value)) return;
    lastObserved = value;
    const details =
      pendingRequest && Object.is(pendingRequest.value, value) ? pendingRequest.details : undefined;
    if (details) pendingRequest = undefined;
    if (!requesting) onExternalChange(value, details);
  };
  let externalUnsubscribe: Unsubscribe = () => undefined;
  externalUnsubscribe = options.subscribeValue?.(notifyExternal) ?? externalUnsubscribe;
  const commit = (value: TValue, details: ChangeDetails<TReason>): boolean => {
    if (destroyed || Object.is(get(), value)) return false;
    if (notifying) {
      queued = { value, details };
      return true;
    }
    notifying = true;
    try {
      let next: { value: TValue; details: ChangeDetails<TReason> } | undefined = {
        value,
        details,
      };
      let changed = false;
      while (next) {
        queued = undefined;
        const before = get();
        if (!Object.is(before, next.value)) {
          if (controlled) pendingRequest = next;
          else internal = next.value;
          requesting = true;
          try {
            options.onValueChange?.(next.value, next.details);
          } finally {
            requesting = false;
          }
          const observed = get();
          if (!Object.is(before, observed)) {
            lastObserved = observed;
            if (controlled) pendingRequest = undefined;
            changed = true;
          }
        }
        next = queued;
      }
      return changed;
    } catch (error) {
      queued = undefined;
      pendingRequest = undefined;
      throw error;
    } finally {
      notifying = false;
      requesting = false;
    }
  };
  return {
    get,
    set: commit,
    controlled,
    destroy() {
      if (destroyed) return;
      destroyed = true;
      queued = undefined;
      pendingRequest = undefined;
      externalUnsubscribe();
    },
  };
}