chat-layout 1.1.1 → 1.1.2

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.
package/README.md CHANGED
@@ -108,6 +108,7 @@ Notes:
108
108
  ## Migration notes
109
109
 
110
110
  - Use `memoRenderItemBy(keyOf, renderItem)` when list items are primitives.
111
+ - `memoRenderItemBy()` now uses a bounded LRU cache by default; pass `{ maxEntries: Infinity }` to keep the old unbounded behavior explicitly.
111
112
  - `FlexItem` exposes `grow`, `shrink`, and `alignSelf`; `basis` is no longer public.
112
113
  - `MultilineText` now uses `align` / `physicalAlign` instead of `alignment`.
113
114
  - `ListState.position` uses `undefined` for the renderer default anchor.
package/index.d.mts CHANGED
@@ -518,7 +518,9 @@ declare function memoRenderItem<C extends CanvasRenderingContext2D, T extends ob
518
518
  /**
519
519
  * Memoizes `renderItem` by a caller-provided cache key.
520
520
  */
521
- declare function memoRenderItemBy<C extends CanvasRenderingContext2D, T, K>(keyOf: (item: T) => K, renderItem: (item: T) => Node<C>): ((item: T) => Node<C>) & {
521
+ declare function memoRenderItemBy<C extends CanvasRenderingContext2D, T, K>(keyOf: (item: T) => K, renderItem: (item: T) => Node<C>, options?: {
522
+ maxEntries?: number;
523
+ }): ((item: T) => Node<C>) & {
522
524
  reset: (item: T) => boolean;
523
525
  resetKey: (key: K) => boolean;
524
526
  };
package/index.mjs CHANGED
@@ -818,17 +818,15 @@ var Place = class extends Wrapper {
818
818
  };
819
819
  const INTRINSIC_MAX_WIDTH = Number.POSITIVE_INFINITY;
820
820
  const MIN_CONTENT_WIDTH_EPSILON = .001;
821
- const fontShiftCache = /* @__PURE__ */ new Map();
822
- const ellipsisWidthCache = /* @__PURE__ */ new Map();
823
821
  let sharedGraphemeSegmenter;
824
- function readLruValue(cache, key) {
822
+ function readLruValue$1(cache, key) {
825
823
  const cached = cache.get(key);
826
824
  if (cached == null) return;
827
825
  cache.delete(key);
828
826
  cache.set(key, cached);
829
827
  return cached;
830
828
  }
831
- function writeLruValue(cache, key, value, capacity) {
829
+ function writeLruValue$1(cache, key, value, capacity) {
832
830
  if (cache.has(key)) cache.delete(key);
833
831
  else if (cache.size >= capacity) {
834
832
  const firstKey = cache.keys().next().value;
@@ -838,17 +836,11 @@ function writeLruValue(cache, key, value, capacity) {
838
836
  return value;
839
837
  }
840
838
  function measureFontShift(ctx) {
841
- const font = ctx.graphics.font;
842
- const cached = readLruValue(fontShiftCache, font);
843
- if (cached != null) return cached;
844
839
  const { fontBoundingBoxAscent: ascent = 0, fontBoundingBoxDescent: descent = 0 } = ctx.graphics.measureText("M");
845
- return writeLruValue(fontShiftCache, font, ascent - descent, 64);
840
+ return ascent - descent;
846
841
  }
847
842
  function measureEllipsisWidth(ctx) {
848
- const font = ctx.graphics.font;
849
- const cached = readLruValue(ellipsisWidthCache, font);
850
- if (cached != null) return cached;
851
- return writeLruValue(ellipsisWidthCache, font, ctx.graphics.measureText("…").width, 64);
843
+ return ctx.graphics.measureText("…").width;
852
844
  }
853
845
  function getGraphemeSegmenter() {
854
846
  if (sharedGraphemeSegmenter !== void 0) return sharedGraphemeSegmenter;
@@ -936,15 +928,14 @@ const LINE_START_CURSOR$1 = {
936
928
  graphemeIndex: 0
937
929
  };
938
930
  const preparedTextCache = /* @__PURE__ */ new Map();
939
- const preparedUnitCache = /* @__PURE__ */ new WeakMap();
940
931
  function getPreparedTextCacheKey(text, font, whiteSpace, wordBreak) {
941
932
  return `${font}\u0000${whiteSpace}\u0000${wordBreak}\u0000${text}`;
942
933
  }
943
934
  function readPreparedText(text, font, whiteSpace, wordBreak) {
944
935
  const key = getPreparedTextCacheKey(text, font, whiteSpace, wordBreak);
945
- const cached = readLruValue(preparedTextCache, key);
936
+ const cached = readLruValue$1(preparedTextCache, key);
946
937
  if (cached != null) return cached;
947
- return writeLruValue(preparedTextCache, key, prepareWithSegments(text, font, {
938
+ return writeLruValue$1(preparedTextCache, key, prepareWithSegments(text, font, {
948
939
  whiteSpace,
949
940
  wordBreak
950
941
  }), 512);
@@ -973,8 +964,6 @@ function measurePreparedMinContentWidth(prepared, overflowWrap = "break-word") {
973
964
  return maxWidth > 0 ? maxWidth : maxAnyWidth;
974
965
  }
975
966
  function getPreparedUnits(prepared) {
976
- const cached = preparedUnitCache.get(prepared);
977
- if (cached != null) return cached;
978
967
  const units = [];
979
968
  for (let index = 0; index < prepared.segments.length; index += 1) {
980
969
  const segment = prepared.segments[index] ?? "";
@@ -995,7 +984,6 @@ function getPreparedUnits(prepared) {
995
984
  width: segmentWidth
996
985
  });
997
986
  }
998
- preparedUnitCache.set(prepared, units);
999
987
  return units;
1000
988
  }
1001
989
  function joinUnitText(units, start, end) {
@@ -1248,7 +1236,7 @@ function getRichPreparedCacheKey(spans, defaultFont) {
1248
1236
  }
1249
1237
  function readRichPrepared(spans, defaultFont) {
1250
1238
  const key = getRichPreparedCacheKey(spans, defaultFont);
1251
- const cached = readLruValue(richPreparedCache, key);
1239
+ const cached = readLruValue$1(richPreparedCache, key);
1252
1240
  if (cached != null) return cached;
1253
1241
  const items = spans.map((span) => ({
1254
1242
  text: span.text,
@@ -1257,7 +1245,7 @@ function readRichPrepared(spans, defaultFont) {
1257
1245
  extraWidth: span.extraWidth
1258
1246
  }));
1259
1247
  const preparedItemIndexBySourceItemIndex = buildPreparedItemIndexBySourceItemIndex(spans);
1260
- return writeLruValue(richPreparedCache, key, {
1248
+ return writeLruValue$1(richPreparedCache, key, {
1261
1249
  prepared: prepareRichInline(items),
1262
1250
  preparedItemIndexBySourceItemIndex
1263
1251
  }, RICH_PREPARED_CACHE_CAPACITY);
@@ -2155,27 +2143,56 @@ var DebugRenderer = class extends BaseRenderer {
2155
2143
  }
2156
2144
  };
2157
2145
  //#endregion
2146
+ //#region src/renderer/weak-listeners.ts
2147
+ function pruneWeakListenerMap(listeners) {
2148
+ for (const [token, listener] of listeners) if (listener.ownerRef.deref() == null) listeners.delete(token);
2149
+ }
2150
+ function emitWeakListeners(listeners, event) {
2151
+ for (const [token, listener] of [...listeners]) {
2152
+ const owner = listener.ownerRef.deref();
2153
+ if (owner == null) {
2154
+ listeners.delete(token);
2155
+ continue;
2156
+ }
2157
+ listener.notify(owner, event);
2158
+ }
2159
+ }
2160
+ //#endregion
2158
2161
  //#region src/renderer/list-state.ts
2159
2162
  const listStateListeners = /* @__PURE__ */ new WeakMap();
2163
+ const listStateListenerRegistry = typeof FinalizationRegistry === "function" ? new FinalizationRegistry(({ listRef, token }) => {
2164
+ const list = listRef.deref();
2165
+ if (list == null) return;
2166
+ deleteListStateListener(list, token);
2167
+ }) : null;
2168
+ function deleteListStateListener(list, token) {
2169
+ const listeners = listStateListeners.get(list);
2170
+ if (listeners == null) return;
2171
+ listeners.delete(token);
2172
+ if (listeners.size === 0) listStateListeners.delete(list);
2173
+ }
2160
2174
  function emitListStateChange(list, change) {
2161
2175
  const listeners = listStateListeners.get(list);
2162
- if (listeners == null || listeners.size === 0) return;
2163
- for (const listener of [...listeners]) listener(change);
2176
+ if (listeners == null) return;
2177
+ emitWeakListeners(listeners, change);
2178
+ if (listeners.size === 0) listStateListeners.delete(list);
2164
2179
  }
2165
- function subscribeListState(list, listener) {
2180
+ function subscribeListState(list, owner, listener) {
2166
2181
  const key = list;
2167
2182
  let listeners = listStateListeners.get(key);
2168
2183
  if (listeners == null) {
2169
- listeners = /* @__PURE__ */ new Set();
2184
+ listeners = /* @__PURE__ */ new Map();
2170
2185
  listStateListeners.set(key, listeners);
2171
- }
2172
- listeners.add(listener);
2173
- return () => {
2174
- const current = listStateListeners.get(key);
2175
- if (current == null) return;
2176
- current.delete(listener);
2177
- if (current.size === 0) listStateListeners.delete(key);
2178
- };
2186
+ } else pruneWeakListenerMap(listeners);
2187
+ const token = Symbol();
2188
+ listeners.set(token, {
2189
+ ownerRef: new WeakRef(owner),
2190
+ notify: listener
2191
+ });
2192
+ listStateListenerRegistry?.register(owner, {
2193
+ listRef: new WeakRef(key),
2194
+ token
2195
+ });
2179
2196
  }
2180
2197
  var ListState = class {
2181
2198
  #items;
@@ -2269,9 +2286,31 @@ var ListState = class {
2269
2286
  };
2270
2287
  //#endregion
2271
2288
  //#region src/renderer/memo.ts
2289
+ const DEFAULT_MEMO_RENDER_ITEM_BY_MAX_ENTRIES = 512;
2272
2290
  function isWeakMapKey(value) {
2273
2291
  return typeof value === "object" && value !== null || typeof value === "function";
2274
2292
  }
2293
+ function normalizeMaxEntries(maxEntries) {
2294
+ if (maxEntries === Number.POSITIVE_INFINITY) return Number.POSITIVE_INFINITY;
2295
+ if (maxEntries == null || !Number.isFinite(maxEntries)) return DEFAULT_MEMO_RENDER_ITEM_BY_MAX_ENTRIES;
2296
+ return Math.max(0, Math.trunc(maxEntries));
2297
+ }
2298
+ function readLruValue(cache, key) {
2299
+ const cached = cache.get(key);
2300
+ if (cached == null) return;
2301
+ cache.delete(key);
2302
+ cache.set(key, cached);
2303
+ return cached;
2304
+ }
2305
+ function writeLruValue(cache, key, value, maxEntries) {
2306
+ if (cache.has(key)) cache.delete(key);
2307
+ else if (Number.isFinite(maxEntries) && cache.size >= maxEntries) {
2308
+ const oldestKey = cache.keys().next().value;
2309
+ if (oldestKey != null) cache.delete(oldestKey);
2310
+ }
2311
+ if (maxEntries > 0) cache.set(key, value);
2312
+ return value;
2313
+ }
2275
2314
  /**
2276
2315
  * Memoizes `renderItem` by object identity.
2277
2316
  */
@@ -2291,15 +2330,14 @@ function memoRenderItem(renderItem) {
2291
2330
  /**
2292
2331
  * Memoizes `renderItem` by a caller-provided cache key.
2293
2332
  */
2294
- function memoRenderItemBy(keyOf, renderItem) {
2333
+ function memoRenderItemBy(keyOf, renderItem, options = {}) {
2295
2334
  const cache = /* @__PURE__ */ new Map();
2335
+ const maxEntries = normalizeMaxEntries(options.maxEntries);
2296
2336
  function fn(item) {
2297
2337
  const key = keyOf(item);
2298
- const cached = cache.get(key);
2338
+ const cached = readLruValue(cache, key);
2299
2339
  if (cached != null) return cached;
2300
- const result = renderItem(item);
2301
- cache.set(key, result);
2302
- return result;
2340
+ return writeLruValue(cache, key, renderItem(item), maxEntries);
2303
2341
  }
2304
2342
  return Object.assign(fn, {
2305
2343
  reset: (item) => cache.delete(keyOf(item)),
@@ -2341,11 +2379,10 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
2341
2379
  #jumpAnimation;
2342
2380
  #replacementAnimations = /* @__PURE__ */ new Map();
2343
2381
  #nextReplacementLayerKey = 0;
2344
- #unsubscribeListState;
2345
2382
  constructor(graphics, options) {
2346
2383
  super(graphics, options);
2347
- this.#unsubscribeListState = subscribeListState(options.list, (change) => {
2348
- this.#handleListStateChange(change);
2384
+ subscribeListState(options.list, this, (owner, change) => {
2385
+ owner.#handleListStateChange(change);
2349
2386
  });
2350
2387
  }
2351
2388
  /** Current anchor item index. */