@mparticle/web-sdk 3.28.0 → 3.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mparticle.js CHANGED
@@ -204,7 +204,7 @@ var mParticle = (function () {
204
204
  Base64: Base64$1
205
205
  };
206
206
 
207
- var version = "3.28.0";
207
+ var version = "3.29.1";
208
208
 
209
209
  var Constants = {
210
210
  sdkVersion: version,
@@ -11209,6 +11209,9 @@ var mParticle = (function () {
11209
11209
  this.pendingCheckoutIntents = [];
11210
11210
  // Off until SDK init finds the Rokt connection receives auto button clicks.
11211
11211
  this.autoButtonClicksForwarded = false;
11212
+ // Off until SDK init finds the Rokt connection receives auto link clicks, or
11213
+ // link tracking is off. Only link clicks lose their intent without it.
11214
+ this.autoLinkClicksForwarded = false;
11212
11215
  }
11213
11216
  /**
11214
11217
  * Sets a callback to be invoked when RoktManager becomes ready
@@ -11338,6 +11341,14 @@ var mParticle = (function () {
11338
11341
  RoktManager.prototype.setAutoButtonClicksForwarded = function (forwarded) {
11339
11342
  this.autoButtonClicksForwarded = forwarded;
11340
11343
  };
11344
+ RoktManager.prototype.setAutoLinkClicksForwarded = function (forwarded) {
11345
+ this.autoLinkClicksForwarded = forwarded;
11346
+ };
11347
+ // The Rokt kit reads this too: while false, its page views keep
11348
+ // dispatchDelayMs, since a checkout link could never release the wait.
11349
+ RoktManager.prototype.areAutoLinkClicksForwarded = function () {
11350
+ return this.autoLinkClicksForwarded;
11351
+ };
11341
11352
  // noTargeting (which noDeviceId implies), doNotShareOrSell or Global Privacy
11342
11353
  // Control. Unlike noFunctional, these also stop auto button and link clicks
11343
11354
  // from being logged.
@@ -12472,6 +12483,9 @@ var mParticle = (function () {
12472
12483
  }
12473
12484
 
12474
12485
  var DOWNLOAD_EXTENSIONS = /\.(pdf|zip|csv|txt|ics|docx?|xlsx?|pptx?|dmg|exe|apk|mp3|mp4)$/i;
12486
+ // A link run by script reads as a link, as an anchor does, so whoever reads
12487
+ // the click treats it the same.
12488
+ var LINK_CONTROL = 'a[href]:not([role="button"]), [role~="link"]:not([role~="button"])';
12475
12489
  function elementKind(control) {
12476
12490
  if (isWalletElement(control)) {
12477
12491
  return 'wallet';
@@ -12480,9 +12494,7 @@ var mParticle = (function () {
12480
12494
  if (tag === 'button' || tag === 'input') {
12481
12495
  return tag;
12482
12496
  }
12483
- // A link run by script reads as a link, as an anchor does, so whoever reads
12484
- // the click treats it the same.
12485
- return control.matches('a[href]:not([role="button"]), [role~="link"]:not([role~="button"])') ? 'link' : 'role';
12497
+ return control.matches(LINK_CONTROL) ? 'link' : 'role';
12486
12498
  }
12487
12499
  var NOT_RENDERED = 'script, style, template, [hidden], [aria-hidden="true"], [inert]';
12488
12500
  // The most nodes read for one label source, and images looked at for one
@@ -12587,12 +12599,14 @@ var mParticle = (function () {
12587
12599
  function textOf(element) {
12588
12600
  return element && !isUnsafeTextSource(element) && isShown(element) ? renderedText(element) : '';
12589
12601
  }
12590
- // The control itself is already known to be shown.
12602
+ // The control itself is already known to be shown. An inline icon names
12603
+ // itself with aria-label rather than alt.
12591
12604
  function shownImageAlt(control) {
12592
- var images = control.querySelectorAll('img[alt]');
12605
+ var images = control.querySelectorAll('img[alt], svg[aria-label], [role="img"][aria-label]');
12593
12606
  for (var i = 0; i < images.length && i < MAX_READS; i++) {
12594
- if (isShown(images[i], control) && isVisible(images[i])) {
12595
- return nonBlank(images[i].getAttribute('alt'));
12607
+ var alt = nonBlank(images[i].getAttribute('alt') || images[i].getAttribute('aria-label'));
12608
+ if (alt && isShown(images[i], control) && isVisible(images[i])) {
12609
+ return alt;
12596
12610
  }
12597
12611
  }
12598
12612
  return undefined;
@@ -12771,11 +12785,28 @@ var mParticle = (function () {
12771
12785
  var digits = id.replace(/[^0-9]/g, '').length;
12772
12786
  return id.length <= 64 && digits <= 3 && HAND_WRITTEN_ID.test(id) ? id : undefined;
12773
12787
  }
12788
+ // An id React's useId generates, in any of the forms React has used: `:r1:`,
12789
+ // `\u00ABr1\u00BB` and `_r_1_`. On the client it is r and a short base-32 counter; on
12790
+ // the server, R and a base-32 tree id, where a component's later ids add an H
12791
+ // and a counter (`:R1H1:`). A word that only starts with r, such as
12792
+ // `:region:`, is not one.
12793
+ var REACT_ID = /:(?:r[0-9a-v]{1,4}|R[0-9a-v]+(?:H[0-9a-v]+)?):|\u00ab(?:r[0-9a-v]{1,4}|R[0-9a-v]+(?:H[0-9a-v]+)?)\u00bb|_(?:r_[0-9a-v]{1,4}|R_[0-9a-v]+(?:H[0-9a-v]+)?)_/g;
12794
+ // What a UI library puts before the React id it names an unnamed group with
12795
+ // (`radix-:r1:`, `react-aria-:R1:`). Left on its own once that id is removed,
12796
+ // it names the library, not the question.
12797
+ var LIBRARY_ID_PREFIX = /^(?:radix|react-aria|headlessui(?:-[a-z]+)*|mui|chakra|reach|ark|mantine|base-ui)$/i;
12774
12798
  // A picker's input name, which names its question. Sites often end it with the
12775
- // id of the product it belongs to (`color-swatches-48213907`); without that
12776
- // number it still names the question.
12799
+ // id of the product it belongs to (`color-swatches-48213907`), or build it
12800
+ // with a React id (`variant_group__R_1mn59b_`); without either it still names
12801
+ // the question.
12777
12802
  function questionName(value) {
12778
- return stableIdentifier(value && value.trim().replace(/[-_][0-9]{4,}$/, ''));
12803
+ if (!value) {
12804
+ return undefined;
12805
+ }
12806
+ var trimmed = value.trim();
12807
+ var withoutReactId = trimmed.replace(REACT_ID, '-');
12808
+ var name = stableIdentifier(withoutReactId.replace(/([-_])[-_]+/g, '$1').replace(/^[-_]+|[-_]+$/g, '').replace(/[-_][0-9]{4,}$/, ''));
12809
+ return name && withoutReactId !== trimmed && LIBRARY_ID_PREFIX.test(name) ? undefined : name;
12779
12810
  }
12780
12811
  var TEST_ID_ATTRIBUTES = ['data-testid', 'data-test-id', 'data-test', 'data-qa', 'data-cy'];
12781
12812
  // The first of these attributes that holds a developer-written identifier.
@@ -12796,10 +12827,13 @@ var mParticle = (function () {
12796
12827
  return stableIdentifier(ariaControls ? ariaControls.trim().split(/\s+/)[0] : null) || firstIdentifier(control, ['popovertarget', 'commandfor']);
12797
12828
  }
12798
12829
  // The id of the form a button or input actually submits, which the browser
12799
- // resolves from its form attribute or its owning form. getAttribute, because a
12800
- // field named "id" shadows form.id.
12830
+ // resolves from its form attribute or its owning form. A role control, such as
12831
+ // a styled radio box, belongs to the form around it, read without following a
12832
+ // form attribute or leaving its shadow root, since neither makes it part of
12833
+ // the form; a link, anchor or role, to none. getAttribute, because a field
12834
+ // named "id" shadows form.id.
12801
12835
  function formIdentifier(control) {
12802
- var form = control instanceof HTMLButtonElement || control instanceof HTMLInputElement ? control.form : null;
12836
+ var form = control instanceof HTMLButtonElement || control instanceof HTMLInputElement ? control.form : control.matches(LINK_CONTROL) ? null : control.closest('form');
12803
12837
  return form ? stableIdentifier(form.getAttribute('id')) : undefined;
12804
12838
  }
12805
12839
  // The browser's normalised type: a button defaults to 'submit' and an unknown
@@ -13002,7 +13036,7 @@ var mParticle = (function () {
13002
13036
  // aria-label, aria-labelledby or legend, masked like a label and cut at its
13003
13037
  // first colon, since a legend can repeat the pick ("Color: Navy"). Never the
13004
13038
  // option's own text, which is the shopper's answer.
13005
- var NAMED_GROUP = '[role="radiogroup"], [role="listbox"], [role="group"], fieldset';
13039
+ var NAMED_GROUP = '[role="radiogroup"], [role="listbox"], [role="group"], fieldset, ' + '[role="region"][aria-label], [role="region"][aria-labelledby]';
13006
13040
  // A control such as the button that opens a list box shows the option picked.
13007
13041
  var NOT_A_QUESTION = CHOICE + ', ' + CONTROL_SELECTOR + ', a[href], input, select, textarea, slot, [aria-haspopup], [role~="button"], ' + '[role~="link"], [role~="tab"], [role~="combobox"]';
13008
13042
  var OPTION_KINDS = ['radio', 'menuitemradio', 'checkbox', 'menuitemcheckbox', 'switch', 'option'];
@@ -13027,6 +13061,113 @@ var mParticle = (function () {
13027
13061
  var name = choice.getAttribute('name');
13028
13062
  return option instanceof HTMLInputElement && option.type === choice.type && !!name && option.getAttribute('name') === name && option.form === choice.form;
13029
13063
  }
13064
+ function isTransparent(element, until) {
13065
+ return someComposedAncestor(element, function (node) {
13066
+ return window.getComputedStyle(node).opacity === '0';
13067
+ }, until);
13068
+ }
13069
+ // Content that is never painted where it sits: SVG that is only referenced or
13070
+ // read, and a canvas's fallback, which some browsers still give boxes.
13071
+ var UNDRAWN = 'svg title, svg desc, svg metadata, svg defs, svg symbol, svg clipPath, ' + 'svg mask, svg pattern, svg marker, svg linearGradient, ' + 'svg radialGradient, svg filter, canvas';
13072
+ // Whether text sits in any closed details element outside its summary.
13073
+ function isInClosedDetails(text, parent) {
13074
+ var details = parent.closest('details:not([open])');
13075
+ while (details) {
13076
+ var summary = Array.from(details.children).filter(function (child) {
13077
+ return child.tagName === 'SUMMARY';
13078
+ })[0];
13079
+ if (!summary || !summary.contains(text)) {
13080
+ return true;
13081
+ }
13082
+ details = details.parentElement ? details.parentElement.closest('details:not([open])') : null;
13083
+ }
13084
+ return false;
13085
+ }
13086
+ // Whether the browser shows an element through every ancestor it renders
13087
+ // under, shadow trees included, which a walk of light ancestors can't see
13088
+ // into when a shadow root is closed. Where it can't say (checkVisibility
13089
+ // arrived in Chrome 105, Firefox 125 and Safari 17.4), the element counts as
13090
+ // not shown, so option rows name no group in older browsers.
13091
+ function isComposedVisible(element) {
13092
+ var check = element.checkVisibility;
13093
+ return isFunction(check) &&
13094
+ // Older Chrome versions read the first two names, newer browsers the rest.
13095
+ check.call(element, {
13096
+ checkOpacity: true,
13097
+ checkVisibilityCSS: true,
13098
+ opacityProperty: true,
13099
+ visibilityProperty: true
13100
+ });
13101
+ }
13102
+ // Whether the browser lays a text node out in a box of its own. Text it never
13103
+ // draws, such as an SVG definition, canvas fallback, closed details or an
13104
+ // unslotted child of a shadow host, has none. Where boxes can't be read, the
13105
+ // text counts as not drawn.
13106
+ function isDrawn(text) {
13107
+ var range = isFunction(document.createRange) ? document.createRange() : null;
13108
+ if (!range || !isFunction(range.getClientRects)) {
13109
+ return false;
13110
+ }
13111
+ range.selectNodeContents(text);
13112
+ return Array.from(range.getClientRects()).some(function (box) {
13113
+ return box.width > 0 && box.height > 0;
13114
+ });
13115
+ }
13116
+ // Whether a row shows text of its own: a drawn, shown, non-transparent text
13117
+ // node, outside any form field, editable area or opted-out part, beside
13118
+ // nothing but the row's own input. Not a row with a shadow root, slot or
13119
+ // custom element, whose rendered text a walk of its light children can't see.
13120
+ function showsText(row, own) {
13121
+ if (isUnsafeTextSource(row, own) || hasComposedAncestor(own, '[data-mp-no-track]') || row.shadowRoot || row.tagName.indexOf('-') !== -1 || row.querySelector('slot')) {
13122
+ return false;
13123
+ }
13124
+ // A row too large to check in full counts as showing nothing.
13125
+ var descendants = row.querySelectorAll('*');
13126
+ if (descendants.length > MAX_READS) {
13127
+ return false;
13128
+ }
13129
+ for (var i = 0; i < descendants.length; i++) {
13130
+ if (descendants[i].shadowRoot || descendants[i].tagName.indexOf('-') !== -1) {
13131
+ return false;
13132
+ }
13133
+ }
13134
+ var walker = document.createTreeWalker(row, NodeFilter.SHOW_TEXT);
13135
+ for (var i = 0; i < MAX_READS && walker.nextNode(); i++) {
13136
+ var parent_1 = walker.currentNode.parentElement;
13137
+ if (parent_1 && nonBlank(walker.currentNode.nodeValue) &&
13138
+ // Undrawn content, and the body of a closed details element,
13139
+ // which browsers still give layout boxes.
13140
+ !parent_1.closest(UNDRAWN) && !isInClosedDetails(walker.currentNode, parent_1) && isShown(parent_1, row) && isVisible(parent_1) && !isTransparent(parent_1, row) && isComposedVisible(parent_1) && isDrawn(walker.currentNode)) {
13141
+ return true;
13142
+ }
13143
+ }
13144
+ return false;
13145
+ }
13146
+ // A native input inside a shown control of its own, such as a menu option row
13147
+ // with a styled radio, which shows text of its own and doesn't also hold the
13148
+ // option picked.
13149
+ function showsRow(option, choice, group) {
13150
+ var row = option instanceof HTMLInputElement && option.parentElement ? option.parentElement.closest(CONTROL_SELECTOR) : null;
13151
+ return !!row && group.contains(row) && !row.contains(choice) && isShown(row, group) && isVisible(row) && !isTransparent(row, group) && showsText(row, option);
13152
+ }
13153
+ // Native boxes with no name, which the browser doesn't group, answer one
13154
+ // question only as menu option rows do: each in a shown row of its own, in the
13155
+ // same group. A card around a "make default" checkbox and another box has no
13156
+ // such rows. Rows alone can't tell a question from a card named for a person
13157
+ // around separate settings, so only a group that shows its question and holds
13158
+ // the rows directly counts.
13159
+ function unnamedOptionRows(option, choice, group) {
13160
+ return option instanceof HTMLInputElement && choice instanceof HTMLInputElement && option.type === choice.type && !option.getAttribute('name') && !choice.getAttribute('name') && option.form === choice.form && option.closest(NAMED_GROUP) === group && choice.closest(NAMED_GROUP) === group && showsRow(option, choice, group) && showsRow(choice, option, group) && showsQuestion(group);
13161
+ }
13162
+ // A native input the page hides, styling its label as the option instead, is
13163
+ // shown by that label: one of its own, in the group, that the browser draws
13164
+ // and that shows text of its own, as an option row must. Neither an opted-out
13165
+ // input nor an opted-out label counts.
13166
+ function showsLabel(option, choice, group) {
13167
+ return option instanceof HTMLInputElement && !!option.labels && !hasComposedAncestor(option, '[data-mp-no-track]') && Array.from(option.labels).some(function (label) {
13168
+ return group.contains(label) && !label.contains(choice) && !hasComposedAncestor(label, '[data-mp-no-track]') && isShown(label, group) && isVisible(label) && !isTransparent(label, group) && isComposedVisible(label) && showsText(label, option);
13169
+ });
13170
+ }
13030
13171
  // Whether a group offers another answer to the question picked: a shown option
13031
13172
  // to the same question that isn't part of the one picked. A card around one
13032
13173
  // address radio and a "make default" checkbox, or around a role radio and the
@@ -13035,12 +13176,11 @@ var mParticle = (function () {
13035
13176
  var options = group.querySelectorAll(CHOICE);
13036
13177
  for (var i = 0; i < options.length; i++) {
13037
13178
  var option = options[i];
13038
- if (!option.contains(choice) && !choice.contains(option) && sameQuestion(option, choice) && isShown(option, group) && isVisible(option) &&
13039
- // A fully transparent option, as a native input behind a styled
13040
- // one often is, shows the shopper no second answer.
13041
- !someComposedAncestor(option, function (node) {
13042
- return window.getComputedStyle(node).opacity === '0';
13043
- }, group)) {
13179
+ if (!option.contains(choice) && !choice.contains(option) && (sameQuestion(option, choice) || unnamedOptionRows(option, choice, group)) && (isShown(option, group) && isVisible(option) && (
13180
+ // A fully transparent option, as a native input behind a
13181
+ // styled one often is, shows the shopper no second answer,
13182
+ // unless a shown row around it does.
13183
+ !isTransparent(option, group) || showsRow(option, choice, group)) || showsLabel(option, choice, group))) {
13044
13184
  return true;
13045
13185
  }
13046
13186
  }
@@ -13058,35 +13198,72 @@ var mParticle = (function () {
13058
13198
  });
13059
13199
  return labels;
13060
13200
  }
13061
- // The group asking the question a pick answers. A role radio's is its
13062
- // radiogroup and an option's its list box; a role toggle, such as a switch,
13063
- // stands alone. A native box's is the nearest group offering other answers,
13064
- // since a group around one option, such as a saved address card, can be
13065
- // named by that option.
13066
- function questionGroup(choice) {
13201
+ // The name of the group asking the question a pick answers. A role radio's
13202
+ // group is its radiogroup and an option's its list box; a role toggle, such as
13203
+ // a switch, stands alone. A native box's is the nearest group offering other
13204
+ // answers, since a group around one option, such as a saved address card, can
13205
+ // be named by that option. A region with no usable name is passed over, so a
13206
+ // group around it can still name the pick. Each group's name is read once.
13207
+ function groupName(choice) {
13067
13208
  if (!(choice instanceof HTMLInputElement)) {
13068
13209
  var container = choice.matches('[role~="option"]') ? choice.closest('[role="listbox"]') : choice.matches('[role~="radio"]') ? choice.closest('[role="radiogroup"]') : null;
13069
- return container && holdsOptions(container, choice) ? container : null;
13210
+ return container && holdsOptions(container, choice) ? nameOf(container) : undefined;
13070
13211
  }
13071
13212
  var group = choice.closest(NAMED_GROUP);
13072
- while (group && !holdsOptions(group, choice)) {
13213
+ while (group) {
13214
+ if (holdsOptions(group, choice)) {
13215
+ var name_2 = nameOf(group);
13216
+ if (name_2 || !group.matches('[role="region"]')) {
13217
+ return name_2;
13218
+ }
13219
+ }
13073
13220
  group = group.parentElement ? group.parentElement.closest(NAMED_GROUP) : null;
13074
13221
  }
13075
- return group;
13222
+ return undefined;
13076
13223
  }
13077
- function groupName(choice) {
13078
- var group = questionGroup(choice);
13079
- if (!group) {
13080
- return undefined;
13081
- }
13224
+ // Whether what names a group is shown as its question: a fieldset's own
13225
+ // legend, or the header that opens a section. nameOf reads an aria-label first,
13226
+ // so a group carrying one is named by that instead.
13227
+ function showsQuestion(group) {
13228
+ if (group.tagName === 'FIELDSET') {
13229
+ return namedByLegend(group);
13230
+ }
13231
+ return !nonBlank(group.getAttribute('aria-label')) && opensRegion(group, labelledBy(group));
13232
+ }
13233
+ // A fieldset's own legend, a direct child, as the browser reads it.
13234
+ function legendOf(group) {
13235
+ return group.tagName === 'FIELDSET' ? Array.from(group.children).filter(function (child) {
13236
+ return child.tagName === 'LEGEND';
13237
+ })[0] : undefined;
13238
+ }
13239
+ // Whether a fieldset is named by its own legend, which nameOf reads only when
13240
+ // no aria-label overrides it and any aria-labelledby points at that legend.
13241
+ function namedByLegend(group) {
13242
+ var legend = legendOf(group);
13243
+ return !!legend && !nonBlank(group.getAttribute('aria-label')) && (!group.getAttribute('aria-labelledby') || labelledBy(group) === legend);
13244
+ }
13245
+ // The first element a group's aria-labelledby points at.
13246
+ function labelledBy(group) {
13247
+ var ids = group.getAttribute('aria-labelledby');
13248
+ var root = isFunction(group.getRootNode) ? group.getRootNode() : document;
13249
+ return ids && root.getElementById ? root.getElementById(ids.trim().split(/\s+/)[0]) : null;
13250
+ }
13251
+ // Whether a region is opened by this accordion header: a tab outside any tab
13252
+ // list, holding no control or option of its own.
13253
+ function opensRegion(group, header) {
13254
+ return !!header && group.matches('[role="region"]') && (header.getAttribute('role') || '').trim() === 'tab' && !hasComposedAncestor(header, '[role~="tablist"]') && !header.querySelector(NOT_A_QUESTION);
13255
+ }
13256
+ function nameOf(group) {
13082
13257
  var raw = nonBlank(group.getAttribute('aria-label'));
13083
13258
  if (!raw) {
13084
13259
  var ids = group.getAttribute('aria-labelledby');
13085
- var legend = group.tagName === 'FIELDSET' ? Array.from(group.children).filter(function (child) {
13086
- return child.tagName === 'LEGEND';
13087
- })[0] : undefined;
13260
+ var legend = legendOf(group);
13088
13261
  var root_2 = isFunction(group.getRootNode) ? group.getRootNode() : document;
13089
- var source_1 = ids ? root_2.getElementById ? root_2.getElementById(ids.trim().split(/\s+/)[0]) : null : legend || null;
13262
+ var target = ids ? labelledBy(group) : legend || null;
13263
+ // A region opened by an accordion header is named by the header's
13264
+ // first heading, never the rest of what it shows.
13265
+ var header_1 = ids && opensRegion(group, target) ? target : null;
13266
+ var source_1 = header_1 ? header_1.querySelector('h1, h2, h3, h4, h5, h6, [role="heading"]') : target;
13090
13267
  // A label that is, holds or sits in an option or a control, or in an
13091
13268
  // option's label, would name the answer.
13092
13269
  var holder = source_1 ? source_1.closest('label') : null;
@@ -13097,7 +13274,9 @@ var mParticle = (function () {
13097
13274
  return source_1.contains(label) || label.contains(source_1);
13098
13275
  });
13099
13276
  });
13100
- raw = source_1 && !labelsOption && !hasComposedAncestor(source_1, NOT_A_QUESTION) && !source_1.querySelector(NOT_A_QUESTION) && !(holder && (holder.control || holder.querySelector(CHOICE))) ? textOf(source_1) : undefined;
13277
+ raw = source_1 && !labelsOption && !someComposedAncestor(source_1, function (node) {
13278
+ return node !== header_1 && node.matches(NOT_A_QUESTION);
13279
+ }) && !source_1.querySelector(NOT_A_QUESTION) && !(holder && (holder.control || holder.querySelector(CHOICE))) ? textOf(source_1) : undefined;
13101
13280
  }
13102
13281
  return raw ? maskLabel(raw.split(':')[0]) : undefined;
13103
13282
  }
@@ -13115,9 +13294,9 @@ var mParticle = (function () {
13115
13294
  return PAYMENT_QUESTION.test(nameWords(words));
13116
13295
  });
13117
13296
  }
13118
- // What names a payment option: the radio's accessible name, the text and
13119
- // image of each label, and its hand-written ids, text masked like a label.
13120
- function optionNames(radio) {
13297
+ // What names a payment option to shoppers: the radio's accessible name and the
13298
+ // text and image of each label, masked like a label.
13299
+ function shownOptionNames(radio) {
13121
13300
  var names = [maskLabel(accessibleName(radio).raw)];
13122
13301
  var labels = radio instanceof HTMLInputElement && radio.labels ? Array.from(radio.labels).slice(0, 2) : [];
13123
13302
  labels.forEach(function (label) {
@@ -13126,11 +13305,18 @@ var mParticle = (function () {
13126
13305
  names.push(maskLabel(shownImageAlt(label)));
13127
13306
  }
13128
13307
  });
13129
- names.push(stableIdentifier(radio.getAttribute('id')), firstIdentifier(radio, TEST_ID_ATTRIBUTES));
13308
+ return names;
13309
+ }
13310
+ function distinctNames(names) {
13130
13311
  return names.filter(function (name, index) {
13131
13312
  return !!name && names.indexOf(name) === index;
13132
13313
  });
13133
13314
  }
13315
+ // What names a payment option: the names shoppers read, then its hand-written
13316
+ // ids.
13317
+ function optionNames(radio) {
13318
+ return distinctNames(shownOptionNames(radio).concat(stableIdentifier(radio.getAttribute('id')), firstIdentifier(radio, TEST_ID_ATTRIBUTES)));
13319
+ }
13134
13320
  function describePaymentChoice(control, path) {
13135
13321
  if (path === void 0) {
13136
13322
  path = [];
@@ -13145,6 +13331,39 @@ var mParticle = (function () {
13145
13331
  names: names
13146
13332
  } : undefined;
13147
13333
  }
13334
+ var CHECKED_RADIO = 'input[type="radio"]:checked, [role~="radio"][aria-checked="true"], [role~="menuitemradio"][aria-checked="true"]';
13335
+ // The payment radio the page holds checked, such as a card option checked by
13336
+ // default that the shopper never clicked. A page holding more than one option
13337
+ // names no single method, so it describes nothing. One option drawn twice
13338
+ // counts once: a role radio wrapping its native input, a hidden input a
13339
+ // component library keeps beside its role radio, or one group rendered for two
13340
+ // layouts, whose copies share the names shoppers read but not their ids. An
13341
+ // option shoppers see no name for is told apart by its ids.
13342
+ function describeCheckedPaymentChoice() {
13343
+ var radios = Array.from(document.querySelectorAll(CHECKED_RADIO)).filter(function (radio) {
13344
+ return isPicked(radio) && radio.getAttribute('aria-hidden') !== 'true' && !hasComposedAncestor(radio, '[data-mp-no-track]') && isPaymentQuestion(radio);
13345
+ });
13346
+ var choices = radios.filter(function (radio) {
13347
+ return !radios.some(function (other) {
13348
+ return other !== radio && radio.contains(other);
13349
+ });
13350
+ }).map(function (radio) {
13351
+ var shown = distinctNames(shownOptionNames(radio));
13352
+ var names = optionNames(radio);
13353
+ return {
13354
+ radio: radio,
13355
+ names: names,
13356
+ option: (shown.length ? shown : names).join('\n')
13357
+ };
13358
+ });
13359
+ var options = new Set(choices.map(function (choice) {
13360
+ return choice.option;
13361
+ }));
13362
+ return options.size === 1 && choices[0].names.length ? {
13363
+ radio: choices[0].radio,
13364
+ names: choices[0].names
13365
+ } : undefined;
13366
+ }
13148
13367
  // Read once the page has handled the click, which may have cancelled it.
13149
13368
  function isPicked(radio) {
13150
13369
  return radio instanceof HTMLInputElement ? radio.checked : radio.getAttribute('aria-checked') === 'true';
@@ -13191,6 +13410,9 @@ var mParticle = (function () {
13191
13410
  var walletPhrases = new RegExp('^(?:(?:' + WALLET_VERBS + ')(?: now| securely)? (?:with|using|via|through|by) (?:' + WALLET_NAMES + ')(?: express(?: checkout)?| checkout)?|(?:continue|proceed|go) (?:to|with) (?:' + WALLET_NAMES + '))$');
13192
13411
  // A wallet's own name on a button: "PayPal", "Klarna checkout", "Apple Pay logo".
13193
13412
  var walletBrands = new RegExp('^(?:' + WALLET_NAMES + ')(?: (?:checkout|check out|express(?: checkout)?|express pay|buy now|pay now|pay|now)' + '| (?:logo|icon|mark|button)(?: (?:logo|icon|mark|button))*)?$');
13413
+ // The wallets the Rokt kit reads from a click's label or identifiers, as words:
13414
+ // "Place order with PayPal!" and "paypal-pay-button" both name PayPal.
13415
+ var KIT_WALLET_WORDS = /(?:^| )(?:apple ?pay|google ?pay|g ?pay|android ?pay|venmo|pay ?pal|amazon ?pay|shop ?pay|klarna|after ?pay|clear ?pay|affirm|cash ?app|sezzle|samsung ?pay)(?= |$)/;
13194
13416
  // Exact HTTPS hosts of hosted wallet button frames.
13195
13417
  var walletFrameFamilies = {
13196
13418
  'pay.google.com': 'google_pay',
@@ -13394,6 +13616,24 @@ var mParticle = (function () {
13394
13616
  }
13395
13617
  return cart;
13396
13618
  }
13619
+ // The Rokt kit's own word split, so "PayPal!" and "paypal2" both read as
13620
+ // "paypal" here as they do there.
13621
+ function kitWords(value) {
13622
+ return (value || '').replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase().replace(/[^a-z]+/g, ' ').trim();
13623
+ }
13624
+ // A confirm control may name its wallet, so "Place order with PayPal" and a
13625
+ // "Place order" button with id paypal-pay-button are confirm clicks that still
13626
+ // name their own method. A logged click is judged on the fields the kit reads
13627
+ // a method from; a click that isn't logged, on the control's own label and
13628
+ // identifiers.
13629
+ function namesWallet(control, click) {
13630
+ var values = click ? [click.label, click.ariaLabel, click.elementId, click.elementName, click.testId] : labelSources(control).concat(['id', 'name'].concat(TEST_ID_ATTRIBUTES).map(function (attribute) {
13631
+ return control.getAttribute(attribute);
13632
+ }));
13633
+ return values.some(function (value) {
13634
+ return KIT_WALLET_WORDS.test(kitWords(value));
13635
+ });
13636
+ }
13397
13637
  // The host is the domain or one of its subdomains, never a look-alike suffix.
13398
13638
  function isOnDomain(host, domain) {
13399
13639
  return host === domain || host.length > domain.length && host.slice(host.length - domain.length - 1) === '.' + domain;
@@ -13588,8 +13828,9 @@ var mParticle = (function () {
13588
13828
  // Bumped on reset so classification queued before it is dropped.
13589
13829
  this.generation = 0;
13590
13830
  this.lastCommitClick = -Infinity;
13591
- // The latest trusted click: whether the page excluded it, and the intent it
13592
- // already carries to the kit, with its source, if any.
13831
+ // The latest trusted click: whether the page excluded it or it was withheld
13832
+ // from the kit, and the intent it already carries to the kit, with its
13833
+ // source, if any.
13593
13834
  this.lastClick = null;
13594
13835
  this.lastSent = {};
13595
13836
  this.pagePath = null;
@@ -13768,6 +14009,7 @@ var mParticle = (function () {
13768
14009
  var source;
13769
14010
  var adds;
13770
14011
  var choice;
14012
+ var checkedNames;
13771
14013
  var family;
13772
14014
  var unconfirmedCommit = false;
13773
14015
  if (layer === 'click') {
@@ -13776,11 +14018,19 @@ var mParticle = (function () {
13776
14018
  var excluded = isExcludedPath(path);
13777
14019
  var control = excluded ? undefined : findControl(path, true);
13778
14020
  var logged = control || (excluded ? undefined : findControl(path, true, PATH_LIMIT) || findRoleControl(path, PATH_LIMIT));
14021
+ // A click that link tracking logs as Auto Link Click gives the kit no
14022
+ // intent when the Rokt connection does not receive those events.
14023
+ var withheld = false;
13779
14024
  if (logged) {
13780
14025
  // With links alone, only an anchor can be logged.
13781
- click = logsEvents && (this.logButtons || logged.matches('a[href]')) ? describeButtonClick(logged, path) : undefined;
14026
+ var logsThis = logsEvents && (this.logButtons || logged.matches('a[href]'));
14027
+ var linksWithheld = signalsKit && this.logLinks && !this.linkClicksReachKit();
14028
+ var described = logsThis || linksWithheld ? describeButtonClick(logged, path) : undefined;
14029
+ var linkClick = this.logLinks && !!described && isLinkClick(described);
14030
+ withheld = linksWithheld && linkClick;
14031
+ click = logsThis ? described : undefined;
13782
14032
  if (click) {
13783
- asLink = this.logLinks && isLinkClick(click);
14033
+ asLink = linkClick;
13784
14034
  // Without link tracking, a link that leaves the page stays a
13785
14035
  // button click, except a plain link to another page of the
13786
14036
  // site, which button tracking never logged.
@@ -13793,17 +14043,28 @@ var mParticle = (function () {
13793
14043
  box = logged;
13794
14044
  }
13795
14045
  }
13796
- if (control) {
14046
+ if (control && !withheld) {
13797
14047
  // Checkout intent is only worked out when the kit can use it.
13798
14048
  var intent = signalsKit ? classifyIntent(control, pathname) : undefined;
13799
14049
  kind = intent ? intent.kind : undefined;
13800
14050
  source = intent ? intent.source : undefined;
13801
14051
  adds = intent ? intent.adds : undefined;
13802
14052
  choice = signalsKit ? describePaymentChoice(control, path) : undefined;
13803
- }
14053
+ // A confirm or checkout-step click picks no radio itself, so it
14054
+ // carries the one the page holds checked as read here. It is not
14055
+ // read again at delivery: the page's own handlers may clear or
14056
+ // replace it by then. A wallet tap names its own method, and
14057
+ // so does a confirm that names a wallet.
14058
+ if (!choice && (kind === 'commit' || kind === 'checkout_step') && !namesWallet(control, click)) {
14059
+ var checked = describeCheckedPaymentChoice();
14060
+ checkedNames = checked ? checked.names : undefined;
14061
+ }
14062
+ }
14063
+ // A withheld click cannot start an Apple Pay session for the kit
14064
+ // either.
13804
14065
  this.lastClick = {
13805
14066
  t: t,
13806
- excluded: excluded,
14067
+ excluded: excluded || withheld,
13807
14068
  kind: kind,
13808
14069
  source: source
13809
14070
  };
@@ -13854,6 +14115,8 @@ var mParticle = (function () {
13854
14115
  // with this pick.
13855
14116
  if (choice && isPicked(choice.radio)) {
13856
14117
  _this.pickPayment(t, pathname, choice.names);
14118
+ } else if (checkedNames) {
14119
+ _this.pickPayment(t, pathname, checkedNames);
13857
14120
  } else if (family) {
13858
14121
  _this.pickPayment(t, pathname, undefined, family);
13859
14122
  }
@@ -13944,6 +14207,10 @@ var mParticle = (function () {
13944
14207
  var roktManager = this.mpInstance._RoktManager;
13945
14208
  return !this.hasRoktKit || !roktManager || roktManager.isCheckoutIntentSuppressed();
13946
14209
  };
14210
+ ButtonTracker.prototype.linkClicksReachKit = function () {
14211
+ var roktManager = this.mpInstance._RoktManager;
14212
+ return !!roktManager && roktManager.areAutoLinkClicksForwarded();
14213
+ };
13947
14214
  ButtonTracker.prototype.log = function (message) {
13948
14215
  this.mpInstance.Logger.verbose('mParticle button tracking: ' + message);
13949
14216
  };
@@ -14902,6 +15169,7 @@ var mParticle = (function () {
14902
15169
  // Button tracking never runs as a native passthrough.
14903
15170
  resetButtonTracking();
14904
15171
  mpInstance._RoktManager.setAutoButtonClicksForwarded(false);
15172
+ mpInstance._RoktManager.setAutoLinkClicksForwarded(false);
14905
15173
  } else {
14906
15174
  // Main SDK initialization flow
14907
15175
  // Load any settings/identities/attributes from cookie or localStorage
@@ -14952,14 +15220,15 @@ var mParticle = (function () {
14952
15220
  // init or from a returning shopper's landing page view.
14953
15221
  var logButtons = !!getFeatureFlag(AutoButtonTracking);
14954
15222
  var logLinks = !!getFeatureFlag(AutoLinkTracking);
14955
- // Links feed intent too, so with link tracking on the kit must also
14956
- // receive Auto Link Click events.
15223
+ // With link tracking on, a click logged as Auto Link Click feeds intent
15224
+ // only when the kit also receives those events; button clicks still do.
14957
15225
  var buttonClickBlocker = roktConfig_1 ? getButtonClickBlocker(roktConfig_1, kitBlocker) : undefined;
14958
15226
  var linkClickBlocker = roktConfig_1 && logLinks ? getButtonClickBlocker(roktConfig_1, kitBlocker, true) : undefined;
14959
15227
  if (logButtons && (buttonClickBlocker || linkClickBlocker)) {
14960
- mpInstance.Logger.warning(buttonClickBlocker ? "Checkout intent is off: the ".concat(buttonClickBlocker, " keeps some or all Auto Button Click events from the Rokt kit.") : "Checkout intent is off: the ".concat(linkClickBlocker, " keeps some or all Auto Link Click events from the Rokt kit."));
15228
+ mpInstance.Logger.warning(buttonClickBlocker ? "Checkout intent is off: the ".concat(buttonClickBlocker, " keeps some or all Auto Button Click events from the Rokt kit.") : "Checkout intent skips link clicks: the ".concat(linkClickBlocker, " keeps some or all Auto Link Click events from the Rokt kit."));
14961
15229
  }
14962
- mpInstance._RoktManager.setAutoButtonClicksForwarded(!!roktConfig_1 && !buttonClickBlocker && !linkClickBlocker);
15230
+ mpInstance._RoktManager.setAutoButtonClicksForwarded(!!roktConfig_1 && !buttonClickBlocker);
15231
+ mpInstance._RoktManager.setAutoLinkClicksForwarded(!!roktConfig_1 && !linkClickBlocker);
14963
15232
  mpInstance._Forwarders.processForwarders(config, mpInstance._APIClient.prepareForwardingStats);
14964
15233
  mpInstance._Forwarders.processPixelConfigs(config);
14965
15234
  // Checks if session is created, resumed, or needs to be ended
@@ -20,6 +20,7 @@ export interface IButtonClick {
20
20
  fileExtension?: string;
21
21
  checked?: boolean;
22
22
  }
23
+ export declare const TEST_ID_ATTRIBUTES: string[];
23
24
  export declare function isLinkClick(click: IButtonClick): boolean;
24
25
  export declare function describeButtonClick(control: Element, path?: Array<EventTarget | null>): IButtonClick | undefined;
25
26
  export interface IPaymentChoice {
@@ -27,4 +28,5 @@ export interface IPaymentChoice {
27
28
  names: string[];
28
29
  }
29
30
  export declare function describePaymentChoice(control: Element, path?: Array<EventTarget | null>): IPaymentChoice | undefined;
31
+ export declare function describeCheckedPaymentChoice(): IPaymentChoice | undefined;
30
32
  export declare function isPicked(radio: Element): boolean;
@@ -39,5 +39,6 @@ export declare class ButtonTracker {
39
39
  private eventsEnabled;
40
40
  private hasTargetingOptOut;
41
41
  private isKitSuppressed;
42
+ private linkClicksReachKit;
42
43
  private log;
43
44
  }
@@ -1,3 +1,4 @@
1
+ import { IButtonClick } from './buttonDescription';
1
2
  export type CheckoutIntentKind = 'commit' | 'wallet' | 'checkout_step' | 'frame' | 'cart';
2
3
  export type PaymentFrameFamily = 'apple_pay' | 'google_pay' | 'amazon_pay' | 'shop_pay' | 'card';
3
4
  export type CheckoutIntentSource = 'vendor' | 'label' | 'brand' | 'frame' | 'submit' | 'name' | 'api';
@@ -7,6 +8,7 @@ export interface ICheckoutIntent {
7
8
  adds?: true;
8
9
  }
9
10
  export declare function classifyIntent(control: Element, pathname: string): ICheckoutIntent | undefined;
11
+ export declare function namesWallet(control: Element, click?: IButtonClick): boolean;
10
12
  export declare function classifyIntentControl(control: Element, pathname: string): Exclude<CheckoutIntentKind, 'frame'> | undefined;
11
13
  export interface IPaymentFrame {
12
14
  kind: 'wallet' | 'frame';
@@ -111,6 +111,7 @@ export default class RoktManager {
111
111
  private stopRouteChangeWatch;
112
112
  private pendingCheckoutIntents;
113
113
  private autoButtonClicksForwarded;
114
+ private autoLinkClicksForwarded;
114
115
  constructor(instanceName?: string);
115
116
  /**
116
117
  * Sets a callback to be invoked when RoktManager becomes ready
@@ -137,6 +138,8 @@ export default class RoktManager {
137
138
  private watchRouteChanges;
138
139
  private notifyRouteChange;
139
140
  setAutoButtonClicksForwarded(forwarded: boolean): void;
141
+ setAutoLinkClicksForwarded(forwarded: boolean): void;
142
+ areAutoLinkClicksForwarded(): boolean;
140
143
  hasTargetingOptOut(): boolean;
141
144
  isCheckoutIntentSuppressed(): boolean;
142
145
  isCheckoutIntentLive(): boolean;