@paubox/ui 1.19.1 → 3.0.0
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/index.esm.js +81 -69
- package/package.json +1 -1
- package/src/lib/SearchOmnibox/SearchOmnibox.d.ts +45 -14
package/index.esm.js
CHANGED
|
@@ -30939,12 +30939,12 @@ function _templateObject9() {
|
|
|
30939
30939
|
}
|
|
30940
30940
|
function _templateObject10() {
|
|
30941
30941
|
var data = _tagged_template_literal$5([
|
|
30942
|
-
"\n
|
|
30943
|
-
";\n ",
|
|
30942
|
+
"\n ",
|
|
30944
30943
|
"\n color: ",
|
|
30945
|
-
";\n html.dark & {\n
|
|
30946
|
-
";\n
|
|
30947
|
-
";\n
|
|
30944
|
+
";\n html.dark & {\n color: ",
|
|
30945
|
+
";\n }\n /* Divider above the footer only when there are suggestion rows above\n it. When the footer is the only item in the dropdown (fetchSuggestions\n false, no candidates, non-empty query), the static border-top would\n bleed past the rounded corners of the dropdown container as a stray\n line at the top of the menu. */\n &:not(:first-child) {\n border-top: 1px solid ",
|
|
30946
|
+
";\n html.dark & {\n border-top-color: ",
|
|
30947
|
+
";\n }\n }\n"
|
|
30948
30948
|
]);
|
|
30949
30949
|
_templateObject10 = function _templateObject() {
|
|
30950
30950
|
return data;
|
|
@@ -30992,7 +30992,7 @@ var DropdownRow = styled.button(_templateObject6$1(), spacing(1), spacing(1), sp
|
|
|
30992
30992
|
var RowIcon = styled.span(_templateObject7$1(), neutral500$1);
|
|
30993
30993
|
var RowText = styled.span(_templateObject8$1());
|
|
30994
30994
|
var RowMeta = styled.span(_templateObject9(), $paragraph300Regular, neutral600$1, neutral300$1);
|
|
30995
|
-
var FooterRow = styled(DropdownRow)(_templateObject10(),
|
|
30995
|
+
var FooterRow = styled(DropdownRow)(_templateObject10(), $paragraph200Regular, neutral700$1, neutral300$1, neutral200$1, surfaceRaised);
|
|
30996
30996
|
var PressEnter = styled.span(_templateObject11(), $paragraph300Regular, neutral500$1);
|
|
30997
30997
|
var DeleteHandle = styled.span(_templateObject12());
|
|
30998
30998
|
// ─── Inline icons (kept local to avoid bloating @icons) ─────────────────────
|
|
@@ -31073,7 +31073,7 @@ var EMPTY_CANDIDATES = [];
|
|
|
31073
31073
|
var SearchOmnibox = function(param) {
|
|
31074
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() {
|
|
31075
31075
|
return '';
|
|
31076
|
-
} : _param_getAuthToken,
|
|
31076
|
+
} : _param_getAuthToken, _param_fetchSuggestions = param.fetchSuggestions, fetchSuggestions = _param_fetchSuggestions === void 0 ? true : _param_fetchSuggestions, _param_withHistory = param.withHistory, withHistory = _param_withHistory === void 0 ? true : _param_withHistory, onResultClick = param.onResultClick, onCommit = param.onCommit, onLiveChange = param.onLiveChange, _param_localCandidates = param.localCandidates, localCandidates = _param_localCandidates === void 0 ? EMPTY_CANDIDATES : _param_localCandidates, _param_testId = param.testId, testId = _param_testId === void 0 ? 'search-omnibox' : _param_testId, _param_initialQuery = param.initialQuery, initialQuery = _param_initialQuery === void 0 ? '' : _param_initialQuery;
|
|
31077
31077
|
var _useState = _sliced_to_array$4(useState(initialQuery), 2), query = _useState[0], setQuery = _useState[1];
|
|
31078
31078
|
var _useState1 = _sliced_to_array$4(useState(initialQuery), 2), debouncedQuery = _useState1[0], setDebouncedQuery = _useState1[1];
|
|
31079
31079
|
var _useState2 = _sliced_to_array$4(useState(false), 2), focused = _useState2[0], setFocused = _useState2[1];
|
|
@@ -31217,7 +31217,7 @@ var SearchOmnibox = function(param) {
|
|
|
31217
31217
|
]);
|
|
31218
31218
|
// Remote autocomplete fetch
|
|
31219
31219
|
useEffect(function() {
|
|
31220
|
-
if (
|
|
31220
|
+
if (!fetchSuggestions || !debouncedQuery) {
|
|
31221
31221
|
setResults([]);
|
|
31222
31222
|
return;
|
|
31223
31223
|
}
|
|
@@ -31272,56 +31272,56 @@ var SearchOmnibox = function(param) {
|
|
|
31272
31272
|
}, [
|
|
31273
31273
|
debouncedQuery,
|
|
31274
31274
|
bar,
|
|
31275
|
-
|
|
31275
|
+
fetchSuggestions,
|
|
31276
31276
|
apiFetch
|
|
31277
31277
|
]);
|
|
31278
|
-
// Tracks the last query value we've notified the
|
|
31279
|
-
//
|
|
31280
|
-
// echoed ~200ms later
|
|
31281
|
-
|
|
31282
|
-
|
|
31283
|
-
//
|
|
31284
|
-
//
|
|
31285
|
-
// re-allocate
|
|
31286
|
-
//
|
|
31287
|
-
//
|
|
31288
|
-
|
|
31289
|
-
|
|
31290
|
-
var onSubmitFullSearchRef = useRef(onSubmitFullSearch);
|
|
31291
|
-
var onLocalQueryChangeRef = useRef(onLocalQueryChange);
|
|
31278
|
+
// Tracks the last query value we've notified the parent about via the
|
|
31279
|
+
// debounced live-change path, so a synchronous commit (Enter / footer /
|
|
31280
|
+
// recent / local) isn't echoed ~200ms later when the debounced effect
|
|
31281
|
+
// fires for the same query.
|
|
31282
|
+
var lastLiveChangeRef = useRef(undefined);
|
|
31283
|
+
// Hold the parent callbacks in refs so the firing functions stay stable
|
|
31284
|
+
// across renders. Otherwise any caller passing inline arrow functions
|
|
31285
|
+
// (the common case) would re-allocate the firing closures every render,
|
|
31286
|
+
// churning effect dependencies and triggering loops. Refs decouple the
|
|
31287
|
+
// parent's reference identity from our effect deps.
|
|
31288
|
+
var onCommitRef = useRef(onCommit);
|
|
31289
|
+
var onLiveChangeRef = useRef(onLiveChange);
|
|
31292
31290
|
useEffect(function() {
|
|
31293
|
-
|
|
31291
|
+
onCommitRef.current = onCommit;
|
|
31294
31292
|
}, [
|
|
31295
|
-
|
|
31293
|
+
onCommit
|
|
31296
31294
|
]);
|
|
31297
31295
|
useEffect(function() {
|
|
31298
|
-
|
|
31299
|
-
}, [
|
|
31300
|
-
onLocalQueryChange
|
|
31301
|
-
]);
|
|
31302
|
-
// Route parent notifications to the mode-appropriate callback. In local
|
|
31303
|
-
// mode we dedupe so non-idempotent consumers don't see the same query
|
|
31304
|
-
// twice (once synchronously, once via debounce).
|
|
31305
|
-
var notifyParent = useCallback(function(q) {
|
|
31306
|
-
if (mode === 'local') {
|
|
31307
|
-
var _onLocalQueryChangeRef_current;
|
|
31308
|
-
if (lastLocalNotifyRef.current === q) return;
|
|
31309
|
-
lastLocalNotifyRef.current = q;
|
|
31310
|
-
(_onLocalQueryChangeRef_current = onLocalQueryChangeRef.current) === null || _onLocalQueryChangeRef_current === void 0 ? void 0 : _onLocalQueryChangeRef_current.call(onLocalQueryChangeRef, q);
|
|
31311
|
-
} else {
|
|
31312
|
-
var _onSubmitFullSearchRef_current;
|
|
31313
|
-
(_onSubmitFullSearchRef_current = onSubmitFullSearchRef.current) === null || _onSubmitFullSearchRef_current === void 0 ? void 0 : _onSubmitFullSearchRef_current.call(onSubmitFullSearchRef, q);
|
|
31314
|
-
}
|
|
31296
|
+
onLiveChangeRef.current = onLiveChange;
|
|
31315
31297
|
}, [
|
|
31316
|
-
|
|
31317
|
-
]);
|
|
31298
|
+
onLiveChange
|
|
31299
|
+
]);
|
|
31300
|
+
// Fire when the user has explicitly committed (Enter, footer click,
|
|
31301
|
+
// recent-row select, local-row select). Always invokes onCommit, in both
|
|
31302
|
+
// modes. Also seeds `lastLiveChangeRef` so the imminent debounced
|
|
31303
|
+
// `notifyLiveChange` doesn't re-fire for the same query.
|
|
31304
|
+
var fireCommit = useCallback(function(q) {
|
|
31305
|
+
var _onCommitRef_current;
|
|
31306
|
+
lastLiveChangeRef.current = q;
|
|
31307
|
+
(_onCommitRef_current = onCommitRef.current) === null || _onCommitRef_current === void 0 ? void 0 : _onCommitRef_current.call(onCommitRef, q);
|
|
31308
|
+
}, []);
|
|
31309
|
+
// Fire when the debounced query changes — every keystroke (200ms after
|
|
31310
|
+
// the last). Deduped so repeated identical values don't fire twice.
|
|
31311
|
+
// Invokes onLiveChange in both modes.
|
|
31312
|
+
var notifyLiveChange = useCallback(function(q) {
|
|
31313
|
+
var _onLiveChangeRef_current;
|
|
31314
|
+
if (lastLiveChangeRef.current === q) return;
|
|
31315
|
+
lastLiveChangeRef.current = q;
|
|
31316
|
+
(_onLiveChangeRef_current = onLiveChangeRef.current) === null || _onLiveChangeRef_current === void 0 ? void 0 : _onLiveChangeRef_current.call(onLiveChangeRef, q);
|
|
31317
|
+
}, []);
|
|
31318
31318
|
// Local autocomplete: filter candidates client-side.
|
|
31319
31319
|
// The `setLocalMatches` calls are guarded against no-op writes — calling
|
|
31320
31320
|
// setState with a fresh `[]` literal every render is enough to cause a
|
|
31321
31321
|
// re-render even when the previous state was also `[]`, because React
|
|
31322
31322
|
// bails out only on Object.is equality.
|
|
31323
31323
|
useEffect(function() {
|
|
31324
|
-
if (
|
|
31324
|
+
if (fetchSuggestions) return;
|
|
31325
31325
|
var q = debouncedQuery.trim().toLowerCase();
|
|
31326
31326
|
if (!q) {
|
|
31327
31327
|
setLocalMatches(function(prev) {
|
|
@@ -31337,12 +31337,20 @@ var SearchOmnibox = function(param) {
|
|
|
31337
31337
|
}) ? prev : next;
|
|
31338
31338
|
});
|
|
31339
31339
|
}
|
|
31340
|
-
notifyParent(debouncedQuery);
|
|
31341
31340
|
}, [
|
|
31342
31341
|
debouncedQuery,
|
|
31343
|
-
|
|
31344
|
-
localCandidates
|
|
31345
|
-
|
|
31342
|
+
fetchSuggestions,
|
|
31343
|
+
localCandidates
|
|
31344
|
+
]);
|
|
31345
|
+
// Live-change notification: fires on debounced typing in both modes.
|
|
31346
|
+
// Pages that want to live-filter their table (or fetch as the user
|
|
31347
|
+
// types) pass `onLiveChange`; pages that only care about explicit
|
|
31348
|
+
// commits leave it out. Independent of `fetchSuggestions`.
|
|
31349
|
+
useEffect(function() {
|
|
31350
|
+
notifyLiveChange(debouncedQuery);
|
|
31351
|
+
}, [
|
|
31352
|
+
debouncedQuery,
|
|
31353
|
+
notifyLiveChange
|
|
31346
31354
|
]);
|
|
31347
31355
|
// Note: click-outside dismissal is handled by Popper's built-in useClickOutside
|
|
31348
31356
|
// (it watches the anchorRef + its own container ref) via the onClose callback.
|
|
@@ -31474,20 +31482,24 @@ var SearchOmnibox = function(param) {
|
|
|
31474
31482
|
});
|
|
31475
31483
|
}
|
|
31476
31484
|
if (dropdownState === 'results') {
|
|
31477
|
-
|
|
31478
|
-
|
|
31479
|
-
|
|
31480
|
-
|
|
31481
|
-
|
|
31482
|
-
|
|
31483
|
-
|
|
31484
|
-
|
|
31485
|
-
|
|
31485
|
+
// Per PRD §1.5 every typing-state dropdown ends in the
|
|
31486
|
+
// "All search results for X — Press ENTER" footer row, regardless
|
|
31487
|
+
// of how many (or whether any) suggestion rows precede it. Bars
|
|
31488
|
+
// with fetchSuggestions=false and no localCandidates therefore show
|
|
31489
|
+
// only the footer when the user has typed text, which is the only
|
|
31490
|
+
// mobile-friendly affordance for committing the search.
|
|
31491
|
+
var suggestionRows = !fetchSuggestions ? localMatches.map(function(s) {
|
|
31492
|
+
return {
|
|
31493
|
+
kind: 'local',
|
|
31494
|
+
key: s
|
|
31495
|
+
};
|
|
31496
|
+
}) : results.slice(0, 5).map(function(r) {
|
|
31486
31497
|
return {
|
|
31487
31498
|
kind: 'result',
|
|
31488
31499
|
key: r.message_id
|
|
31489
31500
|
};
|
|
31490
|
-
})
|
|
31501
|
+
});
|
|
31502
|
+
return _to_consumable_array$2(suggestionRows).concat([
|
|
31491
31503
|
{
|
|
31492
31504
|
kind: 'footer',
|
|
31493
31505
|
key: '__footer__'
|
|
@@ -31500,7 +31512,7 @@ var SearchOmnibox = function(param) {
|
|
|
31500
31512
|
recent,
|
|
31501
31513
|
results,
|
|
31502
31514
|
localMatches,
|
|
31503
|
-
|
|
31515
|
+
fetchSuggestions
|
|
31504
31516
|
]);
|
|
31505
31517
|
// Reset highlight whenever the item set changes — including same-length
|
|
31506
31518
|
// swaps (e.g. one page of 5 results replaced by a different 5 + footer).
|
|
@@ -31541,7 +31553,7 @@ var SearchOmnibox = function(param) {
|
|
|
31541
31553
|
// empty input is a legitimate "clear filter, show everything" action.
|
|
31542
31554
|
// recordSearch itself guards against recording empty strings, so we
|
|
31543
31555
|
// don't pollute history.
|
|
31544
|
-
|
|
31556
|
+
fireCommit(q);
|
|
31545
31557
|
void recordSearch(q);
|
|
31546
31558
|
setFocused(false);
|
|
31547
31559
|
return [
|
|
@@ -31553,14 +31565,14 @@ var SearchOmnibox = function(param) {
|
|
|
31553
31565
|
return _ref.apply(this, arguments);
|
|
31554
31566
|
};
|
|
31555
31567
|
}(), [
|
|
31556
|
-
|
|
31568
|
+
fireCommit,
|
|
31557
31569
|
recordSearch
|
|
31558
31570
|
]);
|
|
31559
31571
|
var selectRecent = useCallback(function() {
|
|
31560
31572
|
var _ref = _async_to_generator(function(q) {
|
|
31561
31573
|
return _ts_generator(this, function(_state) {
|
|
31562
31574
|
setQuery(q);
|
|
31563
|
-
|
|
31575
|
+
fireCommit(q);
|
|
31564
31576
|
void recordSearch(q);
|
|
31565
31577
|
setFocused(false);
|
|
31566
31578
|
return [
|
|
@@ -31572,7 +31584,7 @@ var SearchOmnibox = function(param) {
|
|
|
31572
31584
|
return _ref.apply(this, arguments);
|
|
31573
31585
|
};
|
|
31574
31586
|
}(), [
|
|
31575
|
-
|
|
31587
|
+
fireCommit,
|
|
31576
31588
|
recordSearch
|
|
31577
31589
|
]);
|
|
31578
31590
|
var selectResult = useCallback(function(result) {
|
|
@@ -31586,11 +31598,11 @@ var SearchOmnibox = function(param) {
|
|
|
31586
31598
|
]);
|
|
31587
31599
|
var selectLocal = useCallback(function(s) {
|
|
31588
31600
|
setQuery(s);
|
|
31589
|
-
|
|
31601
|
+
fireCommit(s);
|
|
31590
31602
|
void recordSearch(s);
|
|
31591
31603
|
setFocused(false);
|
|
31592
31604
|
}, [
|
|
31593
|
-
|
|
31605
|
+
fireCommit,
|
|
31594
31606
|
recordSearch
|
|
31595
31607
|
]);
|
|
31596
31608
|
var handleKeyDown = function(e) {
|
|
@@ -31725,10 +31737,10 @@ var SearchOmnibox = function(param) {
|
|
|
31725
31737
|
onClick: function() {
|
|
31726
31738
|
var _inputRef_current;
|
|
31727
31739
|
setQuery('');
|
|
31728
|
-
// Clearing the input is a "remove the filter" action
|
|
31729
|
-
//
|
|
31740
|
+
// Clearing the input is a "remove the filter" action — fire
|
|
31741
|
+
// commit so the page-level data resets to its unfiltered
|
|
31730
31742
|
// state, the same way pressing Enter on an empty input would.
|
|
31731
|
-
|
|
31743
|
+
fireCommit('');
|
|
31732
31744
|
// After a prior commit the dropdown is closed (`focused=false`)
|
|
31733
31745
|
// even though the input has DOM focus. Re-engage explicitly so
|
|
31734
31746
|
// the recent-searches view reappears for the now-empty input.
|
package/package.json
CHANGED
|
@@ -25,19 +25,38 @@ export interface SearchOmniboxProps {
|
|
|
25
25
|
placeholder?: string;
|
|
26
26
|
/**
|
|
27
27
|
* Base URL of the pb_search service (no trailing slash).
|
|
28
|
-
* Required when `
|
|
28
|
+
* Required when `fetchSuggestions !== false` or `withHistory !== false`. Ignored otherwise.
|
|
29
29
|
*/
|
|
30
30
|
pbSearchBaseUrl?: string;
|
|
31
31
|
/**
|
|
32
32
|
* Returns the current Bearer token to send to pb_search.
|
|
33
|
-
* Required when `
|
|
33
|
+
* Required when `fetchSuggestions !== false` or `withHistory !== false`. Ignored otherwise.
|
|
34
34
|
*/
|
|
35
35
|
getAuthToken?: () => Promise<string> | string;
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* Whether to fetch suggestion rows from pb_search as the user types.
|
|
38
|
+
* Defaults to `true`.
|
|
39
|
+
*
|
|
40
|
+
* - `true` — fires `GET /search/autocomplete` against pb_search on
|
|
41
|
+
* debounced typing and renders the top-5 hits above the footer row.
|
|
42
|
+
* Use on bars whose data is indexed in pb_search (every mail bar
|
|
43
|
+
* today).
|
|
44
|
+
* - `false` — never fetches `/search/autocomplete`. If `localCandidates`
|
|
45
|
+
* is passed, those are filtered client-side and shown as suggestions;
|
|
46
|
+
* otherwise only the footer row appears. Use on bars whose data
|
|
47
|
+
* isn't indexed in pb_search (rulesets, relays, settings, etc.).
|
|
48
|
+
*
|
|
49
|
+
* Independent of:
|
|
50
|
+
* - `withHistory`, which gates recent-search behavior (Redis), and
|
|
51
|
+
* - the Tab-accept ghost text, which is sourced from recent search
|
|
52
|
+
* history and is always on when `withHistory` is enabled.
|
|
53
|
+
*
|
|
54
|
+
* Named `fetchSuggestions` rather than `autocomplete` to avoid
|
|
55
|
+
* collision with the Tab-accept ghost-text autocomplete feature —
|
|
56
|
+
* both could reasonably be called "autocomplete" but they're
|
|
57
|
+
* independent.
|
|
39
58
|
*/
|
|
40
|
-
|
|
59
|
+
fetchSuggestions?: boolean;
|
|
41
60
|
/**
|
|
42
61
|
* Whether to use Redis-backed recent-search history. Defaults to `true`.
|
|
43
62
|
*
|
|
@@ -47,21 +66,33 @@ export interface SearchOmniboxProps {
|
|
|
47
66
|
* - Ghost-text tab autocomplete is disabled (it's sourced from recent searches)
|
|
48
67
|
*
|
|
49
68
|
* Set this to `false` for config / list pages that don't need per-user history.
|
|
50
|
-
* Pairs naturally with `
|
|
51
|
-
* from pb_search.
|
|
69
|
+
* Pairs naturally with `fetchSuggestions={false}` to keep the component
|
|
70
|
+
* fully decoupled from pb_search.
|
|
52
71
|
*/
|
|
53
72
|
withHistory?: boolean;
|
|
54
|
-
/**
|
|
73
|
+
/** Called when the user clicks a result row in the live-results dropdown
|
|
74
|
+
* (only fires when `fetchSuggestions !== false`; the dropdown is a
|
|
75
|
+
* pb_search-fed top-5 list). */
|
|
55
76
|
onResultClick?: (result: SearchOmniboxResult) => void;
|
|
56
|
-
/** Called when
|
|
57
|
-
|
|
58
|
-
|
|
77
|
+
/** Called when the user **commits** a search — explicit action.
|
|
78
|
+
* Triggers: Enter on the input (no row highlighted), click on the
|
|
79
|
+
* "All search results for X" footer row, Enter or click on a
|
|
80
|
+
* recent-search row, Enter or click on a local-match row.
|
|
81
|
+
* Fires in both `local` and `remote` modes. */
|
|
82
|
+
onCommit?: (query: string) => void;
|
|
83
|
+
/** Called when the user's typed query changes, debounced (~200ms).
|
|
84
|
+
* Lets pages live-filter their table or fetch as the user types.
|
|
85
|
+
* Fires in both `local` and `remote` modes. Optional — pages that
|
|
86
|
+
* only care about explicit commits should just not pass this. */
|
|
87
|
+
onLiveChange?: (query: string) => void;
|
|
88
|
+
/** Optional candidate strings to filter client-side when
|
|
89
|
+
* `fetchSuggestions={false}` (e.g. ruleset names, usernames). Ignored
|
|
90
|
+
* when `fetchSuggestions` is truthy — pb_search drives the suggestions
|
|
91
|
+
* instead. */
|
|
59
92
|
localCandidates?: string[];
|
|
60
|
-
/** Local mode: fires when the (debounced) query changes; let the page filter its own table. */
|
|
61
|
-
onLocalQueryChange?: (query: string) => void;
|
|
62
93
|
/** Test id prefix; defaults to `search-omnibox`. */
|
|
63
94
|
testId?: string;
|
|
64
95
|
/** Optional initial query (e.g. when restoring from URL state). */
|
|
65
96
|
initialQuery?: string;
|
|
66
97
|
}
|
|
67
|
-
export declare const SearchOmnibox: ({ bar, placeholder, pbSearchBaseUrl, getAuthToken,
|
|
98
|
+
export declare const SearchOmnibox: ({ bar, placeholder, pbSearchBaseUrl, getAuthToken, fetchSuggestions, withHistory, onResultClick, onCommit, onLiveChange, localCandidates, testId, initialQuery, }: SearchOmniboxProps) => import("@emotion/react/jsx-runtime").JSX.Element;
|