@agg-build/ui 4.4.0 → 4.7.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.
package/README.md CHANGED
@@ -70,6 +70,10 @@ export default function App() {
70
70
  }
71
71
  ```
72
72
 
73
+ Set `config.features.enableEventDiscoveryUrlPersistence` to `true` to persist the primary
74
+ `HomePage` section's `category`, repeated `subcategory`, `venue`, and `sort` filters in browser
75
+ history. It is disabled by default and preserves unrelated query parameters such as `search`.
76
+
73
77
  ## Styling
74
78
 
75
79
  Import the default theme once at your app entrypoint:
@@ -9,12 +9,12 @@ import {
9
9
  getTradingVenueLabel,
10
10
  prepareSettlementDifferences,
11
11
  resolveOrderEligibilityMessage
12
- } from "./chunk-6STRIKUA.mjs";
12
+ } from "./chunk-WYCSTAN7.mjs";
13
13
  import {
14
14
  ConnectKalshiModal,
15
15
  GeoBlockBanner,
16
16
  subscribeAggDepositNotification
17
- } from "./chunk-734VTGCV.mjs";
17
+ } from "./chunk-XN2NB2NP.mjs";
18
18
  import {
19
19
  AGG_TERMS_OF_SERVICE_URL,
20
20
  Button,
@@ -47,7 +47,7 @@ import {
47
47
  resolveResolvedOutcomeLabel,
48
48
  skeletonViews,
49
49
  useOptionalToast
50
- } from "./chunk-N7UPPT5J.mjs";
50
+ } from "./chunk-RDBMHBR2.mjs";
51
51
 
52
52
  // src/trading/types.ts
53
53
  import { Venue, enumGuard, isFiniteNonNeg, safeParse } from "@agg-build/sdk";
@@ -1077,7 +1077,7 @@ var getSmartRouteTotalFees = (feeBreakdown) => {
1077
1077
  if (!feeBreakdown) return null;
1078
1078
  const feeParts = [feeBreakdown.venueFees, feeBreakdown.bridgeFees, feeBreakdown.executionGas];
1079
1079
  if (!feeParts.every(Number.isFinite)) return null;
1080
- return feeParts.reduce((total, fee) => total + fee, 0);
1080
+ return feeParts.reduce((total, fee) => total + Math.round(fee * 100) / 100, 0);
1081
1081
  };
1082
1082
  var getPlaceOrderContainerClassName = () => {
1083
1083
  return PLACE_ORDER_CONTAINER_CLASS_NAME;
@@ -1339,7 +1339,7 @@ var buildLiveRouteCards = ({
1339
1339
  // balance condition, and `handleExecuteQuote` rejects an empty quoteId.
1340
1340
  sq.venue !== primaryWinnerVenue
1341
1341
  ).map((sq) => {
1342
- var _a2;
1342
+ var _a2, _b2;
1343
1343
  const parsedVenue = parseVenue(sq.venue);
1344
1344
  const venue = parsedVenue.success ? parsedVenue.data : void 0;
1345
1345
  if (geoBlockedVenues.has(sq.venue)) {
@@ -1372,7 +1372,12 @@ var buildLiveRouteCards = ({
1372
1372
  estimatedProfit: sq.estimatedProfit,
1373
1373
  returnPct: sq.returnPct,
1374
1374
  // venueSoloQuotes stripped on solo card's quoteData (only the primary needs them)
1375
- venueSoloQuotes: void 0
1375
+ venueSoloQuotes: void 0,
1376
+ // This solo's OWN fee breakdown. Without it the spread above leaves the
1377
+ // primary route's breakdown in place, so selecting a bridge-free venue
1378
+ // still showed the primary's bridging charge. Falls back to the
1379
+ // primary only when the router omitted one (non-"ok" solos).
1380
+ feeBreakdown: (_b2 = sq.feeBreakdown) != null ? _b2 : quoteData.feeBreakdown
1376
1381
  }), sq.quoteId ? { status: "ok", error: void 0 } : null);
1377
1382
  return {
1378
1383
  id: `live-solo-${sq.venue}`,
@@ -24,7 +24,7 @@ import {
24
24
  resolveUnifiedOrderBookEntries,
25
25
  sortOutcomeSelectorOutcomes,
26
26
  useEventTradingContext
27
- } from "./chunk-6STRIKUA.mjs";
27
+ } from "./chunk-WYCSTAN7.mjs";
28
28
  import {
29
29
  AutocompleteSelect,
30
30
  Badge,
@@ -76,8 +76,9 @@ import {
76
76
  resolveYesOutcome,
77
77
  sortCategoriesForNavigation,
78
78
  sortOutcomes,
79
- splitEventsByLifecycle
80
- } from "./chunk-N7UPPT5J.mjs";
79
+ splitEventsByLifecycle,
80
+ validateSubcategoryPath
81
+ } from "./chunk-RDBMHBR2.mjs";
81
82
 
82
83
  // src/events/item/index.tsx
83
84
  import {
@@ -5427,7 +5428,7 @@ import {
5427
5428
  } from "@agg-build/hooks";
5428
5429
  import { VENUES } from "@agg-build/sdk";
5429
5430
  import * as Dialog from "@radix-ui/react-dialog";
5430
- import { useEffect as useEffect8, useMemo as useMemo10, useRef as useRef8, useState as useState7 } from "react";
5431
+ import { useCallback as useCallback4, useEffect as useEffect8, useMemo as useMemo10, useRef as useRef8, useState as useState7 } from "react";
5431
5432
 
5432
5433
  // src/events/list/category-sidebar.tsx
5433
5434
  import { useMemo as useMemo8 } from "react";
@@ -5548,12 +5549,19 @@ CategorySidebar.displayName = "CategorySidebar";
5548
5549
 
5549
5550
  // src/events/list/event-list-tabs.tsx
5550
5551
  import { getVenueAvailabilityState, useAppConfig, useGeoBlock, useLabels as useLabels9 } from "@agg-build/hooks";
5551
- import { isVenueInactive } from "@agg-build/sdk";
5552
+ import { getEffectiveDisabledVenues, isVenueInactive } from "@agg-build/sdk";
5552
5553
  import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo9, useRef as useRef7, useState as useState6 } from "react";
5553
5554
 
5554
5555
  // src/events/list/event-list.constants.ts
5555
5556
  import { MatchStatus, Venue as Venue2 } from "@agg-build/sdk";
5556
5557
  var DEFAULT_EVENTS_LIMIT = 50;
5558
+ var DEFAULT_EVENT_LIST_SORT = "volume24hr";
5559
+ var EVENT_LIST_SORT_VALUES = [
5560
+ "volume24hr",
5561
+ "topArbitrage",
5562
+ "volume",
5563
+ "endDate"
5564
+ ];
5557
5565
  var getDefaultEventListTabs = (labels) => {
5558
5566
  return [
5559
5567
  {
@@ -5623,6 +5631,7 @@ var getDefaultEventListTabs = (labels) => {
5623
5631
 
5624
5632
  // src/events/list/event-list-tabs.tsx
5625
5633
  import { jsx as jsx13 } from "react/jsx-runtime";
5634
+ var DEFAULT_HIDDEN_VENUES = new Set(getEffectiveDisabledVenues(void 0));
5626
5635
  var renderTabIcon = (tab, isActive) => {
5627
5636
  if (tab.iconName === "warning-filled") {
5628
5637
  return /* @__PURE__ */ jsx13(Icon, { name: "warning-filled", size: "small", color: "currentColor" });
@@ -5651,15 +5660,11 @@ var useEventListTabs = (tabs) => {
5651
5660
  const availabilityState = getVenueAvailabilityState({
5652
5661
  venueId: tab.venueLogo,
5653
5662
  disabledVenues,
5654
- // A compile-time-inactive venue (novig today) is hidden by default and
5655
- // un-hidden per-environment by NOVIG_DATA_VISIBLE, so its
5656
- // disabled-ness is only known once `/app/config` resolves — treating
5657
- // it as hidden while that request is in flight stops its tab flashing
5658
- // in and out where the flag is unset. Active venues must NOT be
5659
- // withheld, or they pop in late. Keyed off the shared inactive-venue
5660
- // helper rather than a venue name, so launching or retiring one needs
5661
- // no change here.
5662
- isConfigLoading: isAppConfigLoading && isVenueInactive(tab.venueLogo),
5663
+ // A globally inactive or default-disabled venue is hidden until
5664
+ // `/app/config` resolves. That prevents partner-opt-in venues such as
5665
+ // Novig from flashing briefly for apps that still disable them, while
5666
+ // ordinary active venues continue rendering immediately.
5667
+ isConfigLoading: isAppConfigLoading && (isVenueInactive(tab.venueLogo) || DEFAULT_HIDDEN_VENUES.has(tab.venueLogo)),
5663
5668
  isLocationBlocked
5664
5669
  });
5665
5670
  if (availabilityState === "hidden") return [];
@@ -5837,10 +5842,16 @@ var EventList = ({
5837
5842
  getMarketHref,
5838
5843
  getEventHref,
5839
5844
  initialVenueTab,
5845
+ activeVenueTab: controlledActiveVenueTab,
5846
+ onActiveVenueTabChange,
5847
+ selectedSort: controlledSelectedSort,
5848
+ onSelectedSortChange,
5849
+ categoryPath: controlledCategoryPath,
5850
+ onCategoryPathChange,
5840
5851
  initialLoadedPageCount,
5841
5852
  stateRef
5842
5853
  }) => {
5843
- var _a, _b, _c, _d, _e;
5854
+ var _a, _b, _c, _d, _e, _f;
5844
5855
  const labels = useLabels10();
5845
5856
  const { disabledVenues } = useAppConfig2();
5846
5857
  const {
@@ -5852,13 +5863,37 @@ var EventList = ({
5852
5863
  () => disabledVenues.length === 0 ? void 0 : getVisibleVenueIdsByConfig(VENUES, disabledVenues),
5853
5864
  [disabledVenues]
5854
5865
  );
5855
- const [activeTabValue, setActiveTabValue] = useState7(
5866
+ const [internalActiveTabValue, setInternalActiveTabValue] = useState7(
5856
5867
  (_b = initialVenueTab != null ? initialVenueTab : (_a = resolvedTabs[0]) == null ? void 0 : _a.value) != null ? _b : "matched"
5857
5868
  );
5858
- const [selectedSort, setSelectedSort] = useState7("volume24hr");
5869
+ const activeTabValue = controlledActiveVenueTab != null ? controlledActiveVenueTab : internalActiveTabValue;
5870
+ const [internalSelectedSort, setInternalSelectedSort] = useState7(DEFAULT_EVENT_LIST_SORT);
5871
+ const selectedSort = controlledSelectedSort != null ? controlledSelectedSort : internalSelectedSort;
5859
5872
  const [isSubcategoriesModalOpen, setIsSubcategoriesModalOpen] = useState7(false);
5860
- const [categoryPath, setCategoryPath] = useState7([]);
5873
+ const [internalCategoryPath, setInternalCategoryPath] = useState7([]);
5874
+ const categoryPath = controlledCategoryPath != null ? controlledCategoryPath : internalCategoryPath;
5861
5875
  const [collapsedCategoryIds, setCollapsedCategoryIds] = useState7(() => /* @__PURE__ */ new Set());
5876
+ const handleActiveTabChange = useCallback4(
5877
+ (value) => {
5878
+ if (controlledActiveVenueTab == null) setInternalActiveTabValue(value);
5879
+ onActiveVenueTabChange == null ? void 0 : onActiveVenueTabChange(value, { history: "push" });
5880
+ },
5881
+ [controlledActiveVenueTab, onActiveVenueTabChange]
5882
+ );
5883
+ const handleSelectedSortChange = useCallback4(
5884
+ (value) => {
5885
+ if (controlledSelectedSort == null) setInternalSelectedSort(value);
5886
+ onSelectedSortChange == null ? void 0 : onSelectedSortChange(value, { history: "push" });
5887
+ },
5888
+ [controlledSelectedSort, onSelectedSortChange]
5889
+ );
5890
+ const handleCategoryPathChange = useCallback4(
5891
+ (value, options) => {
5892
+ if (controlledCategoryPath == null) setInternalCategoryPath(value);
5893
+ onCategoryPathChange == null ? void 0 : onCategoryPathChange(value, options);
5894
+ },
5895
+ [controlledCategoryPath, onCategoryPathChange]
5896
+ );
5862
5897
  const sortItems = useMemo10(() => {
5863
5898
  return [
5864
5899
  {
@@ -5891,9 +5926,9 @@ var EventList = ({
5891
5926
  const baseCategoryId = (_c = categoryIds == null ? void 0 : categoryIds[0]) != null ? _c : null;
5892
5927
  useEffect8(() => {
5893
5928
  if (!isDiscoveryFiltersEnabled) return;
5894
- setCategoryPath([]);
5929
+ if (controlledCategoryPath == null) setInternalCategoryPath([]);
5895
5930
  setCollapsedCategoryIds(/* @__PURE__ */ new Set());
5896
- }, [baseCategoryId, isDiscoveryFiltersEnabled]);
5931
+ }, [baseCategoryId, controlledCategoryPath, isDiscoveryFiltersEnabled]);
5897
5932
  const { categories: topLevelCategories } = useCategories({
5898
5933
  queryKeyScope: "event-list-top-categories",
5899
5934
  enabled: isDiscoveryFiltersEnabled,
@@ -5905,12 +5940,13 @@ var EventList = ({
5905
5940
  const baseCategoryChildPrefetchIds = useMemo10(() => {
5906
5941
  return Array.from(
5907
5942
  /* @__PURE__ */ new Set([
5943
+ ...baseCategoryId ? [baseCategoryId] : [],
5908
5944
  ...sortedTopLevelCategories.map((category) => category.id),
5909
5945
  ...categoryPath.map((category) => category.id)
5910
5946
  ])
5911
5947
  );
5912
- }, [categoryPath, sortedTopLevelCategories]);
5913
- const { childrenByParentId: baseChildrenByParentId } = useCategoryChildren({
5948
+ }, [baseCategoryId, categoryPath, sortedTopLevelCategories]);
5949
+ const { childrenByParentId: baseChildrenByParentId, isLoading: isBaseCategoryChildrenLoading } = useCategoryChildren({
5914
5950
  queryKeyScope: "event-list-category-children",
5915
5951
  enabled: isDiscoveryFiltersEnabled,
5916
5952
  parentIds: baseCategoryChildPrefetchIds,
@@ -5960,7 +5996,36 @@ var EventList = ({
5960
5996
  ...visibleGrandchildrenByParentId
5961
5997
  ]);
5962
5998
  }, [baseChildrenByParentId, visibleChildrenByParentId, visibleGrandchildrenByParentId]);
5963
- const shouldRenderSubcategoryFilters = isDiscoveryFiltersEnabled && baseCategoryId !== null && (((_e = (_d = childrenByParentId.get(baseCategoryId)) == null ? void 0 : _d.length) != null ? _e : 0) > 0 || categoryPath.length > 0);
5999
+ const controlledCategoryPathKey = (_d = controlledCategoryPath == null ? void 0 : controlledCategoryPath.map((entry) => entry.id).join(",")) != null ? _d : "";
6000
+ const controlledCategoryPathValidationKey = `${baseCategoryId != null ? baseCategoryId : ""}|${controlledCategoryPathKey}`;
6001
+ const [validatedCategoryPathKey, setValidatedCategoryPathKey] = useState7(
6002
+ () => controlledCategoryPath == null || controlledCategoryPath.length === 0 ? controlledCategoryPathValidationKey : ""
6003
+ );
6004
+ useEffect8(() => {
6005
+ if (controlledCategoryPath == null || controlledCategoryPath.length === 0) {
6006
+ setValidatedCategoryPathKey(controlledCategoryPathValidationKey);
6007
+ return;
6008
+ }
6009
+ if (isBaseCategoryChildrenLoading) return;
6010
+ const requestedIds = controlledCategoryPath.map((entry) => entry.id);
6011
+ const validIds = validateSubcategoryPath(requestedIds, baseCategoryId, baseChildrenByParentId);
6012
+ if (validIds.length !== requestedIds.length) {
6013
+ handleCategoryPathChange(controlledCategoryPath.slice(0, validIds.length), {
6014
+ history: "replace"
6015
+ });
6016
+ return;
6017
+ }
6018
+ setValidatedCategoryPathKey(controlledCategoryPathValidationKey);
6019
+ }, [
6020
+ baseCategoryId,
6021
+ baseChildrenByParentId,
6022
+ controlledCategoryPath,
6023
+ controlledCategoryPathKey,
6024
+ controlledCategoryPathValidationKey,
6025
+ handleCategoryPathChange,
6026
+ isBaseCategoryChildrenLoading
6027
+ ]);
6028
+ const shouldRenderSubcategoryFilters = isDiscoveryFiltersEnabled && baseCategoryId !== null && (((_f = (_e = childrenByParentId.get(baseCategoryId)) == null ? void 0 : _e.length) != null ? _f : 0) > 0 || categoryPath.length > 0);
5964
6029
  const baseCategoryEventCount = useMemo10(() => {
5965
6030
  var _a2, _b2;
5966
6031
  return (_b2 = (_a2 = sortedTopLevelCategories.find((c) => c.id === baseCategoryId)) == null ? void 0 : _a2.eventCount) != null ? _b2 : 0;
@@ -6018,7 +6083,8 @@ var EventList = ({
6018
6083
  // markets experience is enabled (the sports event-detail view loads the
6019
6084
  // folded companions via aggKey). Gated on the same flag as that view.
6020
6085
  grouped: showSportsMarkets,
6021
- initialPages: initialLoadedPageCount
6086
+ initialPages: initialLoadedPageCount,
6087
+ enabled: controlledCategoryPath == null || validatedCategoryPathKey === controlledCategoryPathValidationKey
6022
6088
  });
6023
6089
  useEffect8(() => {
6024
6090
  if (stateRef) {
@@ -6079,7 +6145,7 @@ var EventList = ({
6079
6145
  "grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] md:grid-cols-[repeat(auto-fill,minmax(360px,1fr))] gap-4"
6080
6146
  );
6081
6147
  const handleSelectAllCategories = () => {
6082
- setCategoryPath([]);
6148
+ handleCategoryPathChange([], { history: "push" });
6083
6149
  setCollapsedCategoryIds(/* @__PURE__ */ new Set());
6084
6150
  onCategoryRootChange == null ? void 0 : onCategoryRootChange(baseCategoryId != null ? baseCategoryId : void 0);
6085
6151
  setIsSubcategoriesModalOpen(false);
@@ -6091,7 +6157,7 @@ var EventList = ({
6091
6157
  isExpanded,
6092
6158
  isSelected
6093
6159
  }) => {
6094
- setCategoryPath((prev) => [...prev.slice(0, depth), entry]);
6160
+ handleCategoryPathChange([...categoryPath.slice(0, depth), entry], { history: "push" });
6095
6161
  setCollapsedCategoryIds((prev) => {
6096
6162
  const next = new Set(prev);
6097
6163
  if (hasKnownChildren && isExpanded && isSelected) {
@@ -6131,7 +6197,7 @@ var EventList = ({
6131
6197
  ariaLabel: labels.eventList.tabsAria(title),
6132
6198
  className: "agg-event-list-tabs min-w-0 flex-1 max-w-[600px]!",
6133
6199
  activeTab: activeTabValue,
6134
- onTabChange: setActiveTabValue,
6200
+ onTabChange: handleActiveTabChange,
6135
6201
  overflowBehavior: "scroll"
6136
6202
  }
6137
6203
  ),
@@ -6141,7 +6207,7 @@ var EventList = ({
6141
6207
  ariaLabel: labels.eventList.sortByLabel,
6142
6208
  className: "min-w-[200px] w-auto! shrink-0 min-h-10.5",
6143
6209
  value: selectedSort,
6144
- onChange: setSelectedSort,
6210
+ onChange: handleSelectedSortChange,
6145
6211
  items: sortItems,
6146
6212
  contentClassName: "py-2",
6147
6213
  triggerClassName: "rounded-agg-lg!"
@@ -6158,7 +6224,7 @@ var EventList = ({
6158
6224
  ariaLabel: labels.eventList.tabsAria(title),
6159
6225
  className: "agg-event-list-tabs min-w-0 flex-1",
6160
6226
  activeTab: activeTabValue,
6161
- onTabChange: setActiveTabValue,
6227
+ onTabChange: handleActiveTabChange,
6162
6228
  overflowBehavior: "select"
6163
6229
  }
6164
6230
  ),
@@ -6168,7 +6234,7 @@ var EventList = ({
6168
6234
  ariaLabel: labels.eventList.sortByLabel,
6169
6235
  className: "min-w-0 flex-1 md:min-w-[144px]",
6170
6236
  value: selectedSort,
6171
- onChange: setSelectedSort,
6237
+ onChange: handleSelectedSortChange,
6172
6238
  items: sortItems,
6173
6239
  contentClassName: "rounded-[12px] py-2 shadow-[0_8px_16px_rgba(0,0,0,0.1)]"
6174
6240
  }
@@ -6323,6 +6389,8 @@ export {
6323
6389
  MarketDetails,
6324
6390
  MarketDetailsList,
6325
6391
  DEFAULT_EVENTS_LIMIT,
6392
+ DEFAULT_EVENT_LIST_SORT,
6393
+ EVENT_LIST_SORT_VALUES,
6326
6394
  useEventListTabs,
6327
6395
  EventListTabs,
6328
6396
  useEventListTabsHeaderOverflow,