All files / react-classic/src utils.ts

100% Statements 31/31
100% Branches 15/15
100% Functions 5/5
100% Lines 31/31

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 971x                                     1x   3x 3x 3x                               1x 12x 12x   12x 20x 20x 6x                         1x 12x 12x   12x 19x 19x 19x   12x 12x               1x 30x 15x 15x               1x 30x 11x 11x     19x 19x  
import { BATCH_MUTATION_KEYS, type BatchMutations, type KeyLike, type State, type StateChange } from '@anchorlib/core';
import type { AnchoredProps, Bindable } from './types.js';
 
/**
 * `cleanProps` is a utility function designed to remove the internal
 * `_state_version` prop from a component's props object.
 *
 * When a component is wrapped by the `observed` HOC, it receives an
 * additional `_state_version` prop. This prop is used internally by the
 * `observed` HOC to force re-renders and should typically not be passed
 * down to native DOM elements or other components that don't expect it.
 *
 * Use this function to filter out `_state_version` before spreading props
 * onto child components or DOM elements.
 *
 * @template T The type of the props object, which must extend `Bindable`.
 * @param props The props object that might contain `_state_version`.
 * @returns A new object containing all original props except `_state_version`.
 */
export function cleanProps<T extends Bindable>(props: T) {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const { _state_version, ...rest } = props as T & AnchoredProps;
  return rest;
}
 
/**
 * Compares two arrays for shallow equality, ignoring the order of elements.
 *
 * This function checks if two arrays contain the same elements by comparing:
 * 1. Their lengths
 * 2. Whether all elements in one array exist in the other array
 *
 * It's used to determine if the dependencies of an observer have changed,
 * where the position of elements doesn't matter but their presence does.
 *
 * @param prev - The previous array of dependencies
 * @param next - The next array of dependencies
 * @returns true if the arrays are different, false if they contain the same elements
 */
export function depsChanged(prev: Set<unknown>, next: unknown[]): Set<unknown> | void {
  const nextSet = new Set(next);
  if (nextSet.size !== prev.size) return nextSet;
 
  for (const item of nextSet) {
    if (!prev.has(item)) return nextSet;
  }
}
 
/**
 * Helper function that extracts specific properties from a reactive state object.
 * It returns a tuple containing:
 * 1. An object with the picked properties and their values
 * 2. An array of the values corresponding to the picked keys
 *
 * @template T - The type of the reactive state object
 * @param {T} state - The reactive state object to pick values from
 * @param {(keyof T)[]} keys - An array of keys to pick from the state object
 * @returns {[T, T[keyof T][]]} A tuple containing the picked object and values array
 */
export function pickValues<T extends State>(state: T, keys: (keyof T)[]): [T, T[keyof T][]] {
  const values = [] as T[keyof T][];
  const result = {} as T;
 
  for (const key of keys) {
    values.push(state[key]);
    result[key] = state[key];
  }
 
  return [result, values] as const;
}
 
/**
 * Checks if a state change event is a mutation of a specific key.
 *
 * @param event - The state change event.
 * @param key - The key to check for mutation.
 */
export function isMutationOf(event: StateChange, key: KeyLike) {
  if (event.type === 'init') return false;
  return mutationKeys(event).includes(key as string);
}
 
/**
 * Extracts the keys that were mutated in a state change event.
 *
 * @param event - The state change event.
 * @returns An array of keys that were mutated.
 */
export function mutationKeys(event: StateChange) {
  if (BATCH_MUTATION_KEYS.has(event.type as BatchMutations)) {
    return Object.keys(event.prev ?? {});
  }
 
  // Only expect one key (single level) for non-batch mutations.
  return event.keys.slice(0, 1);
}