@kywi-software/js 0.6.4 → 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.js
CHANGED
|
@@ -23,7 +23,9 @@ var Kywi = (() => {
|
|
|
23
23
|
__export(src_exports, {
|
|
24
24
|
Kywi: () => Kywi,
|
|
25
25
|
bootAudienceEngine: () => bootAudienceEngine,
|
|
26
|
-
bootExperiments: () => bootExperiments
|
|
26
|
+
bootExperiments: () => bootExperiments,
|
|
27
|
+
openTransparencyPanel: () => openTransparencyPanel,
|
|
28
|
+
renderTransparencyPanel: () => renderTransparencyPanel
|
|
27
29
|
});
|
|
28
30
|
|
|
29
31
|
// src/context.ts
|
|
@@ -756,116 +758,45 @@ var Kywi = (() => {
|
|
|
756
758
|
document.cookie = `${name}=; Path=/; Max-Age=0`;
|
|
757
759
|
}
|
|
758
760
|
|
|
759
|
-
// src/audience/transparency-styles.ts
|
|
760
|
-
var STYLE_ELEMENT_ID = "kywi-transparency-styles";
|
|
761
|
-
var CSS = `
|
|
762
|
-
.kywi-transparency-bar,
|
|
763
|
-
.kywi-transparency-bar * {
|
|
764
|
-
box-sizing: border-box;
|
|
765
|
-
}
|
|
766
|
-
.kywi-transparency-bar {
|
|
767
|
-
position: fixed;
|
|
768
|
-
left: 0;
|
|
769
|
-
right: 0;
|
|
770
|
-
bottom: 0;
|
|
771
|
-
z-index: 2147483000;
|
|
772
|
-
display: flex;
|
|
773
|
-
flex-wrap: wrap;
|
|
774
|
-
align-items: center;
|
|
775
|
-
gap: 12px;
|
|
776
|
-
margin: 0;
|
|
777
|
-
padding: 12px 16px;
|
|
778
|
-
background: rgba(23, 23, 28, 0.96);
|
|
779
|
-
color: #f4f4f5;
|
|
780
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
781
|
-
font-size: 13px;
|
|
782
|
-
line-height: 1.4;
|
|
783
|
-
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
|
784
|
-
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.25);
|
|
785
|
-
animation: kywi-transparency-in 220ms ease-out;
|
|
786
|
-
}
|
|
787
|
-
@media (prefers-reduced-motion: reduce) {
|
|
788
|
-
.kywi-transparency-bar { animation: none; }
|
|
789
|
-
}
|
|
790
|
-
@keyframes kywi-transparency-in {
|
|
791
|
-
from { transform: translateY(100%); opacity: 0; }
|
|
792
|
-
to { transform: translateY(0); opacity: 1; }
|
|
793
|
-
}
|
|
794
|
-
.kywi-transparency-text {
|
|
795
|
-
flex: 1 1 240px;
|
|
796
|
-
color: #f4f4f5;
|
|
797
|
-
}
|
|
798
|
-
.kywi-transparency-bar button {
|
|
799
|
-
appearance: none;
|
|
800
|
-
-webkit-appearance: none;
|
|
801
|
-
font-family: inherit;
|
|
802
|
-
font-size: 12px;
|
|
803
|
-
line-height: 1.4;
|
|
804
|
-
cursor: pointer;
|
|
805
|
-
border-radius: 999px;
|
|
806
|
-
transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
|
|
807
|
-
}
|
|
808
|
-
.kywi-transparency-opt-out,
|
|
809
|
-
.kywi-transparency-opt-in {
|
|
810
|
-
flex: 0 0 auto;
|
|
811
|
-
padding: 6px 14px;
|
|
812
|
-
border: 1px solid rgba(255, 255, 255, 0.35);
|
|
813
|
-
background: transparent;
|
|
814
|
-
color: #f4f4f5;
|
|
815
|
-
font-weight: 600;
|
|
816
|
-
}
|
|
817
|
-
.kywi-transparency-opt-out:hover,
|
|
818
|
-
.kywi-transparency-opt-in:hover {
|
|
819
|
-
background: rgba(255, 255, 255, 0.12);
|
|
820
|
-
border-color: rgba(255, 255, 255, 0.5);
|
|
821
|
-
}
|
|
822
|
-
.kywi-transparency-dismiss {
|
|
823
|
-
flex: 0 0 auto;
|
|
824
|
-
width: 26px;
|
|
825
|
-
height: 26px;
|
|
826
|
-
padding: 0;
|
|
827
|
-
border: 1px solid transparent;
|
|
828
|
-
background: transparent;
|
|
829
|
-
color: #d4d4d8;
|
|
830
|
-
font-size: 13px;
|
|
831
|
-
}
|
|
832
|
-
.kywi-transparency-dismiss:hover {
|
|
833
|
-
background: rgba(255, 255, 255, 0.1);
|
|
834
|
-
color: #f4f4f5;
|
|
835
|
-
}
|
|
836
|
-
.kywi-transparency-bar button:focus-visible {
|
|
837
|
-
outline: 2px solid #7dd3fc;
|
|
838
|
-
outline-offset: 2px;
|
|
839
|
-
}
|
|
840
|
-
@media (max-width: 480px) {
|
|
841
|
-
.kywi-transparency-bar {
|
|
842
|
-
padding: 10px 12px;
|
|
843
|
-
}
|
|
844
|
-
.kywi-transparency-text {
|
|
845
|
-
flex-basis: 100%;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
`;
|
|
849
|
-
function ensureTransparencyStyles() {
|
|
850
|
-
if (typeof document === "undefined") return;
|
|
851
|
-
if (document.getElementById(STYLE_ELEMENT_ID)) return;
|
|
852
|
-
const style = document.createElement("style");
|
|
853
|
-
style.id = STYLE_ELEMENT_ID;
|
|
854
|
-
style.textContent = CSS;
|
|
855
|
-
document.head.appendChild(style);
|
|
856
|
-
}
|
|
857
|
-
|
|
858
761
|
// src/audience/transparency.ts
|
|
859
|
-
var
|
|
860
|
-
|
|
861
|
-
|
|
762
|
+
var COLLAPSED_KEY = "kywi_transparency_collapsed";
|
|
763
|
+
var LEGACY_DISMISSED_KEY = "kywi_transparency_dismissed";
|
|
764
|
+
var ROOT_ID = "kywi-transparency";
|
|
765
|
+
var PANEL_ID = "kywi-transparency-panel";
|
|
766
|
+
var TITLE_ID = "kywi-transparency-title";
|
|
767
|
+
var SWITCHER_LABEL_ID = "kywi-transparency-switcher-label";
|
|
768
|
+
var DEFAULT_API_BASE = "/api/v1";
|
|
769
|
+
var lastState = null;
|
|
770
|
+
var forcedOpen = false;
|
|
771
|
+
function readStorage(key) {
|
|
772
|
+
try {
|
|
773
|
+
return localStorage.getItem(key);
|
|
774
|
+
} catch {
|
|
775
|
+
return null;
|
|
776
|
+
}
|
|
862
777
|
}
|
|
863
|
-
function
|
|
864
|
-
|
|
865
|
-
|
|
778
|
+
function writeStorage(key, value) {
|
|
779
|
+
try {
|
|
780
|
+
localStorage.setItem(key, value);
|
|
781
|
+
} catch {
|
|
782
|
+
}
|
|
866
783
|
}
|
|
867
|
-
function
|
|
868
|
-
|
|
784
|
+
function removeStorage(key) {
|
|
785
|
+
try {
|
|
786
|
+
localStorage.removeItem(key);
|
|
787
|
+
} catch {
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function isCollapsed() {
|
|
791
|
+
if (readStorage(LEGACY_DISMISSED_KEY) === "1") {
|
|
792
|
+
removeStorage(LEGACY_DISMISSED_KEY);
|
|
793
|
+
writeStorage(COLLAPSED_KEY, "1");
|
|
794
|
+
return true;
|
|
795
|
+
}
|
|
796
|
+
return readStorage(COLLAPSED_KEY) !== "0";
|
|
797
|
+
}
|
|
798
|
+
function setCollapsed(collapsed) {
|
|
799
|
+
writeStorage(COLLAPSED_KEY, collapsed ? "1" : "0");
|
|
869
800
|
}
|
|
870
801
|
function optOut() {
|
|
871
802
|
setCookie(COOKIE_NAMES.OPTOUT, "1", 365 * 24 * 3600);
|
|
@@ -874,53 +805,199 @@ var Kywi = (() => {
|
|
|
874
805
|
function optIn() {
|
|
875
806
|
deleteCookie(COOKIE_NAMES.OPTOUT);
|
|
876
807
|
}
|
|
877
|
-
function
|
|
808
|
+
function resetToAutomatic() {
|
|
809
|
+
deleteCookie(COOKIE_NAMES.AUDIENCE);
|
|
810
|
+
}
|
|
811
|
+
function hasPin() {
|
|
812
|
+
return readCookies().has(COOKIE_NAMES.AUDIENCE);
|
|
813
|
+
}
|
|
814
|
+
async function pinAudience(audienceId, opts = {}) {
|
|
815
|
+
const apiBase = opts.apiBase ?? DEFAULT_API_BASE;
|
|
816
|
+
try {
|
|
817
|
+
await fetch(`${apiBase}/kywi/audience`, {
|
|
818
|
+
method: "POST",
|
|
819
|
+
headers: { "Content-Type": "application/json", ...opts.csrfToken ? { "X-CSRF-Token": opts.csrfToken } : {} },
|
|
820
|
+
body: JSON.stringify({ audienceId })
|
|
821
|
+
});
|
|
822
|
+
} catch {
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
function reload() {
|
|
826
|
+
try {
|
|
827
|
+
window.location.reload();
|
|
828
|
+
} catch {
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
function removeSurface() {
|
|
832
|
+
document.getElementById(ROOT_ID)?.remove();
|
|
833
|
+
document.querySelector(".kywi-transparency-bar")?.remove();
|
|
834
|
+
}
|
|
835
|
+
function button(className, text, onClick) {
|
|
878
836
|
const btn = document.createElement("button");
|
|
879
|
-
btn.className = className;
|
|
880
837
|
btn.type = "button";
|
|
838
|
+
btn.className = className;
|
|
881
839
|
btn.textContent = text;
|
|
882
840
|
btn.addEventListener("click", onClick);
|
|
883
841
|
return btn;
|
|
884
842
|
}
|
|
885
|
-
function
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
843
|
+
function shouldRenderAutomatically(state) {
|
|
844
|
+
if (state.enabled === false) return false;
|
|
845
|
+
return state.isPersonalized && !state.isOptedOut || state.isOptedOut && state.hasMatchAvailable;
|
|
846
|
+
}
|
|
847
|
+
function audienceName(state) {
|
|
848
|
+
if (!state.audienceId) return null;
|
|
849
|
+
return state.audiences?.find((a) => a.id === state.audienceId)?.name ?? null;
|
|
850
|
+
}
|
|
851
|
+
function renderTransparencyPanel(state) {
|
|
852
|
+
lastState = state;
|
|
853
|
+
if (typeof document === "undefined") return;
|
|
854
|
+
removeSurface();
|
|
855
|
+
if (!forcedOpen && !shouldRenderAutomatically(state)) return;
|
|
856
|
+
const active = state.isPersonalized && !state.isOptedOut;
|
|
857
|
+
const collapsed = forcedOpen ? false : isCollapsed();
|
|
858
|
+
const root = document.createElement("div");
|
|
859
|
+
root.id = ROOT_ID;
|
|
860
|
+
root.className = `kywi-transparency ${active ? "kywi-transparency-active" : "kywi-transparency-opted-out"}`;
|
|
861
|
+
root.dataset.state = collapsed ? "collapsed" : "expanded";
|
|
862
|
+
const pill = document.createElement("button");
|
|
863
|
+
pill.type = "button";
|
|
864
|
+
pill.className = "kywi-transparency-pill";
|
|
865
|
+
pill.setAttribute("aria-expanded", String(!collapsed));
|
|
866
|
+
pill.setAttribute("aria-controls", PANEL_ID);
|
|
867
|
+
const pillIcon = document.createElement("span");
|
|
868
|
+
pillIcon.className = "kywi-transparency-pill-icon";
|
|
869
|
+
pillIcon.setAttribute("aria-hidden", "true");
|
|
870
|
+
pillIcon.textContent = "\u2726";
|
|
871
|
+
const pillLabel = document.createElement("span");
|
|
872
|
+
pillLabel.className = "kywi-transparency-pill-label";
|
|
873
|
+
pillLabel.textContent = active ? "Personalized" : "Personalization off";
|
|
874
|
+
pill.append(pillIcon, pillLabel);
|
|
875
|
+
pill.addEventListener("click", () => expandPanel());
|
|
876
|
+
pill.hidden = !collapsed;
|
|
877
|
+
root.appendChild(pill);
|
|
878
|
+
const panel = document.createElement("section");
|
|
879
|
+
panel.id = PANEL_ID;
|
|
880
|
+
panel.className = "kywi-transparency-panel";
|
|
881
|
+
panel.setAttribute("role", "dialog");
|
|
882
|
+
panel.setAttribute("aria-labelledby", TITLE_ID);
|
|
883
|
+
panel.hidden = collapsed;
|
|
884
|
+
const header = document.createElement("div");
|
|
885
|
+
header.className = "kywi-transparency-header";
|
|
886
|
+
const title = document.createElement("h2");
|
|
887
|
+
title.id = TITLE_ID;
|
|
888
|
+
title.className = "kywi-transparency-title";
|
|
889
|
+
const name = audienceName(state);
|
|
890
|
+
title.textContent = !active ? "Personalization is off" : name ? "This page is personalized for:" : "This page is personalized";
|
|
891
|
+
header.appendChild(title);
|
|
892
|
+
const collapseBtn = button("kywi-transparency-collapse", "\u2715", () => collapsePanel());
|
|
893
|
+
collapseBtn.setAttribute("aria-label", "Collapse");
|
|
894
|
+
header.appendChild(collapseBtn);
|
|
895
|
+
panel.appendChild(header);
|
|
896
|
+
if (active) {
|
|
897
|
+
const audience = document.createElement("p");
|
|
898
|
+
audience.className = "kywi-transparency-audience";
|
|
899
|
+
if (name) {
|
|
900
|
+
const strong = document.createElement("strong");
|
|
901
|
+
strong.textContent = name;
|
|
902
|
+
audience.appendChild(strong);
|
|
903
|
+
} else {
|
|
904
|
+
audience.textContent = "Based on your browsing context.";
|
|
905
|
+
}
|
|
906
|
+
panel.appendChild(audience);
|
|
907
|
+
}
|
|
908
|
+
const note = document.createElement("p");
|
|
909
|
+
note.className = "kywi-transparency-note";
|
|
910
|
+
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.";
|
|
911
|
+
panel.appendChild(note);
|
|
912
|
+
const options = state.audiences ?? [];
|
|
913
|
+
if (active && options.length > 0) {
|
|
914
|
+
const switcher = document.createElement("div");
|
|
915
|
+
switcher.className = "kywi-transparency-switcher";
|
|
916
|
+
const switcherLabel = document.createElement("span");
|
|
917
|
+
switcherLabel.id = SWITCHER_LABEL_ID;
|
|
918
|
+
switcherLabel.className = "kywi-transparency-switcher-label";
|
|
919
|
+
switcherLabel.textContent = "See this page as";
|
|
920
|
+
switcher.appendChild(switcherLabel);
|
|
921
|
+
const group = document.createElement("div");
|
|
922
|
+
group.className = "kywi-transparency-options";
|
|
923
|
+
group.setAttribute("role", "group");
|
|
924
|
+
group.setAttribute("aria-labelledby", SWITCHER_LABEL_ID);
|
|
925
|
+
for (const option of options) {
|
|
926
|
+
const isCurrent = option.id === state.audienceId;
|
|
927
|
+
const optionBtn = button("kywi-transparency-option", option.name, () => {
|
|
928
|
+
if (isCurrent) return;
|
|
929
|
+
void pinAudience(option.id, { apiBase: state.apiBase, csrfToken: state.csrfToken }).then(reload);
|
|
930
|
+
});
|
|
931
|
+
optionBtn.dataset.kywiAudienceId = option.id;
|
|
932
|
+
optionBtn.setAttribute("aria-pressed", String(isCurrent));
|
|
933
|
+
group.appendChild(optionBtn);
|
|
934
|
+
}
|
|
935
|
+
switcher.appendChild(group);
|
|
936
|
+
panel.appendChild(switcher);
|
|
937
|
+
}
|
|
938
|
+
const actions = document.createElement("div");
|
|
939
|
+
actions.className = "kywi-transparency-actions";
|
|
940
|
+
if (state.isOptedOut) {
|
|
941
|
+
actions.appendChild(button("kywi-transparency-opt-in", "Enable personalization", () => {
|
|
916
942
|
optIn();
|
|
917
|
-
|
|
943
|
+
reload();
|
|
944
|
+
}));
|
|
945
|
+
} else {
|
|
946
|
+
if (hasPin()) {
|
|
947
|
+
actions.appendChild(button("kywi-transparency-reset", "Reset to automatic", () => {
|
|
948
|
+
resetToAutomatic();
|
|
949
|
+
reload();
|
|
950
|
+
}));
|
|
951
|
+
}
|
|
952
|
+
actions.appendChild(button("kywi-transparency-opt-out", "Turn off personalization", () => {
|
|
953
|
+
optOut();
|
|
954
|
+
reload();
|
|
918
955
|
}));
|
|
919
|
-
const dismissBtn = createButton("kywi-transparency-dismiss", "\u2715", () => dismiss());
|
|
920
|
-
dismissBtn.setAttribute("aria-label", "Dismiss");
|
|
921
|
-
bar.appendChild(dismissBtn);
|
|
922
|
-
document.body.appendChild(bar);
|
|
923
956
|
}
|
|
957
|
+
panel.appendChild(actions);
|
|
958
|
+
panel.addEventListener("keydown", (e) => {
|
|
959
|
+
if (e.key === "Escape") collapsePanel();
|
|
960
|
+
});
|
|
961
|
+
root.appendChild(panel);
|
|
962
|
+
document.body.appendChild(root);
|
|
963
|
+
}
|
|
964
|
+
function currentSurface() {
|
|
965
|
+
const root = document.getElementById(ROOT_ID);
|
|
966
|
+
if (!root) return null;
|
|
967
|
+
const pill = root.querySelector(".kywi-transparency-pill");
|
|
968
|
+
const panel = root.querySelector(".kywi-transparency-panel");
|
|
969
|
+
if (!pill || !panel) return null;
|
|
970
|
+
return { root, pill, panel };
|
|
971
|
+
}
|
|
972
|
+
function setExpanded(expanded) {
|
|
973
|
+
const surface = currentSurface();
|
|
974
|
+
if (!surface) return;
|
|
975
|
+
surface.root.dataset.state = expanded ? "expanded" : "collapsed";
|
|
976
|
+
surface.pill.hidden = expanded;
|
|
977
|
+
surface.pill.setAttribute("aria-expanded", String(expanded));
|
|
978
|
+
surface.panel.hidden = !expanded;
|
|
979
|
+
const focusTarget = expanded ? surface.panel.querySelector(".kywi-transparency-collapse") : surface.pill;
|
|
980
|
+
focusTarget?.focus();
|
|
981
|
+
}
|
|
982
|
+
function expandPanel() {
|
|
983
|
+
setCollapsed(false);
|
|
984
|
+
setExpanded(true);
|
|
985
|
+
}
|
|
986
|
+
function collapsePanel() {
|
|
987
|
+
forcedOpen = false;
|
|
988
|
+
setCollapsed(true);
|
|
989
|
+
setExpanded(false);
|
|
990
|
+
}
|
|
991
|
+
function openTransparencyPanel() {
|
|
992
|
+
if (typeof document === "undefined") return;
|
|
993
|
+
forcedOpen = true;
|
|
994
|
+
const state = lastState ?? {
|
|
995
|
+
isPersonalized: false,
|
|
996
|
+
isOptedOut: readCookies().has(COOKIE_NAMES.OPTOUT),
|
|
997
|
+
hasMatchAvailable: false
|
|
998
|
+
};
|
|
999
|
+
renderTransparencyPanel(state);
|
|
1000
|
+
currentSurface()?.panel.querySelector(".kywi-transparency-collapse")?.focus();
|
|
924
1001
|
}
|
|
925
1002
|
|
|
926
1003
|
// src/audience/preview.ts
|
|
@@ -942,7 +1019,7 @@ var Kywi = (() => {
|
|
|
942
1019
|
deleteCookie(COOKIE_NAMES.PREVIEW_INIT);
|
|
943
1020
|
}
|
|
944
1021
|
}
|
|
945
|
-
function renderPreviewBanner(
|
|
1022
|
+
function renderPreviewBanner(audienceName2) {
|
|
946
1023
|
document.querySelector(".kywi-preview-banner")?.remove();
|
|
947
1024
|
const banner = document.createElement("div");
|
|
948
1025
|
banner.className = "kywi-preview-banner";
|
|
@@ -951,7 +1028,7 @@ var Kywi = (() => {
|
|
|
951
1028
|
text.className = "kywi-preview-text";
|
|
952
1029
|
text.textContent = "Previewing as: ";
|
|
953
1030
|
const strong = document.createElement("strong");
|
|
954
|
-
strong.textContent =
|
|
1031
|
+
strong.textContent = audienceName2;
|
|
955
1032
|
text.appendChild(strong);
|
|
956
1033
|
banner.appendChild(text);
|
|
957
1034
|
const exitBtn = document.createElement("button");
|
|
@@ -1045,13 +1122,50 @@ var Kywi = (() => {
|
|
|
1045
1122
|
// src/audience/selfid-widget.ts
|
|
1046
1123
|
var SHOWN_KEY = "kywi_selfid_shown";
|
|
1047
1124
|
var MS_PER_DAY = 864e5;
|
|
1048
|
-
var DISMISSIBLE_MODES = /* @__PURE__ */ new Set(["hello_bar_top", "hello_bar_bottom", "drawer"]);
|
|
1125
|
+
var DISMISSIBLE_MODES = /* @__PURE__ */ new Set(["modal", "slide-in", "hello_bar_top", "hello_bar_bottom", "drawer"]);
|
|
1126
|
+
var OVERLAY_MODES = DISMISSIBLE_MODES;
|
|
1127
|
+
var SELFID_MOUNT_SELECTOR = "[data-kywi-selfid-mount]";
|
|
1128
|
+
var HELLO_BAR_TOP_CLASS = "kywi-selfid-hello-bar-top-open";
|
|
1129
|
+
var HELLO_BAR_HEIGHT_PROPERTY = "--kywi-selfid-hello-bar-height";
|
|
1130
|
+
var MODAL_LABEL_ID = "kywi-selfid-headline";
|
|
1131
|
+
function resolveSelfIdMount(displayMode) {
|
|
1132
|
+
if (typeof document === "undefined") return null;
|
|
1133
|
+
if (displayMode && displayMode !== "inline") return null;
|
|
1134
|
+
return document.querySelector(SELFID_MOUNT_SELECTOR);
|
|
1135
|
+
}
|
|
1049
1136
|
function normalizeOption(opt) {
|
|
1050
1137
|
return typeof opt === "string" ? { value: opt, label: opt } : opt;
|
|
1051
1138
|
}
|
|
1139
|
+
var FOCUSABLE_SELECTOR = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
1140
|
+
function focusableElements(root) {
|
|
1141
|
+
return Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => !el.hidden);
|
|
1142
|
+
}
|
|
1143
|
+
function trapTab(root, e) {
|
|
1144
|
+
const focusable = focusableElements(root);
|
|
1145
|
+
if (focusable.length === 0) {
|
|
1146
|
+
e.preventDefault();
|
|
1147
|
+
root.focus();
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
const first = focusable[0];
|
|
1151
|
+
const last = focusable[focusable.length - 1];
|
|
1152
|
+
const active = document.activeElement;
|
|
1153
|
+
const inside = active !== null && root.contains(active) && active !== root;
|
|
1154
|
+
if (e.shiftKey) {
|
|
1155
|
+
if (!inside || active === first) {
|
|
1156
|
+
e.preventDefault();
|
|
1157
|
+
last.focus();
|
|
1158
|
+
}
|
|
1159
|
+
} else if (!inside || active === last) {
|
|
1160
|
+
e.preventDefault();
|
|
1161
|
+
first.focus();
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1052
1164
|
function createSelfIdWidget(config) {
|
|
1053
1165
|
const frequency = config.frequency ?? "once";
|
|
1054
1166
|
const displayMode = config.displayMode ?? "inline";
|
|
1167
|
+
const isModal = displayMode === "modal";
|
|
1168
|
+
let cleanups = [];
|
|
1055
1169
|
function shouldShow() {
|
|
1056
1170
|
if (frequency === "always") return true;
|
|
1057
1171
|
if (frequency === "session") return false;
|
|
@@ -1072,26 +1186,86 @@ var Kywi = (() => {
|
|
|
1072
1186
|
markShown();
|
|
1073
1187
|
}
|
|
1074
1188
|
}
|
|
1189
|
+
function teardown(container) {
|
|
1190
|
+
for (const undo of cleanups) undo();
|
|
1191
|
+
cleanups = [];
|
|
1192
|
+
container.replaceChildren();
|
|
1193
|
+
container.className = "";
|
|
1194
|
+
container.removeAttribute("role");
|
|
1195
|
+
container.removeAttribute("aria-modal");
|
|
1196
|
+
container.removeAttribute("aria-label");
|
|
1197
|
+
container.removeAttribute("aria-labelledby");
|
|
1198
|
+
container.removeAttribute("tabindex");
|
|
1199
|
+
container.hidden = true;
|
|
1200
|
+
}
|
|
1201
|
+
function dismiss(container) {
|
|
1202
|
+
markShown();
|
|
1203
|
+
teardown(container);
|
|
1204
|
+
}
|
|
1075
1205
|
function render(container) {
|
|
1206
|
+
for (const undo of cleanups) undo();
|
|
1207
|
+
cleanups = [];
|
|
1076
1208
|
container.replaceChildren();
|
|
1209
|
+
container.hidden = false;
|
|
1077
1210
|
const displayModeClass = displayMode.replace(/_/g, "-");
|
|
1078
1211
|
container.className = `kywi-selfid-widget kywi-selfid-${displayModeClass}`;
|
|
1212
|
+
if (isModal) {
|
|
1213
|
+
const backdrop = document.createElement("div");
|
|
1214
|
+
backdrop.className = "kywi-selfid-backdrop";
|
|
1215
|
+
backdrop.addEventListener("click", () => dismiss(container));
|
|
1216
|
+
(container.parentElement ?? document.body).appendChild(backdrop);
|
|
1217
|
+
cleanups.push(() => backdrop.remove());
|
|
1218
|
+
const previousOverflow = document.body.style.overflow;
|
|
1219
|
+
document.body.style.overflow = "hidden";
|
|
1220
|
+
cleanups.push(() => {
|
|
1221
|
+
document.body.style.overflow = previousOverflow;
|
|
1222
|
+
});
|
|
1223
|
+
const onKeydown = (e) => {
|
|
1224
|
+
if (e.key === "Escape") {
|
|
1225
|
+
e.preventDefault();
|
|
1226
|
+
dismiss(container);
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
if (e.key === "Tab") trapTab(container, e);
|
|
1230
|
+
};
|
|
1231
|
+
document.addEventListener("keydown", onKeydown, true);
|
|
1232
|
+
cleanups.push(() => document.removeEventListener("keydown", onKeydown, true));
|
|
1233
|
+
container.setAttribute("role", "dialog");
|
|
1234
|
+
container.setAttribute("aria-modal", "true");
|
|
1235
|
+
if (config.headline) {
|
|
1236
|
+
container.setAttribute("aria-labelledby", MODAL_LABEL_ID);
|
|
1237
|
+
} else {
|
|
1238
|
+
container.setAttribute("aria-label", "Personalize your experience");
|
|
1239
|
+
}
|
|
1240
|
+
container.tabIndex = -1;
|
|
1241
|
+
}
|
|
1242
|
+
if (displayMode === "hello_bar_top") {
|
|
1243
|
+
document.body.classList.add(HELLO_BAR_TOP_CLASS);
|
|
1244
|
+
const syncHeight = () => {
|
|
1245
|
+
document.documentElement.style.setProperty(HELLO_BAR_HEIGHT_PROPERTY, `${container.offsetHeight}px`);
|
|
1246
|
+
};
|
|
1247
|
+
window.addEventListener("resize", syncHeight);
|
|
1248
|
+
cleanups.push(() => {
|
|
1249
|
+
document.body.classList.remove(HELLO_BAR_TOP_CLASS);
|
|
1250
|
+
document.documentElement.style.removeProperty(HELLO_BAR_HEIGHT_PROPERTY);
|
|
1251
|
+
window.removeEventListener("resize", syncHeight);
|
|
1252
|
+
});
|
|
1253
|
+
queueMicrotask(syncHeight);
|
|
1254
|
+
}
|
|
1079
1255
|
if (DISMISSIBLE_MODES.has(displayMode)) {
|
|
1080
1256
|
const dismissBtn = document.createElement("button");
|
|
1081
1257
|
dismissBtn.type = "button";
|
|
1082
1258
|
dismissBtn.className = "kywi-selfid-dismiss";
|
|
1083
1259
|
dismissBtn.setAttribute("aria-label", "Dismiss");
|
|
1084
1260
|
dismissBtn.textContent = "\xD7";
|
|
1085
|
-
dismissBtn.addEventListener("click", () =>
|
|
1086
|
-
markShown();
|
|
1087
|
-
container.replaceChildren();
|
|
1088
|
-
});
|
|
1261
|
+
dismissBtn.addEventListener("click", () => dismiss(container));
|
|
1089
1262
|
container.appendChild(dismissBtn);
|
|
1090
1263
|
}
|
|
1091
1264
|
if (config.headline) {
|
|
1092
1265
|
const h = document.createElement("h3");
|
|
1093
1266
|
h.className = "kywi-selfid-headline";
|
|
1094
1267
|
h.textContent = config.headline;
|
|
1268
|
+
if (isModal) h.id = MODAL_LABEL_ID;
|
|
1095
1269
|
container.appendChild(h);
|
|
1096
1270
|
}
|
|
1097
1271
|
if (config.subheadline) {
|
|
@@ -1152,10 +1326,7 @@ var Kywi = (() => {
|
|
|
1152
1326
|
skipBtn.type = "button";
|
|
1153
1327
|
skipBtn.className = "kywi-selfid-skip";
|
|
1154
1328
|
skipBtn.textContent = config.skipLabel;
|
|
1155
|
-
skipBtn.addEventListener("click", () =>
|
|
1156
|
-
markShown();
|
|
1157
|
-
container.replaceChildren();
|
|
1158
|
-
});
|
|
1329
|
+
skipBtn.addEventListener("click", () => dismiss(container));
|
|
1159
1330
|
actions.appendChild(skipBtn);
|
|
1160
1331
|
}
|
|
1161
1332
|
form.appendChild(actions);
|
|
@@ -1167,8 +1338,10 @@ var Kywi = (() => {
|
|
|
1167
1338
|
responses[key] = String(value);
|
|
1168
1339
|
});
|
|
1169
1340
|
handleSubmit(responses);
|
|
1341
|
+
if (OVERLAY_MODES.has(displayMode)) teardown(container);
|
|
1170
1342
|
});
|
|
1171
1343
|
container.appendChild(form);
|
|
1344
|
+
if (isModal) container.focus();
|
|
1172
1345
|
}
|
|
1173
1346
|
return { shouldShow, markShown, render, handleSubmit };
|
|
1174
1347
|
}
|
|
@@ -1177,39 +1350,30 @@ var Kywi = (() => {
|
|
|
1177
1350
|
function isOptedOut() {
|
|
1178
1351
|
return readCookies().has(COOKIE_NAMES.OPTOUT);
|
|
1179
1352
|
}
|
|
1180
|
-
function
|
|
1181
|
-
const btn = document.createElement("button");
|
|
1182
|
-
btn.className = className;
|
|
1183
|
-
btn.type = "button";
|
|
1184
|
-
btn.textContent = text;
|
|
1185
|
-
btn.addEventListener("click", onClick);
|
|
1186
|
-
return btn;
|
|
1187
|
-
}
|
|
1188
|
-
function renderPersonalizationBadge() {
|
|
1353
|
+
function renderPersonalizationBadge(info = {}) {
|
|
1189
1354
|
const containers = document.querySelectorAll("[data-kywi-personalization-badge]");
|
|
1190
1355
|
if (containers.length === 0) return;
|
|
1356
|
+
const optedOut = isOptedOut();
|
|
1191
1357
|
for (const container of Array.from(containers)) {
|
|
1192
|
-
|
|
1193
|
-
if (existing) existing.remove();
|
|
1358
|
+
container.querySelector(".kywi-personalization-badge")?.remove();
|
|
1194
1359
|
const badge = document.createElement("div");
|
|
1195
1360
|
badge.className = "kywi-personalization-badge";
|
|
1196
|
-
const
|
|
1361
|
+
const trigger = document.createElement("button");
|
|
1362
|
+
trigger.type = "button";
|
|
1363
|
+
trigger.className = "kywi-badge-open";
|
|
1364
|
+
trigger.setAttribute("aria-label", "Personalization details");
|
|
1197
1365
|
const status = document.createElement("span");
|
|
1198
1366
|
status.className = "kywi-badge-status";
|
|
1199
|
-
status.textContent = optedOut ? "Personalization
|
|
1200
|
-
|
|
1201
|
-
if (optedOut) {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
} else {
|
|
1207
|
-
badge.appendChild(createButton2("kywi-badge-opt-out", "Opt out", () => {
|
|
1208
|
-
setCookie(COOKIE_NAMES.OPTOUT, "1", 365 * 24 * 3600);
|
|
1209
|
-
deleteCookie(COOKIE_NAMES.AUDIENCE);
|
|
1210
|
-
renderPersonalizationBadge();
|
|
1211
|
-
}));
|
|
1367
|
+
status.textContent = optedOut ? "Personalization off" : "Personalization active";
|
|
1368
|
+
trigger.appendChild(status);
|
|
1369
|
+
if (!optedOut && info.audienceName) {
|
|
1370
|
+
const audience = document.createElement("span");
|
|
1371
|
+
audience.className = "kywi-badge-audience";
|
|
1372
|
+
audience.textContent = info.audienceName;
|
|
1373
|
+
trigger.appendChild(audience);
|
|
1212
1374
|
}
|
|
1375
|
+
trigger.addEventListener("click", () => openTransparencyPanel());
|
|
1376
|
+
badge.appendChild(trigger);
|
|
1213
1377
|
container.appendChild(badge);
|
|
1214
1378
|
}
|
|
1215
1379
|
}
|
|
@@ -1227,10 +1391,12 @@ var Kywi = (() => {
|
|
|
1227
1391
|
checkPreviewInit();
|
|
1228
1392
|
const serverMeta = readServerMeta();
|
|
1229
1393
|
const previewId = getPreviewAudienceId();
|
|
1394
|
+
const activeAudiences = audiences.filter((a) => a.status === "active").map((a) => ({ id: a.id, name: a.name }));
|
|
1395
|
+
const nameOf = (id) => id ? activeAudiences.find((a) => a.id === id)?.name ?? null : null;
|
|
1230
1396
|
if (previewId) {
|
|
1231
1397
|
patchVariantContainers(previewId);
|
|
1232
|
-
renderPreviewBanner(previewId);
|
|
1233
|
-
renderPersonalizationBadge();
|
|
1398
|
+
renderPreviewBanner(nameOf(previewId) ?? previewId);
|
|
1399
|
+
renderPersonalizationBadge({ audienceName: nameOf(previewId) });
|
|
1234
1400
|
populateDataLayer({ visitorId: serverMeta.visitorId, audienceId: previewId, experimentId: null, variantId: null, isPreview: true, isOptedOut: false });
|
|
1235
1401
|
fireKywiReady();
|
|
1236
1402
|
return;
|
|
@@ -1246,12 +1412,24 @@ var Kywi = (() => {
|
|
|
1246
1412
|
merged.adapters = { ...merged.adapters, ...adapterResults };
|
|
1247
1413
|
}
|
|
1248
1414
|
const result = evaluateClientSide(audiences, merged, currentPath, pageCategory);
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1415
|
+
const pinnedId = cookies.get(COOKIE_NAMES.AUDIENCE) ?? null;
|
|
1416
|
+
const isPinned = !result.isOptedOut && pinnedId !== null && activeAudiences.some((a) => a.id === pinnedId);
|
|
1417
|
+
const effectiveAudienceId = isPinned ? pinnedId : result.winningAudienceId;
|
|
1418
|
+
patchVariantContainers(effectiveAudienceId);
|
|
1419
|
+
renderTransparencyPanel({
|
|
1420
|
+
isPersonalized: effectiveAudienceId !== null,
|
|
1421
|
+
isOptedOut: result.isOptedOut,
|
|
1422
|
+
hasMatchAvailable: result.allMatchedIds.length > 0,
|
|
1423
|
+
audienceId: effectiveAudienceId,
|
|
1424
|
+
audiences: activeAudiences,
|
|
1425
|
+
apiBase,
|
|
1426
|
+
csrfToken: Kywi.context?.csrfToken ?? null,
|
|
1427
|
+
enabled: config.transparencyNotice !== false
|
|
1428
|
+
});
|
|
1429
|
+
renderPersonalizationBadge({ audienceName: nameOf(effectiveAudienceId) });
|
|
1430
|
+
populateDataLayer({ visitorId: result.signals.identity.visitorId, audienceId: effectiveAudienceId, experimentId: serverMeta.experimentId, variantId: serverMeta.variantId, isPreview: false, isOptedOut: result.isOptedOut });
|
|
1253
1431
|
fireKywiReady();
|
|
1254
|
-
if (result.winningAudienceId && result.winningAudienceId !== serverMeta.audienceId) {
|
|
1432
|
+
if (!isPinned && result.winningAudienceId && result.winningAudienceId !== serverMeta.audienceId) {
|
|
1255
1433
|
fetch(`${apiBase}/kywi/audience`, {
|
|
1256
1434
|
method: "POST",
|
|
1257
1435
|
headers: { "Content-Type": "application/json", ...Kywi.context?.csrfToken ? { "X-CSRF-Token": Kywi.context.csrfToken } : {} },
|
|
@@ -1263,6 +1441,11 @@ var Kywi = (() => {
|
|
|
1263
1441
|
const widget = createSelfIdWidget(config.selfIdWidget);
|
|
1264
1442
|
if (widget.shouldShow()) {
|
|
1265
1443
|
const doRender = () => {
|
|
1444
|
+
const mount = resolveSelfIdMount(config.selfIdWidget.displayMode);
|
|
1445
|
+
if (mount) {
|
|
1446
|
+
widget.render(mount);
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1266
1449
|
const existing = document.getElementById("kywi-selfid-container");
|
|
1267
1450
|
const container = existing ?? document.createElement("div");
|
|
1268
1451
|
if (!container.id) {
|