@gait-financial/react 0.1.11 → 0.1.13

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
@@ -1,10 +1,8 @@
1
1
  # @gait-financial/react
2
2
 
3
- React wrappers for Gait Web Components. Thin, type-safe bindings that map React props to the underlying custom elements.
3
+ React wrapper for Gait Web Components. Use `<GaitButton>` with `data-id` and optional props for checkout/split payment.
4
4
 
5
- The button always shows **"Gait"** with the Gait logo icon and uses brand color **#4147BF**. No `label`, `theme`, or `brandColor` props.
6
-
7
- ## Installation
5
+ ## Install
8
6
 
9
7
  ```bash
10
8
  npm install @gait-financial/react
@@ -12,103 +10,46 @@ npm install @gait-financial/react
12
10
 
13
11
  ## Setup
14
12
 
15
- ### 1. Register the Web Component
16
-
17
- Import the registration module once (e.g. in `_app.tsx`, `layout.tsx`, or your root component):
13
+ **1. Register once** (e.g. in your root or layout):
18
14
 
19
15
  ```tsx
20
16
  import '@gait-financial/react/register';
21
17
  ```
22
18
 
23
- This loads and registers the Gait Web Components in the browser. Safe for SSR—no-op when `window`/`customElements` are unavailable.
24
-
25
- **Alternative:** Load the Web Component via script tag and skip the register import:
26
-
27
- ```html
28
- <script src="https://cdn.usegait.com/button.js"></script>
29
- ```
30
-
31
- ### 2. Use the Components
19
+ **2. Use the component:**
32
20
 
33
21
  ```tsx
34
22
  import { GaitButton } from '@gait-financial/react';
35
23
 
36
- function Checkout() {
37
- return (
38
- <GaitButton
39
- data-id="your-checkout-id"
40
- totalCost={99.99}
41
- onClick={(detail) => console.log('Clicked', detail)}
42
- onSplitFeedback={(detail) => {
43
- if (detail.success) console.log('Split sent!');
44
- else console.error('Failed', detail.error);
45
- }}
46
- />
47
- );
48
- }
24
+ <GaitButton
25
+ data-id="your-checkout-id"
26
+ totalCost={99.99}
27
+ onSplitFeedback={(d) => console.log(d.success ? 'OK' : d.error)}
28
+ />
49
29
  ```
50
30
 
51
- ## GaitButton Props
52
-
53
- | Prop | Type | Description |
54
- |------|------|-------------|
55
- | `data-id` | `string` | Checkout/data identity (required for payments) |
56
- | `disabled` | `boolean` | Disable the button |
57
- | `size` | `'small' \| 'medium' \| 'large'` | Button size |
58
- | `style` | `string \| React.CSSProperties` | Inline styles |
59
- | `items` | `GaitButtonItem[] \| string` | Cart items for split payment modal |
60
- | `priceBreakdown` | `GaitPriceBreakdownItem[] \| string` | Price breakdown |
61
- | `totalCost` | `number \| string` | Total cost |
62
- | `customer` | `GaitCustomer \| string` | Customer info for split payment |
63
- | `webhook` | `GaitWebhook \| string` | Webhook config for split callbacks |
64
- | `merchantStoreUrl` | `string` | Merchant store URL |
65
- | `onClick` | `(detail) => void` | Button clicked |
66
- | `onLoaded` | `(detail) => void` | Component loaded |
67
- | `onDataProcessed` | `(detail) => void` | Data processed |
68
- | `onSplitFeedback` | `(detail) => void` | Split payment API result |
69
- | `onMerchantIdError` | `(detail) => void` | Merchant ID lookup failed |
70
- | `onConfirm` | `(detail) => void` | Pay remaining confirmed |
71
-
72
- The button label is always **"Gait"** (with icon). Brand color is fixed to **#4147BF**; split-option styling uses **#F3F2FF**.
73
-
74
31
  ## Vite
75
32
 
76
- If using Vite, add this to `vite.config.ts` so the web component script loads correctly:
33
+ Add to `vite.config.ts` so the web component script loads:
77
34
 
78
35
  ```ts
79
- export default defineConfig({
80
- optimizeDeps: {
81
- exclude: ['@gait-financial/react'],
82
- },
83
- // ...
84
- });
36
+ optimizeDeps: { exclude: ['@gait-financial/react'] },
85
37
  ```
86
38
 
87
- ## Next.js
88
-
89
- The package uses `"use client"` directives. Import and use in Client Components:
90
-
91
- ```tsx
92
- 'use client';
93
-
94
- import '@gait-financial/react/register';
95
- import { GaitButton } from '@gait-financial/react';
96
-
97
- export default function CheckoutPage() {
98
- return <GaitButton data-id="your-checkout-id" />;
99
- }
100
- ```
101
-
102
- ## Architecture
103
-
104
- This package is a thin wrapper. The Web Component implementation is the source of truth. The structure allows adding other framework packages (e.g. `@gait/vue`, `@gait/svelte`) later without refactoring the core.
39
+ ## Builds (from repo root)
105
40
 
106
- ### Adding @gait/vue later
41
+ - **Production:** `npm run publish:react` — builds WC + React package, publishes to `latest`
42
+ - **Staging:** `npm run publish:react:staging` — builds with `.env.staging`, publishes with tag `staging`
43
+ From `packages/react`: `npm run publish:staging`
107
44
 
108
- Copy the `packages/react` structure to `packages/vue`:
45
+ ## Props
109
46
 
110
- 1. **register.ts** Same logic: load the bundled WC script, guard for SSR.
111
- 2. **components/GaitButton.vue** – Vue wrapper: use `ref` + `onMounted`/`watch` to sync props to the element, map DOM events to emits.
112
- 3. **types.ts** Copy event detail types from the Web Component (source of truth).
113
- 4. **Build** Use Vite or similar to output ESM, CJS, and `.d.ts`.
114
- 5. **Entry** Export components and types; keep registration as a separate import.
47
+ | Prop | Description |
48
+ |------|-------------|
49
+ | `data-id` | Checkout identity (required for payments) |
50
+ | `totalCost` | Total amount |
51
+ | `items` | Cart items for split payment |
52
+ | `customer` | `{ email, name? }` |
53
+ | `webhook` | `{ url, body? }` for split callbacks |
54
+ | `disabled`, `size`, `style` | Button options |
55
+ | `onClick`, `onLoaded`, `onDataProcessed`, `onSplitFeedback`, `onMerchantIdError`, `onConfirm` | Event callbacks |
package/dist/index.cjs CHANGED
@@ -41,45 +41,28 @@ var import_react = __toESM(require("react"), 1);
41
41
  // src/register.ts
42
42
  var import_meta = {};
43
43
  var loadPromise = null;
44
- async function defineCustomElements(scriptUrl) {
45
- if (typeof window === "undefined" || typeof customElements === "undefined") {
46
- return;
47
- }
48
- if (customElements.get("gait-button")) {
49
- return;
50
- }
51
- if (loadPromise) {
52
- return loadPromise;
53
- }
54
- const tryLoad = (url) => new Promise((resolve, reject) => {
44
+ function defineCustomElements(scriptUrl) {
45
+ if (typeof window === "undefined" || typeof customElements === "undefined") return Promise.resolve();
46
+ if (customElements.get("gait-button")) return Promise.resolve();
47
+ if (loadPromise) return loadPromise;
48
+ loadPromise = new Promise((resolve, reject) => {
49
+ let src;
50
+ if (scriptUrl) src = scriptUrl;
51
+ else {
52
+ try {
53
+ src = new URL("./wc/button.js", import_meta.url).href;
54
+ } catch {
55
+ reject(new Error('[@gait-financial/react] Could not resolve script. With Vite, add optimizeDeps: { exclude: ["@gait-financial/react"] } to vite.config.'));
56
+ return;
57
+ }
58
+ }
55
59
  const script = document.createElement("script");
56
60
  script.async = true;
57
- script.src = url;
61
+ script.src = src;
58
62
  script.onload = () => resolve();
59
- script.onerror = () => reject();
63
+ script.onerror = () => reject(new Error("[@gait-financial/react] Failed to load web component."));
60
64
  document.head.appendChild(script);
61
65
  });
62
- loadPromise = (async () => {
63
- const fallbackUrl = `${typeof window !== "undefined" ? window.location.origin : ""}/node_modules/@gait-financial/react/dist/wc/button.js`;
64
- if (scriptUrl) {
65
- await tryLoad(scriptUrl);
66
- return;
67
- }
68
- try {
69
- const url = new URL("./wc/button.js", import_meta.url).href;
70
- await tryLoad(url);
71
- return;
72
- } catch {
73
- }
74
- try {
75
- await tryLoad(fallbackUrl);
76
- return;
77
- } catch {
78
- throw new Error(
79
- '[@gait-financial/react] Failed to load web component script. If using Vite, add optimizeDeps: { exclude: ["@gait-financial/react"] } to vite.config.'
80
- );
81
- }
82
- })();
83
66
  return loadPromise;
84
67
  }
85
68
  if (typeof window !== "undefined" && typeof customElements !== "undefined") {
@@ -87,85 +70,33 @@ if (typeof window !== "undefined" && typeof customElements !== "undefined") {
87
70
  }
88
71
 
89
72
  // src/components/GaitButton.tsx
90
- var GaitButton = (0, import_react.forwardRef)(function GaitButton2({
91
- "data-id": dataId,
92
- disabled,
93
- size,
94
- style,
95
- items,
96
- priceBreakdown,
97
- totalCost,
98
- customer,
99
- webhook,
100
- merchantStoreUrl,
101
- onClick,
102
- onLoaded,
103
- onDataProcessed,
104
- onSplitFeedback,
105
- onMerchantIdError,
106
- onConfirm,
107
- className,
108
- id,
109
- ...rest
110
- }, ref) {
111
- const elementRef = (0, import_react.useRef)(null);
112
- const [isReady, setIsReady] = (0, import_react.useState)(false);
113
- (0, import_react.useImperativeHandle)(ref, () => elementRef.current, []);
114
- (0, import_react.useEffect)(() => {
115
- if (typeof customElements !== "undefined" && customElements.get("gait-button")) {
116
- setIsReady(true);
117
- return;
118
- }
119
- defineCustomElements().then(() => setIsReady(true)).catch(() => setIsReady(false));
120
- }, []);
121
- (0, import_react.useEffect)(() => {
122
- const el = elementRef.current;
123
- if (!el) return;
124
- if (dataId !== void 0) el.setAttribute("data-id", dataId);
125
- if (size !== void 0) el.setAttribute("size", size);
126
- if (disabled) {
127
- el.setAttribute("disabled", "");
128
- } else {
129
- el.removeAttribute("disabled");
130
- }
131
- if (style !== void 0) {
132
- const styleStr = typeof style === "string" ? style : Object.entries(style).map(
133
- ([k, v]) => `${k.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${v}`
134
- ).join("; ");
135
- el.setAttribute("style", styleStr);
136
- }
137
- if (items !== void 0) {
138
- el.setAttribute(
139
- "items",
140
- typeof items === "string" ? items : JSON.stringify(items)
141
- );
142
- }
143
- if (priceBreakdown !== void 0) {
144
- el.setAttribute(
145
- "price-breakdown",
146
- typeof priceBreakdown === "string" ? priceBreakdown : JSON.stringify(priceBreakdown)
147
- );
148
- }
149
- if (totalCost !== void 0) {
150
- el.setAttribute("total-cost", String(totalCost));
151
- }
152
- if (customer !== void 0) {
153
- el.setAttribute(
154
- "customer",
155
- typeof customer === "string" ? customer : JSON.stringify(customer)
156
- );
157
- }
158
- if (webhook !== void 0) {
159
- el.setAttribute(
160
- "webhook",
161
- typeof webhook === "string" ? webhook : JSON.stringify(webhook)
162
- );
163
- }
164
- if (merchantStoreUrl !== void 0) {
165
- el.setAttribute("merchant-store-url", merchantStoreUrl);
166
- }
167
- }, [
168
- dataId,
73
+ var EVENT_HANDLERS = [
74
+ { event: "gait-click", key: "onClick" },
75
+ { event: "gait-loaded", key: "onLoaded" },
76
+ { event: "gait-data-processed", key: "onDataProcessed" },
77
+ { event: "gait-split-feedback", key: "onSplitFeedback" },
78
+ { event: "gait-merchant-id-error", key: "onMerchantIdError" },
79
+ { event: "gait-confirm", key: "onConfirm" }
80
+ ];
81
+ function applyAttributes(el, props) {
82
+ const { "data-id": dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl } = props;
83
+ if (dataId !== void 0) el.setAttribute("data-id", dataId);
84
+ if (size !== void 0) el.setAttribute("size", size);
85
+ disabled ? el.setAttribute("disabled", "") : el.removeAttribute("disabled");
86
+ if (style !== void 0) {
87
+ const s = typeof style === "string" ? style : Object.entries(style).map(([k, v]) => `${k.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${v}`).join("; ");
88
+ el.setAttribute("style", s);
89
+ }
90
+ if (items !== void 0) el.setAttribute("items", typeof items === "string" ? items : JSON.stringify(items));
91
+ if (priceBreakdown !== void 0) el.setAttribute("price-breakdown", typeof priceBreakdown === "string" ? priceBreakdown : JSON.stringify(priceBreakdown));
92
+ if (totalCost !== void 0) el.setAttribute("total-cost", String(totalCost));
93
+ if (customer !== void 0) el.setAttribute("customer", typeof customer === "string" ? customer : JSON.stringify(customer));
94
+ if (webhook !== void 0) el.setAttribute("webhook", typeof webhook === "string" ? webhook : JSON.stringify(webhook));
95
+ if (merchantStoreUrl !== void 0) el.setAttribute("merchant-store-url", merchantStoreUrl);
96
+ }
97
+ var GaitButton = (0, import_react.forwardRef)(function GaitButton2(props, ref) {
98
+ const {
99
+ "data-id": dataId,
169
100
  disabled,
170
101
  size,
171
102
  style,
@@ -174,73 +105,64 @@ var GaitButton = (0, import_react.forwardRef)(function GaitButton2({
174
105
  totalCost,
175
106
  customer,
176
107
  webhook,
177
- merchantStoreUrl
178
- ]);
179
- (0, import_react.useEffect)(() => {
180
- const el = elementRef.current;
181
- if (!el) return;
182
- const handleClick = (e) => {
183
- if (onClick && e instanceof CustomEvent && e.detail) {
184
- onClick(e.detail);
185
- }
186
- };
187
- const handleLoaded = (e) => {
188
- if (onLoaded && e instanceof CustomEvent && e.detail) {
189
- onLoaded(e.detail);
190
- }
191
- };
192
- const handleDataProcessed = (e) => {
193
- if (onDataProcessed && e instanceof CustomEvent && e.detail) {
194
- onDataProcessed(e.detail);
195
- }
196
- };
197
- const handleSplitFeedback = (e) => {
198
- if (onSplitFeedback && e instanceof CustomEvent && e.detail) {
199
- onSplitFeedback(e.detail);
200
- }
201
- };
202
- const handleMerchantIdError = (e) => {
203
- if (onMerchantIdError && e instanceof CustomEvent && e.detail) {
204
- onMerchantIdError(e.detail);
205
- }
206
- };
207
- const handleConfirm = (e) => {
208
- if (onConfirm && e instanceof CustomEvent && e.detail) {
209
- onConfirm(e.detail);
210
- }
211
- };
212
- el.addEventListener("gait-click", handleClick);
213
- el.addEventListener("gait-loaded", handleLoaded);
214
- el.addEventListener("gait-data-processed", handleDataProcessed);
215
- el.addEventListener("gait-split-feedback", handleSplitFeedback);
216
- el.addEventListener("gait-merchant-id-error", handleMerchantIdError);
217
- el.addEventListener("gait-confirm", handleConfirm);
218
- return () => {
219
- el.removeEventListener("gait-click", handleClick);
220
- el.removeEventListener("gait-loaded", handleLoaded);
221
- el.removeEventListener("gait-data-processed", handleDataProcessed);
222
- el.removeEventListener("gait-split-feedback", handleSplitFeedback);
223
- el.removeEventListener("gait-merchant-id-error", handleMerchantIdError);
224
- el.removeEventListener("gait-confirm", handleConfirm);
225
- };
226
- }, [
108
+ merchantStoreUrl,
227
109
  onClick,
228
110
  onLoaded,
229
111
  onDataProcessed,
230
112
  onSplitFeedback,
231
113
  onMerchantIdError,
232
- onConfirm
233
- ]);
234
- if (!isReady) {
235
- return null;
236
- }
237
- const elementProps = {
238
- ref: elementRef
239
- };
240
- if (className) elementProps.className = className;
241
- if (id) elementProps.id = id;
242
- Object.assign(elementProps, rest);
243
- return import_react.default.createElement("gait-button", elementProps);
114
+ onConfirm,
115
+ className,
116
+ id
117
+ } = props;
118
+ const containerRef = (0, import_react.useRef)(null);
119
+ const elementRef = (0, import_react.useRef)(null);
120
+ const [ready, setReady] = (0, import_react.useState)(false);
121
+ const callbacksRef = (0, import_react.useRef)({ onClick, onLoaded, onDataProcessed, onSplitFeedback, onMerchantIdError, onConfirm });
122
+ callbacksRef.current = { onClick, onLoaded, onDataProcessed, onSplitFeedback, onMerchantIdError, onConfirm };
123
+ (0, import_react.useImperativeHandle)(ref, () => elementRef.current, []);
124
+ (0, import_react.useEffect)(() => {
125
+ if (typeof customElements !== "undefined" && customElements.get("gait-button")) {
126
+ setReady(true);
127
+ return;
128
+ }
129
+ defineCustomElements().then(() => setReady(true), () => setReady(false));
130
+ }, []);
131
+ (0, import_react.useEffect)(() => {
132
+ if (!ready || !containerRef.current) return;
133
+ const container = containerRef.current;
134
+ const el = document.createElement("gait-button");
135
+ applyAttributes(el, { "data-id": dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl });
136
+ if (className) el.className = className;
137
+ if (id) el.id = id;
138
+ const listeners = [];
139
+ EVENT_HANDLERS.forEach(({ event, key }) => {
140
+ const handler = (e) => {
141
+ const cb = callbacksRef.current[key];
142
+ if (cb && e instanceof CustomEvent && e.detail) cb(e.detail);
143
+ };
144
+ el.addEventListener(event, handler);
145
+ listeners.push(() => el.removeEventListener(event, handler));
146
+ });
147
+ elementRef.current = el;
148
+ container.appendChild(el);
149
+ return () => {
150
+ listeners.forEach((off) => off());
151
+ if (el.parentNode) el.parentNode.removeChild(el);
152
+ elementRef.current = null;
153
+ };
154
+ }, [ready]);
155
+ (0, import_react.useEffect)(() => {
156
+ const el = elementRef.current;
157
+ if (!el) return;
158
+ applyAttributes(el, { "data-id": dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl });
159
+ if (className) el.className = className;
160
+ else el.removeAttribute("class");
161
+ if (id) el.id = id;
162
+ else el.removeAttribute("id");
163
+ }, [dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl, className, id]);
164
+ if (!ready) return null;
165
+ return import_react.default.createElement("div", { ref: containerRef });
244
166
  });
245
167
  GaitButton.displayName = "GaitButton";
246
168
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/components/GaitButton.tsx","../src/register.ts"],"sourcesContent":["'use client';\n\n/**\n * @gait-financial/react - React wrappers for Gait Web Components\n *\n * Usage:\n * 1. Register the Web Component (run once, e.g. in _app.tsx or layout):\n * import '@gait-financial/react/register';\n *\n * 2. Use the React components:\n * import { GaitButton } from '@gait-financial/react';\n */\n\nexport { GaitButton } from './components/GaitButton';\nexport type { GaitButtonProps } from './components/GaitButton';\n\nexport type {\n ButtonSize,\n GaitButtonItem,\n GaitPriceBreakdownItem,\n GaitCustomer,\n GaitWebhook,\n GaitClickDetail,\n GaitLoadedDetail,\n GaitDataProcessedDetail,\n GaitSplitFeedbackDetail,\n GaitMerchantIdErrorDetail,\n GaitConfirmDetail,\n} from './types';\n","'use client';\n\n/**\n * React wrapper for GaitButton Web Component\n * Maps React props to element attributes (WC uses attributes as source of truth).\n * Uses ref + useEffect for binding - no direct JSX attribute spread to avoid React's\n * string coercion of booleans/objects.\n * Waits for WC registration before rendering to avoid invisible element (race condition).\n */\n\nimport React, {\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from 'react';\nimport { defineCustomElements } from '../register';\nimport type {\n GaitButtonItem,\n GaitPriceBreakdownItem,\n GaitCustomer,\n GaitWebhook,\n GaitClickDetail,\n GaitLoadedDetail,\n GaitDataProcessedDetail,\n GaitSplitFeedbackDetail,\n GaitMerchantIdErrorDetail,\n GaitConfirmDetail,\n} from '../types';\n\nexport interface GaitButtonProps\n extends Omit<React.HTMLAttributes<HTMLElement>, 'onClick' | 'style'> {\n /** Checkout/data identity (required for payment flow) */\n 'data-id'?: string;\n /** Disable the button */\n disabled?: boolean;\n /** Button size */\n size?: 'small' | 'medium' | 'large';\n /** Inline styles - string (for WC) or React.CSSProperties */\n style?: React.CSSProperties | string;\n /** Cart/order items for split payment modal */\n items?: GaitButtonItem[] | string;\n /** Price breakdown items */\n priceBreakdown?: GaitPriceBreakdownItem[] | string;\n /** Total cost */\n totalCost?: number | string;\n /** Customer info for split payment */\n customer?: GaitCustomer | string;\n /** Webhook config for split payment callbacks */\n webhook?: GaitWebhook | string;\n /** Merchant store URL (defaults to window.location.origin) */\n merchantStoreUrl?: string;\n\n /** Button clicked */\n onClick?: (detail: GaitClickDetail) => void;\n /** Component loaded/rendered */\n onLoaded?: (detail: GaitLoadedDetail) => void;\n /** Data processed (e.g. checkoutId resolved) */\n onDataProcessed?: (detail: GaitDataProcessedDetail) => void;\n /** Split payment API feedback (success/failure) */\n onSplitFeedback?: (detail: GaitSplitFeedbackDetail) => void;\n /** Merchant ID lookup failed */\n onMerchantIdError?: (detail: GaitMerchantIdErrorDetail) => void;\n /** Pay remaining confirmed */\n onConfirm?: (detail: GaitConfirmDetail) => void;\n}\n\nconst GaitButton = forwardRef<HTMLElement, GaitButtonProps>(function GaitButton(\n {\n 'data-id': dataId,\n disabled,\n size,\n style,\n items,\n priceBreakdown,\n totalCost,\n customer,\n webhook,\n merchantStoreUrl,\n onClick,\n onLoaded,\n onDataProcessed,\n onSplitFeedback,\n onMerchantIdError,\n onConfirm,\n className,\n id,\n ...rest\n },\n ref\n) {\n const elementRef = useRef<HTMLElement>(null);\n const [isReady, setIsReady] = useState(false);\n\n useImperativeHandle(ref, () => elementRef.current as HTMLElement, []);\n\n // Wait for WC to be registered before rendering (avoids invisible element)\n useEffect(() => {\n if (typeof customElements !== 'undefined' && customElements.get('gait-button')) {\n setIsReady(true);\n return;\n }\n defineCustomElements().then(() => setIsReady(true)).catch(() => setIsReady(false));\n }, []);\n\n // Sync props to element attributes via ref (WC reads attributes) - must run every render (hooks order)\n useEffect(() => {\n const el = elementRef.current;\n if (!el) return;\n\n if (dataId !== undefined) el.setAttribute('data-id', dataId);\n if (size !== undefined) el.setAttribute('size', size);\n\n if (disabled) {\n el.setAttribute('disabled', '');\n } else {\n el.removeAttribute('disabled');\n }\n\n if (style !== undefined) {\n const styleStr =\n typeof style === 'string'\n ? style\n : Object.entries(style)\n .map(\n ([k, v]) =>\n `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${v}`\n )\n .join('; ');\n el.setAttribute('style', styleStr);\n }\n\n if (items !== undefined) {\n el.setAttribute(\n 'items',\n typeof items === 'string' ? items : JSON.stringify(items)\n );\n }\n\n if (priceBreakdown !== undefined) {\n el.setAttribute(\n 'price-breakdown',\n typeof priceBreakdown === 'string'\n ? priceBreakdown\n : JSON.stringify(priceBreakdown)\n );\n }\n\n if (totalCost !== undefined) {\n el.setAttribute('total-cost', String(totalCost));\n }\n\n if (customer !== undefined) {\n el.setAttribute(\n 'customer',\n typeof customer === 'string' ? customer : JSON.stringify(customer)\n );\n }\n\n if (webhook !== undefined) {\n el.setAttribute(\n 'webhook',\n typeof webhook === 'string' ? webhook : JSON.stringify(webhook)\n );\n }\n\n if (merchantStoreUrl !== undefined) {\n el.setAttribute('merchant-store-url', merchantStoreUrl);\n }\n }, [\n dataId,\n disabled,\n size,\n style,\n items,\n priceBreakdown,\n totalCost,\n customer,\n webhook,\n merchantStoreUrl,\n ]);\n\n // Event listeners\n useEffect(() => {\n const el = elementRef.current;\n if (!el) return;\n\n const handleClick = (e: Event) => {\n if (onClick && e instanceof CustomEvent && e.detail) {\n onClick(e.detail as GaitClickDetail);\n }\n };\n const handleLoaded = (e: Event) => {\n if (onLoaded && e instanceof CustomEvent && e.detail) {\n onLoaded(e.detail as GaitLoadedDetail);\n }\n };\n const handleDataProcessed = (e: Event) => {\n if (onDataProcessed && e instanceof CustomEvent && e.detail) {\n onDataProcessed(e.detail as GaitDataProcessedDetail);\n }\n };\n const handleSplitFeedback = (e: Event) => {\n if (onSplitFeedback && e instanceof CustomEvent && e.detail) {\n onSplitFeedback(e.detail as GaitSplitFeedbackDetail);\n }\n };\n const handleMerchantIdError = (e: Event) => {\n if (onMerchantIdError && e instanceof CustomEvent && e.detail) {\n onMerchantIdError(e.detail as GaitMerchantIdErrorDetail);\n }\n };\n const handleConfirm = (e: Event) => {\n if (onConfirm && e instanceof CustomEvent && e.detail) {\n onConfirm(e.detail as GaitConfirmDetail);\n }\n };\n\n el.addEventListener('gait-click', handleClick as EventListener);\n el.addEventListener('gait-loaded', handleLoaded as EventListener);\n el.addEventListener('gait-data-processed', handleDataProcessed as EventListener);\n el.addEventListener('gait-split-feedback', handleSplitFeedback as EventListener);\n el.addEventListener('gait-merchant-id-error', handleMerchantIdError as EventListener);\n el.addEventListener('gait-confirm', handleConfirm as EventListener);\n\n return () => {\n el.removeEventListener('gait-click', handleClick as EventListener);\n el.removeEventListener('gait-loaded', handleLoaded as EventListener);\n el.removeEventListener('gait-data-processed', handleDataProcessed as EventListener);\n el.removeEventListener('gait-split-feedback', handleSplitFeedback as EventListener);\n el.removeEventListener('gait-merchant-id-error', handleMerchantIdError as EventListener);\n el.removeEventListener('gait-confirm', handleConfirm as EventListener);\n };\n }, [\n onClick,\n onLoaded,\n onDataProcessed,\n onSplitFeedback,\n onMerchantIdError,\n onConfirm,\n ]);\n\n // Conditional render AFTER all hooks - required by Rules of Hooks\n if (!isReady) {\n return null;\n }\n\n // Only pass ref, className, id - React assigns props as properties, which throws\n // \"readonly property\" on custom elements. All other attrs set via setAttribute in useEffect.\n const elementProps: Record<string, unknown> = {\n ref: elementRef,\n };\n if (className) elementProps.className = className;\n if (id) elementProps.id = id;\n Object.assign(elementProps, rest);\n\n return React.createElement('gait-button', elementProps);\n});\n\nGaitButton.displayName = 'GaitButton';\n\nexport { GaitButton };\n","/**\n * Registers Gait Web Components in the browser.\n * Safe to call in SSR environments - no-op when window/customElements unavailable.\n *\n * Usage:\n * import '@gait-financial/react/register'; // Side-effect: registers when in browser\n * // or\n * import { defineCustomElements } from '@gait-financial/react/register';\n * await defineCustomElements(); // Optional: load from custom URL\n */\n\nlet loadPromise: Promise<void> | null = null;\n\n/**\n * Loads and registers Gait Web Components.\n * In browser: loads the WC script if not already registered.\n * In SSR: no-op.\n * Deduplicated: multiple calls return the same promise.\n *\n * @param scriptUrl - Optional URL to the button.js script. If omitted, uses the bundled script.\n */\nexport async function defineCustomElements(\n scriptUrl?: string\n): Promise<void> {\n if (typeof window === 'undefined' || typeof customElements === 'undefined') {\n return;\n }\n if (customElements.get('gait-button')) {\n return;\n }\n if (loadPromise) {\n return loadPromise;\n }\n\n const tryLoad = (url: string): Promise<void> =>\n new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.async = true;\n script.src = url;\n script.onload = () => resolve();\n script.onerror = () => reject();\n document.head.appendChild(script);\n });\n\n loadPromise = (async () => {\n const fallbackUrl = `${typeof window !== 'undefined' ? window.location.origin : ''}/node_modules/@gait-financial/react/dist/wc/button.js`;\n\n if (scriptUrl) {\n await tryLoad(scriptUrl);\n return;\n }\n\n // Try relative to module first (works when not pre-bundled)\n try {\n const url = new URL('./wc/button.js', import.meta.url).href;\n await tryLoad(url);\n return;\n } catch {\n // ignore\n }\n\n // Fallback: direct path (works when Vite pre-bundles and breaks import.meta.url)\n try {\n await tryLoad(fallbackUrl);\n return;\n } catch {\n throw new Error(\n '[@gait-financial/react] Failed to load web component script. If using Vite, add optimizeDeps: { exclude: [\"@gait-financial/react\"] } to vite.config.'\n );\n }\n })();\n return loadPromise;\n}\n\n// Side-effect: auto-register when imported in browser (SSR-safe)\nif (typeof window !== 'undefined' && typeof customElements !== 'undefined') {\n void defineCustomElements();\n}\n"],"mappings":"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","names":["GaitButton","React"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/components/GaitButton.tsx","../src/register.ts"],"sourcesContent":["'use client';\n\nexport { GaitButton } from './components/GaitButton';\nexport type { GaitButtonProps } from './components/GaitButton';\nexport type {\n ButtonSize,\n GaitButtonItem,\n GaitPriceBreakdownItem,\n GaitCustomer,\n GaitWebhook,\n GaitClickDetail,\n GaitLoadedDetail,\n GaitDataProcessedDetail,\n GaitSplitFeedbackDetail,\n GaitMerchantIdErrorDetail,\n GaitConfirmDetail,\n} from './types';\n","'use client';\n\nimport React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';\nimport { defineCustomElements } from '../register';\nimport type {\n GaitButtonItem,\n GaitPriceBreakdownItem,\n GaitCustomer,\n GaitWebhook,\n GaitClickDetail,\n GaitLoadedDetail,\n GaitDataProcessedDetail,\n GaitSplitFeedbackDetail,\n GaitMerchantIdErrorDetail,\n GaitConfirmDetail,\n} from '../types';\n\nexport interface GaitButtonProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onClick' | 'style'> {\n 'data-id'?: string;\n disabled?: boolean;\n size?: 'small' | 'medium' | 'large';\n style?: React.CSSProperties | string;\n items?: GaitButtonItem[] | string;\n priceBreakdown?: GaitPriceBreakdownItem[] | string;\n totalCost?: number | string;\n customer?: GaitCustomer | string;\n webhook?: GaitWebhook | string;\n merchantStoreUrl?: string;\n onClick?: (detail: GaitClickDetail) => void;\n onLoaded?: (detail: GaitLoadedDetail) => void;\n onDataProcessed?: (detail: GaitDataProcessedDetail) => void;\n onSplitFeedback?: (detail: GaitSplitFeedbackDetail) => void;\n onMerchantIdError?: (detail: GaitMerchantIdErrorDetail) => void;\n onConfirm?: (detail: GaitConfirmDetail) => void;\n}\n\nconst EVENT_HANDLERS: Array<{ event: string; key: keyof Pick<GaitButtonProps, 'onClick' | 'onLoaded' | 'onDataProcessed' | 'onSplitFeedback' | 'onMerchantIdError' | 'onConfirm'> }> = [\n { event: 'gait-click', key: 'onClick' },\n { event: 'gait-loaded', key: 'onLoaded' },\n { event: 'gait-data-processed', key: 'onDataProcessed' },\n { event: 'gait-split-feedback', key: 'onSplitFeedback' },\n { event: 'gait-merchant-id-error', key: 'onMerchantIdError' },\n { event: 'gait-confirm', key: 'onConfirm' },\n];\n\nfunction applyAttributes(\n el: HTMLElement,\n props: Pick<GaitButtonProps, 'data-id' | 'disabled' | 'size' | 'style' | 'items' | 'priceBreakdown' | 'totalCost' | 'customer' | 'webhook' | 'merchantStoreUrl'>\n) {\n const { 'data-id': dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl } = props;\n if (dataId !== undefined) el.setAttribute('data-id', dataId);\n if (size !== undefined) el.setAttribute('size', size);\n disabled ? el.setAttribute('disabled', '') : el.removeAttribute('disabled');\n if (style !== undefined) {\n const s = typeof style === 'string' ? style : Object.entries(style).map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${v}`).join('; ');\n el.setAttribute('style', s);\n }\n if (items !== undefined) el.setAttribute('items', typeof items === 'string' ? items : JSON.stringify(items));\n if (priceBreakdown !== undefined) el.setAttribute('price-breakdown', typeof priceBreakdown === 'string' ? priceBreakdown : JSON.stringify(priceBreakdown));\n if (totalCost !== undefined) el.setAttribute('total-cost', String(totalCost));\n if (customer !== undefined) el.setAttribute('customer', typeof customer === 'string' ? customer : JSON.stringify(customer));\n if (webhook !== undefined) el.setAttribute('webhook', typeof webhook === 'string' ? webhook : JSON.stringify(webhook));\n if (merchantStoreUrl !== undefined) el.setAttribute('merchant-store-url', merchantStoreUrl);\n}\n\nconst GaitButton = forwardRef<HTMLElement, GaitButtonProps>(function GaitButton(props, ref) {\n const {\n 'data-id': dataId,\n disabled,\n size,\n style,\n items,\n priceBreakdown,\n totalCost,\n customer,\n webhook,\n merchantStoreUrl,\n onClick,\n onLoaded,\n onDataProcessed,\n onSplitFeedback,\n onMerchantIdError,\n onConfirm,\n className,\n id,\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const elementRef = useRef<HTMLElement | null>(null);\n const [ready, setReady] = useState(false);\n const callbacksRef = useRef({ onClick, onLoaded, onDataProcessed, onSplitFeedback, onMerchantIdError, onConfirm });\n callbacksRef.current = { onClick, onLoaded, onDataProcessed, onSplitFeedback, onMerchantIdError, onConfirm };\n\n useImperativeHandle(ref, () => elementRef.current as HTMLElement, []);\n\n useEffect(() => {\n if (typeof customElements !== 'undefined' && customElements.get('gait-button')) {\n setReady(true);\n return;\n }\n defineCustomElements().then(() => setReady(true), () => setReady(false));\n }, []);\n\n useEffect(() => {\n if (!ready || !containerRef.current) return;\n\n const container = containerRef.current;\n const el = document.createElement('gait-button') as HTMLElement;\n\n applyAttributes(el, { 'data-id': dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl });\n if (className) el.className = className;\n if (id) el.id = id;\n\n const listeners: Array<() => void> = [];\n EVENT_HANDLERS.forEach(({ event, key }) => {\n const handler = (e: Event) => {\n const cb = callbacksRef.current[key];\n if (cb && e instanceof CustomEvent && e.detail) cb(e.detail as never);\n };\n el.addEventListener(event, handler as EventListener);\n listeners.push(() => el.removeEventListener(event, handler as EventListener));\n });\n\n elementRef.current = el;\n container.appendChild(el);\n\n return () => {\n listeners.forEach((off) => off());\n if (el.parentNode) el.parentNode.removeChild(el);\n elementRef.current = null;\n };\n }, [ready]);\n\n useEffect(() => {\n const el = elementRef.current;\n if (!el) return;\n applyAttributes(el, { 'data-id': dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl });\n if (className) el.className = className;\n else el.removeAttribute('class');\n if (id) el.id = id;\n else el.removeAttribute('id');\n }, [dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl, className, id]);\n\n if (!ready) return null;\n return React.createElement('div', { ref: containerRef });\n});\n\nGaitButton.displayName = 'GaitButton';\nexport { GaitButton };\n","/**\n * Loads and registers Gait Web Components. Safe for SSR (no-op when no window).\n * Import once: import '@gait-financial/react/register';\n */\n\nlet loadPromise: Promise<void> | null = null;\n\nexport function defineCustomElements(scriptUrl?: string): Promise<void> {\n if (typeof window === 'undefined' || typeof customElements === 'undefined') return Promise.resolve();\n if (customElements.get('gait-button')) return Promise.resolve();\n if (loadPromise) return loadPromise;\n\n loadPromise = new Promise((resolve, reject) => {\n let src: string;\n if (scriptUrl) src = scriptUrl;\n else {\n try {\n src = new URL('./wc/button.js', import.meta.url).href;\n } catch {\n reject(new Error('[@gait-financial/react] Could not resolve script. With Vite, add optimizeDeps: { exclude: [\"@gait-financial/react\"] } to vite.config.'));\n return;\n }\n }\n const script = document.createElement('script');\n script.async = true;\n script.src = src;\n script.onload = () => resolve();\n script.onerror = () => reject(new Error('[@gait-financial/react] Failed to load web component.'));\n document.head.appendChild(script);\n });\n return loadPromise;\n}\n\nif (typeof window !== 'undefined' && typeof customElements !== 'undefined') {\n void defineCustomElements();\n}\n"],"mappings":"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","names":["GaitButton","React"]}
package/dist/index.d.cts CHANGED
@@ -68,46 +68,22 @@ interface GaitConfirmDetail {
68
68
  paymentId: string;
69
69
  }
70
70
 
71
- /**
72
- * React wrapper for GaitButton Web Component
73
- * Maps React props to element attributes (WC uses attributes as source of truth).
74
- * Uses ref + useEffect for binding - no direct JSX attribute spread to avoid React's
75
- * string coercion of booleans/objects.
76
- * Waits for WC registration before rendering to avoid invisible element (race condition).
77
- */
78
-
79
71
  interface GaitButtonProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onClick' | 'style'> {
80
- /** Checkout/data identity (required for payment flow) */
81
72
  'data-id'?: string;
82
- /** Disable the button */
83
73
  disabled?: boolean;
84
- /** Button size */
85
74
  size?: 'small' | 'medium' | 'large';
86
- /** Inline styles - string (for WC) or React.CSSProperties */
87
75
  style?: React.CSSProperties | string;
88
- /** Cart/order items for split payment modal */
89
76
  items?: GaitButtonItem[] | string;
90
- /** Price breakdown items */
91
77
  priceBreakdown?: GaitPriceBreakdownItem[] | string;
92
- /** Total cost */
93
78
  totalCost?: number | string;
94
- /** Customer info for split payment */
95
79
  customer?: GaitCustomer | string;
96
- /** Webhook config for split payment callbacks */
97
80
  webhook?: GaitWebhook | string;
98
- /** Merchant store URL (defaults to window.location.origin) */
99
81
  merchantStoreUrl?: string;
100
- /** Button clicked */
101
82
  onClick?: (detail: GaitClickDetail) => void;
102
- /** Component loaded/rendered */
103
83
  onLoaded?: (detail: GaitLoadedDetail) => void;
104
- /** Data processed (e.g. checkoutId resolved) */
105
84
  onDataProcessed?: (detail: GaitDataProcessedDetail) => void;
106
- /** Split payment API feedback (success/failure) */
107
85
  onSplitFeedback?: (detail: GaitSplitFeedbackDetail) => void;
108
- /** Merchant ID lookup failed */
109
86
  onMerchantIdError?: (detail: GaitMerchantIdErrorDetail) => void;
110
- /** Pay remaining confirmed */
111
87
  onConfirm?: (detail: GaitConfirmDetail) => void;
112
88
  }
113
89
  declare const GaitButton: React.ForwardRefExoticComponent<GaitButtonProps & React.RefAttributes<HTMLElement>>;
package/dist/index.d.ts CHANGED
@@ -68,46 +68,22 @@ interface GaitConfirmDetail {
68
68
  paymentId: string;
69
69
  }
70
70
 
71
- /**
72
- * React wrapper for GaitButton Web Component
73
- * Maps React props to element attributes (WC uses attributes as source of truth).
74
- * Uses ref + useEffect for binding - no direct JSX attribute spread to avoid React's
75
- * string coercion of booleans/objects.
76
- * Waits for WC registration before rendering to avoid invisible element (race condition).
77
- */
78
-
79
71
  interface GaitButtonProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onClick' | 'style'> {
80
- /** Checkout/data identity (required for payment flow) */
81
72
  'data-id'?: string;
82
- /** Disable the button */
83
73
  disabled?: boolean;
84
- /** Button size */
85
74
  size?: 'small' | 'medium' | 'large';
86
- /** Inline styles - string (for WC) or React.CSSProperties */
87
75
  style?: React.CSSProperties | string;
88
- /** Cart/order items for split payment modal */
89
76
  items?: GaitButtonItem[] | string;
90
- /** Price breakdown items */
91
77
  priceBreakdown?: GaitPriceBreakdownItem[] | string;
92
- /** Total cost */
93
78
  totalCost?: number | string;
94
- /** Customer info for split payment */
95
79
  customer?: GaitCustomer | string;
96
- /** Webhook config for split payment callbacks */
97
80
  webhook?: GaitWebhook | string;
98
- /** Merchant store URL (defaults to window.location.origin) */
99
81
  merchantStoreUrl?: string;
100
- /** Button clicked */
101
82
  onClick?: (detail: GaitClickDetail) => void;
102
- /** Component loaded/rendered */
103
83
  onLoaded?: (detail: GaitLoadedDetail) => void;
104
- /** Data processed (e.g. checkoutId resolved) */
105
84
  onDataProcessed?: (detail: GaitDataProcessedDetail) => void;
106
- /** Split payment API feedback (success/failure) */
107
85
  onSplitFeedback?: (detail: GaitSplitFeedbackDetail) => void;
108
- /** Merchant ID lookup failed */
109
86
  onMerchantIdError?: (detail: GaitMerchantIdErrorDetail) => void;
110
- /** Pay remaining confirmed */
111
87
  onConfirm?: (detail: GaitConfirmDetail) => void;
112
88
  }
113
89
  declare const GaitButton: React.ForwardRefExoticComponent<GaitButtonProps & React.RefAttributes<HTMLElement>>;
package/dist/index.js CHANGED
@@ -1,55 +1,32 @@
1
1
  "use client";
2
2
 
3
3
  // src/components/GaitButton.tsx
4
- import React, {
5
- forwardRef,
6
- useEffect,
7
- useImperativeHandle,
8
- useRef,
9
- useState
10
- } from "react";
4
+ import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
11
5
 
12
6
  // src/register.ts
13
7
  var loadPromise = null;
14
- async function defineCustomElements(scriptUrl) {
15
- if (typeof window === "undefined" || typeof customElements === "undefined") {
16
- return;
17
- }
18
- if (customElements.get("gait-button")) {
19
- return;
20
- }
21
- if (loadPromise) {
22
- return loadPromise;
23
- }
24
- const tryLoad = (url) => new Promise((resolve, reject) => {
8
+ function defineCustomElements(scriptUrl) {
9
+ if (typeof window === "undefined" || typeof customElements === "undefined") return Promise.resolve();
10
+ if (customElements.get("gait-button")) return Promise.resolve();
11
+ if (loadPromise) return loadPromise;
12
+ loadPromise = new Promise((resolve, reject) => {
13
+ let src;
14
+ if (scriptUrl) src = scriptUrl;
15
+ else {
16
+ try {
17
+ src = new URL("./wc/button.js", import.meta.url).href;
18
+ } catch {
19
+ reject(new Error('[@gait-financial/react] Could not resolve script. With Vite, add optimizeDeps: { exclude: ["@gait-financial/react"] } to vite.config.'));
20
+ return;
21
+ }
22
+ }
25
23
  const script = document.createElement("script");
26
24
  script.async = true;
27
- script.src = url;
25
+ script.src = src;
28
26
  script.onload = () => resolve();
29
- script.onerror = () => reject();
27
+ script.onerror = () => reject(new Error("[@gait-financial/react] Failed to load web component."));
30
28
  document.head.appendChild(script);
31
29
  });
32
- loadPromise = (async () => {
33
- const fallbackUrl = `${typeof window !== "undefined" ? window.location.origin : ""}/node_modules/@gait-financial/react/dist/wc/button.js`;
34
- if (scriptUrl) {
35
- await tryLoad(scriptUrl);
36
- return;
37
- }
38
- try {
39
- const url = new URL("./wc/button.js", import.meta.url).href;
40
- await tryLoad(url);
41
- return;
42
- } catch {
43
- }
44
- try {
45
- await tryLoad(fallbackUrl);
46
- return;
47
- } catch {
48
- throw new Error(
49
- '[@gait-financial/react] Failed to load web component script. If using Vite, add optimizeDeps: { exclude: ["@gait-financial/react"] } to vite.config.'
50
- );
51
- }
52
- })();
53
30
  return loadPromise;
54
31
  }
55
32
  if (typeof window !== "undefined" && typeof customElements !== "undefined") {
@@ -57,85 +34,33 @@ if (typeof window !== "undefined" && typeof customElements !== "undefined") {
57
34
  }
58
35
 
59
36
  // src/components/GaitButton.tsx
60
- var GaitButton = forwardRef(function GaitButton2({
61
- "data-id": dataId,
62
- disabled,
63
- size,
64
- style,
65
- items,
66
- priceBreakdown,
67
- totalCost,
68
- customer,
69
- webhook,
70
- merchantStoreUrl,
71
- onClick,
72
- onLoaded,
73
- onDataProcessed,
74
- onSplitFeedback,
75
- onMerchantIdError,
76
- onConfirm,
77
- className,
78
- id,
79
- ...rest
80
- }, ref) {
81
- const elementRef = useRef(null);
82
- const [isReady, setIsReady] = useState(false);
83
- useImperativeHandle(ref, () => elementRef.current, []);
84
- useEffect(() => {
85
- if (typeof customElements !== "undefined" && customElements.get("gait-button")) {
86
- setIsReady(true);
87
- return;
88
- }
89
- defineCustomElements().then(() => setIsReady(true)).catch(() => setIsReady(false));
90
- }, []);
91
- useEffect(() => {
92
- const el = elementRef.current;
93
- if (!el) return;
94
- if (dataId !== void 0) el.setAttribute("data-id", dataId);
95
- if (size !== void 0) el.setAttribute("size", size);
96
- if (disabled) {
97
- el.setAttribute("disabled", "");
98
- } else {
99
- el.removeAttribute("disabled");
100
- }
101
- if (style !== void 0) {
102
- const styleStr = typeof style === "string" ? style : Object.entries(style).map(
103
- ([k, v]) => `${k.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${v}`
104
- ).join("; ");
105
- el.setAttribute("style", styleStr);
106
- }
107
- if (items !== void 0) {
108
- el.setAttribute(
109
- "items",
110
- typeof items === "string" ? items : JSON.stringify(items)
111
- );
112
- }
113
- if (priceBreakdown !== void 0) {
114
- el.setAttribute(
115
- "price-breakdown",
116
- typeof priceBreakdown === "string" ? priceBreakdown : JSON.stringify(priceBreakdown)
117
- );
118
- }
119
- if (totalCost !== void 0) {
120
- el.setAttribute("total-cost", String(totalCost));
121
- }
122
- if (customer !== void 0) {
123
- el.setAttribute(
124
- "customer",
125
- typeof customer === "string" ? customer : JSON.stringify(customer)
126
- );
127
- }
128
- if (webhook !== void 0) {
129
- el.setAttribute(
130
- "webhook",
131
- typeof webhook === "string" ? webhook : JSON.stringify(webhook)
132
- );
133
- }
134
- if (merchantStoreUrl !== void 0) {
135
- el.setAttribute("merchant-store-url", merchantStoreUrl);
136
- }
137
- }, [
138
- dataId,
37
+ var EVENT_HANDLERS = [
38
+ { event: "gait-click", key: "onClick" },
39
+ { event: "gait-loaded", key: "onLoaded" },
40
+ { event: "gait-data-processed", key: "onDataProcessed" },
41
+ { event: "gait-split-feedback", key: "onSplitFeedback" },
42
+ { event: "gait-merchant-id-error", key: "onMerchantIdError" },
43
+ { event: "gait-confirm", key: "onConfirm" }
44
+ ];
45
+ function applyAttributes(el, props) {
46
+ const { "data-id": dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl } = props;
47
+ if (dataId !== void 0) el.setAttribute("data-id", dataId);
48
+ if (size !== void 0) el.setAttribute("size", size);
49
+ disabled ? el.setAttribute("disabled", "") : el.removeAttribute("disabled");
50
+ if (style !== void 0) {
51
+ const s = typeof style === "string" ? style : Object.entries(style).map(([k, v]) => `${k.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${v}`).join("; ");
52
+ el.setAttribute("style", s);
53
+ }
54
+ if (items !== void 0) el.setAttribute("items", typeof items === "string" ? items : JSON.stringify(items));
55
+ if (priceBreakdown !== void 0) el.setAttribute("price-breakdown", typeof priceBreakdown === "string" ? priceBreakdown : JSON.stringify(priceBreakdown));
56
+ if (totalCost !== void 0) el.setAttribute("total-cost", String(totalCost));
57
+ if (customer !== void 0) el.setAttribute("customer", typeof customer === "string" ? customer : JSON.stringify(customer));
58
+ if (webhook !== void 0) el.setAttribute("webhook", typeof webhook === "string" ? webhook : JSON.stringify(webhook));
59
+ if (merchantStoreUrl !== void 0) el.setAttribute("merchant-store-url", merchantStoreUrl);
60
+ }
61
+ var GaitButton = forwardRef(function GaitButton2(props, ref) {
62
+ const {
63
+ "data-id": dataId,
139
64
  disabled,
140
65
  size,
141
66
  style,
@@ -144,73 +69,64 @@ var GaitButton = forwardRef(function GaitButton2({
144
69
  totalCost,
145
70
  customer,
146
71
  webhook,
147
- merchantStoreUrl
148
- ]);
149
- useEffect(() => {
150
- const el = elementRef.current;
151
- if (!el) return;
152
- const handleClick = (e) => {
153
- if (onClick && e instanceof CustomEvent && e.detail) {
154
- onClick(e.detail);
155
- }
156
- };
157
- const handleLoaded = (e) => {
158
- if (onLoaded && e instanceof CustomEvent && e.detail) {
159
- onLoaded(e.detail);
160
- }
161
- };
162
- const handleDataProcessed = (e) => {
163
- if (onDataProcessed && e instanceof CustomEvent && e.detail) {
164
- onDataProcessed(e.detail);
165
- }
166
- };
167
- const handleSplitFeedback = (e) => {
168
- if (onSplitFeedback && e instanceof CustomEvent && e.detail) {
169
- onSplitFeedback(e.detail);
170
- }
171
- };
172
- const handleMerchantIdError = (e) => {
173
- if (onMerchantIdError && e instanceof CustomEvent && e.detail) {
174
- onMerchantIdError(e.detail);
175
- }
176
- };
177
- const handleConfirm = (e) => {
178
- if (onConfirm && e instanceof CustomEvent && e.detail) {
179
- onConfirm(e.detail);
180
- }
181
- };
182
- el.addEventListener("gait-click", handleClick);
183
- el.addEventListener("gait-loaded", handleLoaded);
184
- el.addEventListener("gait-data-processed", handleDataProcessed);
185
- el.addEventListener("gait-split-feedback", handleSplitFeedback);
186
- el.addEventListener("gait-merchant-id-error", handleMerchantIdError);
187
- el.addEventListener("gait-confirm", handleConfirm);
188
- return () => {
189
- el.removeEventListener("gait-click", handleClick);
190
- el.removeEventListener("gait-loaded", handleLoaded);
191
- el.removeEventListener("gait-data-processed", handleDataProcessed);
192
- el.removeEventListener("gait-split-feedback", handleSplitFeedback);
193
- el.removeEventListener("gait-merchant-id-error", handleMerchantIdError);
194
- el.removeEventListener("gait-confirm", handleConfirm);
195
- };
196
- }, [
72
+ merchantStoreUrl,
197
73
  onClick,
198
74
  onLoaded,
199
75
  onDataProcessed,
200
76
  onSplitFeedback,
201
77
  onMerchantIdError,
202
- onConfirm
203
- ]);
204
- if (!isReady) {
205
- return null;
206
- }
207
- const elementProps = {
208
- ref: elementRef
209
- };
210
- if (className) elementProps.className = className;
211
- if (id) elementProps.id = id;
212
- Object.assign(elementProps, rest);
213
- return React.createElement("gait-button", elementProps);
78
+ onConfirm,
79
+ className,
80
+ id
81
+ } = props;
82
+ const containerRef = useRef(null);
83
+ const elementRef = useRef(null);
84
+ const [ready, setReady] = useState(false);
85
+ const callbacksRef = useRef({ onClick, onLoaded, onDataProcessed, onSplitFeedback, onMerchantIdError, onConfirm });
86
+ callbacksRef.current = { onClick, onLoaded, onDataProcessed, onSplitFeedback, onMerchantIdError, onConfirm };
87
+ useImperativeHandle(ref, () => elementRef.current, []);
88
+ useEffect(() => {
89
+ if (typeof customElements !== "undefined" && customElements.get("gait-button")) {
90
+ setReady(true);
91
+ return;
92
+ }
93
+ defineCustomElements().then(() => setReady(true), () => setReady(false));
94
+ }, []);
95
+ useEffect(() => {
96
+ if (!ready || !containerRef.current) return;
97
+ const container = containerRef.current;
98
+ const el = document.createElement("gait-button");
99
+ applyAttributes(el, { "data-id": dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl });
100
+ if (className) el.className = className;
101
+ if (id) el.id = id;
102
+ const listeners = [];
103
+ EVENT_HANDLERS.forEach(({ event, key }) => {
104
+ const handler = (e) => {
105
+ const cb = callbacksRef.current[key];
106
+ if (cb && e instanceof CustomEvent && e.detail) cb(e.detail);
107
+ };
108
+ el.addEventListener(event, handler);
109
+ listeners.push(() => el.removeEventListener(event, handler));
110
+ });
111
+ elementRef.current = el;
112
+ container.appendChild(el);
113
+ return () => {
114
+ listeners.forEach((off) => off());
115
+ if (el.parentNode) el.parentNode.removeChild(el);
116
+ elementRef.current = null;
117
+ };
118
+ }, [ready]);
119
+ useEffect(() => {
120
+ const el = elementRef.current;
121
+ if (!el) return;
122
+ applyAttributes(el, { "data-id": dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl });
123
+ if (className) el.className = className;
124
+ else el.removeAttribute("class");
125
+ if (id) el.id = id;
126
+ else el.removeAttribute("id");
127
+ }, [dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl, className, id]);
128
+ if (!ready) return null;
129
+ return React.createElement("div", { ref: containerRef });
214
130
  });
215
131
  GaitButton.displayName = "GaitButton";
216
132
  export {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/GaitButton.tsx","../src/register.ts"],"sourcesContent":["'use client';\n\n/**\n * React wrapper for GaitButton Web Component\n * Maps React props to element attributes (WC uses attributes as source of truth).\n * Uses ref + useEffect for binding - no direct JSX attribute spread to avoid React's\n * string coercion of booleans/objects.\n * Waits for WC registration before rendering to avoid invisible element (race condition).\n */\n\nimport React, {\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from 'react';\nimport { defineCustomElements } from '../register';\nimport type {\n GaitButtonItem,\n GaitPriceBreakdownItem,\n GaitCustomer,\n GaitWebhook,\n GaitClickDetail,\n GaitLoadedDetail,\n GaitDataProcessedDetail,\n GaitSplitFeedbackDetail,\n GaitMerchantIdErrorDetail,\n GaitConfirmDetail,\n} from '../types';\n\nexport interface GaitButtonProps\n extends Omit<React.HTMLAttributes<HTMLElement>, 'onClick' | 'style'> {\n /** Checkout/data identity (required for payment flow) */\n 'data-id'?: string;\n /** Disable the button */\n disabled?: boolean;\n /** Button size */\n size?: 'small' | 'medium' | 'large';\n /** Inline styles - string (for WC) or React.CSSProperties */\n style?: React.CSSProperties | string;\n /** Cart/order items for split payment modal */\n items?: GaitButtonItem[] | string;\n /** Price breakdown items */\n priceBreakdown?: GaitPriceBreakdownItem[] | string;\n /** Total cost */\n totalCost?: number | string;\n /** Customer info for split payment */\n customer?: GaitCustomer | string;\n /** Webhook config for split payment callbacks */\n webhook?: GaitWebhook | string;\n /** Merchant store URL (defaults to window.location.origin) */\n merchantStoreUrl?: string;\n\n /** Button clicked */\n onClick?: (detail: GaitClickDetail) => void;\n /** Component loaded/rendered */\n onLoaded?: (detail: GaitLoadedDetail) => void;\n /** Data processed (e.g. checkoutId resolved) */\n onDataProcessed?: (detail: GaitDataProcessedDetail) => void;\n /** Split payment API feedback (success/failure) */\n onSplitFeedback?: (detail: GaitSplitFeedbackDetail) => void;\n /** Merchant ID lookup failed */\n onMerchantIdError?: (detail: GaitMerchantIdErrorDetail) => void;\n /** Pay remaining confirmed */\n onConfirm?: (detail: GaitConfirmDetail) => void;\n}\n\nconst GaitButton = forwardRef<HTMLElement, GaitButtonProps>(function GaitButton(\n {\n 'data-id': dataId,\n disabled,\n size,\n style,\n items,\n priceBreakdown,\n totalCost,\n customer,\n webhook,\n merchantStoreUrl,\n onClick,\n onLoaded,\n onDataProcessed,\n onSplitFeedback,\n onMerchantIdError,\n onConfirm,\n className,\n id,\n ...rest\n },\n ref\n) {\n const elementRef = useRef<HTMLElement>(null);\n const [isReady, setIsReady] = useState(false);\n\n useImperativeHandle(ref, () => elementRef.current as HTMLElement, []);\n\n // Wait for WC to be registered before rendering (avoids invisible element)\n useEffect(() => {\n if (typeof customElements !== 'undefined' && customElements.get('gait-button')) {\n setIsReady(true);\n return;\n }\n defineCustomElements().then(() => setIsReady(true)).catch(() => setIsReady(false));\n }, []);\n\n // Sync props to element attributes via ref (WC reads attributes) - must run every render (hooks order)\n useEffect(() => {\n const el = elementRef.current;\n if (!el) return;\n\n if (dataId !== undefined) el.setAttribute('data-id', dataId);\n if (size !== undefined) el.setAttribute('size', size);\n\n if (disabled) {\n el.setAttribute('disabled', '');\n } else {\n el.removeAttribute('disabled');\n }\n\n if (style !== undefined) {\n const styleStr =\n typeof style === 'string'\n ? style\n : Object.entries(style)\n .map(\n ([k, v]) =>\n `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${v}`\n )\n .join('; ');\n el.setAttribute('style', styleStr);\n }\n\n if (items !== undefined) {\n el.setAttribute(\n 'items',\n typeof items === 'string' ? items : JSON.stringify(items)\n );\n }\n\n if (priceBreakdown !== undefined) {\n el.setAttribute(\n 'price-breakdown',\n typeof priceBreakdown === 'string'\n ? priceBreakdown\n : JSON.stringify(priceBreakdown)\n );\n }\n\n if (totalCost !== undefined) {\n el.setAttribute('total-cost', String(totalCost));\n }\n\n if (customer !== undefined) {\n el.setAttribute(\n 'customer',\n typeof customer === 'string' ? customer : JSON.stringify(customer)\n );\n }\n\n if (webhook !== undefined) {\n el.setAttribute(\n 'webhook',\n typeof webhook === 'string' ? webhook : JSON.stringify(webhook)\n );\n }\n\n if (merchantStoreUrl !== undefined) {\n el.setAttribute('merchant-store-url', merchantStoreUrl);\n }\n }, [\n dataId,\n disabled,\n size,\n style,\n items,\n priceBreakdown,\n totalCost,\n customer,\n webhook,\n merchantStoreUrl,\n ]);\n\n // Event listeners\n useEffect(() => {\n const el = elementRef.current;\n if (!el) return;\n\n const handleClick = (e: Event) => {\n if (onClick && e instanceof CustomEvent && e.detail) {\n onClick(e.detail as GaitClickDetail);\n }\n };\n const handleLoaded = (e: Event) => {\n if (onLoaded && e instanceof CustomEvent && e.detail) {\n onLoaded(e.detail as GaitLoadedDetail);\n }\n };\n const handleDataProcessed = (e: Event) => {\n if (onDataProcessed && e instanceof CustomEvent && e.detail) {\n onDataProcessed(e.detail as GaitDataProcessedDetail);\n }\n };\n const handleSplitFeedback = (e: Event) => {\n if (onSplitFeedback && e instanceof CustomEvent && e.detail) {\n onSplitFeedback(e.detail as GaitSplitFeedbackDetail);\n }\n };\n const handleMerchantIdError = (e: Event) => {\n if (onMerchantIdError && e instanceof CustomEvent && e.detail) {\n onMerchantIdError(e.detail as GaitMerchantIdErrorDetail);\n }\n };\n const handleConfirm = (e: Event) => {\n if (onConfirm && e instanceof CustomEvent && e.detail) {\n onConfirm(e.detail as GaitConfirmDetail);\n }\n };\n\n el.addEventListener('gait-click', handleClick as EventListener);\n el.addEventListener('gait-loaded', handleLoaded as EventListener);\n el.addEventListener('gait-data-processed', handleDataProcessed as EventListener);\n el.addEventListener('gait-split-feedback', handleSplitFeedback as EventListener);\n el.addEventListener('gait-merchant-id-error', handleMerchantIdError as EventListener);\n el.addEventListener('gait-confirm', handleConfirm as EventListener);\n\n return () => {\n el.removeEventListener('gait-click', handleClick as EventListener);\n el.removeEventListener('gait-loaded', handleLoaded as EventListener);\n el.removeEventListener('gait-data-processed', handleDataProcessed as EventListener);\n el.removeEventListener('gait-split-feedback', handleSplitFeedback as EventListener);\n el.removeEventListener('gait-merchant-id-error', handleMerchantIdError as EventListener);\n el.removeEventListener('gait-confirm', handleConfirm as EventListener);\n };\n }, [\n onClick,\n onLoaded,\n onDataProcessed,\n onSplitFeedback,\n onMerchantIdError,\n onConfirm,\n ]);\n\n // Conditional render AFTER all hooks - required by Rules of Hooks\n if (!isReady) {\n return null;\n }\n\n // Only pass ref, className, id - React assigns props as properties, which throws\n // \"readonly property\" on custom elements. All other attrs set via setAttribute in useEffect.\n const elementProps: Record<string, unknown> = {\n ref: elementRef,\n };\n if (className) elementProps.className = className;\n if (id) elementProps.id = id;\n Object.assign(elementProps, rest);\n\n return React.createElement('gait-button', elementProps);\n});\n\nGaitButton.displayName = 'GaitButton';\n\nexport { GaitButton };\n","/**\n * Registers Gait Web Components in the browser.\n * Safe to call in SSR environments - no-op when window/customElements unavailable.\n *\n * Usage:\n * import '@gait-financial/react/register'; // Side-effect: registers when in browser\n * // or\n * import { defineCustomElements } from '@gait-financial/react/register';\n * await defineCustomElements(); // Optional: load from custom URL\n */\n\nlet loadPromise: Promise<void> | null = null;\n\n/**\n * Loads and registers Gait Web Components.\n * In browser: loads the WC script if not already registered.\n * In SSR: no-op.\n * Deduplicated: multiple calls return the same promise.\n *\n * @param scriptUrl - Optional URL to the button.js script. If omitted, uses the bundled script.\n */\nexport async function defineCustomElements(\n scriptUrl?: string\n): Promise<void> {\n if (typeof window === 'undefined' || typeof customElements === 'undefined') {\n return;\n }\n if (customElements.get('gait-button')) {\n return;\n }\n if (loadPromise) {\n return loadPromise;\n }\n\n const tryLoad = (url: string): Promise<void> =>\n new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.async = true;\n script.src = url;\n script.onload = () => resolve();\n script.onerror = () => reject();\n document.head.appendChild(script);\n });\n\n loadPromise = (async () => {\n const fallbackUrl = `${typeof window !== 'undefined' ? window.location.origin : ''}/node_modules/@gait-financial/react/dist/wc/button.js`;\n\n if (scriptUrl) {\n await tryLoad(scriptUrl);\n return;\n }\n\n // Try relative to module first (works when not pre-bundled)\n try {\n const url = new URL('./wc/button.js', import.meta.url).href;\n await tryLoad(url);\n return;\n } catch {\n // ignore\n }\n\n // Fallback: direct path (works when Vite pre-bundles and breaks import.meta.url)\n try {\n await tryLoad(fallbackUrl);\n return;\n } catch {\n throw new Error(\n '[@gait-financial/react] Failed to load web component script. If using Vite, add optimizeDeps: { exclude: [\"@gait-financial/react\"] } to vite.config.'\n );\n }\n })();\n return loadPromise;\n}\n\n// Side-effect: auto-register when imported in browser (SSR-safe)\nif (typeof window !== 'undefined' && typeof customElements !== 'undefined') {\n void defineCustomElements();\n}\n"],"mappings":"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","names":["GaitButton"]}
1
+ {"version":3,"sources":["../src/components/GaitButton.tsx","../src/register.ts"],"sourcesContent":["'use client';\n\nimport React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';\nimport { defineCustomElements } from '../register';\nimport type {\n GaitButtonItem,\n GaitPriceBreakdownItem,\n GaitCustomer,\n GaitWebhook,\n GaitClickDetail,\n GaitLoadedDetail,\n GaitDataProcessedDetail,\n GaitSplitFeedbackDetail,\n GaitMerchantIdErrorDetail,\n GaitConfirmDetail,\n} from '../types';\n\nexport interface GaitButtonProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onClick' | 'style'> {\n 'data-id'?: string;\n disabled?: boolean;\n size?: 'small' | 'medium' | 'large';\n style?: React.CSSProperties | string;\n items?: GaitButtonItem[] | string;\n priceBreakdown?: GaitPriceBreakdownItem[] | string;\n totalCost?: number | string;\n customer?: GaitCustomer | string;\n webhook?: GaitWebhook | string;\n merchantStoreUrl?: string;\n onClick?: (detail: GaitClickDetail) => void;\n onLoaded?: (detail: GaitLoadedDetail) => void;\n onDataProcessed?: (detail: GaitDataProcessedDetail) => void;\n onSplitFeedback?: (detail: GaitSplitFeedbackDetail) => void;\n onMerchantIdError?: (detail: GaitMerchantIdErrorDetail) => void;\n onConfirm?: (detail: GaitConfirmDetail) => void;\n}\n\nconst EVENT_HANDLERS: Array<{ event: string; key: keyof Pick<GaitButtonProps, 'onClick' | 'onLoaded' | 'onDataProcessed' | 'onSplitFeedback' | 'onMerchantIdError' | 'onConfirm'> }> = [\n { event: 'gait-click', key: 'onClick' },\n { event: 'gait-loaded', key: 'onLoaded' },\n { event: 'gait-data-processed', key: 'onDataProcessed' },\n { event: 'gait-split-feedback', key: 'onSplitFeedback' },\n { event: 'gait-merchant-id-error', key: 'onMerchantIdError' },\n { event: 'gait-confirm', key: 'onConfirm' },\n];\n\nfunction applyAttributes(\n el: HTMLElement,\n props: Pick<GaitButtonProps, 'data-id' | 'disabled' | 'size' | 'style' | 'items' | 'priceBreakdown' | 'totalCost' | 'customer' | 'webhook' | 'merchantStoreUrl'>\n) {\n const { 'data-id': dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl } = props;\n if (dataId !== undefined) el.setAttribute('data-id', dataId);\n if (size !== undefined) el.setAttribute('size', size);\n disabled ? el.setAttribute('disabled', '') : el.removeAttribute('disabled');\n if (style !== undefined) {\n const s = typeof style === 'string' ? style : Object.entries(style).map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${v}`).join('; ');\n el.setAttribute('style', s);\n }\n if (items !== undefined) el.setAttribute('items', typeof items === 'string' ? items : JSON.stringify(items));\n if (priceBreakdown !== undefined) el.setAttribute('price-breakdown', typeof priceBreakdown === 'string' ? priceBreakdown : JSON.stringify(priceBreakdown));\n if (totalCost !== undefined) el.setAttribute('total-cost', String(totalCost));\n if (customer !== undefined) el.setAttribute('customer', typeof customer === 'string' ? customer : JSON.stringify(customer));\n if (webhook !== undefined) el.setAttribute('webhook', typeof webhook === 'string' ? webhook : JSON.stringify(webhook));\n if (merchantStoreUrl !== undefined) el.setAttribute('merchant-store-url', merchantStoreUrl);\n}\n\nconst GaitButton = forwardRef<HTMLElement, GaitButtonProps>(function GaitButton(props, ref) {\n const {\n 'data-id': dataId,\n disabled,\n size,\n style,\n items,\n priceBreakdown,\n totalCost,\n customer,\n webhook,\n merchantStoreUrl,\n onClick,\n onLoaded,\n onDataProcessed,\n onSplitFeedback,\n onMerchantIdError,\n onConfirm,\n className,\n id,\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const elementRef = useRef<HTMLElement | null>(null);\n const [ready, setReady] = useState(false);\n const callbacksRef = useRef({ onClick, onLoaded, onDataProcessed, onSplitFeedback, onMerchantIdError, onConfirm });\n callbacksRef.current = { onClick, onLoaded, onDataProcessed, onSplitFeedback, onMerchantIdError, onConfirm };\n\n useImperativeHandle(ref, () => elementRef.current as HTMLElement, []);\n\n useEffect(() => {\n if (typeof customElements !== 'undefined' && customElements.get('gait-button')) {\n setReady(true);\n return;\n }\n defineCustomElements().then(() => setReady(true), () => setReady(false));\n }, []);\n\n useEffect(() => {\n if (!ready || !containerRef.current) return;\n\n const container = containerRef.current;\n const el = document.createElement('gait-button') as HTMLElement;\n\n applyAttributes(el, { 'data-id': dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl });\n if (className) el.className = className;\n if (id) el.id = id;\n\n const listeners: Array<() => void> = [];\n EVENT_HANDLERS.forEach(({ event, key }) => {\n const handler = (e: Event) => {\n const cb = callbacksRef.current[key];\n if (cb && e instanceof CustomEvent && e.detail) cb(e.detail as never);\n };\n el.addEventListener(event, handler as EventListener);\n listeners.push(() => el.removeEventListener(event, handler as EventListener));\n });\n\n elementRef.current = el;\n container.appendChild(el);\n\n return () => {\n listeners.forEach((off) => off());\n if (el.parentNode) el.parentNode.removeChild(el);\n elementRef.current = null;\n };\n }, [ready]);\n\n useEffect(() => {\n const el = elementRef.current;\n if (!el) return;\n applyAttributes(el, { 'data-id': dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl });\n if (className) el.className = className;\n else el.removeAttribute('class');\n if (id) el.id = id;\n else el.removeAttribute('id');\n }, [dataId, disabled, size, style, items, priceBreakdown, totalCost, customer, webhook, merchantStoreUrl, className, id]);\n\n if (!ready) return null;\n return React.createElement('div', { ref: containerRef });\n});\n\nGaitButton.displayName = 'GaitButton';\nexport { GaitButton };\n","/**\n * Loads and registers Gait Web Components. Safe for SSR (no-op when no window).\n * Import once: import '@gait-financial/react/register';\n */\n\nlet loadPromise: Promise<void> | null = null;\n\nexport function defineCustomElements(scriptUrl?: string): Promise<void> {\n if (typeof window === 'undefined' || typeof customElements === 'undefined') return Promise.resolve();\n if (customElements.get('gait-button')) return Promise.resolve();\n if (loadPromise) return loadPromise;\n\n loadPromise = new Promise((resolve, reject) => {\n let src: string;\n if (scriptUrl) src = scriptUrl;\n else {\n try {\n src = new URL('./wc/button.js', import.meta.url).href;\n } catch {\n reject(new Error('[@gait-financial/react] Could not resolve script. With Vite, add optimizeDeps: { exclude: [\"@gait-financial/react\"] } to vite.config.'));\n return;\n }\n }\n const script = document.createElement('script');\n script.async = true;\n script.src = src;\n script.onload = () => resolve();\n script.onerror = () => reject(new Error('[@gait-financial/react] Failed to load web component.'));\n document.head.appendChild(script);\n });\n return loadPromise;\n}\n\nif (typeof window !== 'undefined' && typeof customElements !== 'undefined') {\n void defineCustomElements();\n}\n"],"mappings":"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","names":["GaitButton"]}
package/dist/register.cjs CHANGED
@@ -25,45 +25,28 @@ __export(register_exports, {
25
25
  module.exports = __toCommonJS(register_exports);
26
26
  var import_meta = {};
27
27
  var loadPromise = null;
28
- async function defineCustomElements(scriptUrl) {
29
- if (typeof window === "undefined" || typeof customElements === "undefined") {
30
- return;
31
- }
32
- if (customElements.get("gait-button")) {
33
- return;
34
- }
35
- if (loadPromise) {
36
- return loadPromise;
37
- }
38
- const tryLoad = (url) => new Promise((resolve, reject) => {
28
+ function defineCustomElements(scriptUrl) {
29
+ if (typeof window === "undefined" || typeof customElements === "undefined") return Promise.resolve();
30
+ if (customElements.get("gait-button")) return Promise.resolve();
31
+ if (loadPromise) return loadPromise;
32
+ loadPromise = new Promise((resolve, reject) => {
33
+ let src;
34
+ if (scriptUrl) src = scriptUrl;
35
+ else {
36
+ try {
37
+ src = new URL("./wc/button.js", import_meta.url).href;
38
+ } catch {
39
+ reject(new Error('[@gait-financial/react] Could not resolve script. With Vite, add optimizeDeps: { exclude: ["@gait-financial/react"] } to vite.config.'));
40
+ return;
41
+ }
42
+ }
39
43
  const script = document.createElement("script");
40
44
  script.async = true;
41
- script.src = url;
45
+ script.src = src;
42
46
  script.onload = () => resolve();
43
- script.onerror = () => reject();
47
+ script.onerror = () => reject(new Error("[@gait-financial/react] Failed to load web component."));
44
48
  document.head.appendChild(script);
45
49
  });
46
- loadPromise = (async () => {
47
- const fallbackUrl = `${typeof window !== "undefined" ? window.location.origin : ""}/node_modules/@gait-financial/react/dist/wc/button.js`;
48
- if (scriptUrl) {
49
- await tryLoad(scriptUrl);
50
- return;
51
- }
52
- try {
53
- const url = new URL("./wc/button.js", import_meta.url).href;
54
- await tryLoad(url);
55
- return;
56
- } catch {
57
- }
58
- try {
59
- await tryLoad(fallbackUrl);
60
- return;
61
- } catch {
62
- throw new Error(
63
- '[@gait-financial/react] Failed to load web component script. If using Vite, add optimizeDeps: { exclude: ["@gait-financial/react"] } to vite.config.'
64
- );
65
- }
66
- })();
67
50
  return loadPromise;
68
51
  }
69
52
  if (typeof window !== "undefined" && typeof customElements !== "undefined") {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/register.ts"],"sourcesContent":["/**\n * Registers Gait Web Components in the browser.\n * Safe to call in SSR environments - no-op when window/customElements unavailable.\n *\n * Usage:\n * import '@gait-financial/react/register'; // Side-effect: registers when in browser\n * // or\n * import { defineCustomElements } from '@gait-financial/react/register';\n * await defineCustomElements(); // Optional: load from custom URL\n */\n\nlet loadPromise: Promise<void> | null = null;\n\n/**\n * Loads and registers Gait Web Components.\n * In browser: loads the WC script if not already registered.\n * In SSR: no-op.\n * Deduplicated: multiple calls return the same promise.\n *\n * @param scriptUrl - Optional URL to the button.js script. If omitted, uses the bundled script.\n */\nexport async function defineCustomElements(\n scriptUrl?: string\n): Promise<void> {\n if (typeof window === 'undefined' || typeof customElements === 'undefined') {\n return;\n }\n if (customElements.get('gait-button')) {\n return;\n }\n if (loadPromise) {\n return loadPromise;\n }\n\n const tryLoad = (url: string): Promise<void> =>\n new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.async = true;\n script.src = url;\n script.onload = () => resolve();\n script.onerror = () => reject();\n document.head.appendChild(script);\n });\n\n loadPromise = (async () => {\n const fallbackUrl = `${typeof window !== 'undefined' ? window.location.origin : ''}/node_modules/@gait-financial/react/dist/wc/button.js`;\n\n if (scriptUrl) {\n await tryLoad(scriptUrl);\n return;\n }\n\n // Try relative to module first (works when not pre-bundled)\n try {\n const url = new URL('./wc/button.js', import.meta.url).href;\n await tryLoad(url);\n return;\n } catch {\n // ignore\n }\n\n // Fallback: direct path (works when Vite pre-bundles and breaks import.meta.url)\n try {\n await tryLoad(fallbackUrl);\n return;\n } catch {\n throw new Error(\n '[@gait-financial/react] Failed to load web component script. If using Vite, add optimizeDeps: { exclude: [\"@gait-financial/react\"] } to vite.config.'\n );\n }\n })();\n return loadPromise;\n}\n\n// Side-effect: auto-register when imported in browser (SSR-safe)\nif (typeof window !== 'undefined' && typeof customElements !== 'undefined') {\n void defineCustomElements();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,IAAI,cAAoC;AAUxC,eAAsB,qBACpB,WACe;AACf,MAAI,OAAO,WAAW,eAAe,OAAO,mBAAmB,aAAa;AAC1E;AAAA,EACF;AACA,MAAI,eAAe,IAAI,aAAa,GAAG;AACrC;AAAA,EACF;AACA,MAAI,aAAa;AACf,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,CAAC,QACf,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC/B,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,QAAQ;AACf,WAAO,MAAM;AACb,WAAO,SAAS,MAAM,QAAQ;AAC9B,WAAO,UAAU,MAAM,OAAO;AAC9B,aAAS,KAAK,YAAY,MAAM;AAAA,EAClC,CAAC;AAEH,iBAAe,YAAY;AACzB,UAAM,cAAc,GAAG,OAAO,WAAW,cAAc,OAAO,SAAS,SAAS,EAAE;AAElF,QAAI,WAAW;AACb,YAAM,QAAQ,SAAS;AACvB;AAAA,IACF;AAGA,QAAI;AACF,YAAM,MAAM,IAAI,IAAI,kBAAkB,YAAY,GAAG,EAAE;AACvD,YAAM,QAAQ,GAAG;AACjB;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,QAAI;AACF,YAAM,QAAQ,WAAW;AACzB;AAAA,IACF,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG;AACH,SAAO;AACT;AAGA,IAAI,OAAO,WAAW,eAAe,OAAO,mBAAmB,aAAa;AAC1E,OAAK,qBAAqB;AAC5B;","names":[]}
1
+ {"version":3,"sources":["../src/register.ts"],"sourcesContent":["/**\n * Loads and registers Gait Web Components. Safe for SSR (no-op when no window).\n * Import once: import '@gait-financial/react/register';\n */\n\nlet loadPromise: Promise<void> | null = null;\n\nexport function defineCustomElements(scriptUrl?: string): Promise<void> {\n if (typeof window === 'undefined' || typeof customElements === 'undefined') return Promise.resolve();\n if (customElements.get('gait-button')) return Promise.resolve();\n if (loadPromise) return loadPromise;\n\n loadPromise = new Promise((resolve, reject) => {\n let src: string;\n if (scriptUrl) src = scriptUrl;\n else {\n try {\n src = new URL('./wc/button.js', import.meta.url).href;\n } catch {\n reject(new Error('[@gait-financial/react] Could not resolve script. With Vite, add optimizeDeps: { exclude: [\"@gait-financial/react\"] } to vite.config.'));\n return;\n }\n }\n const script = document.createElement('script');\n script.async = true;\n script.src = src;\n script.onload = () => resolve();\n script.onerror = () => reject(new Error('[@gait-financial/react] Failed to load web component.'));\n document.head.appendChild(script);\n });\n return loadPromise;\n}\n\nif (typeof window !== 'undefined' && typeof customElements !== 'undefined') {\n void defineCustomElements();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,IAAI,cAAoC;AAEjC,SAAS,qBAAqB,WAAmC;AACtE,MAAI,OAAO,WAAW,eAAe,OAAO,mBAAmB,YAAa,QAAO,QAAQ,QAAQ;AACnG,MAAI,eAAe,IAAI,aAAa,EAAG,QAAO,QAAQ,QAAQ;AAC9D,MAAI,YAAa,QAAO;AAExB,gBAAc,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC7C,QAAI;AACJ,QAAI,UAAW,OAAM;AAAA,SAChB;AACH,UAAI;AACF,cAAM,IAAI,IAAI,kBAAkB,YAAY,GAAG,EAAE;AAAA,MACnD,QAAQ;AACN,eAAO,IAAI,MAAM,uIAAuI,CAAC;AACzJ;AAAA,MACF;AAAA,IACF;AACA,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,QAAQ;AACf,WAAO,MAAM;AACb,WAAO,SAAS,MAAM,QAAQ;AAC9B,WAAO,UAAU,MAAM,OAAO,IAAI,MAAM,uDAAuD,CAAC;AAChG,aAAS,KAAK,YAAY,MAAM;AAAA,EAClC,CAAC;AACD,SAAO;AACT;AAEA,IAAI,OAAO,WAAW,eAAe,OAAO,mBAAmB,aAAa;AAC1E,OAAK,qBAAqB;AAC5B;","names":[]}
@@ -1,20 +1,6 @@
1
1
  /**
2
- * Registers Gait Web Components in the browser.
3
- * Safe to call in SSR environments - no-op when window/customElements unavailable.
4
- *
5
- * Usage:
6
- * import '@gait-financial/react/register'; // Side-effect: registers when in browser
7
- * // or
8
- * import { defineCustomElements } from '@gait-financial/react/register';
9
- * await defineCustomElements(); // Optional: load from custom URL
10
- */
11
- /**
12
- * Loads and registers Gait Web Components.
13
- * In browser: loads the WC script if not already registered.
14
- * In SSR: no-op.
15
- * Deduplicated: multiple calls return the same promise.
16
- *
17
- * @param scriptUrl - Optional URL to the button.js script. If omitted, uses the bundled script.
2
+ * Loads and registers Gait Web Components. Safe for SSR (no-op when no window).
3
+ * Import once: import '@gait-financial/react/register';
18
4
  */
19
5
  declare function defineCustomElements(scriptUrl?: string): Promise<void>;
20
6
 
@@ -1,20 +1,6 @@
1
1
  /**
2
- * Registers Gait Web Components in the browser.
3
- * Safe to call in SSR environments - no-op when window/customElements unavailable.
4
- *
5
- * Usage:
6
- * import '@gait-financial/react/register'; // Side-effect: registers when in browser
7
- * // or
8
- * import { defineCustomElements } from '@gait-financial/react/register';
9
- * await defineCustomElements(); // Optional: load from custom URL
10
- */
11
- /**
12
- * Loads and registers Gait Web Components.
13
- * In browser: loads the WC script if not already registered.
14
- * In SSR: no-op.
15
- * Deduplicated: multiple calls return the same promise.
16
- *
17
- * @param scriptUrl - Optional URL to the button.js script. If omitted, uses the bundled script.
2
+ * Loads and registers Gait Web Components. Safe for SSR (no-op when no window).
3
+ * Import once: import '@gait-financial/react/register';
18
4
  */
19
5
  declare function defineCustomElements(scriptUrl?: string): Promise<void>;
20
6
 
package/dist/register.js CHANGED
@@ -1,44 +1,27 @@
1
1
  // src/register.ts
2
2
  var loadPromise = null;
3
- async function defineCustomElements(scriptUrl) {
4
- if (typeof window === "undefined" || typeof customElements === "undefined") {
5
- return;
6
- }
7
- if (customElements.get("gait-button")) {
8
- return;
9
- }
10
- if (loadPromise) {
11
- return loadPromise;
12
- }
13
- const tryLoad = (url) => new Promise((resolve, reject) => {
3
+ function defineCustomElements(scriptUrl) {
4
+ if (typeof window === "undefined" || typeof customElements === "undefined") return Promise.resolve();
5
+ if (customElements.get("gait-button")) return Promise.resolve();
6
+ if (loadPromise) return loadPromise;
7
+ loadPromise = new Promise((resolve, reject) => {
8
+ let src;
9
+ if (scriptUrl) src = scriptUrl;
10
+ else {
11
+ try {
12
+ src = new URL("./wc/button.js", import.meta.url).href;
13
+ } catch {
14
+ reject(new Error('[@gait-financial/react] Could not resolve script. With Vite, add optimizeDeps: { exclude: ["@gait-financial/react"] } to vite.config.'));
15
+ return;
16
+ }
17
+ }
14
18
  const script = document.createElement("script");
15
19
  script.async = true;
16
- script.src = url;
20
+ script.src = src;
17
21
  script.onload = () => resolve();
18
- script.onerror = () => reject();
22
+ script.onerror = () => reject(new Error("[@gait-financial/react] Failed to load web component."));
19
23
  document.head.appendChild(script);
20
24
  });
21
- loadPromise = (async () => {
22
- const fallbackUrl = `${typeof window !== "undefined" ? window.location.origin : ""}/node_modules/@gait-financial/react/dist/wc/button.js`;
23
- if (scriptUrl) {
24
- await tryLoad(scriptUrl);
25
- return;
26
- }
27
- try {
28
- const url = new URL("./wc/button.js", import.meta.url).href;
29
- await tryLoad(url);
30
- return;
31
- } catch {
32
- }
33
- try {
34
- await tryLoad(fallbackUrl);
35
- return;
36
- } catch {
37
- throw new Error(
38
- '[@gait-financial/react] Failed to load web component script. If using Vite, add optimizeDeps: { exclude: ["@gait-financial/react"] } to vite.config.'
39
- );
40
- }
41
- })();
42
25
  return loadPromise;
43
26
  }
44
27
  if (typeof window !== "undefined" && typeof customElements !== "undefined") {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/register.ts"],"sourcesContent":["/**\n * Registers Gait Web Components in the browser.\n * Safe to call in SSR environments - no-op when window/customElements unavailable.\n *\n * Usage:\n * import '@gait-financial/react/register'; // Side-effect: registers when in browser\n * // or\n * import { defineCustomElements } from '@gait-financial/react/register';\n * await defineCustomElements(); // Optional: load from custom URL\n */\n\nlet loadPromise: Promise<void> | null = null;\n\n/**\n * Loads and registers Gait Web Components.\n * In browser: loads the WC script if not already registered.\n * In SSR: no-op.\n * Deduplicated: multiple calls return the same promise.\n *\n * @param scriptUrl - Optional URL to the button.js script. If omitted, uses the bundled script.\n */\nexport async function defineCustomElements(\n scriptUrl?: string\n): Promise<void> {\n if (typeof window === 'undefined' || typeof customElements === 'undefined') {\n return;\n }\n if (customElements.get('gait-button')) {\n return;\n }\n if (loadPromise) {\n return loadPromise;\n }\n\n const tryLoad = (url: string): Promise<void> =>\n new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.async = true;\n script.src = url;\n script.onload = () => resolve();\n script.onerror = () => reject();\n document.head.appendChild(script);\n });\n\n loadPromise = (async () => {\n const fallbackUrl = `${typeof window !== 'undefined' ? window.location.origin : ''}/node_modules/@gait-financial/react/dist/wc/button.js`;\n\n if (scriptUrl) {\n await tryLoad(scriptUrl);\n return;\n }\n\n // Try relative to module first (works when not pre-bundled)\n try {\n const url = new URL('./wc/button.js', import.meta.url).href;\n await tryLoad(url);\n return;\n } catch {\n // ignore\n }\n\n // Fallback: direct path (works when Vite pre-bundles and breaks import.meta.url)\n try {\n await tryLoad(fallbackUrl);\n return;\n } catch {\n throw new Error(\n '[@gait-financial/react] Failed to load web component script. If using Vite, add optimizeDeps: { exclude: [\"@gait-financial/react\"] } to vite.config.'\n );\n }\n })();\n return loadPromise;\n}\n\n// Side-effect: auto-register when imported in browser (SSR-safe)\nif (typeof window !== 'undefined' && typeof customElements !== 'undefined') {\n void defineCustomElements();\n}\n"],"mappings":";AAWA,IAAI,cAAoC;AAUxC,eAAsB,qBACpB,WACe;AACf,MAAI,OAAO,WAAW,eAAe,OAAO,mBAAmB,aAAa;AAC1E;AAAA,EACF;AACA,MAAI,eAAe,IAAI,aAAa,GAAG;AACrC;AAAA,EACF;AACA,MAAI,aAAa;AACf,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,CAAC,QACf,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC/B,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,QAAQ;AACf,WAAO,MAAM;AACb,WAAO,SAAS,MAAM,QAAQ;AAC9B,WAAO,UAAU,MAAM,OAAO;AAC9B,aAAS,KAAK,YAAY,MAAM;AAAA,EAClC,CAAC;AAEH,iBAAe,YAAY;AACzB,UAAM,cAAc,GAAG,OAAO,WAAW,cAAc,OAAO,SAAS,SAAS,EAAE;AAElF,QAAI,WAAW;AACb,YAAM,QAAQ,SAAS;AACvB;AAAA,IACF;AAGA,QAAI;AACF,YAAM,MAAM,IAAI,IAAI,kBAAkB,YAAY,GAAG,EAAE;AACvD,YAAM,QAAQ,GAAG;AACjB;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,QAAI;AACF,YAAM,QAAQ,WAAW;AACzB;AAAA,IACF,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG;AACH,SAAO;AACT;AAGA,IAAI,OAAO,WAAW,eAAe,OAAO,mBAAmB,aAAa;AAC1E,OAAK,qBAAqB;AAC5B;","names":[]}
1
+ {"version":3,"sources":["../src/register.ts"],"sourcesContent":["/**\n * Loads and registers Gait Web Components. Safe for SSR (no-op when no window).\n * Import once: import '@gait-financial/react/register';\n */\n\nlet loadPromise: Promise<void> | null = null;\n\nexport function defineCustomElements(scriptUrl?: string): Promise<void> {\n if (typeof window === 'undefined' || typeof customElements === 'undefined') return Promise.resolve();\n if (customElements.get('gait-button')) return Promise.resolve();\n if (loadPromise) return loadPromise;\n\n loadPromise = new Promise((resolve, reject) => {\n let src: string;\n if (scriptUrl) src = scriptUrl;\n else {\n try {\n src = new URL('./wc/button.js', import.meta.url).href;\n } catch {\n reject(new Error('[@gait-financial/react] Could not resolve script. With Vite, add optimizeDeps: { exclude: [\"@gait-financial/react\"] } to vite.config.'));\n return;\n }\n }\n const script = document.createElement('script');\n script.async = true;\n script.src = src;\n script.onload = () => resolve();\n script.onerror = () => reject(new Error('[@gait-financial/react] Failed to load web component.'));\n document.head.appendChild(script);\n });\n return loadPromise;\n}\n\nif (typeof window !== 'undefined' && typeof customElements !== 'undefined') {\n void defineCustomElements();\n}\n"],"mappings":";AAKA,IAAI,cAAoC;AAEjC,SAAS,qBAAqB,WAAmC;AACtE,MAAI,OAAO,WAAW,eAAe,OAAO,mBAAmB,YAAa,QAAO,QAAQ,QAAQ;AACnG,MAAI,eAAe,IAAI,aAAa,EAAG,QAAO,QAAQ,QAAQ;AAC9D,MAAI,YAAa,QAAO;AAExB,gBAAc,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC7C,QAAI;AACJ,QAAI,UAAW,OAAM;AAAA,SAChB;AACH,UAAI;AACF,cAAM,IAAI,IAAI,kBAAkB,YAAY,GAAG,EAAE;AAAA,MACnD,QAAQ;AACN,eAAO,IAAI,MAAM,uIAAuI,CAAC;AACzJ;AAAA,MACF;AAAA,IACF;AACA,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,QAAQ;AACf,WAAO,MAAM;AACb,WAAO,SAAS,MAAM,QAAQ;AAC9B,WAAO,UAAU,MAAM,OAAO,IAAI,MAAM,uDAAuD,CAAC;AAChG,aAAS,KAAK,YAAY,MAAM;AAAA,EAClC,CAAC;AACD,SAAO;AACT;AAEA,IAAI,OAAO,WAAW,eAAe,OAAO,mBAAmB,aAAa;AAC1E,OAAK,qBAAqB;AAC5B;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gait-financial/react",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "private": false,
5
5
  "description": "React wrappers for Gait Web Components",
6
6
  "type": "module",