@paubox/ui 1.19.0 → 1.19.1

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.
Files changed (2) hide show
  1. package/index.esm.js +47 -10
  2. package/package.json +1 -1
package/index.esm.js CHANGED
@@ -31063,11 +31063,17 @@ var formatDate = function(iso) {
31063
31063
  }
31064
31064
  return null;
31065
31065
  };
31066
+ // Stable empty-array reference for the `localCandidates` default. Using
31067
+ // `localCandidates = []` would allocate a fresh array on every render,
31068
+ // which churns the local-autocomplete effect's dep array and (combined
31069
+ // with non-memoized callbacks from the parent) drove an infinite render
31070
+ // loop on pages that don't pass `localCandidates`.
31071
+ var EMPTY_CANDIDATES = [];
31066
31072
  // ─── Component ──────────────────────────────────────────────────────────────
31067
31073
  var SearchOmnibox = function(param) {
31068
31074
  var bar = param.bar, _param_placeholder = param.placeholder, placeholder = _param_placeholder === void 0 ? 'Search' : _param_placeholder, _param_pbSearchBaseUrl = param.pbSearchBaseUrl, pbSearchBaseUrl = _param_pbSearchBaseUrl === void 0 ? '' : _param_pbSearchBaseUrl, _param_getAuthToken = param.getAuthToken, getAuthToken = _param_getAuthToken === void 0 ? function() {
31069
31075
  return '';
31070
- } : _param_getAuthToken, _param_mode = param.mode, mode = _param_mode === void 0 ? 'remote' : _param_mode, _param_withHistory = param.withHistory, withHistory = _param_withHistory === void 0 ? true : _param_withHistory, onResultClick = param.onResultClick, onSubmitFullSearch = param.onSubmitFullSearch, _param_localCandidates = param.localCandidates, localCandidates = _param_localCandidates === void 0 ? [] : _param_localCandidates, onLocalQueryChange = param.onLocalQueryChange, _param_testId = param.testId, testId = _param_testId === void 0 ? 'search-omnibox' : _param_testId, _param_initialQuery = param.initialQuery, initialQuery = _param_initialQuery === void 0 ? '' : _param_initialQuery;
31076
+ } : _param_getAuthToken, _param_mode = param.mode, mode = _param_mode === void 0 ? 'remote' : _param_mode, _param_withHistory = param.withHistory, withHistory = _param_withHistory === void 0 ? true : _param_withHistory, onResultClick = param.onResultClick, onSubmitFullSearch = param.onSubmitFullSearch, _param_localCandidates = param.localCandidates, localCandidates = _param_localCandidates === void 0 ? EMPTY_CANDIDATES : _param_localCandidates, onLocalQueryChange = param.onLocalQueryChange, _param_testId = param.testId, testId = _param_testId === void 0 ? 'search-omnibox' : _param_testId, _param_initialQuery = param.initialQuery, initialQuery = _param_initialQuery === void 0 ? '' : _param_initialQuery;
31071
31077
  var _useState = _sliced_to_array$4(useState(initialQuery), 2), query = _useState[0], setQuery = _useState[1];
31072
31078
  var _useState1 = _sliced_to_array$4(useState(initialQuery), 2), debouncedQuery = _useState1[0], setDebouncedQuery = _useState1[1];
31073
31079
  var _useState2 = _sliced_to_array$4(useState(false), 2), focused = _useState2[0], setFocused = _useState2[1];
@@ -31273,32 +31279,63 @@ var SearchOmnibox = function(param) {
31273
31279
  // so synchronous notifications from clear / Enter / selectLocal aren't
31274
31280
  // echoed ~200ms later by the debounced local-autocomplete effect.
31275
31281
  var lastLocalNotifyRef = useRef(undefined);
31282
+ // Hold the parent callbacks in refs so `notifyParent` is stable across
31283
+ // renders. Otherwise any caller passing inline arrow functions for
31284
+ // `onSubmitFullSearch` / `onLocalQueryChange` (the common case) would
31285
+ // re-allocate `notifyParent` every render, triggering the local-
31286
+ // autocomplete effect on every render, which in turn calls
31287
+ // `setLocalMatches([])` — a new empty-array reference every time — and
31288
+ // re-renders forever. Refs decouple the parent's reference identity
31289
+ // from the effect's dep array.
31290
+ var onSubmitFullSearchRef = useRef(onSubmitFullSearch);
31291
+ var onLocalQueryChangeRef = useRef(onLocalQueryChange);
31292
+ useEffect(function() {
31293
+ onSubmitFullSearchRef.current = onSubmitFullSearch;
31294
+ }, [
31295
+ onSubmitFullSearch
31296
+ ]);
31297
+ useEffect(function() {
31298
+ onLocalQueryChangeRef.current = onLocalQueryChange;
31299
+ }, [
31300
+ onLocalQueryChange
31301
+ ]);
31276
31302
  // Route parent notifications to the mode-appropriate callback. In local
31277
31303
  // mode we dedupe so non-idempotent consumers don't see the same query
31278
31304
  // twice (once synchronously, once via debounce).
31279
31305
  var notifyParent = useCallback(function(q) {
31280
31306
  if (mode === 'local') {
31307
+ var _onLocalQueryChangeRef_current;
31281
31308
  if (lastLocalNotifyRef.current === q) return;
31282
31309
  lastLocalNotifyRef.current = q;
31283
- onLocalQueryChange === null || onLocalQueryChange === void 0 ? void 0 : onLocalQueryChange(q);
31310
+ (_onLocalQueryChangeRef_current = onLocalQueryChangeRef.current) === null || _onLocalQueryChangeRef_current === void 0 ? void 0 : _onLocalQueryChangeRef_current.call(onLocalQueryChangeRef, q);
31284
31311
  } else {
31285
- onSubmitFullSearch === null || onSubmitFullSearch === void 0 ? void 0 : onSubmitFullSearch(q);
31312
+ var _onSubmitFullSearchRef_current;
31313
+ (_onSubmitFullSearchRef_current = onSubmitFullSearchRef.current) === null || _onSubmitFullSearchRef_current === void 0 ? void 0 : _onSubmitFullSearchRef_current.call(onSubmitFullSearchRef, q);
31286
31314
  }
31287
31315
  }, [
31288
- mode,
31289
- onLocalQueryChange,
31290
- onSubmitFullSearch
31316
+ mode
31291
31317
  ]);
31292
- // Local autocomplete: filter candidates client-side
31318
+ // Local autocomplete: filter candidates client-side.
31319
+ // The `setLocalMatches` calls are guarded against no-op writes — calling
31320
+ // setState with a fresh `[]` literal every render is enough to cause a
31321
+ // re-render even when the previous state was also `[]`, because React
31322
+ // bails out only on Object.is equality.
31293
31323
  useEffect(function() {
31294
31324
  if (mode !== 'local') return;
31295
31325
  var q = debouncedQuery.trim().toLowerCase();
31296
31326
  if (!q) {
31297
- setLocalMatches([]);
31327
+ setLocalMatches(function(prev) {
31328
+ return prev.length === 0 ? prev : [];
31329
+ });
31298
31330
  } else {
31299
- setLocalMatches(localCandidates.filter(function(c) {
31331
+ var next = localCandidates.filter(function(c) {
31300
31332
  return c.toLowerCase().includes(q);
31301
- }).slice(0, 5));
31333
+ }).slice(0, 5);
31334
+ setLocalMatches(function(prev) {
31335
+ return prev.length === next.length && prev.every(function(v, i) {
31336
+ return v === next[i];
31337
+ }) ? prev : next;
31338
+ });
31302
31339
  }
31303
31340
  notifyParent(debouncedQuery);
31304
31341
  }, [
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@paubox/ui",
3
3
  "author": "Paubox, Inc.",
4
4
  "description": "Paubox Component Library",
5
- "version": "1.19.0",
5
+ "version": "1.19.1",
6
6
  "type": "module",
7
7
  "private": false,
8
8
  "publishConfig": {