@tapcart/mobile-components 0.17.2 → 0.18.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.
@@ -1 +1 @@
1
- {"version":3,"file":"swr-retry.d.ts","sourceRoot":"","sources":["../../../components/hooks/swr-retry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAE3C,eAAO,MAAM,WAAW,IAAI,CAAA;AAC5B,eAAO,MAAM,gBAAgB,MAAM,CAAA;AACnC,eAAO,MAAM,kBAAkB,OAAO,CAAA;AAOtC,eAAO,MAAM,YAAY,QAAS,OAAO,KAAG,OAG3C,CAAA;AAED,eAAO,MAAM,kBAAkB,QAAS,OAAO,KAAG,OAA6B,CAAA;AAiC/E,KAAK,YAAY,GAAG,WAAW,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC,CAAA;AACjE,KAAK,SAAS,GAAG,WAAW,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAA;AAE3D,MAAM,MAAM,cAAc,GAAG;IAC3B,eAAe,EAAE,MAAM,CAAA;IACvB,kBAAkB,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,CAAA;IAC1D,YAAY,EAAE,YAAY,CAAA;IAC1B,SAAS,EAAE,SAAS,CAAA;IACpB,UAAU,EAAE,OAAO,CAAA;CACpB,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,QAAO,cAkCpC,CAAA"}
1
+ {"version":3,"file":"swr-retry.d.ts","sourceRoot":"","sources":["../../../components/hooks/swr-retry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAE3C,eAAO,MAAM,WAAW,IAAI,CAAA;AAC5B,eAAO,MAAM,gBAAgB,MAAM,CAAA;AACnC,eAAO,MAAM,kBAAkB,OAAO,CAAA;AAOtC,eAAO,MAAM,YAAY,QAAS,OAAO,KAAG,OAG3C,CAAA;AAmDD,eAAO,MAAM,kBAAkB,QAAS,OAAO,KAAG,OACE,CAAA;AAiCpD,KAAK,YAAY,GAAG,WAAW,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC,CAAA;AACjE,KAAK,SAAS,GAAG,WAAW,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAA;AAE3D,MAAM,MAAM,cAAc,GAAG;IAC3B,eAAe,EAAE,MAAM,CAAA;IACvB,kBAAkB,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,CAAA;IAC1D,YAAY,EAAE,YAAY,CAAA;IAC1B,SAAS,EAAE,SAAS,CAAA;IACpB,UAAU,EAAE,OAAO,CAAA;CACpB,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,QAAO,cAkCpC,CAAA"}
@@ -8,7 +8,53 @@ export const isAbortError = (err) => {
8
8
  return false;
9
9
  return err.name === "AbortError";
10
10
  };
11
- export const shouldRetryOnError = (err) => !isAbortError(err);
11
+ /**
12
+ * The 4xx statuses that mean "later", not "never" — the request may well succeed
13
+ * unchanged, so retrying is not wasted.
14
+ *
15
+ * 425 Too Early is deliberately absent: it only arises from TLS 1.3 early data,
16
+ * which we never send, so treating it as retryable would describe a state this
17
+ * client cannot reach.
18
+ */
19
+ const RETRYABLE_CLIENT_STATUSES = new Set([
20
+ // The server gave up waiting for the request, rather than refusing what it
21
+ // received. Nothing about the request is wrong, so a retry is the correct
22
+ // response — note `getRetryAfterMs` still only honors `Retry-After` on 429,
23
+ // so a 408 takes the normal backoff.
24
+ 408,
25
+ 429,
26
+ ]);
27
+ /**
28
+ * A 4xx outside RETRYABLE_CLIENT_STATUSES is deterministic: the request itself
29
+ * is what the upstream refused, so every retry earns the identical rejection.
30
+ *
31
+ * WHY THIS MATTERS: retrying spends the upstream's quota for no chance of
32
+ * success. Google Retail calls are billed and quota'd against the *merchant's*
33
+ * GCP project, and on 2026-08-14 one store's filter — referencing a catalog
34
+ * attribute that was never marked indexable — was retried 4x per page view
35
+ * until it exhausted that quota. Google then 429'd every search for that store,
36
+ * including the ones that would have succeeded. A permanent rejection has to
37
+ * cost one call, not four.
38
+ *
39
+ * This predicate is shared across consumers (the cluster feed included), which
40
+ * is why the retryable set is defined by HTTP semantics rather than by what any
41
+ * one upstream happens to emit. The google-retail proxy collapses every upstream
42
+ * 4xx to 400 or 429 (see tapcart-microservices integrations/app/googleRetail/
43
+ * errors.js), so 408 reaches this predicate only from the other consumers.
44
+ *
45
+ * Errors carrying no `status` keep the previous behaviour (retry unless
46
+ * aborted), so integrations that do not populate it are unaffected.
47
+ */
48
+ const isPermanentClientError = (err) => {
49
+ if (!err || typeof err !== "object")
50
+ return false;
51
+ const { status } = err;
52
+ return (typeof status === "number" &&
53
+ status >= 400 &&
54
+ status < 500 &&
55
+ !RETRYABLE_CLIENT_STATUSES.has(status));
56
+ };
57
+ export const shouldRetryOnError = (err) => !isAbortError(err) && !isPermanentClientError(err);
12
58
  const parseRetryAfter = (value) => {
13
59
  if (value === null || value === undefined)
14
60
  return null;
@@ -31,6 +31,85 @@ describe("swr-retry helpers", () => {
31
31
  expect(shouldRetryOnError(new Error("boom"))).toBe(true);
32
32
  expect(shouldRetryOnError({ status: 500 })).toBe(true);
33
33
  });
34
+ // A 4xx is deterministic — retrying it burns the upstream's quota for no
35
+ // chance of success. This is the guard that stopped one bad Google Retail
36
+ // filter costing 4 API calls per page view.
37
+ it("returns false for a 4xx, which cannot succeed on retry", () => {
38
+ expect(shouldRetryOnError({ status: 400 })).toBe(false);
39
+ expect(shouldRetryOnError({ status: 403 })).toBe(false);
40
+ expect(shouldRetryOnError({ status: 404 })).toBe(false);
41
+ expect(shouldRetryOnError({ status: 499 })).toBe(false);
42
+ });
43
+ // 429 is the 4xx that means "later", not "never".
44
+ it("returns true for 429 so Retry-After is honored", () => {
45
+ expect(shouldRetryOnError({ status: 429 })).toBe(true);
46
+ });
47
+ /**
48
+ * 408 is the other one: the server gave up waiting for the request rather
49
+ * than refusing what it received, so the same request may well succeed. The
50
+ * google-retail proxy collapses upstream 408s to 400 before they get here,
51
+ * so this matters for the cluster feed and any future consumer — which is
52
+ * the reason the retryable set is defined by HTTP semantics and not by what
53
+ * one upstream emits.
54
+ */
55
+ it("returns true for 408, which is transient rather than deterministic", () => {
56
+ expect(shouldRetryOnError({ status: 408 })).toBe(true);
57
+ const clusterFeed408 = Object.assign(new Error("Cluster feed HTTP 408"), {
58
+ reason: "cluster-feed-http",
59
+ status: 408,
60
+ retryAfter: null,
61
+ });
62
+ expect(shouldRetryOnError(clusterFeed408)).toBe(true);
63
+ });
64
+ // 425 Too Early is retryable in the abstract but unreachable here — we send
65
+ // no TLS early data — so it stays in the permanent set deliberately.
66
+ it("returns false for 425, which this client cannot receive", () => {
67
+ expect(shouldRetryOnError({ status: 425 })).toBe(false);
68
+ });
69
+ it("returns true for 5xx and for statuses below 400", () => {
70
+ expect(shouldRetryOnError({ status: 500 })).toBe(true);
71
+ expect(shouldRetryOnError({ status: 503 })).toBe(true);
72
+ expect(shouldRetryOnError({ status: 399 })).toBe(true);
73
+ });
74
+ // Integrations that never set `status` must behave exactly as before.
75
+ it("retries when status is absent or not a number", () => {
76
+ expect(shouldRetryOnError(new Error("network down"))).toBe(true);
77
+ expect(shouldRetryOnError({ status: undefined })).toBe(true);
78
+ expect(shouldRetryOnError({ status: "400" })).toBe(true);
79
+ expect(shouldRetryOnError({})).toBe(true);
80
+ });
81
+ // An abort still wins even if a status rode along.
82
+ it("returns false for an AbortError carrying a retryable status", () => {
83
+ const err = Object.assign(new Error("aborted"), {
84
+ name: "AbortError",
85
+ status: 500,
86
+ });
87
+ expect(shouldRetryOnError(err)).toBe(false);
88
+ });
89
+ it("does not throw on null / undefined / primitives", () => {
90
+ expect(shouldRetryOnError(null)).toBe(true);
91
+ expect(shouldRetryOnError(undefined)).toBe(true);
92
+ expect(shouldRetryOnError(404)).toBe(true);
93
+ });
94
+ /**
95
+ * This hook is shared. Both known callers — use-infinite-scroll (the
96
+ * search-client path) and usePersonalizedClusterFeed — throw errors that
97
+ * carry `status`, so both stop retrying 4xx. That is deliberate: a 4xx is
98
+ * deterministic whichever API produced it, and the cluster feed already
99
+ * attached `status`/`retryAfter` (see use-personalized-cluster-feed-core.ts
100
+ * :258) purely so this layer could act on them.
101
+ */
102
+ it("applies to the cluster-feed error shape too, not just search", () => {
103
+ const clusterFeed4xx = Object.assign(new Error("Cluster feed HTTP 404"), {
104
+ reason: "cluster-feed-http",
105
+ status: 404,
106
+ retryAfter: null,
107
+ });
108
+ expect(shouldRetryOnError(clusterFeed4xx)).toBe(false);
109
+ // A network failure there has no status, so it still retries.
110
+ const clusterFeedNetwork = Object.assign(new Error("Cluster feed request failed"), { reason: "cluster-feed-network" });
111
+ expect(shouldRetryOnError(clusterFeedNetwork)).toBe(true);
112
+ });
34
113
  });
35
114
  });
36
115
  describe("useSwrRetryConfig", () => {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-infinite-scroll-sort-key.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-infinite-scroll-sort-key.test.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-infinite-scroll-sort-key.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,68 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { createElement } from "react";
11
+ import { renderHook, waitFor } from "@testing-library/react";
12
+ import { SWRConfig } from "swr";
13
+ import useSWRInfinite from "swr/infinite";
14
+ // Mirrors the client contract: getKey exposes only the shopper-chosen sort,
15
+ // and the provider default arrives while the page-1 response is handled.
16
+ const makeClient = (hydrateInto) => ({
17
+ userSort: "",
18
+ displaySort: "",
19
+ getKey(pageIndex, previousPageData) {
20
+ if (previousPageData && !previousPageData.hasMore)
21
+ return null;
22
+ return {
23
+ page: pageIndex + 1,
24
+ sort: this.userSort,
25
+ collectionId: "12345",
26
+ };
27
+ },
28
+ onResponse() {
29
+ if (!this.userSort)
30
+ this[hydrateInto] = "creation_date";
31
+ },
32
+ });
33
+ // SWR's cache is global, so each test needs its own provider or the second
34
+ // render would read the first one's page-1 entry and never fetch.
35
+ const wrapper = ({ children }) => createElement(SWRConfig, { value: { provider: () => new Map() } }, children);
36
+ const renderPages = (client, fetcher) => renderHook(() => useSWRInfinite((i, prev) => client.getKey(i, prev), fetcher, {
37
+ revalidateFirstPage: false,
38
+ revalidateOnFocus: false,
39
+ revalidateOnMount: true,
40
+ dedupingInterval: 0,
41
+ }), { wrapper });
42
+ const makeFetcher = (client) => jest.fn((key) => __awaiter(void 0, void 0, void 0, function* () {
43
+ client.onResponse();
44
+ return { products: [{ id: `p${key.page}` }], hasMore: false };
45
+ }));
46
+ describe("useSWRInfinite key stability across page-1 sort hydration", () => {
47
+ it("refetches page 1 when the response hydrates a key-visible field", () => __awaiter(void 0, void 0, void 0, function* () {
48
+ const client = makeClient("userSort");
49
+ const fetcher = makeFetcher(client);
50
+ const { result } = renderPages(client, fetcher);
51
+ yield waitFor(() => expect(result.current.data).toBeDefined());
52
+ yield waitFor(() => expect(fetcher).toHaveBeenCalledTimes(2));
53
+ // Proves the harness detects the regression: the key moved, so SWR issued
54
+ // a second page-1 request, this time carrying the hydrated sort.
55
+ expect(fetcher.mock.calls[0][0].sort).toBe("");
56
+ expect(fetcher.mock.calls[1][0].sort).toBe("creation_date");
57
+ }));
58
+ it("fetches page 1 once when the response hydrates a display-only field", () => __awaiter(void 0, void 0, void 0, function* () {
59
+ const client = makeClient("displaySort");
60
+ const fetcher = makeFetcher(client);
61
+ const { result } = renderPages(client, fetcher);
62
+ yield waitFor(() => expect(result.current.data).toBeDefined());
63
+ expect(fetcher).toHaveBeenCalledTimes(1);
64
+ expect(fetcher.mock.calls[0][0].sort).toBe("");
65
+ // The default is still available to label the sort drawer.
66
+ expect(client.displaySort).toBe("creation_date");
67
+ }));
68
+ });
@@ -5,6 +5,13 @@ interface UseRecommendationProps {
5
5
  customSearchConfig?: Record<string, any>;
6
6
  queryVariables: Record<string, any>;
7
7
  apiURL: string;
8
+ /**
9
+ * Opt out of fetching entirely. Rules of Hooks force callers to invoke this
10
+ * hook unconditionally, and an empty query otherwise counts as "fetch
11
+ * trending", so a caller that only sometimes wants recommendations has no
12
+ * other way to stay quiet. Defaults to true to preserve existing behavior.
13
+ */
14
+ enabled?: boolean;
8
15
  }
9
16
  interface UseRecommendationReturn {
10
17
  products: Product[];
@@ -14,6 +21,6 @@ interface UseRecommendationReturn {
14
21
  isLoading: boolean;
15
22
  error: any;
16
23
  }
17
- declare const useRecommendations: ({ searchClient, query, customSearchConfig, queryVariables, apiURL, }: UseRecommendationProps) => UseRecommendationReturn;
24
+ declare const useRecommendations: ({ searchClient, query, customSearchConfig, queryVariables, apiURL, enabled, }: UseRecommendationProps) => UseRecommendationReturn;
18
25
  export { useRecommendations };
19
26
  //# sourceMappingURL=use-recommendations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-recommendations.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-recommendations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAOxE,UAAU,sBAAsB;IAE9B,YAAY,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAGxC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,MAAM,EAAE,MAAM,CAAA;CACf;AAUD,UAAU,uBAAuB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,MAAM,EAAE,GAAG,EAAE,CAAA;IACb,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,GAAG,CAAA;CACX;AAmBD,QAAA,MAAM,kBAAkB,yEASrB,sBAAsB,KAAG,uBA6H3B,CAAA;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
1
+ {"version":3,"file":"use-recommendations.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-recommendations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAOxE,UAAU,sBAAsB;IAE9B,YAAY,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAGxC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,MAAM,EAAE,MAAM,CAAA;IAEd;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAUD,UAAU,uBAAuB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,MAAM,EAAE,GAAG,EAAE,CAAA;IACb,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,GAAG,CAAA;CACX;AAmBD,QAAA,MAAM,kBAAkB,kFAWrB,sBAAsB,KAAG,uBA8H3B,CAAA;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
@@ -27,7 +27,7 @@ const useRecommendations = ({
27
27
  // Search client props
28
28
  searchClient, query = "", customSearchConfig,
29
29
  // API props (backwards compatibility)
30
- queryVariables, apiURL, }) => {
30
+ queryVariables, apiURL, enabled = true, }) => {
31
31
  // When searchClient is provided, use search client approach
32
32
  const usingSearchClient = Boolean(searchClient);
33
33
  const searchParams = useSearchParams();
@@ -39,9 +39,10 @@ queryVariables, apiURL, }) => {
39
39
  // - query is empty → show trending searches
40
40
  // - query meets minimum length (≥3 chars)
41
41
  // - searchClient has a customSearchConfig (e.g., filter-only queries on PDP)
42
- const shouldFetch = !recommendation.trim().length ||
43
- recommendation.length >= MIN_QUERY_LENGTH ||
44
- (usingSearchClient && Boolean(customSearchConfig));
42
+ const shouldFetch = enabled &&
43
+ (!recommendation.trim().length ||
44
+ recommendation.length >= MIN_QUERY_LENGTH ||
45
+ (usingSearchClient && Boolean(customSearchConfig)));
45
46
  const [cachedRecommendation, setCachedRecommendations] = React.useState(recommendationsLocalStorage.getCacheItem({
46
47
  id: `${recommendation}-${queryVariables === null || queryVariables === void 0 ? void 0 : queryVariables.language}`,
47
48
  }));
@@ -82,4 +82,25 @@ describe("useRecommendations", () => {
82
82
  // Empty query now triggers a trending fetch regardless of searchClient
83
83
  expect(result.current.isLoading).toBe(true);
84
84
  });
85
+ describe("enabled", () => {
86
+ it("should not fetch when enabled is false", () => {
87
+ const { result } = renderHook(() => useRecommendations(Object.assign({ searchClient: mockSearchClient, query: "", enabled: false }, defaultProps)));
88
+ expect(result.current.products).toEqual([]);
89
+ expect(result.current.isLoading).toBe(false);
90
+ });
91
+ it("should not fetch the legacy API path when enabled is false", () => {
92
+ // Collection-mode carousels land here: no searchClient and an empty
93
+ // query, which would otherwise POST to /{appId}/api/v1/recommendations.
94
+ const { result } = renderHook(() => useRecommendations(Object.assign({ query: "", enabled: false }, defaultProps)));
95
+ expect(result.current.isLoading).toBe(false);
96
+ });
97
+ it("should fetch when enabled is omitted, preserving existing behavior", () => {
98
+ const { result } = renderHook(() => useRecommendations(Object.assign({ searchClient: mockSearchClient, query: "" }, defaultProps)));
99
+ expect(result.current.isLoading).toBe(true);
100
+ });
101
+ it("should fetch when enabled is explicitly true", () => {
102
+ const { result } = renderHook(() => useRecommendations(Object.assign({ searchClient: mockSearchClient, query: "abc", enabled: true }, defaultProps)));
103
+ expect(result.current.isLoading).toBe(true);
104
+ });
105
+ });
85
106
  });
@@ -88,7 +88,7 @@ const useSortFilter = ({ initialData, queryVariables, dynamicKey, searchClient,
88
88
  relatedCategories: [],
89
89
  },
90
90
  sortOptions: [],
91
- dynamicFiltersEnabled: false,
91
+ dynamicFiltersEnabled: searchClient.isDynamicFiltersEnabled(),
92
92
  integrations: [],
93
93
  },
94
94
  data: [],
package/dist/styles.css CHANGED
@@ -1124,6 +1124,9 @@ video {
1124
1124
  .hidden {
1125
1125
  display: none;
1126
1126
  }
1127
+ .aspect-\[2\/3\] {
1128
+ aspect-ratio: 2/3;
1129
+ }
1127
1130
  .aspect-\[327\/280\] {
1128
1131
  aspect-ratio: 327/280;
1129
1132
  }
@@ -1845,6 +1848,9 @@ video {
1845
1848
  .items-baseline {
1846
1849
  align-items: baseline;
1847
1850
  }
1851
+ .items-stretch {
1852
+ align-items: stretch;
1853
+ }
1848
1854
  .justify-start {
1849
1855
  justify-content: flex-start;
1850
1856
  }
@@ -2238,6 +2244,9 @@ video {
2238
2244
  --tw-bg-opacity: 1;
2239
2245
  background-color: rgb(253 255 218 / var(--tw-bg-opacity, 1));
2240
2246
  }
2247
+ .bg-\[var\(--element-divider-color\2c var\(--coreColors-dividingLines\)\)\] {
2248
+ background-color: var(--element-divider-color,var(--coreColors-dividingLines));
2249
+ }
2241
2250
  .bg-\[var\(--stateColors-skeleton\)\] {
2242
2251
  background-color: var(--stateColors-skeleton);
2243
2252
  }
@@ -3107,6 +3116,9 @@ video {
3107
3116
  --tw-grayscale: grayscale(100%);
3108
3117
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
3109
3118
  }
3119
+ .\!filter {
3120
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) !important;
3121
+ }
3110
3122
  .filter {
3111
3123
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
3112
3124
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tapcart/mobile-components",
3
- "version": "0.17.2",
3
+ "version": "0.18.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "style": "dist/styles.css",
@@ -80,8 +80,8 @@
80
80
  "clsx": "^1.2.1",
81
81
  "dayjs": "^1.11.13",
82
82
  "dompurify": "^3.2.2",
83
- "embla-carousel-autoplay": "8.5.2",
84
- "embla-carousel-fade": "8.5.2",
83
+ "embla-carousel-autoplay": "8.6.0",
84
+ "embla-carousel-fade": "8.6.0",
85
85
  "embla-carousel-react": "^8.3.0",
86
86
  "input-otp": "^1.4.2",
87
87
  "lucide-react": "^0.488.0",