acinguiux-preact-components 0.0.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/package.json +56 -0
- package/src/content/themes/theme-acinguiux-amg/theme-acinguiux-amg.css +23 -0
- package/src/content/themes/theme-acinguiux-cafe/theme-acinguiux-cafe.css +47 -0
- package/src/content/themes/theme-acinguiux-energy/theme-acinguiux-energy.css +45 -0
- package/src/content/themes/theme-acinguiux-livewire/theme-acinguiux-livewire.css +22 -0
- package/src/content/themes/theme-acinguiux-livewire-italy/theme-acinguiux-livewire-italy.css +22 -0
- package/src/content/themes/theme-acinguiux-recharge/theme-acinguiux-recharge.css +49 -0
- package/src/content/themes/theme-allon/theme-allon.css +25 -0
- package/src/content/themes/theme-atlas/theme-atlas.css +31 -0
- package/src/content/themes/theme-aurvana/resources/favicon/apple-touch-icon.png +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/favico.ico +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/favicon-96x96.png +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/favicon.ico +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/favicon.png +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/favicon.svg +13 -0
- package/src/content/themes/theme-aurvana/resources/favicon/google-touch-icon.png +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/manifest.json +14 -0
- package/src/content/themes/theme-aurvana/resources/favicon/site.webmanifest +21 -0
- package/src/content/themes/theme-aurvana/resources/favicon/web-app-manifest-192x192.png +0 -0
- package/src/content/themes/theme-aurvana/resources/favicon/web-app-manifest-512x512.png +0 -0
- package/src/content/themes/theme-aurvana/theme-aurvana.css +49 -0
- package/src/content/themes/theme-base/theme-base.css +49 -0
- package/src/content/themes/theme-base2/resources/favicon/android-chrome-192x192.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/android-chrome-512x512.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/apple-touch-icon.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favico.ico +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favicon-16x16.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favicon-32x32.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favicon-96x96.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favicon.ico +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favicon.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/favicon.svg +9 -0
- package/src/content/themes/theme-base2/resources/favicon/google-touch-icon.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/manifest.json +14 -0
- package/src/content/themes/theme-base2/resources/favicon/site.webmanifest +1 -0
- package/src/content/themes/theme-base2/resources/favicon/web-app-manifest-192x192.png +0 -0
- package/src/content/themes/theme-base2/resources/favicon/web-app-manifest-512x512.png +0 -0
- package/src/content/themes/theme-base2/resources/fonts/acinguiux-typeface-la-heavy-221208.woff2 +0 -0
- package/src/content/themes/theme-base2/theme-base2.css +47 -0
- package/src/content/themes/theme-eco-marathon/theme-eco-marathon.css +22 -0
- package/src/content/themes/theme-energy-transition-campus-amsterdam/theme-energy-transition-campus-amsterdam.css +26 -0
- package/src/content/themes/theme-evpass/theme-evpass.css +46 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/apple-touch-icon.png +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/favico.ico +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/favicon-96x96.png +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/favicon.ico +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/favicon.png +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/favicon.svg +9 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/google-touch-icon.png +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/manifest.json +14 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/site.webmanifest +21 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/web-app-manifest-192x192.png +0 -0
- package/src/content/themes/theme-nam-2025/resources/favicon/web-app-manifest-512x512.png +0 -0
- package/src/content/themes/theme-nam-2025/theme-nam-2025.css +47 -0
- package/src/content/themes/theme-pennzoil/theme-pennzoil.css +36 -0
- package/src/content/themes/theme-quaker-state/theme-quaker-state.css +63 -0
- package/src/content/themes/theme-tafawoq/theme-tafawoq.css +26 -0
- package/src/content/themes/theme-vegetable/resources/favicon/apple-touch-icon.png +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/favico.ico +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/favicon-96x96.png +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/favicon.ico +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/favicon.png +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/favicon.svg +13 -0
- package/src/content/themes/theme-vegetable/resources/favicon/google-touch-icon.png +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/manifest.json +14 -0
- package/src/content/themes/theme-vegetable/resources/favicon/site.webmanifest +21 -0
- package/src/content/themes/theme-vegetable/resources/favicon/web-app-manifest-192x192.png +0 -0
- package/src/content/themes/theme-vegetable/resources/favicon/web-app-manifest-512x512.png +0 -0
- package/src/content/themes/theme-vegetable/theme-vegetable.css +49 -0
- package/src/content/themes/theme-zeolyst/resources/fonts/type-ar-medium.woff2 +0 -0
- package/src/content/themes/theme-zeolyst/theme-zeolyst.css +29 -0
- package/src/main/atoms/audio.js +16 -0
- package/src/main/atoms/box.js +5 -0
- package/src/main/atoms/button.js +40 -0
- package/src/main/atoms/card.js +22 -0
- package/src/main/atoms/form.js +30 -0
- package/src/main/atoms/heading.js +17 -0
- package/src/main/atoms/icon.js +24 -0
- package/src/main/atoms/img.js +131 -0
- package/src/main/atoms/input.js +55 -0
- package/src/main/atoms/link-text.js +21 -0
- package/src/main/atoms/link.js +60 -0
- package/src/main/atoms/list.js +12 -0
- package/src/main/atoms/logo.js +9 -0
- package/src/main/atoms/menu.js +10 -0
- package/src/main/atoms/message.js +5 -0
- package/src/main/atoms/nav-link.js +49 -0
- package/src/main/atoms/popup.js +47 -0
- package/src/main/atoms/rich-text.js +128 -0
- package/src/main/atoms/scroller.js +224 -0
- package/src/main/atoms/svg.js +65 -0
- package/src/main/atoms/table.js +32 -0
- package/src/main/atoms/textarea.js +10 -0
- package/src/main/atoms/time.js +12 -0
- package/src/main/atoms/video.js +100 -0
- package/src/main/export-main.js +12 -0
- package/src/main/export-matter.js +86 -0
- package/src/main/export-preact-hooks.js +1 -0
- package/src/main/export-preact.js +1 -0
- package/src/main/index.js +13 -0
- package/src/main/molecules/asset.js +23 -0
- package/src/main/molecules/glossary.js +44 -0
- package/src/main/molecules/links.js +23 -0
- package/src/main/molecules/promo-text.js +27 -0
- package/src/main/molecules/tags.js +15 -0
- package/src/main/molecules/tree.js +51 -0
- package/src/main/organisms/accordion-item.js +106 -0
- package/src/main/organisms/author.js +29 -0
- package/src/main/organisms/breadcrumb.js +69 -0
- package/src/main/organisms/call-to-action.js +24 -0
- package/src/main/organisms/carousel.js +178 -0
- package/src/main/organisms/cart-item.js +156 -0
- package/src/main/organisms/cart.js +162 -0
- package/src/main/organisms/contact-form.js +141 -0
- package/src/main/organisms/container/ab-test.js +47 -0
- package/src/main/organisms/container/default.js +6 -0
- package/src/main/organisms/container/filtered-section.js +293 -0
- package/src/main/organisms/container/footer.js +12 -0
- package/src/main/organisms/container/grid.js +44 -0
- package/src/main/organisms/container/header.js +13 -0
- package/src/main/organisms/container/list.js +7 -0
- package/src/main/organisms/container/main.js +6 -0
- package/src/main/organisms/container/raw.js +7 -0
- package/src/main/organisms/container/section.js +28 -0
- package/src/main/organisms/container.js +29 -0
- package/src/main/organisms/content-owner.js +15 -0
- package/src/main/organisms/date-entry.js +56 -0
- package/src/main/organisms/external-search.js +73 -0
- package/src/main/organisms/filtered-item.js +163 -0
- package/src/main/organisms/footer-item.js +17 -0
- package/src/main/organisms/image-gallery.js +164 -0
- package/src/main/organisms/last-modified.js +20 -0
- package/src/main/organisms/legal-footer.js +16 -0
- package/src/main/organisms/list-item.js +48 -0
- package/src/main/organisms/metadata.js +11 -0
- package/src/main/organisms/navigation.js +232 -0
- package/src/main/organisms/notification.js +87 -0
- package/src/main/organisms/order-tracker.js +203 -0
- package/src/main/organisms/page-header-banner.js +26 -0
- package/src/main/organisms/page-header.js +33 -0
- package/src/main/organisms/page-tags.js +14 -0
- package/src/main/organisms/page.js +260 -0
- package/src/main/organisms/press-release.js +24 -0
- package/src/main/organisms/product-admin.js +204 -0
- package/src/main/organisms/promo-banner.js +28 -0
- package/src/main/organisms/promo-bottom.js +23 -0
- package/src/main/organisms/promo-button.js +8 -0
- package/src/main/organisms/promo-card-cover.js +35 -0
- package/src/main/organisms/promo-card.js +33 -0
- package/src/main/organisms/promo-full.js +20 -0
- package/src/main/organisms/promo-image.js +22 -0
- package/src/main/organisms/promo-lure.js +22 -0
- package/src/main/organisms/promo-product-card.js +187 -0
- package/src/main/organisms/promo-product-full.js +293 -0
- package/src/main/organisms/promo-simple.js +23 -0
- package/src/main/organisms/quote.js +21 -0
- package/src/main/organisms/search-form.js +42 -0
- package/src/main/organisms/search-nav.js +66 -0
- package/src/main/organisms/search-result.js +53 -0
- package/src/main/organisms/slider.js +26 -0
- package/src/main/organisms/standalone-asset.js +22 -0
- package/src/main/organisms/tabs.js +277 -0
- package/src/main/organisms/topbar.js +83 -0
- package/src/main/organisms/web-component.js +53 -0
- package/src/main/routing/annotation.js +9 -0
- package/src/main/routing/component.js +138 -0
- package/src/main/routing/empty.js +5 -0
- package/src/main/routing/error-handler.js +64 -0
- package/src/main/routing/placeholder-image.svg +5 -0
- package/src/main/routing/router.js +219 -0
- package/src/main/shared/analytics.js +677 -0
- package/src/main/shared/bubble-event.js +11 -0
- package/src/main/shared/custom-element.js +21 -0
- package/src/main/shared/deep-selector.js +28 -0
- package/src/main/shared/disable-transparency.js +10 -0
- package/src/main/shared/format-time.js +8 -0
- package/src/main/shared/get-id.js +5 -0
- package/src/main/shared/get-meta.js +3 -0
- package/src/main/shared/get-size-class.js +3 -0
- package/src/main/shared/get-size.js +11 -0
- package/src/main/shared/h.js +88 -0
- package/src/main/shared/hash-jump.js +33 -0
- package/src/main/shared/icons/arrow-back.svg +1 -0
- package/src/main/shared/icons/arrow-down.svg +1 -0
- package/src/main/shared/icons/arrow-next.svg +1 -0
- package/src/main/shared/icons/arrow-tail-right.svg +1 -0
- package/src/main/shared/icons/arrow-tail-up.svg +1 -0
- package/src/main/shared/icons/arrow-up.svg +1 -0
- package/src/main/shared/icons/asset-download.svg +1 -0
- package/src/main/shared/icons/logo.svg +5 -0
- package/src/main/shared/icons/low-carbon-placeholder.svg +9 -0
- package/src/main/shared/icons/media-pause.svg +1 -0
- package/src/main/shared/icons/media-play.svg +1 -0
- package/src/main/shared/icons/navigation-burger.svg +1 -0
- package/src/main/shared/icons/navigation-close.svg +1 -0
- package/src/main/shared/icons/navigation-link.svg +1 -0
- package/src/main/shared/icons/navigation-refresh.svg +1 -0
- package/src/main/shared/icons/navigation-search.svg +1 -0
- package/src/main/shared/icons/navigation-share.svg +1 -0
- package/src/main/shared/icons/toggle-newwindow.svg +1 -0
- package/src/main/shared/icons.js +18 -0
- package/src/main/shared/id-from-string.js +5 -0
- package/src/main/shared/mark-selection.js +19 -0
- package/src/main/shared/register.js +26 -0
- package/src/main/shared/renderer.js +43 -0
- package/src/main/shared/simple-consent-api.js +70 -0
- package/src/main/shared/split-links.js +11 -0
- package/src/main/shared/t.js +60 -0
- package/src/main/shared/twind.js +837 -0
- package/src/main/shared/update-head.js +34 -0
- package/src/main/shared/update-scrollbar-width.js +30 -0
- package/src/main/shared/use-link.js +151 -0
- package/src/main/shared/use-persistent-state.js +42 -0
- package/src/main/shared/wait-for-dom-ready.js +6 -0
- package/src/main/shared/wcm-mode.js +4 -0
- package/src/wcs/components/acinguiux-preact-doc/acinguiux-preact-doc.js +207 -0
- package/src/wcs/components/admin-dashboard/admin-dashboard.js +487 -0
- package/src/wcs/components/admin-login/admin-login.js +91 -0
- package/src/wcs/components/bazaar-voice/bazaar-voice.js +56 -0
- package/src/wcs/components/chatbot-koreai/chatbot-koreai.js +176 -0
- package/src/wcs/components/chatbot-koreai/koreai-transport.js +217 -0
- package/src/wcs/components/chatbot-ms/chatbot-ms.js +210 -0
- package/src/wcs/components/chatbot-test/chatbot-test.js +44 -0
- package/src/wcs/components/comparison-chart/comparison-chart.js +111 -0
- package/src/wcs/components/consent-banner/consent-banner.js +248 -0
- package/src/wcs/components/consent-banner/icons/ccpa.svg +6 -0
- package/src/wcs/components/consent-banner/icons/info.svg +1 -0
- package/src/wcs/components/consent-banner/provider-onetrust.js +131 -0
- package/src/wcs/components/decision-tree/arrow-back.svg +3 -0
- package/src/wcs/components/decision-tree/badges.js +37 -0
- package/src/wcs/components/decision-tree/decision-tree.js +162 -0
- package/src/wcs/components/dynamic-contact-details/dynamic-contact-details.js +111 -0
- package/src/wcs/components/example-accordion/example-accordion.js +10 -0
- package/src/wcs/components/example-asset/example-asset.js +12 -0
- package/src/wcs/components/example-form/example-form.js +59 -0
- package/src/wcs/components/example-nested/example-nested.js +10 -0
- package/src/wcs/components/example-routing/example-routing.js +51 -0
- package/src/wcs/components/example-rtl/example-rtl.js +28 -0
- package/src/wcs/components/example-tabs/example-tabs.js +12 -0
- package/src/wcs/components/example-web-component/example-web-component.js +34 -0
- package/src/wcs/components/floating-button/floating-button.js +17 -0
- package/src/wcs/components/formstack-form/fields/address.js +38 -0
- package/src/wcs/components/formstack-form/fields/checkbox.js +42 -0
- package/src/wcs/components/formstack-form/fields/date.js +22 -0
- package/src/wcs/components/formstack-form/fields/description.js +8 -0
- package/src/wcs/components/formstack-form/fields/input.js +8 -0
- package/src/wcs/components/formstack-form/fields/name.js +39 -0
- package/src/wcs/components/formstack-form/fields/radio.js +24 -0
- package/src/wcs/components/formstack-form/fields/rating.js +53 -0
- package/src/wcs/components/formstack-form/fields/section.js +8 -0
- package/src/wcs/components/formstack-form/fields/select.js +10 -0
- package/src/wcs/components/formstack-form/fields/textarea.js +8 -0
- package/src/wcs/components/formstack-form/fields/wrapper.js +11 -0
- package/src/wcs/components/formstack-form/formstack-form.js +280 -0
- package/src/wcs/components/fuel-prices/fuel-prices.js +45 -0
- package/src/wcs/components/furniture-overview/furniture-overview.js +115 -0
- package/src/wcs/components/gauge-value/gauge-value.js +65 -0
- package/src/wcs/components/help-centre/api.js +150 -0
- package/src/wcs/components/help-centre/help-centre.js +400 -0
- package/src/wcs/components/help-centre/icon-search.svg +1 -0
- package/src/wcs/components/image-gen/admin-panel.js +248 -0
- package/src/wcs/components/image-gen/image-gen.js +385 -0
- package/src/wcs/components/image-gen/labels.js +37 -0
- package/src/wcs/components/image-gen/use-api.js +392 -0
- package/src/wcs/components/inspired-gallery/inspired-gallery.js +118 -0
- package/src/wcs/components/launch-container/launch-container.js +95 -0
- package/src/wcs/components/launch-container/ledger.js +140 -0
- package/src/wcs/components/lng-map/lng-map.js +44 -0
- package/src/wcs/components/mouseflow-analytics/mouseflow-analytics.js +39 -0
- package/src/wcs/components/msds-search/msds-search.js +127 -0
- package/src/wcs/components/msds-search/navigation-search.svg +3 -0
- package/src/wcs/components/product-catalogue/icon-back.svg +3 -0
- package/src/wcs/components/product-catalogue/icon-cart.svg +3 -0
- package/src/wcs/components/product-catalogue/icon-close.svg +3 -0
- package/src/wcs/components/product-catalogue/product-catalogue.js +215 -0
- package/src/wcs/components/product-links/icon-cart.svg +3 -0
- package/src/wcs/components/product-links/product-links.js +43 -0
- package/src/wcs/components/rio-iframe/rio-iframe.js +137 -0
- package/src/wcs/components/salsify-products/filter-tools.js +60 -0
- package/src/wcs/components/salsify-products/icon-cart.svg +3 -0
- package/src/wcs/components/salsify-products/process-products.js +53 -0
- package/src/wcs/components/salsify-products/route-tools.js +54 -0
- package/src/wcs/components/salsify-products/salsify-products.js +281 -0
- package/src/wcs/components/shout-out/shout-out.js +51 -0
- package/src/wcs/components/simple-chart/simple-chart.js +53 -0
- package/src/wcs/components/single-stat/single-stat.js +85 -0
- package/src/wcs/components/site-feedback/site-feedback.js +56 -0
- package/src/wcs/components/skds-search/navigation-search.svg +3 -0
- package/src/wcs/components/skds-search/skds-search.js +103 -0
- package/src/wcs/components/smart-banner/smart-banner.js +104 -0
- package/src/wcs/components/standalone-table/arrow-up-down.svg +3 -0
- package/src/wcs/components/standalone-table/arrow-up.svg +3 -0
- package/src/wcs/components/standalone-table/standalone-table.js +440 -0
- package/src/wcs/components/station-locator/station-locator.js +49 -0
- package/src/wcs/components/store-badges/badges.js +60 -0
- package/src/wcs/components/store-badges/store-badges.js +93 -0
- package/src/wcs/components/topbar-button/person.svg +1 -0
- package/src/wcs/components/topbar-button/topbar-button.js +22 -0
- package/src/wcs/components/universal-gallery/universal-gallery.js +308 -0
- package/src/wcs/components/zendesk-chat/zendesk-chat.js +133 -0
- package/src/wcs/shared/chat-bot/README.md +61 -0
- package/src/wcs/shared/chat-bot/chat-bot.js +216 -0
- package/src/wcs/shared/chat-bot/resources/arrow-next.svg +1 -0
- package/src/wcs/shared/chat-bot/resources/navigation-close.svg +1 -0
- package/src/wcs/shared/chat-bot/resources/person.svg +1 -0
- package/src/wcs/shared/chat-bot/resources/upload.svg +1 -0
- package/src/wcs/shared/filtered-data/README.md +52 -0
- package/src/wcs/shared/filtered-data/fetch-data.js +33 -0
- package/src/wcs/shared/filtered-data/filtered-data.js +337 -0
- package/src/wcs/shared/promo-with-popup/icon-close.svg +3 -0
- package/src/wcs/shared/promo-with-popup/icon-next.svg +3 -0
- package/src/wcs/shared/promo-with-popup/icon-prev.svg +3 -0
- package/src/wcs/shared/promo-with-popup/promo-with-popup.js +93 -0
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
/* global location, customElements, localStorage, crypto */
|
|
2
|
+
import { getConsent } from '#acinguiux-preact/main/shared/simple-consent-api.js'
|
|
3
|
+
import { isPublish } from '#acinguiux-preact/main/shared/wcm-mode.js'
|
|
4
|
+
import deepSelector from '#acinguiux-preact/main/shared/deep-selector.js'
|
|
5
|
+
|
|
6
|
+
const DOWNLOADABLE_EXTENSIONS = ['zip', 'pdf', 'docx', 'xlsx', 'pptx', 'txt', 'csv', 'xps']
|
|
7
|
+
const SCROLL_DEBOUNCE = 500
|
|
8
|
+
const EXPERIMENT_SELECTOR = '[data-experiment]'
|
|
9
|
+
const USER_ID_STORAGE_KEY = 'aa_user_id'
|
|
10
|
+
const YT_ORIGINS = new Set(['https://www.youtube.com', 'https://www.youtube-nocookie.com'])
|
|
11
|
+
const MILESTONES = { event15: 90, event14: 75, event13: 50, event12: 25, event11: 1 }
|
|
12
|
+
const FORM_ERROR_TOLERANCE = 100
|
|
13
|
+
const DEFAULT_SAMPLE_RATE = 1
|
|
14
|
+
const SAMPLE_RATE = Number.parseFloat(localStorage.getItem('acinguiux-preact_sample_rate') ?? DEFAULT_SAMPLE_RATE)
|
|
15
|
+
const SKIP_TRACKING = SAMPLE_RATE < Math.random() // eslint-disable-line sonarjs/pseudo-random
|
|
16
|
+
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
// Private fields
|
|
19
|
+
// -----------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
let isInitialised = false
|
|
22
|
+
let userId
|
|
23
|
+
let newOrRepeat
|
|
24
|
+
let apiUrl
|
|
25
|
+
let pageViewParams = {}
|
|
26
|
+
let searchContext = []
|
|
27
|
+
let pageViewCount = 0
|
|
28
|
+
let scrolled = 0
|
|
29
|
+
|
|
30
|
+
// -----------------------------------------------------------------------------
|
|
31
|
+
// Adobe Analytics
|
|
32
|
+
// -----------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
// Using GET API as per the reference:
|
|
35
|
+
// https://github.com/AdobeDocs/analytics.en/blob/main/help/implement/validate/query-parameters.md
|
|
36
|
+
export async function send (params = {}) {
|
|
37
|
+
if (!apiUrl || !isPublish || SKIP_TRACKING) {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const [resolvedUserId, resolvedNewOrRepeat] = getUserId()
|
|
42
|
+
|
|
43
|
+
// Set everything up for a page view.
|
|
44
|
+
if (!params.events) {
|
|
45
|
+
const pageUrl = getUrl(location.href)
|
|
46
|
+
const pageUrlWithoutParams = pageUrl.origin + pageUrl.pathname
|
|
47
|
+
const usp = pageUrl.searchParams
|
|
48
|
+
const campaign = pageUrl.search.includes('utm_')
|
|
49
|
+
? ['medium', 'source', 'campaign', 'term', 'content'].map(p => usp.get(`utm_${p}`) || 'undefined').join(':')
|
|
50
|
+
: null
|
|
51
|
+
|
|
52
|
+
const isVanity = pageUrl.hash.startsWith('#vanity-')
|
|
53
|
+
const referrer = pageViewParams.v26 || document.referrer
|
|
54
|
+
|
|
55
|
+
searchContext = [] // Reset search context.
|
|
56
|
+
pageViewParams = {
|
|
57
|
+
// Standard properties.
|
|
58
|
+
bh: window.innerHeight,
|
|
59
|
+
bw: window.innerWidth,
|
|
60
|
+
c: window.screen.colorDepth,
|
|
61
|
+
ce: document.characterSet,
|
|
62
|
+
ch: document.title,
|
|
63
|
+
g: pageUrl.href,
|
|
64
|
+
r: referrer || 'No Referrer',
|
|
65
|
+
s: `${window.screen.availWidth}x${window.screen.availHeight}`,
|
|
66
|
+
server: pageUrl.host,
|
|
67
|
+
vid: resolvedUserId,
|
|
68
|
+
|
|
69
|
+
// Context evars.
|
|
70
|
+
v1: document.documentElement.lang.split('-').reverse().join('|'), // Locale.
|
|
71
|
+
v0: campaign,
|
|
72
|
+
v21: params.pageName,
|
|
73
|
+
v22: pageViewParams.pageName, // Get the previous page name.
|
|
74
|
+
v26: pageUrl.href, // Page URL.
|
|
75
|
+
v27: pageUrlWithoutParams,
|
|
76
|
+
v29: pageUrl.host,
|
|
77
|
+
v32: pageUrl.hash.startsWith('#vanity-') ? pageUrlWithoutParams : null, // Redirect URL.
|
|
78
|
+
v34: getTrafficOrigin(referrer),
|
|
79
|
+
v45: campaign,
|
|
80
|
+
v47: navigator.userAgent,
|
|
81
|
+
v49: resolvedNewOrRepeat,
|
|
82
|
+
v53: new Date().toISOString(),
|
|
83
|
+
v87: usp.get('gclid'),
|
|
84
|
+
v116: pageViewCount++,
|
|
85
|
+
v117: getColorScheme(),
|
|
86
|
+
v118: document.querySelector(EXPERIMENT_SELECTOR)?.dataset.experiment,
|
|
87
|
+
|
|
88
|
+
// Mapped properties.
|
|
89
|
+
c1: 'D=v1', // Locale
|
|
90
|
+
c9: 'D=v9', // Share medium
|
|
91
|
+
c11: 'D=v11', // Download file name
|
|
92
|
+
c12: 'D=v12', // Form name
|
|
93
|
+
c13: 'D=v13', // Last interacted form field
|
|
94
|
+
c14: 'D=v14', // Time take to complete the form
|
|
95
|
+
c16: 'D=v16', // Onsite search term
|
|
96
|
+
c17: 'D=v17', // Search type
|
|
97
|
+
c18: 'D=v18', // Search result count
|
|
98
|
+
c22: 'D=v22', // Previous page ID
|
|
99
|
+
c24: 'D=v24', // Subsection
|
|
100
|
+
c25: 'D=v25', // Vanity URL
|
|
101
|
+
c27: 'D=v27', // Page URL without params
|
|
102
|
+
c53: 'D=v53', // Timestamp
|
|
103
|
+
c69: 'D=v69' // Consent status
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (isVanity) {
|
|
107
|
+
pageViewParams.v25 = atob(pageUrl.hash.replace(/^#vanity-(.*)$/, '$1'))
|
|
108
|
+
pageViewParams.events = 'event17'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Use previously saved page view params except for v69, which can change at any time.
|
|
113
|
+
params = { ...pageViewParams, v69: getConsentStatus(), ...params }
|
|
114
|
+
|
|
115
|
+
// Delete null or undefined parameters. Otherwise, URLSearchParams.toString() will serialize them.
|
|
116
|
+
for (const [k, v] of Object.entries(params)) {
|
|
117
|
+
if (v === null || v === undefined) {
|
|
118
|
+
delete params[k]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return fetch(`${apiUrl}?${new URLSearchParams(params).toString()}`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// -----------------------------------------------------------------------------
|
|
126
|
+
// Helpers
|
|
127
|
+
// -----------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
// Local-storage session handling.
|
|
130
|
+
// https://github.com/AdobeDocs/analytics-1.4-apis/blob/master/docs/data-insertion-api/overview/c_visitor_id.md
|
|
131
|
+
export function getUserId () {
|
|
132
|
+
const hasConsent = getConsent()?.statistics
|
|
133
|
+
const storedUserId = hasConsent ? localStorage.getItem(USER_ID_STORAGE_KEY) : localStorage.removeItem(USER_ID_STORAGE_KEY)
|
|
134
|
+
const passedUserId = new URL(location).searchParams.get('vid')
|
|
135
|
+
if (!newOrRepeat) {
|
|
136
|
+
newOrRepeat = storedUserId ? 'repeat' : 'new'
|
|
137
|
+
}
|
|
138
|
+
userId = passedUserId ?? storedUserId ?? userId ?? crypto.randomUUID().replaceAll('-', '') // Adobe does not like hyphens.
|
|
139
|
+
hasConsent && localStorage.setItem(USER_ID_STORAGE_KEY, userId)
|
|
140
|
+
|
|
141
|
+
return [userId, newOrRepeat]
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getConsentStatus () {
|
|
145
|
+
const c = getConsent() ?? {}
|
|
146
|
+
|
|
147
|
+
if (c.statistics && c.preferences && c.marketing) {
|
|
148
|
+
return 'granted all'
|
|
149
|
+
} else if (c.statistics || c.preferences || c.marketing) {
|
|
150
|
+
return 'granted partial'
|
|
151
|
+
} else if (Object.keys(c).length === 0) {
|
|
152
|
+
return 'denied pending'
|
|
153
|
+
} else {
|
|
154
|
+
return 'denied all'
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getUrl (href) {
|
|
159
|
+
const url = new URL(href, location)
|
|
160
|
+
url.searchParams.delete('vid') // Ignore vid parameter.
|
|
161
|
+
return url
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function isExternalLink (urlObj) {
|
|
165
|
+
return urlObj.origin !== location.origin && urlObj.protocol?.startsWith('http')
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function isPageLink (urlObj) {
|
|
169
|
+
const { hostname, pathname } = urlObj
|
|
170
|
+
return location.hostname === hostname && (pathname === '/' || pathname?.endsWith('.html'))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isDownloadable (urlObj) {
|
|
174
|
+
return DOWNLOADABLE_EXTENSIONS.some(ext => urlObj.pathname?.endsWith(ext))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getTrafficOrigin (referrer) {
|
|
178
|
+
if (!referrer) {
|
|
179
|
+
return 'No Referrer'
|
|
180
|
+
} else if (isExternalLink(getUrl(referrer))) {
|
|
181
|
+
return 'External'
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return 'Internal'
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getColorScheme () {
|
|
188
|
+
return globalThis.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getFileName (link) {
|
|
192
|
+
return link.substring(link.lastIndexOf('/') + 1)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function extractLinkData (el) {
|
|
196
|
+
const urlObj = el.href ? getUrl(el.href) : {}
|
|
197
|
+
|
|
198
|
+
// Create copy in order to remove SVG and style elements. We only need text content.
|
|
199
|
+
const clone = el.cloneNode(true)
|
|
200
|
+
for (const elem of clone.querySelectorAll('svg, style')) {
|
|
201
|
+
elem.remove()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const data = {
|
|
205
|
+
pe: 'lnk_o',
|
|
206
|
+
text: clone.title || clone.ariaLabel || clone.textContent.trim(),
|
|
207
|
+
region: el.closest('header,main,footer')?.tagName || 'BODY',
|
|
208
|
+
action: 'click',
|
|
209
|
+
type: 'interaction',
|
|
210
|
+
href: urlObj.href
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// The tracking event fires before the expand/collapse event.
|
|
214
|
+
// This means the expand/collapse attributes will be reversed.
|
|
215
|
+
if (el.ariaExpanded === 'true' || el.parentElement?.matches('details:not([open])')) {
|
|
216
|
+
data.action = 'expand'
|
|
217
|
+
} else if (el.ariaExpanded === 'false' || el.parentElement?.matches('details[open]')) {
|
|
218
|
+
data.action = 'collapse'
|
|
219
|
+
} else if (el.getAttribute('role') === 'tab') {
|
|
220
|
+
data.action = 'tab'
|
|
221
|
+
} else if (el.getAttribute('href')?.startsWith('#')) {
|
|
222
|
+
data.type = 'hash'
|
|
223
|
+
} else if (isExternalLink(urlObj)) {
|
|
224
|
+
data.type = 'external'
|
|
225
|
+
data.pe = 'lnk_e'
|
|
226
|
+
// Add vid to exit link.
|
|
227
|
+
if (userId) {
|
|
228
|
+
const url = new URL(el.href)
|
|
229
|
+
url.searchParams.set('vid', userId)
|
|
230
|
+
el.href = url.toString()
|
|
231
|
+
}
|
|
232
|
+
} else if (isPageLink(urlObj)) {
|
|
233
|
+
data.type = 'internal'
|
|
234
|
+
data.pe = 'lnk_o'
|
|
235
|
+
} else if (isDownloadable(urlObj)) {
|
|
236
|
+
data.type = 'download'
|
|
237
|
+
data.pe = 'lnk_d'
|
|
238
|
+
} else if (urlObj.protocol && !urlObj.protocol.startsWith('http')) {
|
|
239
|
+
data.type = urlObj.protocol.replace(':', '')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return data
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function getComponentModel (el) {
|
|
246
|
+
return globalThis?.ami?.componentModels?.get?.(el?.closest('[data-name]')?.dataset?.key)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function getPageComponentModel () {
|
|
250
|
+
return getComponentModel(document.querySelector('[data-name="Page"]'))
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function getPageProps () {
|
|
254
|
+
const model = getPageComponentModel()?.model ?? {}
|
|
255
|
+
const links = model.links ?? []
|
|
256
|
+
const metas = links.reduce((obj, link) => ({ ...obj, [link.name]: link.value }), {})
|
|
257
|
+
const trail = ['homepage', ...(metas?.hierarchy || '').split('/').slice(1)]
|
|
258
|
+
const nameTrail = trail.length === 1 ? trail : trail.slice(1) // Remove homepage from child pages.
|
|
259
|
+
const name = nameTrail.join('|')
|
|
260
|
+
const hierarchy = trail.slice(0, 3).join('|') // Limited to 3 levels
|
|
261
|
+
const [section, subsection] = nameTrail
|
|
262
|
+
return { name, hierarchy, section, subsection }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function trackLinkClick (el, organism, experiment) {
|
|
266
|
+
const { pe, text, href, action, type, region } = extractLinkData(el)
|
|
267
|
+
|
|
268
|
+
const vars = {
|
|
269
|
+
events: 'event1',
|
|
270
|
+
pe,
|
|
271
|
+
pev1: href,
|
|
272
|
+
pev2: text,
|
|
273
|
+
v31: href,
|
|
274
|
+
v40: text,
|
|
275
|
+
v41: organism,
|
|
276
|
+
v42: action,
|
|
277
|
+
v43: type,
|
|
278
|
+
v118: experiment,
|
|
279
|
+
c40: 'D=v40', // Action text
|
|
280
|
+
c41: 'D=v41', // Containing organism
|
|
281
|
+
c42: 'D=v42', // Action
|
|
282
|
+
c43: 'D=v43' // Link type
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Download link.
|
|
286
|
+
if (pe === 'lnk_d') {
|
|
287
|
+
vars.events += ',event3'
|
|
288
|
+
vars.v11 = getFileName(href)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (organism === 'SearchResult') {
|
|
292
|
+
const [searchTerm, resultCount] = getSearchContext()
|
|
293
|
+
|
|
294
|
+
vars.events += ',event6'
|
|
295
|
+
vars.v16 = `Onsite Search: ${searchTerm}`
|
|
296
|
+
vars.v17 = 'Onsite'
|
|
297
|
+
vars.v18 = `${resultCount}`
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
await send({ ...vars, ...getActivityMap(text, region) })
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function getSearchContext () {
|
|
304
|
+
if (searchContext.length > 0) {
|
|
305
|
+
return searchContext
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const pageComponentModel = getPageComponentModel()
|
|
309
|
+
if (!pageComponentModel) {
|
|
310
|
+
return []
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Track only if q parameter is present.
|
|
314
|
+
if (!new URLSearchParams(location.search).has('q')) {
|
|
315
|
+
return []
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const componentModels = [...pageComponentModel.children]
|
|
319
|
+
for (const cm of componentModels) {
|
|
320
|
+
// Track only if SearchForm organism is found.
|
|
321
|
+
if (cm.organism === 'SearchForm') {
|
|
322
|
+
const link = cm.model?.links?.[1] ?? {}
|
|
323
|
+
searchContext = [link.name, Number(link.value)]
|
|
324
|
+
return searchContext
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
cm.children && componentModels.push(...cm.children)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return []
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function getActivityMap (text, region) {
|
|
334
|
+
return {
|
|
335
|
+
'c.': '',
|
|
336
|
+
'a.': '',
|
|
337
|
+
'activitymap.': '',
|
|
338
|
+
page: getPageProps()?.name,
|
|
339
|
+
link: text,
|
|
340
|
+
region,
|
|
341
|
+
pageIDType: 1,
|
|
342
|
+
'.activitymap': '',
|
|
343
|
+
'.a': '',
|
|
344
|
+
'.c': ''
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// -----------------------------------------------------------------------------
|
|
349
|
+
// Tracking API
|
|
350
|
+
// -----------------------------------------------------------------------------
|
|
351
|
+
|
|
352
|
+
export function track (newApiUrl) {
|
|
353
|
+
apiUrl = newApiUrl
|
|
354
|
+
|
|
355
|
+
// Track events.
|
|
356
|
+
if (!isInitialised) {
|
|
357
|
+
isInitialised = true
|
|
358
|
+
trackLinks()
|
|
359
|
+
trackScroll()
|
|
360
|
+
trackYouTubeVideos()
|
|
361
|
+
trackMedia()
|
|
362
|
+
trackIframes().catch(console.error)
|
|
363
|
+
trackForms()
|
|
364
|
+
trackPageViews().catch(console.error)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function trackPageViews () {
|
|
369
|
+
await trackPageView() // First time tracking.
|
|
370
|
+
globalThis.addEventListener('route', () => {
|
|
371
|
+
trackPageView()
|
|
372
|
+
})
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async function trackPageView () {
|
|
376
|
+
scrolled = 0
|
|
377
|
+
|
|
378
|
+
const { name, section, subsection, hierarchy } = getPageProps()
|
|
379
|
+
await send({ pageName: name, v23: section, v24: subsection, h1: hierarchy })
|
|
380
|
+
|
|
381
|
+
// Track onsite search if applicable.
|
|
382
|
+
const [searchTerm, resultCount] = getSearchContext()
|
|
383
|
+
if (searchTerm) {
|
|
384
|
+
const events = `event4${resultCount === 0 ? ',event5' : ''}`
|
|
385
|
+
await send({
|
|
386
|
+
events,
|
|
387
|
+
pev1: getUrl(location).href,
|
|
388
|
+
pev2: 'Onsite Search',
|
|
389
|
+
pe: 'lnk_o',
|
|
390
|
+
v16: `Onsite Search: ${searchTerm}`,
|
|
391
|
+
v17: 'Onsite',
|
|
392
|
+
v18: `${resultCount}`
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function trackLinks () {
|
|
398
|
+
document.addEventListener('click', event => {
|
|
399
|
+
// Get all the clickable elements on the path (works inside Shadow DOM).
|
|
400
|
+
// Ignore submit buttons.
|
|
401
|
+
const els = event.composedPath().filter(e => e?.matches?.('.clickable:not([type="submit"])'))
|
|
402
|
+
|
|
403
|
+
if (els.length === 0) {
|
|
404
|
+
return
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const tagName = event.target.tagName.toLowerCase()
|
|
408
|
+
const customElementTagName = customElements.get(tagName) ? tagName : null
|
|
409
|
+
|
|
410
|
+
const handler = async () => {
|
|
411
|
+
for (const el of els) {
|
|
412
|
+
// Do not track button clicks inside a form.
|
|
413
|
+
if (!(el.closest('form') && el.tagName.toLowerCase() === 'button')) {
|
|
414
|
+
const experiment = el.closest(EXPERIMENT_SELECTOR)?.dataset.experiment
|
|
415
|
+
const organism = getComponentModel(el)?.organism ?? customElementTagName
|
|
416
|
+
await trackLinkClick(el, organism, experiment)
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
handler().catch(console.error)
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function trackScroll () {
|
|
425
|
+
scrolled = 0
|
|
426
|
+
|
|
427
|
+
let resizeId
|
|
428
|
+
document.addEventListener('scroll', event => {
|
|
429
|
+
const target = event?.detail?.target ?? event.target
|
|
430
|
+
clearTimeout(resizeId)
|
|
431
|
+
resizeId = setTimeout(async () => {
|
|
432
|
+
if (target === document.body) {
|
|
433
|
+
await trackPageScroll()
|
|
434
|
+
} else if (target && target !== document) {
|
|
435
|
+
await trackSliderScroll(target)
|
|
436
|
+
}
|
|
437
|
+
}, SCROLL_DEBOUNCE)
|
|
438
|
+
}, true)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function trackPageScroll () {
|
|
442
|
+
const el = document.body
|
|
443
|
+
if (el.scrollHeight <= el.clientHeight) {
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
const current = Math.round((el.scrollTop + el.clientHeight) / el.scrollHeight * 100)
|
|
447
|
+
const pageHref = getUrl(location).href
|
|
448
|
+
if (scrolled < 75 && current >= 75) {
|
|
449
|
+
await send({ events: 'event102', pe: 'lnk_o', pev1: pageHref, pev2: 'Scroll' })
|
|
450
|
+
scrolled = current
|
|
451
|
+
} else if (scrolled < 50 && current >= 50) {
|
|
452
|
+
await send({ events: 'event101', pe: 'lnk_o', pev1: pageHref, pev2: 'Scroll' })
|
|
453
|
+
scrolled = current
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async function trackSliderScroll (target) {
|
|
458
|
+
const componentModel = target.closest('[data-name]')?.componentModel ?? {}
|
|
459
|
+
const index = Math.abs(Math.round(target.scrollLeft / target.children[0].offsetWidth)) + 1
|
|
460
|
+
if (index > 0) {
|
|
461
|
+
await send({
|
|
462
|
+
events: 'event97',
|
|
463
|
+
pe: 'lnk_o',
|
|
464
|
+
pev1: getUrl(location).href,
|
|
465
|
+
pev2: index,
|
|
466
|
+
v40: index,
|
|
467
|
+
v41: componentModel.organism,
|
|
468
|
+
v42: 'Slide/Swipe'
|
|
469
|
+
})
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function trackIframes () {
|
|
474
|
+
window.addEventListener('message', async event => {
|
|
475
|
+
const { action, actionType, assetName, location, text, url: link } = event.data
|
|
476
|
+
|
|
477
|
+
if (!action || !text || location !== 'iframe') {
|
|
478
|
+
return
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Remap iframe message to analytics parameters according to link tracking.
|
|
482
|
+
const parameterMap = {
|
|
483
|
+
ClickInternal: { v42: 'click', v43: 'internal' },
|
|
484
|
+
ClickExternal: { v42: 'click', v43: 'external', pe: 'lnk_e' },
|
|
485
|
+
ClickHash: { v42: 'click', v43: 'hash' },
|
|
486
|
+
ClickTab: { v42: 'tab', v43: 'interaction' },
|
|
487
|
+
Share: { v42: 'share', v43: 'interaction' },
|
|
488
|
+
Expand: { v42: 'expand', v43: 'interaction' },
|
|
489
|
+
Collapse: { v42: 'collapse', v43: 'interaction' },
|
|
490
|
+
'Slide/SwipeNext': { v42: 'next', v43: 'interaction' },
|
|
491
|
+
'Slide/SwipePrevious': { v42: 'prev', v43: 'interaction' },
|
|
492
|
+
Pagination: { v42: actionType, v43: 'interaction' },
|
|
493
|
+
Download: {
|
|
494
|
+
v42: 'click',
|
|
495
|
+
v43: 'download',
|
|
496
|
+
pe: 'lnk_d',
|
|
497
|
+
events: 'event1,event3',
|
|
498
|
+
pev1: text,
|
|
499
|
+
v31: text,
|
|
500
|
+
v11: getFileName(text)
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const vars = {
|
|
505
|
+
events: 'event1',
|
|
506
|
+
pe: 'lnk_o',
|
|
507
|
+
pev1: link,
|
|
508
|
+
pev2: text,
|
|
509
|
+
v15: assetName,
|
|
510
|
+
v31: link,
|
|
511
|
+
v40: text,
|
|
512
|
+
v41: location,
|
|
513
|
+
c15: 'D=v15', // Asset name
|
|
514
|
+
c40: 'D=v40', // Text
|
|
515
|
+
c41: 'D=v41', // Location
|
|
516
|
+
c42: 'D=v42', // Action
|
|
517
|
+
c43: 'D=v43' // Action type
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const key = Object.keys(parameterMap).find(k => k.startsWith(`${action}${actionType}`))
|
|
521
|
+
if (!key) {
|
|
522
|
+
console.warn(`Unknown iframe analytics action: ${action} ${actionType}`)
|
|
523
|
+
return
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
Object.assign(vars, parameterMap[key] || {})
|
|
527
|
+
await send(vars)
|
|
528
|
+
})
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function sendMilestoneEvent (title, percentage, milestones = []) {
|
|
532
|
+
for (const milestone of milestones) {
|
|
533
|
+
if (percentage >= milestone.percent) {
|
|
534
|
+
if (!milestone.reached) {
|
|
535
|
+
milestone.reached = true
|
|
536
|
+
send({ events: milestone.event, v10: title }).catch(console.error)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
break
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function getPlaybackPercentage (currentTime, duration) {
|
|
545
|
+
return duration ? Math.round((currentTime / duration) * 100) : 0
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function trackYouTubeVideos () {
|
|
549
|
+
// Bubbled-up load event.
|
|
550
|
+
document.addEventListener('load', event => {
|
|
551
|
+
const iframe = event.target
|
|
552
|
+
const url = getUrl(iframe.src)
|
|
553
|
+
|
|
554
|
+
if (YT_ORIGINS.has(url.origin) && url.searchParams.get('autoplay') !== '1') {
|
|
555
|
+
iframe.contentWindow?.postMessage(JSON.stringify({ event: 'listening', id: iframe.id }), url.origin)
|
|
556
|
+
iframe.milestones = Object.entries(MILESTONES).map(([ev, percent]) => ({ reached: false, percent, event: ev }))
|
|
557
|
+
}
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
// Common handler for all YouTube iframes
|
|
561
|
+
window.addEventListener('message', async event => {
|
|
562
|
+
// Only handle message coming from YouTube.
|
|
563
|
+
if (!YT_ORIGINS.has(event.origin)) {
|
|
564
|
+
return
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// If YouTube changes the message format, we want to stop the processing here.
|
|
568
|
+
let data = event.data
|
|
569
|
+
if (typeof event.data === 'string') {
|
|
570
|
+
try {
|
|
571
|
+
data = JSON.parse(event.data)
|
|
572
|
+
} catch {
|
|
573
|
+
console.error('Could not parse YouTube postMessage data, analytics tracking aborted.')
|
|
574
|
+
return
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Find the corresponding iframe.
|
|
579
|
+
const [iframe] = await deepSelector(`#${data.id}`)
|
|
580
|
+
if (!iframe) {
|
|
581
|
+
return
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (['infoDelivery', 'initialDelivery'].includes(data.event)) {
|
|
585
|
+
const info = data.info
|
|
586
|
+
iframe.title ||= info.videoData?.title
|
|
587
|
+
const percentage = getPlaybackPercentage(info.currentTime, info.progressState?.duration)
|
|
588
|
+
sendMilestoneEvent(iframe.title, percentage, iframe.milestones)
|
|
589
|
+
}
|
|
590
|
+
})
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function trackMedia () {
|
|
594
|
+
// Bubbled up timeupdate event.
|
|
595
|
+
document.addEventListener('timeupdate', event => {
|
|
596
|
+
const el = event.target
|
|
597
|
+
if (!el.autoplay) {
|
|
598
|
+
el.milestones ||= Object.entries(MILESTONES).map(([ev, percent]) => ({ reached: false, percent, event: ev }))
|
|
599
|
+
const percentage = getPlaybackPercentage(el.currentTime, el.duration)
|
|
600
|
+
sendMilestoneEvent(el.title, percentage, el.milestones)
|
|
601
|
+
}
|
|
602
|
+
})
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function isValidForm (form) {
|
|
606
|
+
return form?.name
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
async function abandonmentHandler () {
|
|
610
|
+
const forms = await deepSelector('form')
|
|
611
|
+
|
|
612
|
+
for (const form of forms) {
|
|
613
|
+
if (form.lastInteractedField) {
|
|
614
|
+
await send({ events: 'event9', v12: form.name, v13: form.lastInteractedField })
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function handleAbandonment () {
|
|
620
|
+
abandonmentHandler().catch(console.error)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function trackForms () {
|
|
624
|
+
document.addEventListener('input', event => {
|
|
625
|
+
const handler = async () => {
|
|
626
|
+
const input = event.composedPath()[0]
|
|
627
|
+
const form = input.closest('form')
|
|
628
|
+
|
|
629
|
+
if (isValidForm(form)) {
|
|
630
|
+
form.lastInteractedField = input.dataset.label
|
|
631
|
+
if (!form.startTime) {
|
|
632
|
+
form.startTime = Date.now()
|
|
633
|
+
await send({ events: 'event7', v12: form.name })
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
handler().catch(console.error)
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
document.addEventListener('invalid', event => {
|
|
641
|
+
const input = event.composedPath()[0]
|
|
642
|
+
const form = input.closest('form')
|
|
643
|
+
|
|
644
|
+
const handler = async () => {
|
|
645
|
+
await send({ events: 'event8', v12: form.name, l1: form.invalidLabels.join(',') })
|
|
646
|
+
delete form.invalidLabels
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (isValidForm(form)) {
|
|
650
|
+
form.invalidLabels ||= []
|
|
651
|
+
form.invalidLabels.push(input.dataset.label)
|
|
652
|
+
clearTimeout(form.timeout)
|
|
653
|
+
form.timeout = setTimeout(() => {
|
|
654
|
+
handler().catch(console.error)
|
|
655
|
+
}, FORM_ERROR_TOLERANCE)
|
|
656
|
+
}
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
document.addEventListener('submit', event => {
|
|
660
|
+
const submitHandler = async () => {
|
|
661
|
+
const form = event.composedPath()[0]
|
|
662
|
+
|
|
663
|
+
if (isValidForm(form)) {
|
|
664
|
+
const endTimeInSeconds = Math.ceil((Date.now() - form.startTime) / 1000)
|
|
665
|
+
delete form.startTime
|
|
666
|
+
delete form.lastInteractedField
|
|
667
|
+
delete form.invalidLabels
|
|
668
|
+
await send({ events: 'event10', v12: form.name, v14: endTimeInSeconds })
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
submitHandler().catch(console.error)
|
|
673
|
+
})
|
|
674
|
+
|
|
675
|
+
window.addEventListener('beforeunload', handleAbandonment)
|
|
676
|
+
document.addEventListener('route', handleAbandonment)
|
|
677
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* global HTMLElement, customElements */
|
|
2
|
+
import { injectStyles } from './twind.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated Use register() instead.
|
|
6
|
+
*/
|
|
7
|
+
export default function customElement (elementName, callback) {
|
|
8
|
+
class CustomElement extends HTMLElement {
|
|
9
|
+
connectedCallback () {
|
|
10
|
+
const shadow = this.attachShadow({ mode: 'open' })
|
|
11
|
+
const container = document.createElement('span')
|
|
12
|
+
container.setAttribute('dir', document.documentElement.dir) // Apply text direction.
|
|
13
|
+
shadow.appendChild(container)
|
|
14
|
+
injectStyles(shadow)
|
|
15
|
+
const attrs = Object.assign({}, ...Array.from(this.attributes, ({ name, value }) => ({ [name]: value })))
|
|
16
|
+
callback(attrs, container, shadow)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
customElements.get(elementName) || customElements.define(elementName, CustomElement)
|
|
21
|
+
}
|