@kywi-software/js 0.6.5 → 0.6.6
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/audience/index.d.ts +7 -0
- package/dist/audience/index.d.ts.map +1 -1
- package/dist/audience/selfid-widget.d.ts +16 -0
- package/dist/audience/selfid-widget.d.ts.map +1 -1
- package/dist/audience/transparency.d.ts +55 -7
- package/dist/audience/transparency.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/kywi.esm.js +372 -189
- package/dist/kywi.esm.js.map +4 -4
- package/dist/kywi.js +372 -189
- package/dist/kywi.js.map +4 -4
- package/dist/personalization/badge.d.ts +5 -1
- package/dist/personalization/badge.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/kywi.esm.js
CHANGED
|
@@ -728,116 +728,45 @@ function deleteCookie(name) {
|
|
|
728
728
|
document.cookie = `${name}=; Path=/; Max-Age=0`;
|
|
729
729
|
}
|
|
730
730
|
|
|
731
|
-
// src/audience/transparency-styles.ts
|
|
732
|
-
var STYLE_ELEMENT_ID = "kywi-transparency-styles";
|
|
733
|
-
var CSS = `
|
|
734
|
-
.kywi-transparency-bar,
|
|
735
|
-
.kywi-transparency-bar * {
|
|
736
|
-
box-sizing: border-box;
|
|
737
|
-
}
|
|
738
|
-
.kywi-transparency-bar {
|
|
739
|
-
position: fixed;
|
|
740
|
-
left: 0;
|
|
741
|
-
right: 0;
|
|
742
|
-
bottom: 0;
|
|
743
|
-
z-index: 2147483000;
|
|
744
|
-
display: flex;
|
|
745
|
-
flex-wrap: wrap;
|
|
746
|
-
align-items: center;
|
|
747
|
-
gap: 12px;
|
|
748
|
-
margin: 0;
|
|
749
|
-
padding: 12px 16px;
|
|
750
|
-
background: rgba(23, 23, 28, 0.96);
|
|
751
|
-
color: #f4f4f5;
|
|
752
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
753
|
-
font-size: 13px;
|
|
754
|
-
line-height: 1.4;
|
|
755
|
-
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
|
756
|
-
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.25);
|
|
757
|
-
animation: kywi-transparency-in 220ms ease-out;
|
|
758
|
-
}
|
|
759
|
-
@media (prefers-reduced-motion: reduce) {
|
|
760
|
-
.kywi-transparency-bar { animation: none; }
|
|
761
|
-
}
|
|
762
|
-
@keyframes kywi-transparency-in {
|
|
763
|
-
from { transform: translateY(100%); opacity: 0; }
|
|
764
|
-
to { transform: translateY(0); opacity: 1; }
|
|
765
|
-
}
|
|
766
|
-
.kywi-transparency-text {
|
|
767
|
-
flex: 1 1 240px;
|
|
768
|
-
color: #f4f4f5;
|
|
769
|
-
}
|
|
770
|
-
.kywi-transparency-bar button {
|
|
771
|
-
appearance: none;
|
|
772
|
-
-webkit-appearance: none;
|
|
773
|
-
font-family: inherit;
|
|
774
|
-
font-size: 12px;
|
|
775
|
-
line-height: 1.4;
|
|
776
|
-
cursor: pointer;
|
|
777
|
-
border-radius: 999px;
|
|
778
|
-
transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
|
|
779
|
-
}
|
|
780
|
-
.kywi-transparency-opt-out,
|
|
781
|
-
.kywi-transparency-opt-in {
|
|
782
|
-
flex: 0 0 auto;
|
|
783
|
-
padding: 6px 14px;
|
|
784
|
-
border: 1px solid rgba(255, 255, 255, 0.35);
|
|
785
|
-
background: transparent;
|
|
786
|
-
color: #f4f4f5;
|
|
787
|
-
font-weight: 600;
|
|
788
|
-
}
|
|
789
|
-
.kywi-transparency-opt-out:hover,
|
|
790
|
-
.kywi-transparency-opt-in:hover {
|
|
791
|
-
background: rgba(255, 255, 255, 0.12);
|
|
792
|
-
border-color: rgba(255, 255, 255, 0.5);
|
|
793
|
-
}
|
|
794
|
-
.kywi-transparency-dismiss {
|
|
795
|
-
flex: 0 0 auto;
|
|
796
|
-
width: 26px;
|
|
797
|
-
height: 26px;
|
|
798
|
-
padding: 0;
|
|
799
|
-
border: 1px solid transparent;
|
|
800
|
-
background: transparent;
|
|
801
|
-
color: #d4d4d8;
|
|
802
|
-
font-size: 13px;
|
|
803
|
-
}
|
|
804
|
-
.kywi-transparency-dismiss:hover {
|
|
805
|
-
background: rgba(255, 255, 255, 0.1);
|
|
806
|
-
color: #f4f4f5;
|
|
807
|
-
}
|
|
808
|
-
.kywi-transparency-bar button:focus-visible {
|
|
809
|
-
outline: 2px solid #7dd3fc;
|
|
810
|
-
outline-offset: 2px;
|
|
811
|
-
}
|
|
812
|
-
@media (max-width: 480px) {
|
|
813
|
-
.kywi-transparency-bar {
|
|
814
|
-
padding: 10px 12px;
|
|
815
|
-
}
|
|
816
|
-
.kywi-transparency-text {
|
|
817
|
-
flex-basis: 100%;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
`;
|
|
821
|
-
function ensureTransparencyStyles() {
|
|
822
|
-
if (typeof document === "undefined") return;
|
|
823
|
-
if (document.getElementById(STYLE_ELEMENT_ID)) return;
|
|
824
|
-
const style = document.createElement("style");
|
|
825
|
-
style.id = STYLE_ELEMENT_ID;
|
|
826
|
-
style.textContent = CSS;
|
|
827
|
-
document.head.appendChild(style);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
731
|
// src/audience/transparency.ts
|
|
831
|
-
var
|
|
832
|
-
|
|
833
|
-
|
|
732
|
+
var COLLAPSED_KEY = "kywi_transparency_collapsed";
|
|
733
|
+
var LEGACY_DISMISSED_KEY = "kywi_transparency_dismissed";
|
|
734
|
+
var ROOT_ID = "kywi-transparency";
|
|
735
|
+
var PANEL_ID = "kywi-transparency-panel";
|
|
736
|
+
var TITLE_ID = "kywi-transparency-title";
|
|
737
|
+
var SWITCHER_LABEL_ID = "kywi-transparency-switcher-label";
|
|
738
|
+
var DEFAULT_API_BASE = "/api/v1";
|
|
739
|
+
var lastState = null;
|
|
740
|
+
var forcedOpen = false;
|
|
741
|
+
function readStorage(key) {
|
|
742
|
+
try {
|
|
743
|
+
return localStorage.getItem(key);
|
|
744
|
+
} catch {
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
834
747
|
}
|
|
835
|
-
function
|
|
836
|
-
|
|
837
|
-
|
|
748
|
+
function writeStorage(key, value) {
|
|
749
|
+
try {
|
|
750
|
+
localStorage.setItem(key, value);
|
|
751
|
+
} catch {
|
|
752
|
+
}
|
|
838
753
|
}
|
|
839
|
-
function
|
|
840
|
-
|
|
754
|
+
function removeStorage(key) {
|
|
755
|
+
try {
|
|
756
|
+
localStorage.removeItem(key);
|
|
757
|
+
} catch {
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function isCollapsed() {
|
|
761
|
+
if (readStorage(LEGACY_DISMISSED_KEY) === "1") {
|
|
762
|
+
removeStorage(LEGACY_DISMISSED_KEY);
|
|
763
|
+
writeStorage(COLLAPSED_KEY, "1");
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
return readStorage(COLLAPSED_KEY) !== "0";
|
|
767
|
+
}
|
|
768
|
+
function setCollapsed(collapsed) {
|
|
769
|
+
writeStorage(COLLAPSED_KEY, collapsed ? "1" : "0");
|
|
841
770
|
}
|
|
842
771
|
function optOut() {
|
|
843
772
|
setCookie(COOKIE_NAMES.OPTOUT, "1", 365 * 24 * 3600);
|
|
@@ -846,53 +775,199 @@ function optOut() {
|
|
|
846
775
|
function optIn() {
|
|
847
776
|
deleteCookie(COOKIE_NAMES.OPTOUT);
|
|
848
777
|
}
|
|
849
|
-
function
|
|
778
|
+
function resetToAutomatic() {
|
|
779
|
+
deleteCookie(COOKIE_NAMES.AUDIENCE);
|
|
780
|
+
}
|
|
781
|
+
function hasPin() {
|
|
782
|
+
return readCookies().has(COOKIE_NAMES.AUDIENCE);
|
|
783
|
+
}
|
|
784
|
+
async function pinAudience(audienceId, opts = {}) {
|
|
785
|
+
const apiBase = opts.apiBase ?? DEFAULT_API_BASE;
|
|
786
|
+
try {
|
|
787
|
+
await fetch(`${apiBase}/kywi/audience`, {
|
|
788
|
+
method: "POST",
|
|
789
|
+
headers: { "Content-Type": "application/json", ...opts.csrfToken ? { "X-CSRF-Token": opts.csrfToken } : {} },
|
|
790
|
+
body: JSON.stringify({ audienceId })
|
|
791
|
+
});
|
|
792
|
+
} catch {
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
function reload() {
|
|
796
|
+
try {
|
|
797
|
+
window.location.reload();
|
|
798
|
+
} catch {
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
function removeSurface() {
|
|
802
|
+
document.getElementById(ROOT_ID)?.remove();
|
|
803
|
+
document.querySelector(".kywi-transparency-bar")?.remove();
|
|
804
|
+
}
|
|
805
|
+
function button(className, text, onClick) {
|
|
850
806
|
const btn = document.createElement("button");
|
|
851
|
-
btn.className = className;
|
|
852
807
|
btn.type = "button";
|
|
808
|
+
btn.className = className;
|
|
853
809
|
btn.textContent = text;
|
|
854
810
|
btn.addEventListener("click", onClick);
|
|
855
811
|
return btn;
|
|
856
812
|
}
|
|
857
|
-
function
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
813
|
+
function shouldRenderAutomatically(state) {
|
|
814
|
+
if (state.enabled === false) return false;
|
|
815
|
+
return state.isPersonalized && !state.isOptedOut || state.isOptedOut && state.hasMatchAvailable;
|
|
816
|
+
}
|
|
817
|
+
function audienceName(state) {
|
|
818
|
+
if (!state.audienceId) return null;
|
|
819
|
+
return state.audiences?.find((a) => a.id === state.audienceId)?.name ?? null;
|
|
820
|
+
}
|
|
821
|
+
function renderTransparencyPanel(state) {
|
|
822
|
+
lastState = state;
|
|
823
|
+
if (typeof document === "undefined") return;
|
|
824
|
+
removeSurface();
|
|
825
|
+
if (!forcedOpen && !shouldRenderAutomatically(state)) return;
|
|
826
|
+
const active = state.isPersonalized && !state.isOptedOut;
|
|
827
|
+
const collapsed = forcedOpen ? false : isCollapsed();
|
|
828
|
+
const root = document.createElement("div");
|
|
829
|
+
root.id = ROOT_ID;
|
|
830
|
+
root.className = `kywi-transparency ${active ? "kywi-transparency-active" : "kywi-transparency-opted-out"}`;
|
|
831
|
+
root.dataset.state = collapsed ? "collapsed" : "expanded";
|
|
832
|
+
const pill = document.createElement("button");
|
|
833
|
+
pill.type = "button";
|
|
834
|
+
pill.className = "kywi-transparency-pill";
|
|
835
|
+
pill.setAttribute("aria-expanded", String(!collapsed));
|
|
836
|
+
pill.setAttribute("aria-controls", PANEL_ID);
|
|
837
|
+
const pillIcon = document.createElement("span");
|
|
838
|
+
pillIcon.className = "kywi-transparency-pill-icon";
|
|
839
|
+
pillIcon.setAttribute("aria-hidden", "true");
|
|
840
|
+
pillIcon.textContent = "\u2726";
|
|
841
|
+
const pillLabel = document.createElement("span");
|
|
842
|
+
pillLabel.className = "kywi-transparency-pill-label";
|
|
843
|
+
pillLabel.textContent = active ? "Personalized" : "Personalization off";
|
|
844
|
+
pill.append(pillIcon, pillLabel);
|
|
845
|
+
pill.addEventListener("click", () => expandPanel());
|
|
846
|
+
pill.hidden = !collapsed;
|
|
847
|
+
root.appendChild(pill);
|
|
848
|
+
const panel = document.createElement("section");
|
|
849
|
+
panel.id = PANEL_ID;
|
|
850
|
+
panel.className = "kywi-transparency-panel";
|
|
851
|
+
panel.setAttribute("role", "dialog");
|
|
852
|
+
panel.setAttribute("aria-labelledby", TITLE_ID);
|
|
853
|
+
panel.hidden = collapsed;
|
|
854
|
+
const header = document.createElement("div");
|
|
855
|
+
header.className = "kywi-transparency-header";
|
|
856
|
+
const title = document.createElement("h2");
|
|
857
|
+
title.id = TITLE_ID;
|
|
858
|
+
title.className = "kywi-transparency-title";
|
|
859
|
+
const name = audienceName(state);
|
|
860
|
+
title.textContent = !active ? "Personalization is off" : name ? "This page is personalized for:" : "This page is personalized";
|
|
861
|
+
header.appendChild(title);
|
|
862
|
+
const collapseBtn = button("kywi-transparency-collapse", "\u2715", () => collapsePanel());
|
|
863
|
+
collapseBtn.setAttribute("aria-label", "Collapse");
|
|
864
|
+
header.appendChild(collapseBtn);
|
|
865
|
+
panel.appendChild(header);
|
|
866
|
+
if (active) {
|
|
867
|
+
const audience = document.createElement("p");
|
|
868
|
+
audience.className = "kywi-transparency-audience";
|
|
869
|
+
if (name) {
|
|
870
|
+
const strong = document.createElement("strong");
|
|
871
|
+
strong.textContent = name;
|
|
872
|
+
audience.appendChild(strong);
|
|
873
|
+
} else {
|
|
874
|
+
audience.textContent = "Based on your browsing context.";
|
|
875
|
+
}
|
|
876
|
+
panel.appendChild(audience);
|
|
877
|
+
}
|
|
878
|
+
const note = document.createElement("p");
|
|
879
|
+
note.className = "kywi-transparency-note";
|
|
880
|
+
note.textContent = active ? "Parts of this page are chosen to match this audience. You can switch, reset, or turn personalization off." : "Personalization is turned off, so you are seeing the default version of this page.";
|
|
881
|
+
panel.appendChild(note);
|
|
882
|
+
const options = state.audiences ?? [];
|
|
883
|
+
if (active && options.length > 0) {
|
|
884
|
+
const switcher = document.createElement("div");
|
|
885
|
+
switcher.className = "kywi-transparency-switcher";
|
|
886
|
+
const switcherLabel = document.createElement("span");
|
|
887
|
+
switcherLabel.id = SWITCHER_LABEL_ID;
|
|
888
|
+
switcherLabel.className = "kywi-transparency-switcher-label";
|
|
889
|
+
switcherLabel.textContent = "See this page as";
|
|
890
|
+
switcher.appendChild(switcherLabel);
|
|
891
|
+
const group = document.createElement("div");
|
|
892
|
+
group.className = "kywi-transparency-options";
|
|
893
|
+
group.setAttribute("role", "group");
|
|
894
|
+
group.setAttribute("aria-labelledby", SWITCHER_LABEL_ID);
|
|
895
|
+
for (const option of options) {
|
|
896
|
+
const isCurrent = option.id === state.audienceId;
|
|
897
|
+
const optionBtn = button("kywi-transparency-option", option.name, () => {
|
|
898
|
+
if (isCurrent) return;
|
|
899
|
+
void pinAudience(option.id, { apiBase: state.apiBase, csrfToken: state.csrfToken }).then(reload);
|
|
900
|
+
});
|
|
901
|
+
optionBtn.dataset.kywiAudienceId = option.id;
|
|
902
|
+
optionBtn.setAttribute("aria-pressed", String(isCurrent));
|
|
903
|
+
group.appendChild(optionBtn);
|
|
904
|
+
}
|
|
905
|
+
switcher.appendChild(group);
|
|
906
|
+
panel.appendChild(switcher);
|
|
907
|
+
}
|
|
908
|
+
const actions = document.createElement("div");
|
|
909
|
+
actions.className = "kywi-transparency-actions";
|
|
910
|
+
if (state.isOptedOut) {
|
|
911
|
+
actions.appendChild(button("kywi-transparency-opt-in", "Enable personalization", () => {
|
|
888
912
|
optIn();
|
|
889
|
-
|
|
913
|
+
reload();
|
|
914
|
+
}));
|
|
915
|
+
} else {
|
|
916
|
+
if (hasPin()) {
|
|
917
|
+
actions.appendChild(button("kywi-transparency-reset", "Reset to automatic", () => {
|
|
918
|
+
resetToAutomatic();
|
|
919
|
+
reload();
|
|
920
|
+
}));
|
|
921
|
+
}
|
|
922
|
+
actions.appendChild(button("kywi-transparency-opt-out", "Turn off personalization", () => {
|
|
923
|
+
optOut();
|
|
924
|
+
reload();
|
|
890
925
|
}));
|
|
891
|
-
const dismissBtn = createButton("kywi-transparency-dismiss", "\u2715", () => dismiss());
|
|
892
|
-
dismissBtn.setAttribute("aria-label", "Dismiss");
|
|
893
|
-
bar.appendChild(dismissBtn);
|
|
894
|
-
document.body.appendChild(bar);
|
|
895
926
|
}
|
|
927
|
+
panel.appendChild(actions);
|
|
928
|
+
panel.addEventListener("keydown", (e) => {
|
|
929
|
+
if (e.key === "Escape") collapsePanel();
|
|
930
|
+
});
|
|
931
|
+
root.appendChild(panel);
|
|
932
|
+
document.body.appendChild(root);
|
|
933
|
+
}
|
|
934
|
+
function currentSurface() {
|
|
935
|
+
const root = document.getElementById(ROOT_ID);
|
|
936
|
+
if (!root) return null;
|
|
937
|
+
const pill = root.querySelector(".kywi-transparency-pill");
|
|
938
|
+
const panel = root.querySelector(".kywi-transparency-panel");
|
|
939
|
+
if (!pill || !panel) return null;
|
|
940
|
+
return { root, pill, panel };
|
|
941
|
+
}
|
|
942
|
+
function setExpanded(expanded) {
|
|
943
|
+
const surface = currentSurface();
|
|
944
|
+
if (!surface) return;
|
|
945
|
+
surface.root.dataset.state = expanded ? "expanded" : "collapsed";
|
|
946
|
+
surface.pill.hidden = expanded;
|
|
947
|
+
surface.pill.setAttribute("aria-expanded", String(expanded));
|
|
948
|
+
surface.panel.hidden = !expanded;
|
|
949
|
+
const focusTarget = expanded ? surface.panel.querySelector(".kywi-transparency-collapse") : surface.pill;
|
|
950
|
+
focusTarget?.focus();
|
|
951
|
+
}
|
|
952
|
+
function expandPanel() {
|
|
953
|
+
setCollapsed(false);
|
|
954
|
+
setExpanded(true);
|
|
955
|
+
}
|
|
956
|
+
function collapsePanel() {
|
|
957
|
+
forcedOpen = false;
|
|
958
|
+
setCollapsed(true);
|
|
959
|
+
setExpanded(false);
|
|
960
|
+
}
|
|
961
|
+
function openTransparencyPanel() {
|
|
962
|
+
if (typeof document === "undefined") return;
|
|
963
|
+
forcedOpen = true;
|
|
964
|
+
const state = lastState ?? {
|
|
965
|
+
isPersonalized: false,
|
|
966
|
+
isOptedOut: readCookies().has(COOKIE_NAMES.OPTOUT),
|
|
967
|
+
hasMatchAvailable: false
|
|
968
|
+
};
|
|
969
|
+
renderTransparencyPanel(state);
|
|
970
|
+
currentSurface()?.panel.querySelector(".kywi-transparency-collapse")?.focus();
|
|
896
971
|
}
|
|
897
972
|
|
|
898
973
|
// src/audience/preview.ts
|
|
@@ -914,7 +989,7 @@ function checkPreviewInit() {
|
|
|
914
989
|
deleteCookie(COOKIE_NAMES.PREVIEW_INIT);
|
|
915
990
|
}
|
|
916
991
|
}
|
|
917
|
-
function renderPreviewBanner(
|
|
992
|
+
function renderPreviewBanner(audienceName2) {
|
|
918
993
|
document.querySelector(".kywi-preview-banner")?.remove();
|
|
919
994
|
const banner = document.createElement("div");
|
|
920
995
|
banner.className = "kywi-preview-banner";
|
|
@@ -923,7 +998,7 @@ function renderPreviewBanner(audienceName) {
|
|
|
923
998
|
text.className = "kywi-preview-text";
|
|
924
999
|
text.textContent = "Previewing as: ";
|
|
925
1000
|
const strong = document.createElement("strong");
|
|
926
|
-
strong.textContent =
|
|
1001
|
+
strong.textContent = audienceName2;
|
|
927
1002
|
text.appendChild(strong);
|
|
928
1003
|
banner.appendChild(text);
|
|
929
1004
|
const exitBtn = document.createElement("button");
|
|
@@ -1017,13 +1092,50 @@ function handleSelfIdSubmit(responses, audiences, currentPath) {
|
|
|
1017
1092
|
// src/audience/selfid-widget.ts
|
|
1018
1093
|
var SHOWN_KEY = "kywi_selfid_shown";
|
|
1019
1094
|
var MS_PER_DAY = 864e5;
|
|
1020
|
-
var DISMISSIBLE_MODES = /* @__PURE__ */ new Set(["hello_bar_top", "hello_bar_bottom", "drawer"]);
|
|
1095
|
+
var DISMISSIBLE_MODES = /* @__PURE__ */ new Set(["modal", "slide-in", "hello_bar_top", "hello_bar_bottom", "drawer"]);
|
|
1096
|
+
var OVERLAY_MODES = DISMISSIBLE_MODES;
|
|
1097
|
+
var SELFID_MOUNT_SELECTOR = "[data-kywi-selfid-mount]";
|
|
1098
|
+
var HELLO_BAR_TOP_CLASS = "kywi-selfid-hello-bar-top-open";
|
|
1099
|
+
var HELLO_BAR_HEIGHT_PROPERTY = "--kywi-selfid-hello-bar-height";
|
|
1100
|
+
var MODAL_LABEL_ID = "kywi-selfid-headline";
|
|
1101
|
+
function resolveSelfIdMount(displayMode) {
|
|
1102
|
+
if (typeof document === "undefined") return null;
|
|
1103
|
+
if (displayMode && displayMode !== "inline") return null;
|
|
1104
|
+
return document.querySelector(SELFID_MOUNT_SELECTOR);
|
|
1105
|
+
}
|
|
1021
1106
|
function normalizeOption(opt) {
|
|
1022
1107
|
return typeof opt === "string" ? { value: opt, label: opt } : opt;
|
|
1023
1108
|
}
|
|
1109
|
+
var FOCUSABLE_SELECTOR = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
1110
|
+
function focusableElements(root) {
|
|
1111
|
+
return Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => !el.hidden);
|
|
1112
|
+
}
|
|
1113
|
+
function trapTab(root, e) {
|
|
1114
|
+
const focusable = focusableElements(root);
|
|
1115
|
+
if (focusable.length === 0) {
|
|
1116
|
+
e.preventDefault();
|
|
1117
|
+
root.focus();
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const first = focusable[0];
|
|
1121
|
+
const last = focusable[focusable.length - 1];
|
|
1122
|
+
const active = document.activeElement;
|
|
1123
|
+
const inside = active !== null && root.contains(active) && active !== root;
|
|
1124
|
+
if (e.shiftKey) {
|
|
1125
|
+
if (!inside || active === first) {
|
|
1126
|
+
e.preventDefault();
|
|
1127
|
+
last.focus();
|
|
1128
|
+
}
|
|
1129
|
+
} else if (!inside || active === last) {
|
|
1130
|
+
e.preventDefault();
|
|
1131
|
+
first.focus();
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1024
1134
|
function createSelfIdWidget(config) {
|
|
1025
1135
|
const frequency = config.frequency ?? "once";
|
|
1026
1136
|
const displayMode = config.displayMode ?? "inline";
|
|
1137
|
+
const isModal = displayMode === "modal";
|
|
1138
|
+
let cleanups = [];
|
|
1027
1139
|
function shouldShow() {
|
|
1028
1140
|
if (frequency === "always") return true;
|
|
1029
1141
|
if (frequency === "session") return false;
|
|
@@ -1044,26 +1156,86 @@ function createSelfIdWidget(config) {
|
|
|
1044
1156
|
markShown();
|
|
1045
1157
|
}
|
|
1046
1158
|
}
|
|
1159
|
+
function teardown(container) {
|
|
1160
|
+
for (const undo of cleanups) undo();
|
|
1161
|
+
cleanups = [];
|
|
1162
|
+
container.replaceChildren();
|
|
1163
|
+
container.className = "";
|
|
1164
|
+
container.removeAttribute("role");
|
|
1165
|
+
container.removeAttribute("aria-modal");
|
|
1166
|
+
container.removeAttribute("aria-label");
|
|
1167
|
+
container.removeAttribute("aria-labelledby");
|
|
1168
|
+
container.removeAttribute("tabindex");
|
|
1169
|
+
container.hidden = true;
|
|
1170
|
+
}
|
|
1171
|
+
function dismiss(container) {
|
|
1172
|
+
markShown();
|
|
1173
|
+
teardown(container);
|
|
1174
|
+
}
|
|
1047
1175
|
function render(container) {
|
|
1176
|
+
for (const undo of cleanups) undo();
|
|
1177
|
+
cleanups = [];
|
|
1048
1178
|
container.replaceChildren();
|
|
1179
|
+
container.hidden = false;
|
|
1049
1180
|
const displayModeClass = displayMode.replace(/_/g, "-");
|
|
1050
1181
|
container.className = `kywi-selfid-widget kywi-selfid-${displayModeClass}`;
|
|
1182
|
+
if (isModal) {
|
|
1183
|
+
const backdrop = document.createElement("div");
|
|
1184
|
+
backdrop.className = "kywi-selfid-backdrop";
|
|
1185
|
+
backdrop.addEventListener("click", () => dismiss(container));
|
|
1186
|
+
(container.parentElement ?? document.body).appendChild(backdrop);
|
|
1187
|
+
cleanups.push(() => backdrop.remove());
|
|
1188
|
+
const previousOverflow = document.body.style.overflow;
|
|
1189
|
+
document.body.style.overflow = "hidden";
|
|
1190
|
+
cleanups.push(() => {
|
|
1191
|
+
document.body.style.overflow = previousOverflow;
|
|
1192
|
+
});
|
|
1193
|
+
const onKeydown = (e) => {
|
|
1194
|
+
if (e.key === "Escape") {
|
|
1195
|
+
e.preventDefault();
|
|
1196
|
+
dismiss(container);
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
if (e.key === "Tab") trapTab(container, e);
|
|
1200
|
+
};
|
|
1201
|
+
document.addEventListener("keydown", onKeydown, true);
|
|
1202
|
+
cleanups.push(() => document.removeEventListener("keydown", onKeydown, true));
|
|
1203
|
+
container.setAttribute("role", "dialog");
|
|
1204
|
+
container.setAttribute("aria-modal", "true");
|
|
1205
|
+
if (config.headline) {
|
|
1206
|
+
container.setAttribute("aria-labelledby", MODAL_LABEL_ID);
|
|
1207
|
+
} else {
|
|
1208
|
+
container.setAttribute("aria-label", "Personalize your experience");
|
|
1209
|
+
}
|
|
1210
|
+
container.tabIndex = -1;
|
|
1211
|
+
}
|
|
1212
|
+
if (displayMode === "hello_bar_top") {
|
|
1213
|
+
document.body.classList.add(HELLO_BAR_TOP_CLASS);
|
|
1214
|
+
const syncHeight = () => {
|
|
1215
|
+
document.documentElement.style.setProperty(HELLO_BAR_HEIGHT_PROPERTY, `${container.offsetHeight}px`);
|
|
1216
|
+
};
|
|
1217
|
+
window.addEventListener("resize", syncHeight);
|
|
1218
|
+
cleanups.push(() => {
|
|
1219
|
+
document.body.classList.remove(HELLO_BAR_TOP_CLASS);
|
|
1220
|
+
document.documentElement.style.removeProperty(HELLO_BAR_HEIGHT_PROPERTY);
|
|
1221
|
+
window.removeEventListener("resize", syncHeight);
|
|
1222
|
+
});
|
|
1223
|
+
queueMicrotask(syncHeight);
|
|
1224
|
+
}
|
|
1051
1225
|
if (DISMISSIBLE_MODES.has(displayMode)) {
|
|
1052
1226
|
const dismissBtn = document.createElement("button");
|
|
1053
1227
|
dismissBtn.type = "button";
|
|
1054
1228
|
dismissBtn.className = "kywi-selfid-dismiss";
|
|
1055
1229
|
dismissBtn.setAttribute("aria-label", "Dismiss");
|
|
1056
1230
|
dismissBtn.textContent = "\xD7";
|
|
1057
|
-
dismissBtn.addEventListener("click", () =>
|
|
1058
|
-
markShown();
|
|
1059
|
-
container.replaceChildren();
|
|
1060
|
-
});
|
|
1231
|
+
dismissBtn.addEventListener("click", () => dismiss(container));
|
|
1061
1232
|
container.appendChild(dismissBtn);
|
|
1062
1233
|
}
|
|
1063
1234
|
if (config.headline) {
|
|
1064
1235
|
const h = document.createElement("h3");
|
|
1065
1236
|
h.className = "kywi-selfid-headline";
|
|
1066
1237
|
h.textContent = config.headline;
|
|
1238
|
+
if (isModal) h.id = MODAL_LABEL_ID;
|
|
1067
1239
|
container.appendChild(h);
|
|
1068
1240
|
}
|
|
1069
1241
|
if (config.subheadline) {
|
|
@@ -1124,10 +1296,7 @@ function createSelfIdWidget(config) {
|
|
|
1124
1296
|
skipBtn.type = "button";
|
|
1125
1297
|
skipBtn.className = "kywi-selfid-skip";
|
|
1126
1298
|
skipBtn.textContent = config.skipLabel;
|
|
1127
|
-
skipBtn.addEventListener("click", () =>
|
|
1128
|
-
markShown();
|
|
1129
|
-
container.replaceChildren();
|
|
1130
|
-
});
|
|
1299
|
+
skipBtn.addEventListener("click", () => dismiss(container));
|
|
1131
1300
|
actions.appendChild(skipBtn);
|
|
1132
1301
|
}
|
|
1133
1302
|
form.appendChild(actions);
|
|
@@ -1139,8 +1308,10 @@ function createSelfIdWidget(config) {
|
|
|
1139
1308
|
responses[key] = String(value);
|
|
1140
1309
|
});
|
|
1141
1310
|
handleSubmit(responses);
|
|
1311
|
+
if (OVERLAY_MODES.has(displayMode)) teardown(container);
|
|
1142
1312
|
});
|
|
1143
1313
|
container.appendChild(form);
|
|
1314
|
+
if (isModal) container.focus();
|
|
1144
1315
|
}
|
|
1145
1316
|
return { shouldShow, markShown, render, handleSubmit };
|
|
1146
1317
|
}
|
|
@@ -1149,39 +1320,30 @@ function createSelfIdWidget(config) {
|
|
|
1149
1320
|
function isOptedOut() {
|
|
1150
1321
|
return readCookies().has(COOKIE_NAMES.OPTOUT);
|
|
1151
1322
|
}
|
|
1152
|
-
function
|
|
1153
|
-
const btn = document.createElement("button");
|
|
1154
|
-
btn.className = className;
|
|
1155
|
-
btn.type = "button";
|
|
1156
|
-
btn.textContent = text;
|
|
1157
|
-
btn.addEventListener("click", onClick);
|
|
1158
|
-
return btn;
|
|
1159
|
-
}
|
|
1160
|
-
function renderPersonalizationBadge() {
|
|
1323
|
+
function renderPersonalizationBadge(info = {}) {
|
|
1161
1324
|
const containers = document.querySelectorAll("[data-kywi-personalization-badge]");
|
|
1162
1325
|
if (containers.length === 0) return;
|
|
1326
|
+
const optedOut = isOptedOut();
|
|
1163
1327
|
for (const container of Array.from(containers)) {
|
|
1164
|
-
|
|
1165
|
-
if (existing) existing.remove();
|
|
1328
|
+
container.querySelector(".kywi-personalization-badge")?.remove();
|
|
1166
1329
|
const badge = document.createElement("div");
|
|
1167
1330
|
badge.className = "kywi-personalization-badge";
|
|
1168
|
-
const
|
|
1331
|
+
const trigger = document.createElement("button");
|
|
1332
|
+
trigger.type = "button";
|
|
1333
|
+
trigger.className = "kywi-badge-open";
|
|
1334
|
+
trigger.setAttribute("aria-label", "Personalization details");
|
|
1169
1335
|
const status = document.createElement("span");
|
|
1170
1336
|
status.className = "kywi-badge-status";
|
|
1171
|
-
status.textContent = optedOut ? "Personalization
|
|
1172
|
-
|
|
1173
|
-
if (optedOut) {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
} else {
|
|
1179
|
-
badge.appendChild(createButton2("kywi-badge-opt-out", "Opt out", () => {
|
|
1180
|
-
setCookie(COOKIE_NAMES.OPTOUT, "1", 365 * 24 * 3600);
|
|
1181
|
-
deleteCookie(COOKIE_NAMES.AUDIENCE);
|
|
1182
|
-
renderPersonalizationBadge();
|
|
1183
|
-
}));
|
|
1337
|
+
status.textContent = optedOut ? "Personalization off" : "Personalization active";
|
|
1338
|
+
trigger.appendChild(status);
|
|
1339
|
+
if (!optedOut && info.audienceName) {
|
|
1340
|
+
const audience = document.createElement("span");
|
|
1341
|
+
audience.className = "kywi-badge-audience";
|
|
1342
|
+
audience.textContent = info.audienceName;
|
|
1343
|
+
trigger.appendChild(audience);
|
|
1184
1344
|
}
|
|
1345
|
+
trigger.addEventListener("click", () => openTransparencyPanel());
|
|
1346
|
+
badge.appendChild(trigger);
|
|
1185
1347
|
container.appendChild(badge);
|
|
1186
1348
|
}
|
|
1187
1349
|
}
|
|
@@ -1199,10 +1361,12 @@ async function bootAudienceEngine(config) {
|
|
|
1199
1361
|
checkPreviewInit();
|
|
1200
1362
|
const serverMeta = readServerMeta();
|
|
1201
1363
|
const previewId = getPreviewAudienceId();
|
|
1364
|
+
const activeAudiences = audiences.filter((a) => a.status === "active").map((a) => ({ id: a.id, name: a.name }));
|
|
1365
|
+
const nameOf = (id) => id ? activeAudiences.find((a) => a.id === id)?.name ?? null : null;
|
|
1202
1366
|
if (previewId) {
|
|
1203
1367
|
patchVariantContainers(previewId);
|
|
1204
|
-
renderPreviewBanner(previewId);
|
|
1205
|
-
renderPersonalizationBadge();
|
|
1368
|
+
renderPreviewBanner(nameOf(previewId) ?? previewId);
|
|
1369
|
+
renderPersonalizationBadge({ audienceName: nameOf(previewId) });
|
|
1206
1370
|
populateDataLayer({ visitorId: serverMeta.visitorId, audienceId: previewId, experimentId: null, variantId: null, isPreview: true, isOptedOut: false });
|
|
1207
1371
|
fireKywiReady();
|
|
1208
1372
|
return;
|
|
@@ -1218,12 +1382,24 @@ async function bootAudienceEngine(config) {
|
|
|
1218
1382
|
merged.adapters = { ...merged.adapters, ...adapterResults };
|
|
1219
1383
|
}
|
|
1220
1384
|
const result = evaluateClientSide(audiences, merged, currentPath, pageCategory);
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1385
|
+
const pinnedId = cookies.get(COOKIE_NAMES.AUDIENCE) ?? null;
|
|
1386
|
+
const isPinned = !result.isOptedOut && pinnedId !== null && activeAudiences.some((a) => a.id === pinnedId);
|
|
1387
|
+
const effectiveAudienceId = isPinned ? pinnedId : result.winningAudienceId;
|
|
1388
|
+
patchVariantContainers(effectiveAudienceId);
|
|
1389
|
+
renderTransparencyPanel({
|
|
1390
|
+
isPersonalized: effectiveAudienceId !== null,
|
|
1391
|
+
isOptedOut: result.isOptedOut,
|
|
1392
|
+
hasMatchAvailable: result.allMatchedIds.length > 0,
|
|
1393
|
+
audienceId: effectiveAudienceId,
|
|
1394
|
+
audiences: activeAudiences,
|
|
1395
|
+
apiBase,
|
|
1396
|
+
csrfToken: Kywi.context?.csrfToken ?? null,
|
|
1397
|
+
enabled: config.transparencyNotice !== false
|
|
1398
|
+
});
|
|
1399
|
+
renderPersonalizationBadge({ audienceName: nameOf(effectiveAudienceId) });
|
|
1400
|
+
populateDataLayer({ visitorId: result.signals.identity.visitorId, audienceId: effectiveAudienceId, experimentId: serverMeta.experimentId, variantId: serverMeta.variantId, isPreview: false, isOptedOut: result.isOptedOut });
|
|
1225
1401
|
fireKywiReady();
|
|
1226
|
-
if (result.winningAudienceId && result.winningAudienceId !== serverMeta.audienceId) {
|
|
1402
|
+
if (!isPinned && result.winningAudienceId && result.winningAudienceId !== serverMeta.audienceId) {
|
|
1227
1403
|
fetch(`${apiBase}/kywi/audience`, {
|
|
1228
1404
|
method: "POST",
|
|
1229
1405
|
headers: { "Content-Type": "application/json", ...Kywi.context?.csrfToken ? { "X-CSRF-Token": Kywi.context.csrfToken } : {} },
|
|
@@ -1235,6 +1411,11 @@ async function bootAudienceEngine(config) {
|
|
|
1235
1411
|
const widget = createSelfIdWidget(config.selfIdWidget);
|
|
1236
1412
|
if (widget.shouldShow()) {
|
|
1237
1413
|
const doRender = () => {
|
|
1414
|
+
const mount = resolveSelfIdMount(config.selfIdWidget.displayMode);
|
|
1415
|
+
if (mount) {
|
|
1416
|
+
widget.render(mount);
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1238
1419
|
const existing = document.getElementById("kywi-selfid-container");
|
|
1239
1420
|
const container = existing ?? document.createElement("div");
|
|
1240
1421
|
if (!container.id) {
|
|
@@ -1332,6 +1513,8 @@ if (typeof document !== "undefined") {
|
|
|
1332
1513
|
export {
|
|
1333
1514
|
Kywi,
|
|
1334
1515
|
bootAudienceEngine,
|
|
1335
|
-
bootExperiments
|
|
1516
|
+
bootExperiments,
|
|
1517
|
+
openTransparencyPanel,
|
|
1518
|
+
renderTransparencyPanel
|
|
1336
1519
|
};
|
|
1337
1520
|
//# sourceMappingURL=kywi.esm.js.map
|