@mmstack/primitives 21.8.2 → 21.8.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.
@@ -1985,6 +1985,29 @@ function indexArray(source, map, opt = {}) {
1985
1985
  */
1986
1986
  const mapArray = indexArray;
1987
1987
 
1988
+ function effectiveKeys(arr, getKey, report) {
1989
+ const out = new Array(arr.length);
1990
+ const counts = new Map();
1991
+ const firstOrder = [];
1992
+ let duped = null;
1993
+ for (let i = 0; i < arr.length; i++) {
1994
+ const base = String(getKey(arr[i]));
1995
+ const seen = counts.get(base) ?? 0;
1996
+ if (seen === 0) {
1997
+ firstOrder.push(base);
1998
+ }
1999
+ else {
2000
+ (duped ??= new Set()).add(base);
2001
+ }
2002
+ out[i] = seen === 0 ? base : base + '#' + seen;
2003
+ counts.set(base, seen + 1);
2004
+ }
2005
+ if (report && duped) {
2006
+ const bases = firstOrder.filter((b) => duped.has(b));
2007
+ untracked(() => report(bases));
2008
+ }
2009
+ return out;
2010
+ }
1988
2011
  /**
1989
2012
  * Reactively maps items from a source array to a new array by value (identity).
1990
2013
  *
@@ -1998,11 +2021,15 @@ const mapArray = indexArray;
1998
2021
  * when the list is reordered.
1999
2022
  *
2000
2023
  * @param source A `Signal<T[]>` or a function returning `T[]`.
2001
- * @param mapFn The mapping function. Receives the item and its index as a Signal.
2024
+ * @param mapFn The mapping function. Receives the item, its index as a Signal, and the
2025
+ * entry's key (the effective ordinal key when `duplicateKeys` is enabled, otherwise
2026
+ * the raw key) — stable for the lifetime of the mapped entry.
2002
2027
  * @param options Optional configuration:
2003
2028
  * - `onDestroy`: A callback invoked when a mapped item is removed from the array.
2004
2029
  * - `key`: A custom key extractor for identity matching (e.g. `(item) => item.id`)
2005
2030
  * when item references change but conceptual identity is preserved.
2031
+ * - `duplicateKeys`: Opt-in policy for handling duplicate keys. When omitted,
2032
+ * behavior is unchanged (duplicates collapse). See {@link DuplicateKeyPolicy}.
2006
2033
  * @returns A `Signal<U[]>` containing the mapped array.
2007
2034
  *
2008
2035
  * @example
@@ -2037,9 +2064,11 @@ function keyArray(source, mapFn, options = {}) {
2037
2064
  const tempIndexes = [];
2038
2065
  const newIndicesNext = [];
2039
2066
  const newIndexesCache = new Array();
2067
+ const dup = options.duplicateKeys;
2040
2068
  return computed(() => {
2041
2069
  const newItems = sourceSignal() || [];
2042
2070
  return untracked(() => {
2071
+ const newKeys = dup ? effectiveKeys(newItems, getKey, dup.report) : null;
2043
2072
  let i;
2044
2073
  let j;
2045
2074
  const newLen = newItems.length;
@@ -2071,7 +2100,7 @@ function keyArray(source, mapFn, options = {}) {
2071
2100
  items[j] = item;
2072
2101
  const indexSignal = signal(j, ...(ngDevMode ? [{ debugName: "indexSignal" }] : /* istanbul ignore next */ []));
2073
2102
  newIndexes[j] = indexSignal;
2074
- newMapped[j] = mapFn(item, indexSignal);
2103
+ newMapped[j] = mapFn(item, indexSignal, newKeys ? newKeys[j] : getKey(item));
2075
2104
  }
2076
2105
  }
2077
2106
  else {
@@ -2079,26 +2108,29 @@ function keyArray(source, mapFn, options = {}) {
2079
2108
  temp.length = 0;
2080
2109
  tempIndexes.length = 0;
2081
2110
  newIndicesNext.length = 0;
2082
- for (start = 0, end = Math.min(len, newLen); start < end && getKey(items[start]) === getKey(newItems[start]); start++) {
2111
+ const oldKeys = dup ? effectiveKeys(items, getKey) : null;
2112
+ const kNew = newKeys
2113
+ ? (idx) => newKeys[idx]
2114
+ : (idx) => getKey(newItems[idx]);
2115
+ const kOld = oldKeys
2116
+ ? (idx) => oldKeys[idx]
2117
+ : (idx) => getKey(items[idx]);
2118
+ for (start = 0, end = Math.min(len, newLen); start < end && kOld(start) === kNew(start); start++) {
2083
2119
  newMapped[start] = mapped[start];
2084
2120
  newIndexes[start] = indexes[start];
2085
2121
  }
2086
- for (end = len - 1, newEnd = newLen - 1; end >= start &&
2087
- newEnd >= start &&
2088
- getKey(items[end]) === getKey(newItems[newEnd]); end--, newEnd--) {
2122
+ for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && kOld(end) === kNew(newEnd); end--, newEnd--) {
2089
2123
  temp[newEnd] = mapped[end];
2090
2124
  tempIndexes[newEnd] = indexes[end];
2091
2125
  }
2092
2126
  for (j = newEnd; j >= start; j--) {
2093
- item = newItems[j];
2094
- key = getKey(item);
2127
+ key = kNew(j);
2095
2128
  i = newIndices.get(key) ?? -1;
2096
2129
  newIndicesNext[j] = i === undefined ? -1 : i;
2097
2130
  newIndices.set(key, j);
2098
2131
  }
2099
2132
  for (i = start; i <= end; i++) {
2100
- item = items[i];
2101
- key = getKey(item);
2133
+ key = kOld(i);
2102
2134
  j = newIndices.get(key) ?? -1;
2103
2135
  if (j !== undefined && j !== -1) {
2104
2136
  temp[j] = mapped[i];
@@ -2121,7 +2153,7 @@ function keyArray(source, mapFn, options = {}) {
2121
2153
  else {
2122
2154
  const indexSignal = signal(j, ...(ngDevMode ? [{ debugName: "indexSignal" }] : /* istanbul ignore next */ []));
2123
2155
  newIndexes[j] = indexSignal;
2124
- newMapped[j] = mapFn(newItems[j], indexSignal);
2156
+ newMapped[j] = mapFn(newItems[j], indexSignal, kNew(j));
2125
2157
  }
2126
2158
  }
2127
2159
  items.length = newLen;