@buildinams/core 0.3.0 → 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.
Files changed (82) hide show
  1. package/README.md +24 -1
  2. package/dist/analytics/google/index.d.ts +158 -0
  3. package/dist/analytics/google/index.js +274 -0
  4. package/dist/analytics/google/index.js.map +1 -0
  5. package/dist/analytics/index.d.ts +101 -0
  6. package/dist/analytics/index.js +384 -0
  7. package/dist/analytics/index.js.map +1 -0
  8. package/dist/analytics/shopify/index.d.ts +186 -0
  9. package/dist/analytics/shopify/index.js +175 -0
  10. package/dist/analytics/shopify/index.js.map +1 -0
  11. package/dist/{chunk-IAAQVL6M.js → chunk-FJ7SNQQI.js} +24 -5
  12. package/dist/chunk-FJ7SNQQI.js.map +1 -0
  13. package/dist/chunk-G2BKC4BU.js +8 -0
  14. package/dist/chunk-G2BKC4BU.js.map +1 -0
  15. package/dist/chunk-GQVN2ECX.js +14 -0
  16. package/dist/chunk-GQVN2ECX.js.map +1 -0
  17. package/dist/chunk-LL3VTFNY.js +22 -0
  18. package/dist/chunk-LL3VTFNY.js.map +1 -0
  19. package/dist/chunk-M3HNM5HC.js +48 -0
  20. package/dist/chunk-M3HNM5HC.js.map +1 -0
  21. package/dist/chunk-MHFGMH3G.js +11 -0
  22. package/dist/chunk-MHFGMH3G.js.map +1 -0
  23. package/dist/chunk-OS2T6P2T.js +50 -0
  24. package/dist/chunk-OS2T6P2T.js.map +1 -0
  25. package/dist/chunk-QIJGG2F4.js +51 -0
  26. package/dist/chunk-QIJGG2F4.js.map +1 -0
  27. package/dist/consent/cookiebot/index.d.ts +16 -0
  28. package/dist/consent/cookiebot/index.js +27 -0
  29. package/dist/consent/cookiebot/index.js.map +1 -0
  30. package/dist/consent/google/index.d.ts +23 -0
  31. package/dist/consent/google/index.js +48 -0
  32. package/dist/consent/google/index.js.map +1 -0
  33. package/dist/consent/index.d.ts +1 -0
  34. package/dist/consent/index.js +3 -0
  35. package/dist/consent/index.js.map +1 -0
  36. package/dist/consent/onetrust/index.d.ts +23 -0
  37. package/dist/consent/onetrust/index.js +39 -0
  38. package/dist/consent/onetrust/index.js.map +1 -0
  39. package/dist/consent/shopify/index.d.ts +44 -0
  40. package/dist/consent/shopify/index.js +84 -0
  41. package/dist/consent/shopify/index.js.map +1 -0
  42. package/dist/cookies/index.d.ts +230 -0
  43. package/dist/cookies/index.js +188 -0
  44. package/dist/cookies/index.js.map +1 -0
  45. package/dist/define-events-CxZjoUlG.d.ts +255 -0
  46. package/dist/destination-5KJ5PeR3.d.ts +122 -0
  47. package/dist/index-Zef1nnTP.d.ts +36 -0
  48. package/dist/index.d.ts +2 -2
  49. package/dist/index.js +64 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/listeners-DkU-vBPa.d.ts +15 -0
  52. package/dist/navigation/index.d.ts +1 -1
  53. package/dist/navigation/index.js +1 -1
  54. package/dist/schema/index.d.ts +16 -9
  55. package/dist/schema/index.js +1 -1
  56. package/dist/schema-CfQvhbuU.d.ts +87 -0
  57. package/dist/store/index.js +1 -1
  58. package/dist/tracking/cart/index.d.ts +74 -0
  59. package/dist/tracking/cart/index.js +118 -0
  60. package/dist/tracking/cart/index.js.map +1 -0
  61. package/dist/tracking/dom/index.d.ts +320 -0
  62. package/dist/tracking/dom/index.js +1196 -0
  63. package/dist/tracking/dom/index.js.map +1 -0
  64. package/docs/analytics.md +883 -0
  65. package/docs/cookies.md +354 -0
  66. package/docs/decisions/001-analytics-project-events.md +31 -0
  67. package/docs/decisions/002-tracking-dom-opt-in.md +29 -0
  68. package/docs/decisions/003-tracking-element-data.md +27 -0
  69. package/docs/decisions/004-cookies-explicit-store.md +26 -0
  70. package/docs/decisions/005-cookies-invalid-reads-fall-back.md +27 -0
  71. package/docs/decisions/006-cookies-server-only-types.md +19 -0
  72. package/docs/decisions/007-cookies-options-on-definition.md +25 -0
  73. package/docs/decisions/008-cookies-in-vertical-slices.md +24 -0
  74. package/docs/decisions/009-analytics-consent-targets.md +29 -0
  75. package/docs/decisions/010-analytics-destination-contract.md +29 -0
  76. package/docs/decisions/011-tracking-decoupled.md +30 -0
  77. package/docs/tracking.md +221 -0
  78. package/package.json +84 -3
  79. package/dist/chunk-IAAQVL6M.js.map +0 -1
  80. package/dist/chunk-SIU7SKJK.js +0 -139
  81. package/dist/chunk-SIU7SKJK.js.map +0 -1
  82. package/dist/schema-iy92gbTC.d.ts +0 -36
@@ -0,0 +1,320 @@
1
+ import { l as Clicked, d as EventCatalog, m as EcommerceOccurrences, j as EcommerceShapes, n as ShapeLeaf, F as FormUsed, P as PageViewed, o as Scrolled, V as VideoPlayed } from '../../define-events-CxZjoUlG.js';
2
+ export { p as PageType } from '../../define-events-CxZjoUlG.js';
3
+ import { S as Subscribe } from '../../listeners-DkU-vBPa.js';
4
+
5
+ /** The data, or a function that builds it when a plugin first reads it, e.g. when the element becomes visible. */
6
+ type TrackValue<Data> = Data | (() => Data);
7
+
8
+ declare const ROLE = "data-track";
9
+ type AutoTrackKind = "links" | "forms";
10
+
11
+ /** What a plugin reports, as the watcher's listeners receive it. */
12
+ type DomEvent<Name extends string = string, Payload extends object = object, Source extends Element | undefined = Element | undefined> = undefined extends Source ? {
13
+ name: Name;
14
+ payload: Payload;
15
+ } : {
16
+ name: Name;
17
+ payload: Payload;
18
+ element: Source;
19
+ };
20
+ declare const plugin: unique symbol;
21
+ /**
22
+ * Created by `pageViews()`, `ecommerce()`, `clicks()`, `videos()`, `forms()` or `scroll()`, and passed to
23
+ * `createDomWatcher()`. Its type says what it reports, and the fields, data and `auto` kinds `track()` takes.
24
+ */
25
+ type DomPlugin<Event extends DomEvent = DomEvent, Attributes extends object = object, Data extends object = object, Auto extends AutoTrackKind = AutoTrackKind> = {
26
+ readonly [plugin]?: {
27
+ event: Event;
28
+ attributes: Attributes;
29
+ data: Data;
30
+ auto: Auto;
31
+ };
32
+ };
33
+ /** A plugin's own `data-track-<field>` attributes, each a string. */
34
+ type CustomFields = Record<string, string>;
35
+
36
+ /**
37
+ * Plugin for `createDomWatcher()`: `clicked` for links and buttons with `track({ click })`, and links inside
38
+ * `track({ auto: "links" })`, with `location` from the nearest `track({ location })`. `Fields` are the project's own
39
+ * fields of `click`, also in the payload.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * clicks<{ cta: string }>();
44
+ * ```
45
+ */
46
+ declare const clicks: <Fields extends CustomFields = Record<never, never>>() => DomPlugin<{
47
+ name: "clicked";
48
+ payload: Clicked & Partial<Fields>;
49
+ element: Element;
50
+ }, {
51
+ click: {
52
+ id?: string;
53
+ } & Fields;
54
+ location: {
55
+ name: string;
56
+ };
57
+ }, Record<never, never>, "links">;
58
+
59
+ /** What `track()` returns to spread on the element. */
60
+ type TrackProps = {
61
+ [ROLE]?: string;
62
+ } & {
63
+ [name: `${typeof ROLE}-${string}`]: string;
64
+ };
65
+
66
+ type DomWatcherOptions = {
67
+ /** Where the plugins watch for markup and events. Defaults to `document`. */
68
+ root?: Document | Element;
69
+ };
70
+ type UnionToIntersection<Union> = (Union extends unknown ? (value: Union) => void : never) extends (value: infer Intersection) => void ? Intersection : never;
71
+ type Brand<Plugin> = NonNullable<Plugin extends DomPlugin ? Plugin[keyof Plugin] : never>;
72
+ /** What the plugins report, as the watcher's listeners receive it. */
73
+ type DomEventOf<Plugins extends readonly DomPlugin[]> = Brand<Plugins[number]>["event"];
74
+ type AttributesOf<Plugins extends readonly DomPlugin[]> = UnionToIntersection<Brand<Plugins[number]>["attributes"]>;
75
+ type DataOf<Plugins extends readonly DomPlugin[]> = UnionToIntersection<Brand<Plugins[number]>["data"]>;
76
+ type AutoOf<Plugins extends readonly DomPlugin[]> = Brand<Plugins[number]>["auto"];
77
+ /** Payloads of the plugins' events, by name. */
78
+ type EventMapOf<Event extends {
79
+ name: string;
80
+ payload: object;
81
+ }> = {
82
+ [E in Event as E["name"]]: E["payload"];
83
+ };
84
+ /** What `track()` takes: per type, its fields, or `true` when they're all optional, and data for `ecommerce()`. */
85
+ type TrackSpec<Plugins extends readonly DomPlugin[]> = {
86
+ [T in keyof AttributesOf<Plugins>]?: Record<never, never> extends AttributesOf<Plugins>[T] ? AttributesOf<Plugins>[T] | true : AttributesOf<Plugins>[T];
87
+ } & {
88
+ [R in keyof DataOf<Plugins>]?: TrackValue<DataOf<Plugins>[R]> | undefined;
89
+ } & ([AutoOf<Plugins>] extends [never] ? unknown : {
90
+ auto?: AutoOf<Plugins> | readonly [AutoOf<Plugins>, ...AutoOf<Plugins>[]] | false;
91
+ });
92
+ type Tracked<Spec, Data> = [keyof Spec & keyof Data] extends [never] ? TrackProps : TrackProps & {
93
+ ref: (element: Element | null) => void;
94
+ };
95
+ type DomWatcher<Plugins extends readonly DomPlugin[]> = {
96
+ /**
97
+ * Props to spread on an element, so the plugins track it. Each key is a type, written in `data-track`, with its fields
98
+ * as `data-track-<field>` attributes, so server components and CMS markup can write them too. Only the types of the
99
+ * watcher's plugins:
100
+ *
101
+ * - `pageView`: `pageViewed` when the element appears, with `pageViews()`. Put it on each page's root.
102
+ * - `click`: `clicked` for this button or link, with `clicks()`. Buttons always need it.
103
+ * - `location`: `location` on every `clicked` inside it, with `clicks()`. Reports nothing by itself.
104
+ * - `auto`: tracks every link (`"links"`) or form (`"forms"`) inside it, with `clicks()` and `forms()`; `false` turns
105
+ * it off for a subtree. The nearest one decides.
106
+ * - `video`: video events for this `<video>` or the videos inside it, with `videos()`.
107
+ * - `form`: `formStarted` and `formSubmitted` for this form, with `forms()`.
108
+ * - `scroll`: `scrolled` as the visitor scrolls through this element, with `scroll()`.
109
+ *
110
+ * The project's item, list or promotion, for `ecommerce()`, come with a `ref`, which sets the data on the element
111
+ * once it mounts, so it stays out of the HTML. Without data, e.g. while a quick view is closed, the element isn't
112
+ * tracked as one.
113
+ *
114
+ * - `viewItem`: `itemViewed` when visible, once per URL for each product. On a product page.
115
+ * - `quickView`: `itemViewed` each time it becomes visible. On a quick view.
116
+ * - `promotion`: `promotionViewed` when visible, `promotionSelected` for clicks inside it.
117
+ * - `list`: places the items inside it in the list.
118
+ * - `item`: `itemsViewed` when visible, batched per list, `itemSelected` for clicks inside it.
119
+ *
120
+ * Data can be a function, built when the plugin first reads it, e.g. when the element becomes visible; once read, a
121
+ * new function from a later render is built to compare it, so keep it stable where building is costly. An element
122
+ * with its own ref needs a ref that calls both. Without a framework, call `ref` with the element: it also adds or
123
+ * removes the data's types.
124
+ *
125
+ * @example
126
+ * ```tsx
127
+ * <main {...track({ pageView: { pageType: "product" } })}>
128
+ * <footer {...track({ auto: "links", location: { name: "footer" } })}>
129
+ * <section {...track({ promotion: { id: "summer" }, location: { name: "hero" } })}>
130
+ * ```
131
+ */
132
+ track: <Spec extends TrackSpec<Plugins>>(spec: Spec & Record<Exclude<keyof Spec, keyof TrackSpec<Plugins>>, never>) => Tracked<Spec, DataOf<Plugins>>;
133
+ /**
134
+ * Hands what the plugins report to `listener`: every event, or only those named. Pass the watcher to
135
+ * `createAnalytics()`'s `sources` instead to track them. The first subscription starts the plugins in the browser,
136
+ * and the last one to unsubscribe stops them. What they report while starting, e.g. a server-rendered page's view,
137
+ * reaches every subscription made in the same task.
138
+ */
139
+ subscribe: Subscribe<DomEventOf<Plugins>>;
140
+ /** Catalog entries for what the plugins report, to spread into `defineEvents()`. */
141
+ events: EventCatalog<EventMapOf<DomEventOf<Plugins>>>;
142
+ /**
143
+ * Tells the plugins a new page is shown, for a router that tracks page views itself instead of `pageViews()`. Views
144
+ * pending from the previous page are emitted, and what stays in view counts again.
145
+ */
146
+ newPage: () => void;
147
+ /** Emits the views `ecommerce()` batches, e.g. right before tracking an event that must come after them. */
148
+ flush: () => void;
149
+ };
150
+ /**
151
+ * Watches markup with the plugins you pass and reports what happens, so call sites only need the watcher's `track()`.
152
+ * Creating it does nothing, so server components can import it; the first `subscribe()` starts it in the browser:
153
+ *
154
+ * - `pageViews()`: `pageViewed` for `track({ pageView })`.
155
+ * - `ecommerce()`: `itemViewed`, `itemsViewed`, `itemSelected`, `promotionViewed` and `promotionSelected` for
156
+ * `track({ viewItem | quickView | list | item | promotion })`.
157
+ * - `clicks()`: `clicked` for links and buttons with `track({ click })`, and links inside `track({ auto: "links" })`.
158
+ * - `videos()`: `videoStarted`, `videoProgressed` and `videoCompleted` for `track({ video })`.
159
+ * - `forms()`: `formStarted` and `formSubmitted` for `track({ form })`, and forms inside `track({ auto: "forms" })`.
160
+ * - `scroll()`: `scrolled` as `track({ scroll })` is scrolled into view, by default at 25, 50, 75 and 90%.
161
+ *
162
+ * The plugins decide which types `track()` takes, and with which fields and data. Nothing is tracked without its type
163
+ * in `data-track`, so a project decides what reaches analytics. Works with server-rendered and CMS markup, as it
164
+ * listens on the root instead of the elements.
165
+ *
166
+ * @example
167
+ * ```ts
168
+ * export const domWatcher = createDomWatcher([pageViews(), clicks<{ cta: string }>(), ecommerce<{ item: Item }>()]);
169
+ * export const { track } = domWatcher;
170
+ *
171
+ * export const events = defineEvents({ ...domWatcher.events });
172
+ * export const analytics = createAnalytics<EventsOf<typeof events>>({ events, sources: [domWatcher], consent });
173
+ * ```
174
+ */
175
+ declare const createDomWatcher: <const Plugins extends readonly DomPlugin[]>(plugins: Plugins, { root: rootOption }?: DomWatcherOptions) => DomWatcher<Plugins>;
176
+
177
+ type Occurrences = EcommerceOccurrences<EcommerceShapes>;
178
+ /** The project's item, and its list and promotion when it has them. */
179
+ type EcommerceInput = {
180
+ item: object;
181
+ list?: object;
182
+ promotion?: object;
183
+ };
184
+ type Complete<Shapes extends EcommerceInput> = {
185
+ item: Shapes["item"];
186
+ list: ShapeLeaf<Shapes, "list">;
187
+ promotion: ShapeLeaf<Shapes, "promotion">;
188
+ };
189
+ type EcommerceEvent<Shapes extends EcommerceInput> = {
190
+ [K in keyof Occurrences]: DomEvent<K, EcommerceOccurrences<Complete<Shapes>>[K], undefined>;
191
+ }[keyof Occurrences];
192
+ type EcommerceData<Shapes extends EcommerceInput> = {
193
+ viewItem: Shapes["item"];
194
+ quickView: Shapes["item"];
195
+ item: Shapes["item"];
196
+ } & Pick<Complete<Shapes>, Extract<keyof Shapes, "list" | "promotion">>;
197
+ type EcommerceOptions<Item = object> = {
198
+ /**
199
+ * Which product an item is, e.g. `({ product }) => product.id`, so a product page's `{ product, variant }` matches
200
+ * the `{ product }` of the card it was selected on, and counts as one view per product. Defaults to the item's JSON.
201
+ */
202
+ id?: (item: Item) => string;
203
+ /** Share of a promotion or item, or of the viewport for those taller than it, that counts as viewed. */
204
+ threshold?: number;
205
+ /**
206
+ * Milliseconds that views are collected for before they're tracked in the order they happened, with one
207
+ * `itemsViewed` per list.
208
+ */
209
+ batchDelay?: number;
210
+ /**
211
+ * Where the tab keeps the placements of the products last selected, so a product page loaded in full, e.g. on a
212
+ * multi-page site, gets the list it was selected from. Change it when the list's or promotion's shape changes. Default
213
+ * `"analytics-placements"`.
214
+ */
215
+ storageKey?: string;
216
+ };
217
+ /**
218
+ * Plugin for `createDomWatcher()`: views and selections of products, lists and promotions, with the data passed to the
219
+ * watcher's `track()`, placed by the markup around it:
220
+ *
221
+ * - `itemViewed` for `track({ viewItem })` when it becomes visible, and `track({ quickView })` each time it does.
222
+ * - `promotionViewed` and `promotionSelected` for `track({ promotion })`.
223
+ * - `itemsViewed` and `itemSelected` for `track({ item })`, inside `track({ list })`.
224
+ *
225
+ * Views are batched, and emitted before anything else the watcher emits. Leave out `list` or `promotion` when the
226
+ * project has none.
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * ecommerce<{ item: { product: ProductFragment; variant?: VariantFragment }; list: List }>({
231
+ * id: ({ product }) => product.id,
232
+ * });
233
+ * ```
234
+ */
235
+ declare const ecommerce: <Shapes extends EcommerceInput>({ id, threshold, batchDelay, storageKey, }?: EcommerceOptions<Shapes["item"]>) => DomPlugin<EcommerceEvent<Shapes>, Record<never, never>, EcommerceData<Shapes>, never>;
236
+
237
+ /**
238
+ * Plugin for `createDomWatcher()`: `formStarted` and `formSubmitted` for forms with `track({ form })` or inside
239
+ * `track({ auto: "forms" })`. Never field values. `Fields` are the project's own fields of `form`, also in
240
+ * the payload.
241
+ */
242
+ declare const forms: <Fields extends CustomFields = Record<never, never>>() => DomPlugin<{
243
+ name: "formStarted";
244
+ payload: FormUsed & Partial<Fields>;
245
+ element: HTMLFormElement;
246
+ } | {
247
+ name: "formSubmitted";
248
+ payload: FormUsed & {
249
+ formSubmitText?: string;
250
+ } & Partial<Fields>;
251
+ element: HTMLFormElement;
252
+ }, {
253
+ form: {
254
+ id?: string;
255
+ } & Fields;
256
+ }, Record<never, never>, "forms">;
257
+
258
+ /**
259
+ * Plugin for `createDomWatcher()`: `pageViewed` for elements with `track({ pageView })` when they appear or are shown
260
+ * again, once per URL. `Fields` are the project's own fields of `pageView`, also in the payload.
261
+ *
262
+ * @example
263
+ * ```ts
264
+ * pageViews<{ section: string }>();
265
+ * ```
266
+ */
267
+ declare const pageViews: <Fields extends CustomFields = Record<never, never>>() => DomPlugin<{
268
+ name: "pageViewed";
269
+ payload: PageViewed & Partial<Fields>;
270
+ element: Element;
271
+ }, {
272
+ pageView: PageViewed & Fields;
273
+ }, Record<never, never>, never>;
274
+
275
+ type ScrollOptions = {
276
+ /** Percentages of the element scrolled into view that each report `scrolled` once per URL. */
277
+ thresholds?: readonly number[];
278
+ };
279
+ /**
280
+ * Plugin for `createDomWatcher()`: `scrolled` for elements with `track({ scroll })`, once per URL for each threshold
281
+ * (25, 50, 75 and 90%) of the element scrolled into view. Only after a scroll, also a restored scroll position, so an
282
+ * element that fits the viewport isn't tracked on load. Put it on the page's root for scroll depth, or on an article.
283
+ * `Fields` are the project's own fields of `scroll`, also in the payload.
284
+ */
285
+ declare const scroll: <Fields extends CustomFields = Record<never, never>>({ thresholds, }?: ScrollOptions) => DomPlugin<{
286
+ name: "scrolled";
287
+ payload: Scrolled & Partial<Fields>;
288
+ element: Element;
289
+ }, {
290
+ scroll: {
291
+ id?: string;
292
+ } & Fields;
293
+ }, Record<never, never>, never>;
294
+
295
+ /**
296
+ * Plugin for `createDomWatcher()`: `videoStarted`, `videoProgressed` (10, 25, 50 and 75%) and `videoCompleted` for
297
+ * `<video>` elements with, or inside, `track({ video })`, so decorative loops aren't tracked. `Fields` are the
298
+ * project's own fields of `video`, also in the payload.
299
+ */
300
+ declare const videos: <Fields extends CustomFields = Record<never, never>>() => DomPlugin<{
301
+ name: "videoStarted";
302
+ payload: VideoPlayed & Partial<Fields>;
303
+ element: HTMLVideoElement;
304
+ } | {
305
+ name: "videoProgressed";
306
+ payload: VideoPlayed & {
307
+ videoPercent: number;
308
+ } & Partial<Fields>;
309
+ element: HTMLVideoElement;
310
+ } | {
311
+ name: "videoCompleted";
312
+ payload: VideoPlayed & Partial<Fields>;
313
+ element: HTMLVideoElement;
314
+ }, {
315
+ video: {
316
+ title?: string;
317
+ } & Fields;
318
+ }, Record<never, never>, never>;
319
+
320
+ export { type DomPlugin, type DomWatcher, type DomWatcherOptions, type EcommerceOptions, type TrackProps, type TrackSpec, clicks, createDomWatcher, ecommerce, forms, pageViews, scroll, videos };