@widelab-nc/widelab 1.1.35 → 1.1.36
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/index.js +2 -2
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
- package/src/index.js +248 -113
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -260,6 +260,39 @@ if (infoBar) {
|
|
|
260
260
|
});
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
// Safe new version that uses the same classes and structure
|
|
266
|
+
window.customCursorTextUpdate = function(el) {
|
|
267
|
+
const cursorEl = document.querySelector('.custom_cursor_text');
|
|
268
|
+
if (!cursorEl) return;
|
|
269
|
+
|
|
270
|
+
const defaultText = cursorEl.dataset.default || '';
|
|
271
|
+
|
|
272
|
+
const getText = (node) =>
|
|
273
|
+
node.getAttribute('widecursortext') || node.getAttribute('wideCursorText') || '';
|
|
274
|
+
|
|
275
|
+
const activate = () => {
|
|
276
|
+
cursorEl.textContent = getText(el);
|
|
277
|
+
document.documentElement.classList.add('has-custom-cursor');
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const maybeDeactivate = (ev) => {
|
|
281
|
+
const to = ev.relatedTarget;
|
|
282
|
+
// Don't deactivate if moving between elements that also use the custom cursor
|
|
283
|
+
if (to && to.closest && to.closest('[wide="customCursor"]')) return;
|
|
284
|
+
|
|
285
|
+
cursorEl.textContent = defaultText;
|
|
286
|
+
document.documentElement.classList.remove('has-custom-cursor');
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
el.addEventListener('pointerenter', activate);
|
|
290
|
+
el.addEventListener('pointerleave', maybeDeactivate);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
263
296
|
// Hero animation on load
|
|
264
297
|
window.heroAnimationOnLoad = function(e) {
|
|
265
298
|
const videoMask = e.querySelector('.hero_headline_ract-mask');
|
|
@@ -635,11 +668,11 @@ window.partnerSlider = function (e) {
|
|
|
635
668
|
// Initialize Splide with drag restored
|
|
636
669
|
const splide = new Splide(e, {
|
|
637
670
|
type: 'slide',
|
|
638
|
-
fixedWidth: '
|
|
671
|
+
fixedWidth: '31.5%',
|
|
639
672
|
padding: { left: '2.5rem', right: 0 },
|
|
640
673
|
focus: 0,
|
|
641
674
|
gap: 0,
|
|
642
|
-
pagination:
|
|
675
|
+
pagination: false,
|
|
643
676
|
omitEnd: true,
|
|
644
677
|
arrows: false,
|
|
645
678
|
drag: true, // restored dragging
|
|
@@ -1013,149 +1046,251 @@ window.badgeColorChange = function(e) {
|
|
|
1013
1046
|
});
|
|
1014
1047
|
}
|
|
1015
1048
|
|
|
1049
|
+
// Single entry you call from x-init
|
|
1050
|
+
window.clientFilters = function () {
|
|
1051
|
+
// Prevent double initialization if Alpine re-mounts
|
|
1052
|
+
if (window.__clientFiltersInitialized) return;
|
|
1053
|
+
window.__clientFiltersInitialized = true;
|
|
1054
|
+
|
|
1055
|
+
// --- Config ---
|
|
1056
|
+
const FIELDS = ["industry", "country", "services"];
|
|
1057
|
+
const SEL = {
|
|
1058
|
+
dropdown: '[fs-cmsfilter-element="dropdown"]',
|
|
1059
|
+
dropdownHolder: ".dropdown-holder",
|
|
1060
|
+
dropdownTitle: '[filters-clients_dropdown-field="tag-name-title"], .filters-clients_dropdown-field',
|
|
1061
|
+
tagNumber: '[fs-cmsfilter-element="tag-number"]',
|
|
1062
|
+
dynItem: ".w-dyn-item",
|
|
1063
|
+
checkboxWrap: ".w-checkbox",
|
|
1064
|
+
checkboxInput: 'input[type="checkbox"]',
|
|
1065
|
+
webflowChecked: "w--redirected-checked",
|
|
1066
|
+
fsActive: "fs-cmsfilter_active",
|
|
1067
|
+
listContainer: ".collection-list",
|
|
1068
|
+
count: ".count",
|
|
1069
|
+
crossIcon: ".cross-icon",
|
|
1070
|
+
clearButton: ".clear-button",
|
|
1071
|
+
activeDropdown: "active-dropdown",
|
|
1072
|
+
};
|
|
1016
1073
|
|
|
1017
|
-
//
|
|
1018
|
-
|
|
1019
|
-
function
|
|
1074
|
+
// --- Utils ---
|
|
1075
|
+
function $(s, ctx = document) { return ctx.querySelector(s); }
|
|
1076
|
+
function $all(s, ctx = document) { return Array.from(ctx.querySelectorAll(s)); }
|
|
1077
|
+
function debounce(fn, ms) { let t; return (...a)=>{clearTimeout(t); t=setTimeout(()=>fn(...a), ms);} }
|
|
1078
|
+
function textClean(el){ return (el?.textContent || "").trim(); }
|
|
1079
|
+
|
|
1080
|
+
// Wait until Finsweet cmsfilter is ready (query params applied, classes toggled)
|
|
1081
|
+
function waitForCmsFilter() {
|
|
1082
|
+
return new Promise((resolve) => {
|
|
1083
|
+
const w = window;
|
|
1084
|
+
w.fsAttributes = w.fsAttributes || [];
|
|
1085
|
+
// already initialized?
|
|
1086
|
+
try {
|
|
1087
|
+
const exists = (w.fsAttributes.instances || []).some(i => i.name === "cmsfilter");
|
|
1088
|
+
if (exists) return resolve();
|
|
1089
|
+
} catch(e){}
|
|
1090
|
+
w.fsAttributes.push(["cmsfilter", function(){ resolve(); }]);
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1020
1093
|
|
|
1021
|
-
|
|
1094
|
+
// Remember original texts for labels and toggles
|
|
1095
|
+
function hydrateOriginalTexts() {
|
|
1096
|
+
FIELDS.forEach((field) => {
|
|
1097
|
+
$all(`span[fs-cmsfilter-field="${field}"]`).forEach((span) => {
|
|
1098
|
+
if (!span.dataset.originalText) {
|
|
1099
|
+
span.dataset.originalText = textClean(span);
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
});
|
|
1103
|
+
$all(SEL.dropdownHolder).forEach((holder) => {
|
|
1104
|
+
const toggle = $(SEL.dropdownTitle, holder);
|
|
1105
|
+
if (toggle && !toggle.dataset.defaultText) {
|
|
1106
|
+
toggle.dataset.defaultText = textClean(toggle);
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1022
1110
|
|
|
1023
|
-
|
|
1111
|
+
// Count matching results per value and sort UI items by count
|
|
1112
|
+
function updateCheckboxCounts(excludeField = null) {
|
|
1113
|
+
FIELDS.forEach((field) => {
|
|
1024
1114
|
if (field === excludeField) return;
|
|
1025
1115
|
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
checkbox.dataset.originalText = originalText;
|
|
1031
|
-
|
|
1032
|
-
let matchedCount = 0;
|
|
1116
|
+
const spans = $all(`span[fs-cmsfilter-field="${field}"]`);
|
|
1117
|
+
spans.forEach((span) => {
|
|
1118
|
+
const originalText = span.dataset.originalText || textClean(span);
|
|
1119
|
+
span.dataset.originalText = originalText;
|
|
1033
1120
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1121
|
+
// Count visible matches in results
|
|
1122
|
+
let matched = 0;
|
|
1123
|
+
$all(`div[fs-cmsfilter-field="${field}"]`).forEach((div) => {
|
|
1124
|
+
if (textClean(div) === originalText) matched++;
|
|
1038
1125
|
});
|
|
1039
1126
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
countSpan
|
|
1044
|
-
|
|
1127
|
+
// Ensure "(n)" bubble
|
|
1128
|
+
let countSpan = span.nextElementSibling;
|
|
1129
|
+
if (!countSpan || !countSpan.classList.contains("count")) {
|
|
1130
|
+
countSpan = document.createElement("span");
|
|
1131
|
+
countSpan.className = "count";
|
|
1132
|
+
span.insertAdjacentElement("afterend", countSpan);
|
|
1045
1133
|
}
|
|
1046
|
-
countSpan.textContent = `(${
|
|
1134
|
+
countSpan.textContent = `(${matched})`;
|
|
1047
1135
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
} else {
|
|
1052
|
-
parentDiv.classList.remove('empty-category');
|
|
1053
|
-
}
|
|
1136
|
+
// Toggle empty-category on dyn item
|
|
1137
|
+
const dyn = span.closest(SEL.dynItem);
|
|
1138
|
+
if (dyn) dyn.classList.toggle("empty-category", matched === 0);
|
|
1054
1139
|
});
|
|
1055
1140
|
|
|
1056
|
-
|
|
1141
|
+
// Sort items in this field's collection-list by count desc
|
|
1142
|
+
const anySpan = document.querySelector(`span[fs-cmsfilter-field="${field}"]`);
|
|
1143
|
+
if (anySpan) {
|
|
1144
|
+
const list = anySpan.closest(SEL.listContainer);
|
|
1145
|
+
if (list) {
|
|
1146
|
+
const items = Array.from(list.children);
|
|
1147
|
+
items.sort((a, b) => {
|
|
1148
|
+
const ca = parseInt((a.querySelector(SEL.count)?.textContent || "").replace(/[()]/g, "")) || 0;
|
|
1149
|
+
const cb = parseInt((b.querySelector(SEL.count)?.textContent || "").replace(/[()]/g, "")) || 0;
|
|
1150
|
+
return cb - ca;
|
|
1151
|
+
});
|
|
1152
|
+
items.forEach((it) => list.appendChild(it));
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1057
1155
|
});
|
|
1058
1156
|
}
|
|
1059
1157
|
|
|
1060
|
-
|
|
1158
|
+
// Collect selected label names within a dropdown holder
|
|
1159
|
+
function getSelectedNames(holder) {
|
|
1160
|
+
const labels = $all(`${SEL.dynItem} ${SEL.checkboxWrap}, ${SEL.checkboxWrap}`, holder);
|
|
1161
|
+
return labels
|
|
1162
|
+
.filter((label) => {
|
|
1163
|
+
if (label.classList.contains(SEL.fsActive)) return true; // Finsweet
|
|
1164
|
+
const wbox = label.querySelector(".w-checkbox-input");
|
|
1165
|
+
if (wbox && wbox.classList.contains(SEL.webflowChecked)) return true; // Webflow styled
|
|
1166
|
+
const input = label.querySelector(SEL.checkboxInput);
|
|
1167
|
+
return !!(input && input.checked); // Native
|
|
1168
|
+
})
|
|
1169
|
+
.map((label) => {
|
|
1170
|
+
const span = label.querySelector('[fs-cmsfilter-field]');
|
|
1171
|
+
return (span && (span.getAttribute('data-original-text') || textClean(span))) || "";
|
|
1172
|
+
})
|
|
1173
|
+
.filter(Boolean);
|
|
1174
|
+
}
|
|
1061
1175
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1176
|
+
// Update dropdown button label (first selected + +N), class states and numeric tag
|
|
1177
|
+
function updateDropdownTitle(holder) {
|
|
1178
|
+
const toggle = $(SEL.dropdownTitle, holder);
|
|
1179
|
+
if (!toggle) return;
|
|
1180
|
+
|
|
1181
|
+
const defaults = toggle.dataset.defaultText || textClean(toggle);
|
|
1182
|
+
const names = getSelectedNames(holder);
|
|
1183
|
+
|
|
1184
|
+
// Persist "first selected" per dropdown (dataset)
|
|
1185
|
+
if (!holder.dataset.firstSelected) {
|
|
1186
|
+
holder.dataset.firstSelected = names[0] || "";
|
|
1187
|
+
} else if (names.length && !names.includes(holder.dataset.firstSelected)) {
|
|
1188
|
+
holder.dataset.firstSelected = names[0];
|
|
1189
|
+
} else if (!names.length) {
|
|
1190
|
+
holder.dataset.firstSelected = "";
|
|
1191
|
+
}
|
|
1064
1192
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1193
|
+
let display = defaults;
|
|
1194
|
+
if (names.length === 1) {
|
|
1195
|
+
display = names[0];
|
|
1196
|
+
} else if (names.length > 1) {
|
|
1197
|
+
display = `${holder.dataset.firstSelected || names[0]} +${names.length - 1}`;
|
|
1198
|
+
}
|
|
1199
|
+
toggle.textContent = display;
|
|
1070
1200
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1201
|
+
holder.classList.toggle(SEL.activeDropdown, names.length > 0);
|
|
1202
|
+
toggle.classList.toggle(SEL.activeDropdown, names.length > 0);
|
|
1203
|
+
|
|
1204
|
+
const tagNum = $(SEL.tagNumber, holder);
|
|
1205
|
+
if (tagNum) {
|
|
1206
|
+
tagNum.textContent = names.length > 1 ? `+${names.length - 1}` : "";
|
|
1207
|
+
}
|
|
1074
1208
|
}
|
|
1075
1209
|
|
|
1076
|
-
|
|
1210
|
+
function updateAllDropdownTitles() {
|
|
1211
|
+
$all(SEL.dropdownHolder).forEach(updateDropdownTitle);
|
|
1212
|
+
}
|
|
1077
1213
|
|
|
1078
|
-
function
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1214
|
+
function toggleClearButton() {
|
|
1215
|
+
const anyActive = !!$(`.${SEL.activeDropdown}`);
|
|
1216
|
+
const btn = $(SEL.clearButton);
|
|
1217
|
+
if (btn) btn.style.display = anyActive ? "block" : "none";
|
|
1082
1218
|
}
|
|
1083
1219
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1220
|
+
// Unified UI refresh
|
|
1221
|
+
const refreshUI = debounce(function (excludeField = null) {
|
|
1222
|
+
updateCheckboxCounts(excludeField);
|
|
1223
|
+
updateAllDropdownTitles();
|
|
1224
|
+
toggleClearButton();
|
|
1225
|
+
}, 60);
|
|
1226
|
+
|
|
1227
|
+
// --- Event wiring (delegated & once) ---
|
|
1228
|
+
function bindEventsOnce() {
|
|
1229
|
+
// Clicks: checkbox labels, dropdown toggles, cross icons, clear button
|
|
1230
|
+
document.addEventListener("click", (e) => {
|
|
1231
|
+
const label = e.target.closest(SEL.checkboxWrap);
|
|
1232
|
+
if (label) {
|
|
1233
|
+
// Let Finsweet/Webflow toggle classes first
|
|
1234
|
+
setTimeout(() => {
|
|
1235
|
+
const span = label.querySelector('[fs-cmsfilter-field]');
|
|
1236
|
+
const field = span?.getAttribute('fs-cmsfilter-field') || null;
|
|
1237
|
+
refreshUI(field);
|
|
1238
|
+
}, 0);
|
|
1239
|
+
return;
|
|
1090
1240
|
}
|
|
1091
|
-
});
|
|
1092
|
-
});
|
|
1093
1241
|
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1242
|
+
const toggle = e.target.closest(SEL.dropdownTitle);
|
|
1243
|
+
if (toggle) {
|
|
1244
|
+
// After opening/closing or instant selections
|
|
1245
|
+
setTimeout(() => refreshUI(null), 120);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
const cross = e.target.closest(SEL.crossIcon);
|
|
1250
|
+
if (cross) {
|
|
1251
|
+
setTimeout(() => refreshUI(null), 80);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
const clear = e.target.closest(SEL.clearButton);
|
|
1256
|
+
if (clear) {
|
|
1257
|
+
setTimeout(() => refreshUI(null), 120);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1097
1260
|
});
|
|
1098
|
-
});
|
|
1099
1261
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1262
|
+
// Any checkbox state change (keyboard/tap)
|
|
1263
|
+
document.addEventListener("change", (e) => {
|
|
1264
|
+
if (e.target && e.target.matches(SEL.checkboxInput)) {
|
|
1265
|
+
const span = e.target.closest('label')?.querySelector('[fs-cmsfilter-field]') ||
|
|
1266
|
+
e.target.closest(SEL.checkboxWrap)?.querySelector('[fs-cmsfilter-field]');
|
|
1267
|
+
const field = span?.getAttribute('fs-cmsfilter-field') || null;
|
|
1268
|
+
const holder = e.target.closest(SEL.dropdownHolder);
|
|
1269
|
+
if (holder) updateDropdownTitle(holder);
|
|
1270
|
+
refreshUI(field);
|
|
1271
|
+
}
|
|
1103
1272
|
});
|
|
1104
|
-
});
|
|
1105
1273
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1274
|
+
// Observe class mutations that might come from Finsweet
|
|
1275
|
+
const mo = new MutationObserver((muts) => {
|
|
1276
|
+
// If any class changed, softly refresh
|
|
1277
|
+
if (muts.some(m => m.type === "attributes" && m.attributeName === "class")) {
|
|
1278
|
+
refreshUI(null);
|
|
1279
|
+
}
|
|
1280
|
+
});
|
|
1281
|
+
mo.observe(document.body, { subtree: true, attributes: true, attributeFilter: ["class"] });
|
|
1112
1282
|
}
|
|
1113
|
-
toggleClearButton();
|
|
1114
|
-
const observer = new MutationObserver(toggleClearButton);
|
|
1115
|
-
|
|
1116
|
-
observer.observe(document.body, {
|
|
1117
|
-
childList: true,
|
|
1118
|
-
subtree: true,
|
|
1119
|
-
attributes: true,
|
|
1120
|
-
attributeFilter: ['class']
|
|
1121
|
-
});
|
|
1122
1283
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
const dropdownHolder = dropdown.closest('.dropdown-holder');
|
|
1132
|
-
const tagNumber = dropdown.querySelector('[fs-cmsfilter-element="tag-number"]');
|
|
1133
|
-
|
|
1134
|
-
if (checkedBoxes.length > 0) {
|
|
1135
|
-
// Check if firstCheckedText is still checked
|
|
1136
|
-
if (![...checkedBoxes].some(cb => cb.nextElementSibling.textContent.split('(')[0].trim() === firstCheckedText)) {
|
|
1137
|
-
// If not, set it to the current checkbox text without the count
|
|
1138
|
-
firstCheckedText = checkedBoxes[0].nextElementSibling.textContent.split('(')[0].trim();
|
|
1139
|
-
}
|
|
1140
|
-
titleField.textContent = firstCheckedText;
|
|
1141
|
-
dropdownHolder.classList.add('active-dropdown');
|
|
1142
|
-
} else {
|
|
1143
|
-
titleField.textContent = originalText;
|
|
1144
|
-
dropdownHolder.classList.remove('active-dropdown');
|
|
1145
|
-
firstCheckedText = null;
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
if (checkedBoxes.length > 1) {
|
|
1149
|
-
const numberText = `+${checkedBoxes.length - 1}`;
|
|
1150
|
-
tagNumber.textContent = numberText;
|
|
1151
|
-
} else {
|
|
1152
|
-
tagNumber.textContent = '';
|
|
1153
|
-
}
|
|
1154
|
-
});
|
|
1155
|
-
});
|
|
1284
|
+
// --- Boot ---
|
|
1285
|
+
waitForCmsFilter().then(() => {
|
|
1286
|
+
// Allow Finsweet to finish applying query param checks/classes
|
|
1287
|
+
setTimeout(() => {
|
|
1288
|
+
hydrateOriginalTexts();
|
|
1289
|
+
bindEventsOnce();
|
|
1290
|
+
refreshUI(null);
|
|
1291
|
+
}, 120);
|
|
1156
1292
|
});
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1293
|
+
};
|
|
1159
1294
|
// Filtering logic on Client's page
|
|
1160
1295
|
window.formatCounter = function() {
|
|
1161
1296
|
function format_number(x) {
|