@tapcart/mobile-components 0.14.5 → 0.14.6

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":"icon.d.ts","sourceRoot":"","sources":["../../../components/ui/icon.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAEL,eAAe,EAyChB,MAAM,qBAAqB,CAAA;AAI5B,QAAA,MAAM,YAAY;;;mFAiBjB,CAAA;AA+DD,MAAM,WAAW,SACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,EACzD,YAAY,CAAC,OAAO,YAAY,CAAC;IACnC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B;AAuID,iBAAS,IAAI,CAAC,EACZ,SAAS,EACT,IAAI,EACJ,IAAW,EACX,KAAK,EACL,GAAG,EACH,SAAS,EACT,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,cAAc,EACd,GAAG,KAAK,EACT,EAAE,SAAS,2CA+BX;AAED,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,eAAe,EAAE,CAAA"}
1
+ {"version":3,"file":"icon.d.ts","sourceRoot":"","sources":["../../../components/ui/icon.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAEL,eAAe,EAyChB,MAAM,qBAAqB,CAAA;AAI5B,QAAA,MAAM,YAAY;;;mFAiBjB,CAAA;AA+DD,MAAM,WAAW,SACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,EACzD,YAAY,CAAC,OAAO,YAAY,CAAC;IACnC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B;AA8ID,iBAAS,IAAI,CAAC,EACZ,SAAS,EACT,IAAI,EACJ,IAAW,EACX,KAAK,EACL,GAAG,EACH,SAAS,EACT,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,cAAc,EACd,GAAG,KAAK,EACT,EAAE,SAAS,2CA+BX;AAED,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,eAAe,EAAE,CAAA"}
@@ -11,6 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  return t;
12
12
  };
13
13
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
+ import * as React from "react";
14
15
  import { ReactSVG } from "react-svg";
15
16
  import { cva } from "class-variance-authority";
16
17
  import { IconUpload, IconPencilMinus, IconPlus, IconMinus, IconAdjustmentsAlt, IconArrowsSort, IconAlertCircle, IconCircleX, IconInfoCircle, IconCheck, IconSquareOff, IconMapPin, IconRepeat, IconSquare, IconSquareCheck, IconChevronUp, IconChevronRight, IconChevronLeft, IconEyeOff, IconSquareX, IconCircle, IconChevronDown, IconHeartFilled, IconCurrencyDollar, IconX, IconCircleOff, IconCircleDotFilled, IconMoodSad, IconSquareXFilled, IconTrash, IconLoader2, IconColumns1, IconColumns2, IconColumns3, IconSearch, IconShoppingCartOff, IconStarFilled, IconShoppingBagPlus, IconShieldCheckFilled, IconShoppingBag, IconHeartCancel, IconStar, } from "@tabler/icons-react";
@@ -93,62 +94,72 @@ const TablerIcon = ({ name, size }) => {
93
94
  return IconComponent ? (_jsx(IconComponent, { size: sizeMapping[size], strokeWidth: strokeWidthMapping[size] })) : null;
94
95
  };
95
96
  const CustomIcon = ({ url, size, color, strokeColor, strokeWidth, fillColor, fillPercentage, secondaryFillColor, }) => {
96
- return (_jsx(ReactSVG, { src: url, beforeInjection: (svg) => {
97
- svg.setAttribute("style", `width: ${sizeMapping[size]}px; height: ${sizeMapping[size]}px`);
98
- const paths = svg.querySelectorAll("path");
99
- paths.forEach((path) => {
100
- if (color) {
101
- if (path.hasAttribute("fill") && fillPercentage === 1) {
102
- path.setAttribute("fill", color);
103
- }
104
- path.setAttribute("stroke", color);
97
+ // Stable identity keeps ReactSVG from tearing down and re-injecting on every
98
+ // parent re-render concurrent same-URL injections race inside
99
+ // @tanem/svg-injector (Sentry NEXTJS-CONSUMER-PAGES-78B).
100
+ const beforeInjection = React.useCallback((svg) => {
101
+ svg.setAttribute("style", `width: ${sizeMapping[size]}px; height: ${sizeMapping[size]}px`);
102
+ const paths = svg.querySelectorAll("path");
103
+ paths.forEach((path) => {
104
+ if (color) {
105
+ if (path.hasAttribute("fill") && fillPercentage === 1) {
106
+ path.setAttribute("fill", color);
105
107
  }
106
- else {
107
- if (fillColor &&
108
- path.hasAttribute("fill") &&
109
- fillPercentage === 1) {
110
- path.setAttribute("fill", fillColor);
111
- }
112
- if (strokeColor) {
113
- path.setAttribute("stroke", strokeColor);
114
- }
108
+ path.setAttribute("stroke", color);
109
+ }
110
+ else {
111
+ if (fillColor && path.hasAttribute("fill") && fillPercentage === 1) {
112
+ path.setAttribute("fill", fillColor);
115
113
  }
116
- if (path.hasAttribute("stroke")) {
117
- path.setAttribute("stroke-width", strokeWidthMapping[size].toString());
114
+ if (strokeColor) {
115
+ path.setAttribute("stroke", strokeColor);
118
116
  }
119
- // Override stroke width if one is provided
120
- if (strokeWidth != null && path.hasAttribute("stroke")) {
121
- path.setAttribute("stroke-width", strokeWidth.toString());
117
+ }
118
+ if (path.hasAttribute("stroke")) {
119
+ path.setAttribute("stroke-width", strokeWidthMapping[size].toString());
120
+ }
121
+ // Override stroke width if one is provided
122
+ if (strokeWidth != null && path.hasAttribute("stroke")) {
123
+ path.setAttribute("stroke-width", strokeWidth.toString());
124
+ }
125
+ // Create a gradient fill based on the fillPercentage
126
+ if (fillPercentage && fillPercentage > 0 && fillPercentage < 1) {
127
+ const percentage = Math.min(Math.max(fillPercentage, 0), 1);
128
+ const uniqueId = crypto.randomUUID();
129
+ const fillGradientId = `fill-gradient-${uniqueId}`;
130
+ let defs = svg.querySelector("defs");
131
+ if (!defs) {
132
+ defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
133
+ svg.prepend(defs);
122
134
  }
123
- // Create a gradient fill based on the fillPercentage
124
- if (fillPercentage && fillPercentage > 0 && fillPercentage < 1) {
125
- const percentage = Math.min(Math.max(fillPercentage, 0), 1);
126
- const uniqueId = crypto.randomUUID();
127
- const fillGradientId = `fill-gradient-${uniqueId}`;
128
- let defs = svg.querySelector("defs");
129
- if (!defs) {
130
- defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
131
- svg.prepend(defs);
132
- }
133
- const fillGradient = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient");
134
- fillGradient.setAttribute("id", fillGradientId);
135
- fillGradient.setAttribute("x1", "0%");
136
- fillGradient.setAttribute("y1", "0%");
137
- fillGradient.setAttribute("x2", "100%");
138
- fillGradient.setAttribute("y2", "0%");
139
- const fillStop1 = document.createElementNS("http://www.w3.org/2000/svg", "stop");
140
- fillStop1.setAttribute("offset", `${percentage * 100}%`);
141
- fillStop1.setAttribute("stop-color", color || fillColor || "transparent");
142
- fillGradient.appendChild(fillStop1);
143
- const fillStop2 = document.createElementNS("http://www.w3.org/2000/svg", "stop");
144
- fillStop2.setAttribute("offset", `${percentage * 100}%`);
145
- fillStop2.setAttribute("stop-color", secondaryFillColor || "transparent");
146
- fillGradient.appendChild(fillStop2);
147
- defs.appendChild(fillGradient);
148
- path.setAttribute("fill", `url(#${fillGradientId})`);
149
- }
150
- });
151
- } }));
135
+ const fillGradient = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient");
136
+ fillGradient.setAttribute("id", fillGradientId);
137
+ fillGradient.setAttribute("x1", "0%");
138
+ fillGradient.setAttribute("y1", "0%");
139
+ fillGradient.setAttribute("x2", "100%");
140
+ fillGradient.setAttribute("y2", "0%");
141
+ const fillStop1 = document.createElementNS("http://www.w3.org/2000/svg", "stop");
142
+ fillStop1.setAttribute("offset", `${percentage * 100}%`);
143
+ fillStop1.setAttribute("stop-color", color || fillColor || "transparent");
144
+ fillGradient.appendChild(fillStop1);
145
+ const fillStop2 = document.createElementNS("http://www.w3.org/2000/svg", "stop");
146
+ fillStop2.setAttribute("offset", `${percentage * 100}%`);
147
+ fillStop2.setAttribute("stop-color", secondaryFillColor || "transparent");
148
+ fillGradient.appendChild(fillStop2);
149
+ defs.appendChild(fillGradient);
150
+ path.setAttribute("fill", `url(#${fillGradientId})`);
151
+ }
152
+ });
153
+ }, [
154
+ size,
155
+ color,
156
+ fillColor,
157
+ strokeColor,
158
+ strokeWidth,
159
+ fillPercentage,
160
+ secondaryFillColor,
161
+ ]);
162
+ return _jsx(ReactSVG, { src: url, beforeInjection: beforeInjection });
152
163
  };
153
164
  function Icon(_a) {
154
165
  var { className, name, size = "md", color, url, fillColor, secondaryFillColor, strokeColor, strokeWidth, fillPercentage } = _a, props = __rest(_a, ["className", "name", "size", "color", "url", "fillColor", "secondaryFillColor", "strokeColor", "strokeWidth", "fillPercentage"]);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=icon-before-injection-identity.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icon-before-injection-identity.test.d.ts","sourceRoot":"","sources":["../../tests/icon-before-injection-identity.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from "@testing-library/react";
3
+ import { Icon } from "../components/ui/icon";
4
+ // A stable beforeInjection identity is what stops react-svg's shallowDiffers
5
+ // check from tearing down and re-injecting the SVG on every parent re-render —
6
+ // the amplifier behind Sentry NEXTJS-CONSUMER-PAGES-78B.
7
+ const mockBeforeInjections = [];
8
+ jest.mock("react-svg", () => ({
9
+ ReactSVG: ({ beforeInjection, }) => {
10
+ mockBeforeInjections.push(beforeInjection);
11
+ return null;
12
+ },
13
+ }));
14
+ describe("Icon beforeInjection identity", () => {
15
+ beforeEach(() => {
16
+ mockBeforeInjections.length = 0;
17
+ });
18
+ it("keeps the same beforeInjection reference across rerenders with identical props", () => {
19
+ const { rerender } = render(_jsx(Icon, { url: "https://assets.example.com/icon.svg", color: "red", size: "md" }));
20
+ rerender(_jsx(Icon, { url: "https://assets.example.com/icon.svg", color: "red", size: "md" }));
21
+ expect(mockBeforeInjections).toHaveLength(2);
22
+ expect(mockBeforeInjections[1]).toBe(mockBeforeInjections[0]);
23
+ });
24
+ it("produces a new beforeInjection reference when a styling prop changes", () => {
25
+ const { rerender } = render(_jsx(Icon, { url: "https://assets.example.com/icon.svg", color: "red", size: "md" }));
26
+ rerender(_jsx(Icon, { url: "https://assets.example.com/icon.svg", color: "blue", size: "md" }));
27
+ expect(mockBeforeInjections).toHaveLength(2);
28
+ expect(mockBeforeInjections[1]).not.toBe(mockBeforeInjections[0]);
29
+ });
30
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=svg-injector-double-delivery.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svg-injector-double-delivery.test.d.ts","sourceRoot":"","sources":["../../tests/svg-injector-double-delivery.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,117 @@
1
+ import { SVGInjector } from "@tanem/svg-injector";
2
+ // Deterministic repro of Sentry NEXTJS-CONSUMER-PAGES-78B: @tanem/svg-injector
3
+ // shares one module-level request queue + cache per URL; a cached Error
4
+ // re-arms the cache and re-issues the XHR while the old queue entries stay
5
+ // live, so a later successful pass re-delivers to an injection that already
6
+ // finalized (el = null) and crashes on el.getAttribute("id"). Our pnpm patch
7
+ // (patches/@tanem__svg-injector@10.1.68.patch) drops the stale delivery.
8
+ class MockXHR {
9
+ constructor() {
10
+ this.onreadystatechange = null;
11
+ this.readyState = 0;
12
+ this.status = 0;
13
+ this.statusText = "";
14
+ this.responseXML = null;
15
+ this.withCredentials = false;
16
+ MockXHR.instances.push(this);
17
+ }
18
+ open() { }
19
+ send() { }
20
+ overrideMimeType() { }
21
+ getResponseHeader() {
22
+ return "image/svg+xml";
23
+ }
24
+ // No-op: the spec's abort-refires-readystatechange aggravator is excluded
25
+ // so the scripted interleaving stays exact.
26
+ abort() { }
27
+ fail() {
28
+ var _a;
29
+ this.readyState = 4;
30
+ this.status = 404;
31
+ this.responseXML = null;
32
+ (_a = this.onreadystatechange) === null || _a === void 0 ? void 0 : _a.call(this);
33
+ }
34
+ succeed(svgMarkup) {
35
+ var _a;
36
+ this.readyState = 4;
37
+ this.status = 200;
38
+ this.responseXML = new DOMParser().parseFromString(svgMarkup, "image/svg+xml");
39
+ (_a = this.onreadystatechange) === null || _a === void 0 ? void 0 : _a.call(this);
40
+ }
41
+ }
42
+ MockXHR.instances = [];
43
+ const makeTarget = (url) => {
44
+ const wrapper = document.createElement("div");
45
+ wrapper.setAttribute("data-src", url);
46
+ const container = document.createElement("div");
47
+ container.appendChild(wrapper);
48
+ document.body.appendChild(container);
49
+ return { wrapper, container };
50
+ };
51
+ const realXHR = window.XMLHttpRequest;
52
+ describe("svg-injector stale queue re-delivery", () => {
53
+ beforeEach(() => {
54
+ jest.useFakeTimers();
55
+ window.XMLHttpRequest = MockXHR;
56
+ MockXHR.instances.length = 0;
57
+ });
58
+ afterEach(() => {
59
+ jest.useRealTimers();
60
+ window.XMLHttpRequest = realXHR;
61
+ document.body.innerHTML = "";
62
+ });
63
+ it("drops re-delivery to a finalized injection instead of crashing, and still injects the live target", () => {
64
+ const url = "https://assets.example.com/double-delivery.svg";
65
+ const targetA = makeTarget(url);
66
+ const targetC = makeTarget(url);
67
+ const targetE = makeTarget(url);
68
+ const deliveries = [];
69
+ // 1. A starts the request for U -> queue [cbA], XHR1 in flight.
70
+ SVGInjector(targetA.wrapper, {
71
+ afterEach: (error) => deliveries.push({ id: "A", error }),
72
+ });
73
+ expect(MockXHR.instances).toHaveLength(1);
74
+ // XHR1 errors -> cache = Error, error pass P1 scheduled via setTimeout(0).
75
+ MockXHR.instances[0].fail();
76
+ // 2. C injects U before P1's timer fires: the cached Error falls through,
77
+ // re-arms the cache to undefined and re-issues the XHR -> queue
78
+ // [cbA, cbC], XHR2 in flight.
79
+ SVGInjector(targetC.wrapper, {
80
+ afterEach: (error) => {
81
+ deliveries.push({ id: "C", error });
82
+ // 4. E injects U from inside C's error delivery (pass-2, index 1):
83
+ // the queue grows to [cbA, cbC, cbE], so the pass's last-index
84
+ // cleanup never fires and the finalized cbA stays queued.
85
+ SVGInjector(targetE.wrapper, {
86
+ afterEach: (innerError) => deliveries.push({ id: "E", error: innerError }),
87
+ });
88
+ },
89
+ });
90
+ expect(MockXHR.instances).toHaveLength(2);
91
+ // 3. P1 runs against the re-armed (undefined) cache: no delivery, and the
92
+ // queue is not cleaned up because it grew past P1's snapshot.
93
+ jest.runOnlyPendingTimers();
94
+ expect(deliveries).toHaveLength(0);
95
+ // XHR2 errors -> pass P2 delivers the error to A (finalizing it, el =
96
+ // null) and to C, whose callback injects E and re-issues XHR3.
97
+ MockXHR.instances[1].fail();
98
+ jest.runOnlyPendingTimers();
99
+ expect(deliveries.map((d) => d.id)).toEqual(["A", "C"]);
100
+ expect(MockXHR.instances).toHaveLength(3);
101
+ // XHR3 succeeds -> pass P3 walks the surviving queue from index 0,
102
+ // re-delivering a cloned SVG to the finalized cbA. Unpatched, this throws
103
+ // TypeError (el.getAttribute on null) out of the timer flush.
104
+ MockXHR.instances[2].succeed('<svg xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z"/></svg>');
105
+ expect(() => jest.runOnlyPendingTimers()).not.toThrow();
106
+ // The stale deliveries were dropped, not double-fired.
107
+ expect(deliveries.filter((d) => d.id === "A")).toHaveLength(1);
108
+ expect(deliveries.filter((d) => d.id === "C")).toHaveLength(1);
109
+ // The live target E still completes: its wrapper is replaced with the SVG.
110
+ expect(deliveries).toEqual([
111
+ { id: "A", error: expect.any(Error) },
112
+ { id: "C", error: expect.any(Error) },
113
+ { id: "E", error: null },
114
+ ]);
115
+ expect(targetE.container.querySelector("svg.injected-svg")).not.toBeNull();
116
+ });
117
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tapcart/mobile-components",
3
- "version": "0.14.5",
3
+ "version": "0.14.6",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "style": "dist/styles.css",