@buildinams/core 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -1
- package/dist/analytics/google/index.d.ts +158 -0
- package/dist/analytics/google/index.js +274 -0
- package/dist/analytics/google/index.js.map +1 -0
- package/dist/analytics/index.d.ts +101 -0
- package/dist/analytics/index.js +384 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics/shopify/index.d.ts +186 -0
- package/dist/analytics/shopify/index.js +175 -0
- package/dist/analytics/shopify/index.js.map +1 -0
- package/dist/{chunk-5KDGGGVO.js → chunk-FJ7SNQQI.js} +24 -6
- package/dist/chunk-FJ7SNQQI.js.map +1 -0
- package/dist/chunk-G2BKC4BU.js +8 -0
- package/dist/chunk-G2BKC4BU.js.map +1 -0
- package/dist/chunk-GQVN2ECX.js +14 -0
- package/dist/chunk-GQVN2ECX.js.map +1 -0
- package/dist/chunk-LL3VTFNY.js +22 -0
- package/dist/chunk-LL3VTFNY.js.map +1 -0
- package/dist/chunk-M3HNM5HC.js +48 -0
- package/dist/chunk-M3HNM5HC.js.map +1 -0
- package/dist/chunk-MHFGMH3G.js +11 -0
- package/dist/chunk-MHFGMH3G.js.map +1 -0
- package/dist/chunk-OS2T6P2T.js +50 -0
- package/dist/chunk-OS2T6P2T.js.map +1 -0
- package/dist/chunk-QIJGG2F4.js +51 -0
- package/dist/chunk-QIJGG2F4.js.map +1 -0
- package/dist/consent/cookiebot/index.d.ts +16 -0
- package/dist/consent/cookiebot/index.js +27 -0
- package/dist/consent/cookiebot/index.js.map +1 -0
- package/dist/consent/google/index.d.ts +23 -0
- package/dist/consent/google/index.js +48 -0
- package/dist/consent/google/index.js.map +1 -0
- package/dist/consent/index.d.ts +1 -0
- package/dist/consent/index.js +3 -0
- package/dist/consent/index.js.map +1 -0
- package/dist/consent/onetrust/index.d.ts +23 -0
- package/dist/consent/onetrust/index.js +39 -0
- package/dist/consent/onetrust/index.js.map +1 -0
- package/dist/consent/shopify/index.d.ts +44 -0
- package/dist/consent/shopify/index.js +84 -0
- package/dist/consent/shopify/index.js.map +1 -0
- package/dist/cookies/index.d.ts +230 -0
- package/dist/cookies/index.js +188 -0
- package/dist/cookies/index.js.map +1 -0
- package/dist/define-events-CxZjoUlG.d.ts +255 -0
- package/dist/destination-5KJ5PeR3.d.ts +122 -0
- package/dist/index-Zef1nnTP.d.ts +36 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +64 -1
- package/dist/index.js.map +1 -1
- package/dist/listeners-DkU-vBPa.d.ts +15 -0
- package/dist/navigation/index.js +1 -1
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +1 -1
- package/dist/store/index.js +1 -1
- package/dist/tracking/cart/index.d.ts +74 -0
- package/dist/tracking/cart/index.js +118 -0
- package/dist/tracking/cart/index.js.map +1 -0
- package/dist/tracking/dom/index.d.ts +320 -0
- package/dist/tracking/dom/index.js +1196 -0
- package/dist/tracking/dom/index.js.map +1 -0
- package/docs/analytics.md +883 -0
- package/docs/cookies.md +354 -0
- package/docs/decisions/001-analytics-project-events.md +31 -0
- package/docs/decisions/002-tracking-dom-opt-in.md +29 -0
- package/docs/decisions/003-tracking-element-data.md +27 -0
- package/docs/decisions/004-cookies-explicit-store.md +26 -0
- package/docs/decisions/005-cookies-invalid-reads-fall-back.md +27 -0
- package/docs/decisions/006-cookies-server-only-types.md +19 -0
- package/docs/decisions/007-cookies-options-on-definition.md +25 -0
- package/docs/decisions/008-cookies-in-vertical-slices.md +24 -0
- package/docs/decisions/009-analytics-consent-targets.md +29 -0
- package/docs/decisions/010-analytics-destination-contract.md +29 -0
- package/docs/decisions/011-tracking-decoupled.md +30 -0
- package/docs/tracking.md +221 -0
- package/package.json +83 -2
- package/dist/chunk-5KDGGGVO.js.map +0 -1
- package/dist/chunk-SIU7SKJK.js +0 -139
- package/dist/chunk-SIU7SKJK.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { S as SetStateAction, A as AnyObject } from './state-BnfG-Qk9.js';
|
|
2
2
|
export { a as AnyFunction, B as BivariantCallback, E as EmptyObject, L as LooseObject, b as SetState } from './state-BnfG-Qk9.js';
|
|
3
|
-
import
|
|
3
|
+
import { ClassValue } from 'clsx';
|
|
4
4
|
import { ImageProps } from 'next/image';
|
|
5
5
|
|
|
6
6
|
declare const IS_DEVELOPMENT: boolean;
|
|
@@ -147,7 +147,7 @@ declare const transform: (value: number, originRange: [number, number], targetRa
|
|
|
147
147
|
*/
|
|
148
148
|
declare const round: (value: number, decimalPlaces?: number) => number;
|
|
149
149
|
|
|
150
|
-
declare const mergeClassNames: (...classNames:
|
|
150
|
+
declare const mergeClassNames: (...classNames: ClassValue[]) => string;
|
|
151
151
|
|
|
152
152
|
/**
|
|
153
153
|
* Detects if the device has touch capabilities.
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,66 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { shallowEqual } from './chunk-LL3VTFNY.js';
|
|
2
|
+
export { applyState, defaultValue, hasOwnProperty, invariant, isHTMLElement, isInArray, isObject, isTruthy, notEmpty } from './chunk-OS2T6P2T.js';
|
|
3
|
+
import { IS_IN_BROWSER } from './chunk-GQVN2ECX.js';
|
|
4
|
+
export { IS_BUILD, IS_DEVELOPMENT, IS_IN_BROWSER } from './chunk-GQVN2ECX.js';
|
|
5
|
+
import clsx from 'clsx';
|
|
6
|
+
|
|
7
|
+
// src/functions/identity.ts
|
|
8
|
+
function identity(value) {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/functions/math.ts
|
|
13
|
+
var wrap = (value, [min, max]) => {
|
|
14
|
+
const range = max - min + 1;
|
|
15
|
+
return ((value - min) % range + range) % range + min;
|
|
16
|
+
};
|
|
17
|
+
var clamp = (value, [min, max]) => {
|
|
18
|
+
return Math.min(Math.max(value, min), max);
|
|
19
|
+
};
|
|
20
|
+
var transform = (value, originRange, targetRange) => {
|
|
21
|
+
const [oMin, oMax] = originRange;
|
|
22
|
+
const [tMin, tMax] = targetRange;
|
|
23
|
+
const ratio = (value - oMin) / (oMax - oMin);
|
|
24
|
+
const transformedValue = tMin + ratio * (tMax - tMin);
|
|
25
|
+
return transformedValue;
|
|
26
|
+
};
|
|
27
|
+
var round = (value, decimalPlaces = 0) => {
|
|
28
|
+
const factor = Math.pow(10, decimalPlaces);
|
|
29
|
+
return Math.round((value + Number.EPSILON) * factor) / factor;
|
|
30
|
+
};
|
|
31
|
+
var mergeClassNames = (...classNames) => {
|
|
32
|
+
try {
|
|
33
|
+
return clsx(...classNames);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
"mergeClassNames: Invalid input provided. Ensure all class names are strings, undefined, or null."
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/functions/platform.ts
|
|
42
|
+
var isTouchDevice = () => {
|
|
43
|
+
return IS_IN_BROWSER && !!navigator.maxTouchPoints;
|
|
44
|
+
};
|
|
45
|
+
var isApple = () => {
|
|
46
|
+
if (!IS_IN_BROWSER) return false;
|
|
47
|
+
return /mac|iphone|ipad|ipod/i.test(navigator.platform);
|
|
48
|
+
};
|
|
49
|
+
var isMac = () => {
|
|
50
|
+
return IS_IN_BROWSER && navigator.platform.startsWith("Mac") && !isTouchDevice();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/functions/remove-undefined-values.ts
|
|
54
|
+
function removeUndefinedValues(obj) {
|
|
55
|
+
const result = {};
|
|
56
|
+
for (const key in obj) {
|
|
57
|
+
if (obj[key] !== void 0) {
|
|
58
|
+
result[key] = obj[key];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { clamp, identity, isApple, isMac, isTouchDevice, mergeClassNames, removeUndefinedValues, round, transform, wrap };
|
|
2
65
|
//# sourceMappingURL=index.js.map
|
|
3
66
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
1
|
+
{"version":3,"sources":["../src/functions/identity.ts","../src/functions/math.ts","../src/functions/merge-class-names.ts","../src/functions/platform.ts","../src/functions/remove-undefined-values.ts"],"names":[],"mappings":";;;;;;;AAGO,SAAS,SAAY,KAAA,EAAU;AACrC,EAAA,OAAO,KAAA;AACR;;;ACQO,IAAM,OAAO,CAAC,KAAA,EAAe,CAAC,GAAA,EAAK,GAAG,CAAA,KAAkC;AAC9E,EAAA,MAAM,KAAA,GAAQ,MAAM,GAAA,GAAM,CAAA;AAC1B,EAAA,OAAA,CAAA,CAAW,KAAA,GAAQ,GAAA,IAAO,KAAA,GAAS,KAAA,IAAS,KAAA,GAAS,GAAA;AACtD;AAgBO,IAAM,QAAQ,CACpB,KAAA,EACA,CAAC,GAAA,EAAK,GAAG,CAAA,KACL;AACJ,EAAA,OAAO,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,KAAA,EAAO,GAAG,GAAG,GAAG,CAAA;AAC1C;AAcO,IAAM,SAAA,GAAY,CACxB,KAAA,EACA,WAAA,EACA,WAAA,KACI;AACJ,EAAA,MAAM,CAAC,IAAA,EAAM,IAAI,CAAA,GAAI,WAAA;AACrB,EAAA,MAAM,CAAC,IAAA,EAAM,IAAI,CAAA,GAAI,WAAA;AAErB,EAAA,MAAM,KAAA,GAAA,CAAS,KAAA,GAAQ,IAAA,KAAS,IAAA,GAAO,IAAA,CAAA;AACvC,EAAA,MAAM,gBAAA,GAAmB,IAAA,GAAO,KAAA,IAAS,IAAA,GAAO,IAAA,CAAA;AAEhD,EAAA,OAAO,gBAAA;AACR;AAQO,IAAM,KAAA,GAAQ,CAAC,KAAA,EAAe,aAAA,GAAwB,CAAA,KAAM;AAClE,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,aAAa,CAAA;AACzC,EAAA,OAAO,KAAK,KAAA,CAAA,CAAO,KAAA,GAAQ,MAAA,CAAO,OAAA,IAAW,MAAM,CAAA,GAAI,MAAA;AACxD;ACxEA,IAAM,eAAA,GAAkB,IAAI,UAAA,KAA6B;AACxD,EAAA,IAAI;AACH,IAAA,OAAO,IAAA,CAAK,GAAG,UAAU,CAAA;AAAA,EAC1B,SAAS,KAAA,EAAO;AACf,IAAA,MAAM,IAAI,KAAA;AAAA,MACT;AAAA,KACD;AAAA,EACD;AACD;;;ACLO,IAAM,gBAAgB,MAAM;AAClC,EAAA,OAAO,aAAA,IAAiB,CAAC,CAAC,SAAA,CAAU,cAAA;AACrC;AAKO,IAAM,UAAU,MAAM;AAC5B,EAAA,IAAI,CAAC,eAAe,OAAO,KAAA;AAC3B,EAAA,OAAO,uBAAA,CAAwB,IAAA,CAAK,SAAA,CAAU,QAAQ,CAAA;AACvD;AAKO,IAAM,QAAQ,MAAM;AAC1B,EAAA,OACC,iBAAiB,SAAA,CAAU,QAAA,CAAS,WAAW,KAAK,CAAA,IAAK,CAAC,aAAA,EAAc;AAE1E;;;ACnBO,SAAS,sBAA2C,GAAA,EAAQ;AAClE,EAAA,MAAM,SAAS,EAAC;AAChB,EAAA,KAAA,MAAW,OAAO,GAAA,EAAK;AACtB,IAAA,IAAI,GAAA,CAAI,GAAG,CAAA,KAAM,MAAA,EAAW;AAC3B,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,GAAA,CAAI,GAAG,CAAA;AAAA,IACtB;AAAA,EACD;AACA,EAAA,OAAO,MAAA;AACR","file":"index.js","sourcesContent":["/**\n * Returns the same argument.\n */\nexport function identity<T>(value: T) {\n\treturn value;\n}\n","/**\n * Wraps a number within a specified range.\n * @param value - The number to wrap.\n * @param min - The minimum value of the range (inclusive).\n * @param max - The maximum value of the range (inclusive).\n * @returns The wrapped number within the range [min, max].\n *\n * @example\n * wrap(5, [0, 3]); // Returns 1\n * wrap(-1, [0, 3]); // Returns 2\n * wrap(-3, [0, 3]); // Returns 1\n * wrap(7, [0, 3]); // Returns 3\n */\nexport const wrap = (value: number, [min, max]: [min: number, max: number]) => {\n\tconst range = max - min + 1;\n\treturn ((((value - min) % range) + range) % range) + min;\n};\n\n/**\n * Limits a value between a specified range.\n * @param value - The number to limit.\n * @param min - The minimum value of the range (inclusive).\n * @param max - The maximum value of the range (inclusive).\n * @returns The wrapped number within the range [min, max].\n *\n * @example\n * clamp(2, [0, 3]); // Returns 2\n * clamp(5, [0, 3]); // Returns 3\n * clamp(-1, [0, 3]); // Returns 0\n * clamp(-3, [0, 3]); // Returns 0\n * clamp(7, [0, 3]); // Returns 3\n */\nexport const clamp = (\n\tvalue: number,\n\t[min, max]: [min: number, max: number],\n) => {\n\treturn Math.min(Math.max(value, min), max);\n};\n\n/**\n * Maps a number from one range to another.\n * @param value The number to map.\n * @param originRange The original range of the number.\n * @param targetRange The target range to map the number to.\n * @returns The mapped number within the target range.\n *\n * @example\n * transform(5, [0, 10], [0, 100]); // Returns 50\n * transform(0.5, [0, 1], [0, 10]); // Returns 5\n * transform(-5, [-10, 0], [0, 100]); // Returns 50\n */\nexport const transform = (\n\tvalue: number,\n\toriginRange: [number, number],\n\ttargetRange: [number, number],\n) => {\n\tconst [oMin, oMax] = originRange;\n\tconst [tMin, tMax] = targetRange;\n\n\tconst ratio = (value - oMin) / (oMax - oMin);\n\tconst transformedValue = tMin + ratio * (tMax - tMin);\n\n\treturn transformedValue;\n};\n\n/**\n * Rounds a number to a specified number of decimal places.\n * @param value - The number to round.\n * @param decimalPlaces - The number of decimal places to round to. (default is 0)\n * @returns The rounded number.\n */\nexport const round = (value: number, decimalPlaces: number = 0) => {\n\tconst factor = Math.pow(10, decimalPlaces);\n\treturn Math.round((value + Number.EPSILON) * factor) / factor;\n};\n","import clsx, { type ClassValue } from \"clsx\";\n\nconst mergeClassNames = (...classNames: ClassValue[]) => {\n\ttry {\n\t\treturn clsx(...classNames);\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t\"mergeClassNames: Invalid input provided. Ensure all class names are strings, undefined, or null.\",\n\t\t);\n\t}\n};\n\n// Name isn't very memorable, so we export it in this file to alias it\nexport { mergeClassNames };\n","import { IS_IN_BROWSER } from \"src/config\";\n\n/**\n * Detects if the device has touch capabilities.\n */\nexport const isTouchDevice = () => {\n\treturn IS_IN_BROWSER && !!navigator.maxTouchPoints;\n};\n\n/**\n * Detects Apple device.\n */\nexport const isApple = () => {\n\tif (!IS_IN_BROWSER) return false;\n\treturn /mac|iphone|ipad|ipod/i.test(navigator.platform);\n};\n\n/**\n * Detects Mac computer.\n */\nexport const isMac = () => {\n\treturn (\n\t\tIS_IN_BROWSER && navigator.platform.startsWith(\"Mac\") && !isTouchDevice()\n\t);\n};\n","import type { AnyObject } from \"../types\";\n\n/**\n * Removes undefined values from an object.\n */\nexport function removeUndefinedValues<T extends AnyObject>(obj: T) {\n\tconst result = {} as T;\n\tfor (const key in obj) {\n\t\tif (obj[key] !== undefined) {\n\t\t\tresult[key] = obj[key];\n\t\t}\n\t}\n\treturn result;\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type Named = {
|
|
2
|
+
name: string;
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Subscribes `listener` to every event, or only to those named. A listener that throws is logged, so it doesn't break
|
|
6
|
+
* the others. Returns a function that unsubscribes. Does nothing on the server.
|
|
7
|
+
*/
|
|
8
|
+
type Subscribe<Event extends Named> = {
|
|
9
|
+
(listener: (event: Event) => void): () => void;
|
|
10
|
+
<Name extends Event["name"]>(names: Name | readonly Name[], listener: (event: Extract<Event, {
|
|
11
|
+
name: Name;
|
|
12
|
+
}>) => void): () => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type { Subscribe as S };
|
package/dist/navigation/index.js
CHANGED
package/dist/schema/index.d.ts
CHANGED
|
@@ -49,9 +49,11 @@ declare const createStringSchema: <State = never, SerializableState = State>(tem
|
|
|
49
49
|
*/
|
|
50
50
|
declare const hrefSchema: StringSchema<{
|
|
51
51
|
pathname: string;
|
|
52
|
+
hash: string;
|
|
52
53
|
searchParams: URLSearchParams;
|
|
53
54
|
}, {
|
|
54
55
|
pathname: string;
|
|
56
|
+
hash?: string;
|
|
55
57
|
searchParams?: URLSearchParams;
|
|
56
58
|
}>;
|
|
57
59
|
|
package/dist/schema/index.js
CHANGED
package/dist/store/index.js
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { q as CartOccurrences, d as EventCatalog } from '../../define-events-CxZjoUlG.js';
|
|
2
|
+
import { S as Subscribe } from '../../listeners-DkU-vBPa.js';
|
|
3
|
+
|
|
4
|
+
/** A line in a `CartSnapshot`. */
|
|
5
|
+
type CartLine<Item> = {
|
|
6
|
+
/** Stable for the line's lifetime, e.g. Shopify's cart line id. Lines are matched by it. */
|
|
7
|
+
id: string;
|
|
8
|
+
quantity: number;
|
|
9
|
+
/** Unit price, after discounts. */
|
|
10
|
+
price: number;
|
|
11
|
+
/** The project's data for the line, e.g. its product and variant. */
|
|
12
|
+
item: Item;
|
|
13
|
+
};
|
|
14
|
+
/** The cart as the server confirmed it. */
|
|
15
|
+
type CartSnapshot<Item> = {
|
|
16
|
+
id: string;
|
|
17
|
+
/** ISO 4217, e.g. `"EUR"`. */
|
|
18
|
+
currency: string;
|
|
19
|
+
lines: CartLine<Item>[];
|
|
20
|
+
};
|
|
21
|
+
/** What `createCartChanges()` reports, as its listeners receive it. */
|
|
22
|
+
type CartEvent<Item> = {
|
|
23
|
+
[K in keyof CartOccurrences<Item>]: {
|
|
24
|
+
name: K;
|
|
25
|
+
payload: CartOccurrences<Item>[K];
|
|
26
|
+
};
|
|
27
|
+
}[keyof CartOccurrences<Item>];
|
|
28
|
+
type CartChanges<Item> = {
|
|
29
|
+
/**
|
|
30
|
+
* Reports what changed since the last confirmed cart: `addedToCart` for new lines and higher quantities,
|
|
31
|
+
* `removedFromCart` for removed lines and lower quantities. Pass `null` while there's no cart, so the first lines
|
|
32
|
+
* of a new one count as added. Pass only carts the server confirmed, not optimistic ones. The last cart passed
|
|
33
|
+
* before the first `subscribe()` is held until then, e.g. from a child component's effect, which runs before its
|
|
34
|
+
* parent's. Does nothing on the server.
|
|
35
|
+
*/
|
|
36
|
+
setCart: (cart: CartSnapshot<Item> | null) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Hands what `setCart()` reports to `listener`: every event, or only those named. Pass it to `createAnalytics()`'s
|
|
39
|
+
* `sources` instead to track them.
|
|
40
|
+
*/
|
|
41
|
+
subscribe: Subscribe<CartEvent<Item>>;
|
|
42
|
+
/** Catalog entries for what it reports, to spread into `defineEvents()`. */
|
|
43
|
+
events: EventCatalog<CartOccurrences<Item>>;
|
|
44
|
+
};
|
|
45
|
+
type CartChangesOptions = {
|
|
46
|
+
/**
|
|
47
|
+
* Where the last confirmed cart is kept, so other tabs and later page loads don't track it again. Change it when the
|
|
48
|
+
* item's shape changes, so items stored before don't reach the mappings. Default `"analytics-cart"`.
|
|
49
|
+
*/
|
|
50
|
+
storageKey?: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Reports `addedToCart` and `removedFromCart` to its listeners by comparing confirmed carts, so add and remove
|
|
54
|
+
* buttons, quantity pickers and cart drawers don't each have to. `Item` is the project's cart line item. One per page;
|
|
55
|
+
* pass it every cart the server confirms.
|
|
56
|
+
*
|
|
57
|
+
* The first cart on a page load counts as the starting point, unless the last confirmed cart is stored from an
|
|
58
|
+
* earlier page, e.g. after a form that adds to the cart and redirects. A cart another tab already tracked isn't
|
|
59
|
+
* tracked again. See `docs/analytics.md`.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* export const cartChanges = createCartChanges<CartItem>();
|
|
64
|
+
*
|
|
65
|
+
* export const events = defineEvents({ ...cartChanges.events });
|
|
66
|
+
* export const analytics = createAnalytics<EventsOf<typeof events>>({ events, sources: [cartChanges], consent });
|
|
67
|
+
*
|
|
68
|
+
* // Wherever a confirmed cart arrives, e.g. a server action's response, as `CartSnapshot<CartItem>`:
|
|
69
|
+
* cartChanges.setCart(toCartSnapshot(cart));
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
declare const createCartChanges: <Item>({ storageKey, }?: CartChangesOptions) => CartChanges<Item>;
|
|
73
|
+
|
|
74
|
+
export { type CartChanges, type CartChangesOptions, type CartLine, type CartSnapshot, createCartChanges };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createListeners } from '../../chunk-M3HNM5HC.js';
|
|
2
|
+
import { shape } from '../../chunk-G2BKC4BU.js';
|
|
3
|
+
|
|
4
|
+
// src/tracking/cart/cart-changes.ts
|
|
5
|
+
var EMPTY = { id: null, lines: [] };
|
|
6
|
+
var toKey = ({ id, lines }) => JSON.stringify([id, lines.map((line) => [line.id, line.quantity]).sort()]);
|
|
7
|
+
var isLine = (line) => {
|
|
8
|
+
if (typeof line !== "object" || line === null) return false;
|
|
9
|
+
const { id, quantity, price } = line;
|
|
10
|
+
return typeof id === "string" && typeof quantity === "number" && typeof price === "number" && "item" in line;
|
|
11
|
+
};
|
|
12
|
+
var read = (key) => {
|
|
13
|
+
try {
|
|
14
|
+
const value = localStorage.getItem(key);
|
|
15
|
+
const stored = value ? JSON.parse(value) : void 0;
|
|
16
|
+
if (!stored || !(typeof stored.id === "string" || stored.id === null) || !Array.isArray(stored.lines) || !stored.lines.every(isLine)) {
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
return stored;
|
|
20
|
+
} catch {
|
|
21
|
+
return void 0;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var write = (key, value) => {
|
|
25
|
+
try {
|
|
26
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var toPayload = (currency, changes) => {
|
|
31
|
+
const items = changes.map(([{ item, price }, quantity]) => ({
|
|
32
|
+
item,
|
|
33
|
+
quantity,
|
|
34
|
+
price
|
|
35
|
+
}));
|
|
36
|
+
return {
|
|
37
|
+
currency,
|
|
38
|
+
value: items.reduce(
|
|
39
|
+
(total, { price, quantity }) => total + price * quantity,
|
|
40
|
+
0
|
|
41
|
+
),
|
|
42
|
+
items
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
var createCartChanges = ({
|
|
46
|
+
storageKey = "analytics-cart"
|
|
47
|
+
} = {}) => {
|
|
48
|
+
let previous;
|
|
49
|
+
let held;
|
|
50
|
+
let isReplaying = false;
|
|
51
|
+
const listeners = createListeners({
|
|
52
|
+
start: () => {
|
|
53
|
+
if (!held) return;
|
|
54
|
+
isReplaying = true;
|
|
55
|
+
queueMicrotask(() => {
|
|
56
|
+
isReplaying = false;
|
|
57
|
+
if (!held || !listeners.hasListeners()) return;
|
|
58
|
+
const { cart } = held;
|
|
59
|
+
held = void 0;
|
|
60
|
+
track(cart);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const { emit } = listeners;
|
|
65
|
+
const track = (cart) => {
|
|
66
|
+
const current = cart ? { id: cart.id, lines: cart.lines } : EMPTY;
|
|
67
|
+
const stored = read(storageKey);
|
|
68
|
+
const isTracked = stored && toKey(stored) === toKey(current);
|
|
69
|
+
const from = previous ?? stored;
|
|
70
|
+
previous = current;
|
|
71
|
+
write(storageKey, current);
|
|
72
|
+
if (!cart || isTracked || !from) return;
|
|
73
|
+
const before = from.id === cart.id ? from.lines : [];
|
|
74
|
+
const quantities = new Map(before.map((line) => [line.id, line.quantity]));
|
|
75
|
+
const added = [];
|
|
76
|
+
const removed = [];
|
|
77
|
+
for (const line of cart.lines) {
|
|
78
|
+
const delta = line.quantity - (quantities.get(line.id) ?? 0);
|
|
79
|
+
if (delta > 0) added.push([line, delta]);
|
|
80
|
+
if (delta < 0) removed.push([line, -delta]);
|
|
81
|
+
}
|
|
82
|
+
const remaining = new Set(cart.lines.map((line) => line.id));
|
|
83
|
+
for (const line of before) {
|
|
84
|
+
if (!remaining.has(line.id)) removed.push([line, line.quantity]);
|
|
85
|
+
}
|
|
86
|
+
if (removed.length > 0) {
|
|
87
|
+
emit({
|
|
88
|
+
name: "removedFromCart",
|
|
89
|
+
payload: toPayload(cart.currency, removed)
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (added.length > 0) {
|
|
93
|
+
emit({ name: "addedToCart", payload: toPayload(cart.currency, added) });
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
setCart: (cart) => {
|
|
98
|
+
if (typeof window === "undefined") return;
|
|
99
|
+
if (listeners.hasListeners() && !isReplaying) track(cart);
|
|
100
|
+
else held = { cart };
|
|
101
|
+
},
|
|
102
|
+
subscribe: listeners.subscribe,
|
|
103
|
+
events: {
|
|
104
|
+
addedToCart: {
|
|
105
|
+
description: "Lines were added to the cart, or their quantity increased.",
|
|
106
|
+
payload: shape()
|
|
107
|
+
},
|
|
108
|
+
removedFromCart: {
|
|
109
|
+
description: "Lines were removed from the cart, or their quantity decreased.",
|
|
110
|
+
payload: shape()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export { createCartChanges };
|
|
117
|
+
//# sourceMappingURL=index.js.map
|
|
118
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/tracking/cart/cart-changes.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { type EventCatalog, shape } from \"../../analytics/define-events\";\nimport { createListeners, type Subscribe } from \"../listeners\";\nimport type { CartOccurrences } from \"../occurrences\";\n\n/** A line in a `CartSnapshot`. */\nexport type CartLine<Item> = {\n\t/** Stable for the line's lifetime, e.g. Shopify's cart line id. Lines are matched by it. */\n\tid: string;\n\tquantity: number;\n\t/** Unit price, after discounts. */\n\tprice: number;\n\t/** The project's data for the line, e.g. its product and variant. */\n\titem: Item;\n};\n\n/** The cart as the server confirmed it. */\nexport type CartSnapshot<Item> = {\n\tid: string;\n\t/** ISO 4217, e.g. `\"EUR\"`. */\n\tcurrency: string;\n\tlines: CartLine<Item>[];\n};\n\n/** What `createCartChanges()` reports, as its listeners receive it. */\nexport type CartEvent<Item> = {\n\t[K in keyof CartOccurrences<Item>]: {\n\t\tname: K;\n\t\tpayload: CartOccurrences<Item>[K];\n\t};\n}[keyof CartOccurrences<Item>];\n\nexport type CartChanges<Item> = {\n\t/**\n\t * Reports what changed since the last confirmed cart: `addedToCart` for new lines and higher quantities,\n\t * `removedFromCart` for removed lines and lower quantities. Pass `null` while there's no cart, so the first lines\n\t * of a new one count as added. Pass only carts the server confirmed, not optimistic ones. The last cart passed\n\t * before the first `subscribe()` is held until then, e.g. from a child component's effect, which runs before its\n\t * parent's. Does nothing on the server.\n\t */\n\tsetCart: (cart: CartSnapshot<Item> | null) => void;\n\t/**\n\t * Hands what `setCart()` reports to `listener`: every event, or only those named. Pass it to `createAnalytics()`'s\n\t * `sources` instead to track them.\n\t */\n\tsubscribe: Subscribe<CartEvent<Item>>;\n\t/** Catalog entries for what it reports, to spread into `defineEvents()`. */\n\tevents: EventCatalog<CartOccurrences<Item>>;\n};\n\nexport type CartChangesOptions = {\n\t/**\n\t * Where the last confirmed cart is kept, so other tabs and later page loads don't track it again. Change it when the\n\t * item's shape changes, so items stored before don't reach the mappings. Default `\"analytics-cart\"`.\n\t */\n\tstorageKey?: string;\n};\n\n/** What's kept between page loads: the cart, or `id: null` for no cart. */\ntype Stored = { id: string | null; lines: CartLine<unknown>[] };\n\nconst EMPTY: Stored = { id: null, lines: [] };\n\n// Quantities by line, so an equal cart reads as already tracked, whatever else changed on it.\nconst toKey = ({ id, lines }: Stored) =>\n\tJSON.stringify([id, lines.map((line) => [line.id, line.quantity]).sort()]);\n\nconst isLine = (line: unknown): line is CartLine<unknown> => {\n\tif (typeof line !== \"object\" || line === null) return false;\n\tconst { id, quantity, price } = line as Partial<CartLine<unknown>>;\n\treturn (\n\t\ttypeof id === \"string\" &&\n\t\ttypeof quantity === \"number\" &&\n\t\ttypeof price === \"number\" &&\n\t\t\"item\" in line\n\t);\n};\n\n// Written by an earlier deploy too, so a cart in another shape is ignored rather than tracked.\nconst read = (key: string): Stored | undefined => {\n\ttry {\n\t\tconst value = localStorage.getItem(key);\n\t\tconst stored = value ? (JSON.parse(value) as Partial<Stored>) : undefined;\n\t\tif (\n\t\t\t!stored ||\n\t\t\t!(typeof stored.id === \"string\" || stored.id === null) ||\n\t\t\t!Array.isArray(stored.lines) ||\n\t\t\t!stored.lines.every(isLine)\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn stored as Stored;\n\t} catch {\n\t\treturn undefined;\n\t}\n};\n\nconst write = (key: string, value: Stored) => {\n\ttry {\n\t\tlocalStorage.setItem(key, JSON.stringify(value));\n\t} catch {\n\t\t// Storage blocked or full: only this page's changes are deduplicated.\n\t}\n};\n\nconst toPayload = <Item>(\n\tcurrency: string,\n\tchanges: [CartLine<Item>, number][],\n): CartOccurrences<Item>[\"addedToCart\"] => {\n\tconst items = changes.map(([{ item, price }, quantity]) => ({\n\t\titem,\n\t\tquantity,\n\t\tprice,\n\t}));\n\treturn {\n\t\tcurrency,\n\t\tvalue: items.reduce(\n\t\t\t(total, { price, quantity }) => total + price * quantity,\n\t\t\t0,\n\t\t),\n\t\titems,\n\t};\n};\n\n/**\n * Reports `addedToCart` and `removedFromCart` to its listeners by comparing confirmed carts, so add and remove\n * buttons, quantity pickers and cart drawers don't each have to. `Item` is the project's cart line item. One per page;\n * pass it every cart the server confirms.\n *\n * The first cart on a page load counts as the starting point, unless the last confirmed cart is stored from an\n * earlier page, e.g. after a form that adds to the cart and redirects. A cart another tab already tracked isn't\n * tracked again. See `docs/analytics.md`.\n *\n * @example\n * ```ts\n * export const cartChanges = createCartChanges<CartItem>();\n *\n * export const events = defineEvents({ ...cartChanges.events });\n * export const analytics = createAnalytics<EventsOf<typeof events>>({ events, sources: [cartChanges], consent });\n *\n * // Wherever a confirmed cart arrives, e.g. a server action's response, as `CartSnapshot<CartItem>`:\n * cartChanges.setCart(toCartSnapshot(cart));\n * ```\n */\nexport const createCartChanges = <Item>({\n\tstorageKey = \"analytics-cart\",\n}: CartChangesOptions = {}): CartChanges<Item> => {\n\tlet previous: Stored | undefined;\n\tlet held: { cart: CartSnapshot<Item> | null } | undefined;\n\tlet isReplaying = false;\n\n\tconst listeners = createListeners<CartEvent<Item>>({\n\t\tstart: () => {\n\t\t\tif (!held) return;\n\t\t\tisReplaying = true;\n\t\t\t// So every subscription made in the same task receives it, as reporting it marks it tracked.\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tisReplaying = false;\n\t\t\t\tif (!held || !listeners.hasListeners()) return;\n\t\t\t\tconst { cart } = held;\n\t\t\t\theld = undefined;\n\t\t\t\ttrack(cart);\n\t\t\t});\n\t\t},\n\t});\n\tconst { emit } = listeners;\n\n\tconst track = (cart: CartSnapshot<Item> | null) => {\n\t\tconst current: Stored = cart ? { id: cart.id, lines: cart.lines } : EMPTY;\n\t\tconst stored = read(storageKey);\n\t\t// Another tab or page load tracked it already.\n\t\tconst isTracked = stored && toKey(stored) === toKey(current);\n\t\tconst from = previous ?? stored;\n\t\tprevious = current;\n\t\twrite(storageKey, current);\n\t\tif (!cart || isTracked || !from) return;\n\n\t\t// Stored under this key, so with its item.\n\t\tconst before = (from.id === cart.id ? from.lines : []) as CartLine<Item>[];\n\t\tconst quantities = new Map(before.map((line) => [line.id, line.quantity]));\n\t\tconst added: [CartLine<Item>, number][] = [];\n\t\tconst removed: [CartLine<Item>, number][] = [];\n\t\tfor (const line of cart.lines) {\n\t\t\tconst delta = line.quantity - (quantities.get(line.id) ?? 0);\n\t\t\tif (delta > 0) added.push([line, delta]);\n\t\t\tif (delta < 0) removed.push([line, -delta]);\n\t\t}\n\t\tconst remaining = new Set(cart.lines.map((line) => line.id));\n\t\tfor (const line of before) {\n\t\t\tif (!remaining.has(line.id)) removed.push([line, line.quantity]);\n\t\t}\n\n\t\tif (removed.length > 0) {\n\t\t\temit({\n\t\t\t\tname: \"removedFromCart\",\n\t\t\t\tpayload: toPayload(cart.currency, removed),\n\t\t\t});\n\t\t}\n\t\tif (added.length > 0) {\n\t\t\temit({ name: \"addedToCart\", payload: toPayload(cart.currency, added) });\n\t\t}\n\t};\n\n\treturn {\n\t\tsetCart: (cart) => {\n\t\t\tif (typeof window === \"undefined\") return;\n\t\t\tif (listeners.hasListeners() && !isReplaying) track(cart);\n\t\t\telse held = { cart };\n\t\t},\n\t\tsubscribe: listeners.subscribe,\n\t\tevents: {\n\t\t\taddedToCart: {\n\t\t\t\tdescription:\n\t\t\t\t\t\"Lines were added to the cart, or their quantity increased.\",\n\t\t\t\tpayload: shape(),\n\t\t\t},\n\t\t\tremovedFromCart: {\n\t\t\t\tdescription:\n\t\t\t\t\t\"Lines were removed from the cart, or their quantity decreased.\",\n\t\t\t\tpayload: shape(),\n\t\t\t},\n\t\t},\n\t};\n};\n"]}
|