@mmstack/primitives 22.8.2 → 22.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.
@@ -2005,6 +2005,29 @@ function indexArray(source, map, opt = {}) {
2005
2005
  */
2006
2006
  const mapArray = indexArray;
2007
2007
 
2008
+ function effectiveKeys(arr, getKey, report) {
2009
+ const out = new Array(arr.length);
2010
+ const counts = new Map();
2011
+ const firstOrder = [];
2012
+ let duped = null;
2013
+ for (let i = 0; i < arr.length; i++) {
2014
+ const base = String(getKey(arr[i]));
2015
+ const seen = counts.get(base) ?? 0;
2016
+ if (seen === 0) {
2017
+ firstOrder.push(base);
2018
+ }
2019
+ else {
2020
+ (duped ??= new Set()).add(base);
2021
+ }
2022
+ out[i] = seen === 0 ? base : base + '#' + seen;
2023
+ counts.set(base, seen + 1);
2024
+ }
2025
+ if (report && duped) {
2026
+ const bases = firstOrder.filter((b) => duped.has(b));
2027
+ untracked(() => report(bases));
2028
+ }
2029
+ return out;
2030
+ }
2008
2031
  /**
2009
2032
  * Reactively maps items from a source array to a new array by value (identity).
2010
2033
  *
@@ -2018,11 +2041,15 @@ const mapArray = indexArray;
2018
2041
  * when the list is reordered.
2019
2042
  *
2020
2043
  * @param source A `Signal<T[]>` or a function returning `T[]`.
2021
- * @param mapFn The mapping function. Receives the item and its index as a Signal.
2044
+ * @param mapFn The mapping function. Receives the item, its index as a Signal, and the
2045
+ * entry's key (the effective ordinal key when `duplicateKeys` is enabled, otherwise
2046
+ * the raw key) — stable for the lifetime of the mapped entry.
2022
2047
  * @param options Optional configuration:
2023
2048
  * - `onDestroy`: A callback invoked when a mapped item is removed from the array.
2024
2049
  * - `key`: A custom key extractor for identity matching (e.g. `(item) => item.id`)
2025
2050
  * when item references change but conceptual identity is preserved.
2051
+ * - `duplicateKeys`: Opt-in policy for handling duplicate keys. When omitted,
2052
+ * behavior is unchanged (duplicates collapse). See {@link DuplicateKeyPolicy}.
2026
2053
  * @returns A `Signal<U[]>` containing the mapped array.
2027
2054
  *
2028
2055
  * @example
@@ -2057,9 +2084,11 @@ function keyArray(source, mapFn, options = {}) {
2057
2084
  const tempIndexes = [];
2058
2085
  const newIndicesNext = [];
2059
2086
  const newIndexesCache = new Array();
2087
+ const dup = options.duplicateKeys;
2060
2088
  return computed(() => {
2061
2089
  const newItems = sourceSignal() || [];
2062
2090
  return untracked(() => {
2091
+ const newKeys = dup ? effectiveKeys(newItems, getKey, dup.report) : null;
2063
2092
  let i;
2064
2093
  let j;
2065
2094
  const newLen = newItems.length;
@@ -2092,7 +2121,7 @@ function keyArray(source, mapFn, options = {}) {
2092
2121
  const indexSignal = signal(j, /* @ts-ignore */
2093
2122
  ...(ngDevMode ? [{ debugName: "indexSignal" }] : /* istanbul ignore next */ []));
2094
2123
  newIndexes[j] = indexSignal;
2095
- newMapped[j] = mapFn(item, indexSignal);
2124
+ newMapped[j] = mapFn(item, indexSignal, newKeys ? newKeys[j] : getKey(item));
2096
2125
  }
2097
2126
  }
2098
2127
  else {
@@ -2100,26 +2129,29 @@ function keyArray(source, mapFn, options = {}) {
2100
2129
  temp.length = 0;
2101
2130
  tempIndexes.length = 0;
2102
2131
  newIndicesNext.length = 0;
2103
- for (start = 0, end = Math.min(len, newLen); start < end && getKey(items[start]) === getKey(newItems[start]); start++) {
2132
+ const oldKeys = dup ? effectiveKeys(items, getKey) : null;
2133
+ const kNew = newKeys
2134
+ ? (idx) => newKeys[idx]
2135
+ : (idx) => getKey(newItems[idx]);
2136
+ const kOld = oldKeys
2137
+ ? (idx) => oldKeys[idx]
2138
+ : (idx) => getKey(items[idx]);
2139
+ for (start = 0, end = Math.min(len, newLen); start < end && kOld(start) === kNew(start); start++) {
2104
2140
  newMapped[start] = mapped[start];
2105
2141
  newIndexes[start] = indexes[start];
2106
2142
  }
2107
- for (end = len - 1, newEnd = newLen - 1; end >= start &&
2108
- newEnd >= start &&
2109
- getKey(items[end]) === getKey(newItems[newEnd]); end--, newEnd--) {
2143
+ for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && kOld(end) === kNew(newEnd); end--, newEnd--) {
2110
2144
  temp[newEnd] = mapped[end];
2111
2145
  tempIndexes[newEnd] = indexes[end];
2112
2146
  }
2113
2147
  for (j = newEnd; j >= start; j--) {
2114
- item = newItems[j];
2115
- key = getKey(item);
2148
+ key = kNew(j);
2116
2149
  i = newIndices.get(key) ?? -1;
2117
2150
  newIndicesNext[j] = i === undefined ? -1 : i;
2118
2151
  newIndices.set(key, j);
2119
2152
  }
2120
2153
  for (i = start; i <= end; i++) {
2121
- item = items[i];
2122
- key = getKey(item);
2154
+ key = kOld(i);
2123
2155
  j = newIndices.get(key) ?? -1;
2124
2156
  if (j !== undefined && j !== -1) {
2125
2157
  temp[j] = mapped[i];
@@ -2143,7 +2175,7 @@ function keyArray(source, mapFn, options = {}) {
2143
2175
  const indexSignal = signal(j, /* @ts-ignore */
2144
2176
  ...(ngDevMode ? [{ debugName: "indexSignal" }] : /* istanbul ignore next */ []));
2145
2177
  newIndexes[j] = indexSignal;
2146
- newMapped[j] = mapFn(newItems[j], indexSignal);
2178
+ newMapped[j] = mapFn(newItems[j], indexSignal, kNew(j));
2147
2179
  }
2148
2180
  }
2149
2181
  items.length = newLen;