@lime-bundles/widget 2.4.0 → 2.5.0
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.cjs +1114 -162
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +1041 -90
- package/dist/index.js.map +1 -1
- package/dist/lime-bundle.global.js +399 -25
- package/dist/lime-bundle.global.js.map +1 -1
- package/docs/css-variables.md +3 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -21,6 +21,383 @@ import {
|
|
|
21
21
|
resolveBundleQty
|
|
22
22
|
} from "@lime-bundles/core";
|
|
23
23
|
|
|
24
|
+
// src/dropdown/bind-dropdown.ts
|
|
25
|
+
import { dropdown } from "@lime-bundles/core";
|
|
26
|
+
var { computePosition, emptyTypeAheadState, handleKey, pushTypeAheadChar } = dropdown;
|
|
27
|
+
var ITEM_HEIGHT_PX = 32;
|
|
28
|
+
var LIST_PAD_Y = 8;
|
|
29
|
+
var MAX_VISIBLE_ITEMS = 8;
|
|
30
|
+
var openInstances = [];
|
|
31
|
+
function closeOutsideEvent(event) {
|
|
32
|
+
const path = event.composedPath();
|
|
33
|
+
for (let i = openInstances.length - 1; i >= 0; i--) {
|
|
34
|
+
const inst = openInstances[i];
|
|
35
|
+
if (!path.includes(inst.shell) && !path.includes(inst.listbox)) {
|
|
36
|
+
inst.close();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function onDocResize() {
|
|
41
|
+
for (let i = openInstances.length - 1; i >= 0; i--) openInstances[i].close();
|
|
42
|
+
}
|
|
43
|
+
var docListenersAttached = false;
|
|
44
|
+
function attachDocumentListeners() {
|
|
45
|
+
if (docListenersAttached) return;
|
|
46
|
+
document.addEventListener("pointerdown", closeOutsideEvent, true);
|
|
47
|
+
window.addEventListener("scroll", closeOutsideEvent, true);
|
|
48
|
+
window.addEventListener("resize", onDocResize);
|
|
49
|
+
docListenersAttached = true;
|
|
50
|
+
}
|
|
51
|
+
function detachDocumentListeners() {
|
|
52
|
+
if (!docListenersAttached || openInstances.length > 0) return;
|
|
53
|
+
document.removeEventListener("pointerdown", closeOutsideEvent, true);
|
|
54
|
+
window.removeEventListener("scroll", closeOutsideEvent, true);
|
|
55
|
+
window.removeEventListener("resize", onDocResize);
|
|
56
|
+
docListenersAttached = false;
|
|
57
|
+
}
|
|
58
|
+
function readOptions(select) {
|
|
59
|
+
const out = [];
|
|
60
|
+
for (let i = 0; i < select.options.length; i++) {
|
|
61
|
+
const o = select.options[i];
|
|
62
|
+
out.push({ disabled: o.disabled, label: o.textContent || o.value });
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
function firstEnabled(opts) {
|
|
67
|
+
for (let i = 0; i < opts.length; i++) if (!opts[i].disabled) return i;
|
|
68
|
+
return -1;
|
|
69
|
+
}
|
|
70
|
+
var VARIANT_SELECT_CLASSES = [
|
|
71
|
+
"lb-bundle-variant-select",
|
|
72
|
+
"lb-mix-match__variant-select"
|
|
73
|
+
];
|
|
74
|
+
var BIND_SELECTOR = VARIANT_SELECT_CLASSES.map(
|
|
75
|
+
(c) => `select.${c}:not(.lb-dropdown-state)`
|
|
76
|
+
).join(", ");
|
|
77
|
+
function bindDropdown(select) {
|
|
78
|
+
const slot = select;
|
|
79
|
+
if (select.classList.contains("lb-dropdown-state")) {
|
|
80
|
+
return slot.__lbDropdownInstance ?? null;
|
|
81
|
+
}
|
|
82
|
+
const doc = select.ownerDocument;
|
|
83
|
+
const rootNode = select.getRootNode();
|
|
84
|
+
const labelText = select.getAttribute("aria-label") ?? "";
|
|
85
|
+
const idBase = `lb-dd-${Math.random().toString(36).slice(2, 9)}`;
|
|
86
|
+
select.classList.add("lb-dropdown-state");
|
|
87
|
+
select.setAttribute("aria-hidden", "true");
|
|
88
|
+
select.setAttribute("tabindex", "-1");
|
|
89
|
+
const shell = doc.createElement("div");
|
|
90
|
+
shell.className = "lb-dropdown";
|
|
91
|
+
shell.setAttribute("data-lb-dropdown", "");
|
|
92
|
+
const trigger = doc.createElement("button");
|
|
93
|
+
trigger.type = "button";
|
|
94
|
+
trigger.className = "lb-dropdown-trigger";
|
|
95
|
+
trigger.setAttribute("role", "combobox");
|
|
96
|
+
trigger.setAttribute("aria-haspopup", "listbox");
|
|
97
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
98
|
+
const listboxId = `${idBase}-listbox`;
|
|
99
|
+
trigger.setAttribute("aria-controls", listboxId);
|
|
100
|
+
if (labelText) trigger.setAttribute("aria-label", labelText);
|
|
101
|
+
const triggerLabel = doc.createElement("span");
|
|
102
|
+
triggerLabel.className = "lb-dropdown-trigger-value";
|
|
103
|
+
const chevron = doc.createElement("span");
|
|
104
|
+
chevron.className = "lb-dropdown-chevron";
|
|
105
|
+
chevron.setAttribute("aria-hidden", "true");
|
|
106
|
+
trigger.appendChild(triggerLabel);
|
|
107
|
+
trigger.appendChild(chevron);
|
|
108
|
+
const listbox = doc.createElement("ul");
|
|
109
|
+
listbox.id = listboxId;
|
|
110
|
+
listbox.className = "lb-dropdown-listbox";
|
|
111
|
+
listbox.setAttribute("role", "listbox");
|
|
112
|
+
if (labelText) listbox.setAttribute("aria-label", labelText);
|
|
113
|
+
listbox.hidden = true;
|
|
114
|
+
shell.appendChild(trigger);
|
|
115
|
+
select.parentNode?.insertBefore(shell, select.nextSibling);
|
|
116
|
+
const modalOverlay = select.closest("[data-modal-overlay]");
|
|
117
|
+
if (modalOverlay) {
|
|
118
|
+
modalOverlay.appendChild(listbox);
|
|
119
|
+
listbox.setAttribute("data-lb-dropdown-portal", "");
|
|
120
|
+
} else {
|
|
121
|
+
shell.appendChild(listbox);
|
|
122
|
+
}
|
|
123
|
+
let isOpen = false;
|
|
124
|
+
let activeIndex = -1;
|
|
125
|
+
let typeAhead = emptyTypeAheadState();
|
|
126
|
+
let optionEls = [];
|
|
127
|
+
let instance;
|
|
128
|
+
function syncFromSelect() {
|
|
129
|
+
const opts = readOptions(select);
|
|
130
|
+
const idx = select.selectedIndex;
|
|
131
|
+
triggerLabel.textContent = idx >= 0 && opts[idx] ? opts[idx].label : "";
|
|
132
|
+
while (listbox.firstChild) listbox.removeChild(listbox.firstChild);
|
|
133
|
+
optionEls = [];
|
|
134
|
+
for (let i = 0; i < opts.length; i++) {
|
|
135
|
+
const li = doc.createElement("li");
|
|
136
|
+
li.id = `${idBase}-opt-${i}`;
|
|
137
|
+
li.className = "lb-dropdown-option";
|
|
138
|
+
li.setAttribute("role", "option");
|
|
139
|
+
li.setAttribute("aria-selected", i === idx ? "true" : "false");
|
|
140
|
+
if (opts[i].disabled) li.setAttribute("aria-disabled", "true");
|
|
141
|
+
li.setAttribute("data-value", select.options[i].value);
|
|
142
|
+
li.setAttribute("data-index", String(i));
|
|
143
|
+
li.textContent = opts[i].label;
|
|
144
|
+
listbox.appendChild(li);
|
|
145
|
+
optionEls.push(li);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function setActive(newIndex) {
|
|
149
|
+
if (activeIndex >= 0 && optionEls[activeIndex]) {
|
|
150
|
+
optionEls[activeIndex].classList.remove("is-active");
|
|
151
|
+
}
|
|
152
|
+
activeIndex = newIndex;
|
|
153
|
+
if (newIndex >= 0 && optionEls[newIndex]) {
|
|
154
|
+
const li = optionEls[newIndex];
|
|
155
|
+
li.classList.add("is-active");
|
|
156
|
+
trigger.setAttribute("aria-activedescendant", li.id);
|
|
157
|
+
const liTop = li.offsetTop;
|
|
158
|
+
const liBottom = liTop + li.offsetHeight;
|
|
159
|
+
const visTop = listbox.scrollTop;
|
|
160
|
+
const visBottom = visTop + listbox.clientHeight;
|
|
161
|
+
if (liTop < visTop) {
|
|
162
|
+
listbox.scrollTop = liTop;
|
|
163
|
+
} else if (liBottom > visBottom) {
|
|
164
|
+
listbox.scrollTop = liBottom - listbox.clientHeight;
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
trigger.setAttribute("aria-activedescendant", "");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function position() {
|
|
171
|
+
const rect = trigger.getBoundingClientRect();
|
|
172
|
+
if (rect.width === 0) return false;
|
|
173
|
+
const visibleCount = Math.min(optionEls.length || 1, MAX_VISIBLE_ITEMS);
|
|
174
|
+
const desiredHeight = visibleCount * ITEM_HEIGHT_PX + LIST_PAD_Y;
|
|
175
|
+
const result = computePosition({
|
|
176
|
+
trigger: {
|
|
177
|
+
top: rect.top,
|
|
178
|
+
bottom: rect.bottom,
|
|
179
|
+
left: rect.left,
|
|
180
|
+
width: rect.width
|
|
181
|
+
},
|
|
182
|
+
viewportHeight: window.innerHeight,
|
|
183
|
+
desiredHeight
|
|
184
|
+
});
|
|
185
|
+
listbox.setAttribute("data-placement", result.placement);
|
|
186
|
+
listbox.style.maxHeight = `${result.maxHeight}px`;
|
|
187
|
+
if (listbox.hasAttribute("data-lb-dropdown-portal")) {
|
|
188
|
+
listbox.style.top = `${result.offsetTop}px`;
|
|
189
|
+
listbox.style.left = `${result.offsetLeft}px`;
|
|
190
|
+
listbox.style.width = `${result.width}px`;
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
function open() {
|
|
195
|
+
if (isOpen) return;
|
|
196
|
+
for (let i = openInstances.length - 1; i >= 0; i--) {
|
|
197
|
+
if (openInstances[i] !== instance) openInstances[i].close();
|
|
198
|
+
}
|
|
199
|
+
isOpen = true;
|
|
200
|
+
listbox.hidden = false;
|
|
201
|
+
trigger.setAttribute("aria-expanded", "true");
|
|
202
|
+
if (!position()) {
|
|
203
|
+
requestAnimationFrame(() => position());
|
|
204
|
+
}
|
|
205
|
+
const opts = readOptions(select);
|
|
206
|
+
const selIdx = select.selectedIndex;
|
|
207
|
+
if (selIdx >= 0 && opts[selIdx] && !opts[selIdx].disabled) {
|
|
208
|
+
setActive(selIdx);
|
|
209
|
+
} else {
|
|
210
|
+
setActive(firstEnabled(opts));
|
|
211
|
+
}
|
|
212
|
+
openInstances.push(instance);
|
|
213
|
+
if (openInstances.length === 1) attachDocumentListeners();
|
|
214
|
+
}
|
|
215
|
+
function close(restoreFocus) {
|
|
216
|
+
if (!isOpen) return;
|
|
217
|
+
isOpen = false;
|
|
218
|
+
listbox.hidden = true;
|
|
219
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
220
|
+
trigger.setAttribute("aria-activedescendant", "");
|
|
221
|
+
if (activeIndex >= 0 && optionEls[activeIndex]) {
|
|
222
|
+
optionEls[activeIndex].classList.remove("is-active");
|
|
223
|
+
}
|
|
224
|
+
activeIndex = -1;
|
|
225
|
+
const idx = openInstances.indexOf(instance);
|
|
226
|
+
if (idx >= 0) openInstances.splice(idx, 1);
|
|
227
|
+
if (openInstances.length === 0) detachDocumentListeners();
|
|
228
|
+
if (restoreFocus) trigger.focus();
|
|
229
|
+
}
|
|
230
|
+
function commit(index) {
|
|
231
|
+
const opt = select.options[index];
|
|
232
|
+
if (!opt || opt.disabled) return;
|
|
233
|
+
if (select.value !== opt.value) {
|
|
234
|
+
select.value = opt.value;
|
|
235
|
+
const event = new Event("change", { bubbles: true });
|
|
236
|
+
select.dispatchEvent(event);
|
|
237
|
+
}
|
|
238
|
+
syncFromSelect();
|
|
239
|
+
close(true);
|
|
240
|
+
}
|
|
241
|
+
function applyAction(action) {
|
|
242
|
+
switch (action.type) {
|
|
243
|
+
case "open":
|
|
244
|
+
open();
|
|
245
|
+
if (action.activeIndex >= 0) setActive(action.activeIndex);
|
|
246
|
+
return;
|
|
247
|
+
case "close":
|
|
248
|
+
close(action.restoreFocus);
|
|
249
|
+
return;
|
|
250
|
+
case "move-active":
|
|
251
|
+
setActive(action.activeIndex);
|
|
252
|
+
return;
|
|
253
|
+
case "commit":
|
|
254
|
+
commit(action.index);
|
|
255
|
+
return;
|
|
256
|
+
case "type-ahead": {
|
|
257
|
+
const opts = readOptions(select);
|
|
258
|
+
const result = pushTypeAheadChar(
|
|
259
|
+
typeAhead,
|
|
260
|
+
action.char,
|
|
261
|
+
Date.now(),
|
|
262
|
+
opts
|
|
263
|
+
);
|
|
264
|
+
typeAhead = result.newState;
|
|
265
|
+
if (result.matchedIndex !== null) {
|
|
266
|
+
if (!isOpen) open();
|
|
267
|
+
setActive(result.matchedIndex);
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
case "passthrough":
|
|
272
|
+
return;
|
|
273
|
+
default: {
|
|
274
|
+
const _exhaustive = action;
|
|
275
|
+
void _exhaustive;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function onKeydown(event) {
|
|
280
|
+
const opts = readOptions(select);
|
|
281
|
+
const action = handleKey(
|
|
282
|
+
{
|
|
283
|
+
key: event.key,
|
|
284
|
+
ctrlKey: event.ctrlKey,
|
|
285
|
+
metaKey: event.metaKey,
|
|
286
|
+
altKey: event.altKey,
|
|
287
|
+
shiftKey: event.shiftKey
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
isOpen,
|
|
291
|
+
activeIndex,
|
|
292
|
+
selectedIndex: select.selectedIndex,
|
|
293
|
+
options: opts
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
if (action.preventDefault) event.preventDefault();
|
|
297
|
+
applyAction(action);
|
|
298
|
+
}
|
|
299
|
+
function onTriggerClick(event) {
|
|
300
|
+
event.preventDefault();
|
|
301
|
+
if (isOpen) close(false);
|
|
302
|
+
else open();
|
|
303
|
+
}
|
|
304
|
+
function onListboxClick(event) {
|
|
305
|
+
let target = event.target;
|
|
306
|
+
while (target && target !== listbox) {
|
|
307
|
+
if (target.classList?.contains("lb-dropdown-option")) {
|
|
308
|
+
const idx = parseInt(target.getAttribute("data-index") ?? "", 10);
|
|
309
|
+
if (!Number.isNaN(idx)) {
|
|
310
|
+
commit(idx);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
target = target.parentElement;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function onListboxMousemove(event) {
|
|
318
|
+
let target = event.target;
|
|
319
|
+
while (target && target !== listbox) {
|
|
320
|
+
if (target.classList?.contains("lb-dropdown-option")) {
|
|
321
|
+
if (target.getAttribute("aria-disabled") === "true") return;
|
|
322
|
+
const idx = parseInt(target.getAttribute("data-index") ?? "", 10);
|
|
323
|
+
if (!Number.isNaN(idx) && idx !== activeIndex) setActive(idx);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
target = target.parentElement;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function onShellFocusout() {
|
|
330
|
+
setTimeout(() => {
|
|
331
|
+
if (!isOpen) return;
|
|
332
|
+
const active = rootNode.activeElement ?? doc.activeElement;
|
|
333
|
+
if (!shell.contains(active)) close(false);
|
|
334
|
+
}, 0);
|
|
335
|
+
}
|
|
336
|
+
function onSelectChange() {
|
|
337
|
+
syncFromSelect();
|
|
338
|
+
}
|
|
339
|
+
const observer = new MutationObserver(() => {
|
|
340
|
+
syncFromSelect();
|
|
341
|
+
});
|
|
342
|
+
observer.observe(select, {
|
|
343
|
+
childList: true,
|
|
344
|
+
subtree: true,
|
|
345
|
+
attributes: true,
|
|
346
|
+
attributeFilter: ["disabled", "value", "selected"]
|
|
347
|
+
});
|
|
348
|
+
const onListboxMousedown = (event) => event.preventDefault();
|
|
349
|
+
trigger.addEventListener("click", onTriggerClick);
|
|
350
|
+
trigger.addEventListener("keydown", onKeydown);
|
|
351
|
+
shell.addEventListener("focusout", onShellFocusout);
|
|
352
|
+
listbox.addEventListener("mousedown", onListboxMousedown);
|
|
353
|
+
listbox.addEventListener("click", onListboxClick);
|
|
354
|
+
listbox.addEventListener("mousemove", onListboxMousemove);
|
|
355
|
+
select.addEventListener("change", onSelectChange);
|
|
356
|
+
function destroy() {
|
|
357
|
+
if (isOpen) close(false);
|
|
358
|
+
observer.disconnect();
|
|
359
|
+
trigger.removeEventListener("click", onTriggerClick);
|
|
360
|
+
trigger.removeEventListener("keydown", onKeydown);
|
|
361
|
+
shell.removeEventListener("focusout", onShellFocusout);
|
|
362
|
+
listbox.removeEventListener("mousedown", onListboxMousedown);
|
|
363
|
+
listbox.removeEventListener("click", onListboxClick);
|
|
364
|
+
listbox.removeEventListener("mousemove", onListboxMousemove);
|
|
365
|
+
select.removeEventListener("change", onSelectChange);
|
|
366
|
+
if (shell.parentNode) shell.parentNode.removeChild(shell);
|
|
367
|
+
if (listbox.parentNode) listbox.parentNode.removeChild(listbox);
|
|
368
|
+
select.classList.remove("lb-dropdown-state");
|
|
369
|
+
select.removeAttribute("aria-hidden");
|
|
370
|
+
select.removeAttribute("tabindex");
|
|
371
|
+
delete slot.__lbDropdownInstance;
|
|
372
|
+
}
|
|
373
|
+
instance = {
|
|
374
|
+
shell,
|
|
375
|
+
listbox,
|
|
376
|
+
select,
|
|
377
|
+
close: () => close(false),
|
|
378
|
+
destroy
|
|
379
|
+
};
|
|
380
|
+
slot.__lbDropdownInstance = instance;
|
|
381
|
+
syncFromSelect();
|
|
382
|
+
return instance;
|
|
383
|
+
}
|
|
384
|
+
function bindAllDropdowns(root) {
|
|
385
|
+
const selects = root.querySelectorAll(BIND_SELECTOR);
|
|
386
|
+
const instances = [];
|
|
387
|
+
selects.forEach((sel) => {
|
|
388
|
+
const inst = bindDropdown(sel);
|
|
389
|
+
if (inst) instances.push(inst);
|
|
390
|
+
});
|
|
391
|
+
return instances;
|
|
392
|
+
}
|
|
393
|
+
function unbindAllDropdowns(root) {
|
|
394
|
+
const bound = root.querySelectorAll("select.lb-dropdown-state");
|
|
395
|
+
bound.forEach((sel) => {
|
|
396
|
+
const inst = sel.__lbDropdownInstance;
|
|
397
|
+
if (inst) inst.destroy();
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
24
401
|
// src/renderers/pricing.ts
|
|
25
402
|
import {
|
|
26
403
|
parseCents,
|
|
@@ -192,6 +569,8 @@ function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
192
569
|
})
|
|
193
570
|
);
|
|
194
571
|
container.appendChild(root);
|
|
572
|
+
bindAllDropdowns(root);
|
|
573
|
+
onCleanup?.(() => unbindAllDropdowns(root));
|
|
195
574
|
updatePricing();
|
|
196
575
|
function updatePricing() {
|
|
197
576
|
const totalCents = rows.reduce((sum, r) => {
|
|
@@ -212,12 +591,11 @@ function renderFixedBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
212
591
|
}
|
|
213
592
|
function buildRowState(bundle, product, productIndex) {
|
|
214
593
|
const selectedVariantIds = bundle.selectedVariantIds?.[productIndex] ?? null;
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
const selected = eligibleVariants[0] ?? null;
|
|
594
|
+
const merchantScoped = selectedVariantIds && selectedVariantIds.length > 0 ? product.variants.nodes.filter((v) => selectedVariantIds.includes(v.id)) : product.variants.nodes;
|
|
595
|
+
const firstInStock = merchantScoped.find((v) => v.availableForSale) ?? null;
|
|
596
|
+
const eligibleVariants = merchantScoped;
|
|
597
|
+
const isOos = !firstInStock;
|
|
598
|
+
const selected = firstInStock ?? merchantScoped[0] ?? null;
|
|
221
599
|
const qty = selected ? resolveBundleQty(bundle, product.id, selected.id) : 1;
|
|
222
600
|
return { product, eligibleVariants, selected, qty, isOos };
|
|
223
601
|
}
|
|
@@ -288,18 +666,20 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
|
288
666
|
}
|
|
289
667
|
);
|
|
290
668
|
const thumb = el("div", "lb-bundle-thumbnail", { "data-thumbnail": "" });
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
669
|
+
const initialThumbImage = state.selected?.image ?? state.product.featuredImage ?? null;
|
|
670
|
+
let thumbImg = null;
|
|
671
|
+
if (initialThumbImage) {
|
|
672
|
+
thumbImg = document.createElement("img");
|
|
673
|
+
thumbImg.src = transformImageUrl(initialThumbImage.url, {
|
|
294
674
|
width: THUMB_PX,
|
|
295
675
|
height: THUMB_PX,
|
|
296
676
|
crop: "center"
|
|
297
677
|
});
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
thumb.appendChild(
|
|
678
|
+
thumbImg.alt = initialThumbImage.altText ?? state.product.title;
|
|
679
|
+
thumbImg.width = THUMB_PX;
|
|
680
|
+
thumbImg.height = THUMB_PX;
|
|
681
|
+
thumbImg.loading = "lazy";
|
|
682
|
+
thumb.appendChild(thumbImg);
|
|
303
683
|
} else {
|
|
304
684
|
thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG);
|
|
305
685
|
}
|
|
@@ -363,36 +743,97 @@ function renderProductRow(state, currency, qtyFor, onVariantChange) {
|
|
|
363
743
|
} else {
|
|
364
744
|
unitPriceEl.setAttribute("hidden", "");
|
|
365
745
|
}
|
|
746
|
+
const nextImage = variant.image ?? state.product.featuredImage;
|
|
747
|
+
if (thumbImg && nextImage) {
|
|
748
|
+
thumbImg.src = transformImageUrl(nextImage.url, {
|
|
749
|
+
width: THUMB_PX,
|
|
750
|
+
height: THUMB_PX,
|
|
751
|
+
crop: "center"
|
|
752
|
+
});
|
|
753
|
+
thumbImg.alt = nextImage.altText ?? state.product.title;
|
|
754
|
+
}
|
|
366
755
|
};
|
|
367
756
|
applyVariantToRow(state.selected);
|
|
368
757
|
if (state.eligibleVariants.length > 1) {
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
select.setAttribute("data-variant-select", "");
|
|
372
|
-
select.name = `lb-variant-${state.product.id.replace(/^.*\//, "")}`;
|
|
373
|
-
select.setAttribute(
|
|
374
|
-
"aria-label",
|
|
375
|
-
`Select variant for ${state.product.title}`
|
|
758
|
+
const optionNames = state.eligibleVariants[0].selectedOptions.map(
|
|
759
|
+
(o) => o.name
|
|
376
760
|
);
|
|
377
|
-
state.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
761
|
+
const productIdTail = state.product.id.replace(/^.*\//, "");
|
|
762
|
+
const optionSelects = [];
|
|
763
|
+
const resolveVariant = (values) => state.eligibleVariants.find(
|
|
764
|
+
(v) => v.selectedOptions.every((o, i) => o.value === values[i]) && v.selectedOptions.length === values.length
|
|
765
|
+
) ?? null;
|
|
766
|
+
const syncSelectsToVariant = (variant) => {
|
|
767
|
+
variant.selectedOptions.forEach((o, i) => {
|
|
768
|
+
const sel = optionSelects[i];
|
|
769
|
+
if (sel && sel.value !== o.value) sel.value = o.value;
|
|
770
|
+
});
|
|
771
|
+
};
|
|
772
|
+
const isValueAvailable = (optionIndex, value, selected) => state.eligibleVariants.some((v) => {
|
|
773
|
+
if (!v.availableForSale) return false;
|
|
774
|
+
if (v.selectedOptions[optionIndex]?.value !== value) return false;
|
|
775
|
+
return v.selectedOptions.every(
|
|
776
|
+
(o, i) => i === optionIndex || o.value === selected[i]
|
|
387
777
|
);
|
|
388
|
-
|
|
778
|
+
});
|
|
779
|
+
const recomputeDisabled = (selected) => {
|
|
780
|
+
optionSelects.forEach((sel, i) => {
|
|
781
|
+
Array.from(sel.options).forEach((opt) => {
|
|
782
|
+
opt.disabled = !isValueAvailable(i, opt.value, selected);
|
|
783
|
+
});
|
|
784
|
+
});
|
|
785
|
+
};
|
|
786
|
+
const handleChange = () => {
|
|
787
|
+
const values = optionSelects.map((s) => s.value);
|
|
788
|
+
const variant = resolveVariant(values);
|
|
789
|
+
if (!variant) {
|
|
790
|
+
if (state.selected) {
|
|
791
|
+
syncSelectsToVariant(state.selected);
|
|
792
|
+
recomputeDisabled(state.selected.selectedOptions.map((o) => o.value));
|
|
793
|
+
}
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
389
796
|
state.selected = variant;
|
|
390
797
|
state.qty = qtyFor(state.product.id, variant.id);
|
|
391
798
|
if (qtyBadgeRef) qtyBadgeRef.textContent = String(state.qty);
|
|
392
799
|
applyVariantToRow(variant);
|
|
800
|
+
recomputeDisabled(variant.selectedOptions.map((o) => o.value));
|
|
393
801
|
onVariantChange();
|
|
802
|
+
};
|
|
803
|
+
const groupsContainer = el("div", "lb-bundle-variant-option-groups");
|
|
804
|
+
optionNames.forEach((name2, position) => {
|
|
805
|
+
const group = el("div", "lb-bundle-variant-option-group");
|
|
806
|
+
const label = el("span", "lb-bundle-variant-option-label");
|
|
807
|
+
label.textContent = name2;
|
|
808
|
+
group.appendChild(label);
|
|
809
|
+
const select = document.createElement("select");
|
|
810
|
+
select.className = "lb-bundle-variant-select";
|
|
811
|
+
select.setAttribute("data-variant-option", "");
|
|
812
|
+
select.setAttribute("data-option-position", String(position + 1));
|
|
813
|
+
select.name = `lb-variant-${productIdTail}-${position + 1}`;
|
|
814
|
+
select.setAttribute("aria-label", name2);
|
|
815
|
+
const seen = /* @__PURE__ */ new Set();
|
|
816
|
+
state.eligibleVariants.forEach((v) => {
|
|
817
|
+
const value = v.selectedOptions[position]?.value;
|
|
818
|
+
if (!value || seen.has(value)) return;
|
|
819
|
+
seen.add(value);
|
|
820
|
+
const opt = document.createElement("option");
|
|
821
|
+
opt.value = value;
|
|
822
|
+
opt.textContent = value;
|
|
823
|
+
if (state.selected?.selectedOptions[position]?.value === value) {
|
|
824
|
+
opt.selected = true;
|
|
825
|
+
}
|
|
826
|
+
select.appendChild(opt);
|
|
827
|
+
});
|
|
828
|
+
select.addEventListener("change", handleChange);
|
|
829
|
+
optionSelects.push(select);
|
|
830
|
+
group.appendChild(select);
|
|
831
|
+
groupsContainer.appendChild(group);
|
|
394
832
|
});
|
|
395
|
-
info.appendChild(
|
|
833
|
+
info.appendChild(groupsContainer);
|
|
834
|
+
if (state.selected) {
|
|
835
|
+
recomputeDisabled(state.selected.selectedOptions.map((o) => o.value));
|
|
836
|
+
}
|
|
396
837
|
} else if (state.eligibleVariants.length === 1 && state.product.variants.nodes.length > 1) {
|
|
397
838
|
const badge = el("span", "lb-bundle-variant-badge");
|
|
398
839
|
badge.textContent = state.eligibleVariants[0].title;
|
|
@@ -481,6 +922,7 @@ function computeSale(totalCents, discount, rows) {
|
|
|
481
922
|
}
|
|
482
923
|
|
|
483
924
|
// src/renderers/mix-match.ts
|
|
925
|
+
import { resolveBundleQty as resolveBundleQty2 } from "@lime-bundles/core";
|
|
484
926
|
var PLACEHOLDER_THUMB_SVG2 = `
|
|
485
927
|
<svg class="lb-bundle-placeholder-icon" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
|
|
486
928
|
<rect x="4" y="4" width="20" height="20" rx="3"></rect>
|
|
@@ -548,9 +990,6 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
548
990
|
showSearch: wc.showSearch,
|
|
549
991
|
onAdd: (product, variant) => addSelection(product, variant),
|
|
550
992
|
onRemove: (productId, variantId) => removeSelection(productId, variantId),
|
|
551
|
-
countFor: (productId, variantId) => selections.filter(
|
|
552
|
-
(s) => s.productId === productId && s.variantId === variantId
|
|
553
|
-
).length,
|
|
554
993
|
isOverMax: () => selections.length >= maxQty
|
|
555
994
|
});
|
|
556
995
|
root.appendChild(modal.el);
|
|
@@ -560,7 +999,7 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
560
999
|
if (cta.disabled) return;
|
|
561
1000
|
const lines = selections.map((s) => ({
|
|
562
1001
|
merchandiseId: s.variantId,
|
|
563
|
-
quantity:
|
|
1002
|
+
quantity: s.quantity,
|
|
564
1003
|
attributes: [
|
|
565
1004
|
{ key: "_lime_bundle_gid", value: bundle.id },
|
|
566
1005
|
{ key: "_lime_bundle_type", value: bundle.bundleType }
|
|
@@ -579,6 +1018,7 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
579
1018
|
})
|
|
580
1019
|
);
|
|
581
1020
|
container.appendChild(root);
|
|
1021
|
+
onCleanup?.(() => unbindAllDropdowns(root));
|
|
582
1022
|
const firstEligible = eligible.find((ep) => !ep.isOos);
|
|
583
1023
|
const firstVariant = firstEligible?.firstAvailableVariant ?? firstEligible?.variants[0];
|
|
584
1024
|
if (firstEligible && firstVariant) {
|
|
@@ -587,14 +1027,15 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
587
1027
|
productTitle: firstEligible.product.title,
|
|
588
1028
|
variantId: firstVariant.id,
|
|
589
1029
|
variantTitle: firstVariant.title,
|
|
590
|
-
imageUrl: firstEligible.product.featuredImage?.url ?? null,
|
|
1030
|
+
imageUrl: firstVariant.image?.url ?? firstEligible.product.featuredImage?.url ?? null,
|
|
591
1031
|
priceCents: parseCents(firstVariant.price.amount),
|
|
592
1032
|
compareCents: firstVariant.compareAtPrice ? parseCents(firstVariant.compareAtPrice.amount) : null,
|
|
593
1033
|
unitPriceLabel: formatUnitPrice(
|
|
594
1034
|
firstVariant.unitPrice,
|
|
595
1035
|
firstVariant.unitPriceMeasurement,
|
|
596
1036
|
currency
|
|
597
|
-
)
|
|
1037
|
+
),
|
|
1038
|
+
quantity: resolveBundleQty2(bundle, firstEligible.product.id, firstVariant.id)
|
|
598
1039
|
});
|
|
599
1040
|
}
|
|
600
1041
|
afterMutation();
|
|
@@ -605,14 +1046,15 @@ function renderMixMatchBundle(container, bundle, onAddToCart, onCleanup) {
|
|
|
605
1046
|
productTitle: product.title,
|
|
606
1047
|
variantId: variant.id,
|
|
607
1048
|
variantTitle: variant.title,
|
|
608
|
-
imageUrl: product.featuredImage?.url ?? null,
|
|
1049
|
+
imageUrl: variant.image?.url ?? product.featuredImage?.url ?? null,
|
|
609
1050
|
priceCents: parseCents(variant.price.amount),
|
|
610
1051
|
compareCents: variant.compareAtPrice ? parseCents(variant.compareAtPrice.amount) : null,
|
|
611
1052
|
unitPriceLabel: formatUnitPrice(
|
|
612
1053
|
variant.unitPrice,
|
|
613
1054
|
variant.unitPriceMeasurement,
|
|
614
1055
|
currency
|
|
615
|
-
)
|
|
1056
|
+
),
|
|
1057
|
+
quantity: resolveBundleQty2(bundle, product.id, variant.id)
|
|
616
1058
|
});
|
|
617
1059
|
afterMutation();
|
|
618
1060
|
}
|
|
@@ -779,6 +1221,9 @@ function renderFilledSlot(selection, index, currency, onRemove) {
|
|
|
779
1221
|
} else {
|
|
780
1222
|
thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG2);
|
|
781
1223
|
}
|
|
1224
|
+
const qtyBadge = el("span", "lb-bundle-qty-badge");
|
|
1225
|
+
qtyBadge.textContent = String(selection.quantity);
|
|
1226
|
+
thumb.appendChild(qtyBadge);
|
|
782
1227
|
slot.appendChild(thumb);
|
|
783
1228
|
const info = el("div", "lb-mix-match__filled-info");
|
|
784
1229
|
const title = el("span", "lb-mix-match__filled-title");
|
|
@@ -789,14 +1234,16 @@ function renderFilledSlot(selection, index, currency, onRemove) {
|
|
|
789
1234
|
variant.textContent = selection.variantTitle;
|
|
790
1235
|
info.appendChild(variant);
|
|
791
1236
|
}
|
|
1237
|
+
const linePrice = selection.priceCents * selection.quantity;
|
|
1238
|
+
const lineCompare = selection.compareCents !== null ? selection.compareCents * selection.quantity : null;
|
|
792
1239
|
const priceWrap = el("span", "lb-mix-match__filled-price");
|
|
793
|
-
if (
|
|
1240
|
+
if (lineCompare !== null && lineCompare > linePrice) {
|
|
794
1241
|
const compare = el("span", "lb-mix-match__filled-compare");
|
|
795
|
-
compare.textContent = formatCents(
|
|
1242
|
+
compare.textContent = formatCents(lineCompare, currency);
|
|
796
1243
|
priceWrap.appendChild(compare);
|
|
797
1244
|
}
|
|
798
1245
|
const priceEl = document.createElement("span");
|
|
799
|
-
priceEl.textContent = formatCents(
|
|
1246
|
+
priceEl.textContent = formatCents(linePrice, currency);
|
|
800
1247
|
priceWrap.appendChild(priceEl);
|
|
801
1248
|
info.appendChild(priceWrap);
|
|
802
1249
|
if (selection.unitPriceLabel) {
|
|
@@ -837,7 +1284,10 @@ function renderPricingSection(showCompareAtPrice) {
|
|
|
837
1284
|
return;
|
|
838
1285
|
}
|
|
839
1286
|
wrap.style.display = "";
|
|
840
|
-
const totalCents = selections.reduce(
|
|
1287
|
+
const totalCents = selections.reduce(
|
|
1288
|
+
(s, sel) => s + sel.priceCents * sel.quantity,
|
|
1289
|
+
0
|
|
1290
|
+
);
|
|
841
1291
|
const saleCents = computeBundleSaleCents(totalCents, bundle.discountConfig);
|
|
842
1292
|
if (showCompareAtPrice && totalCents > saleCents) {
|
|
843
1293
|
compare.textContent = formatCents(totalCents, currency);
|
|
@@ -864,7 +1314,10 @@ function renderSavingsBar2() {
|
|
|
864
1314
|
wrap.style.display = "none";
|
|
865
1315
|
return;
|
|
866
1316
|
}
|
|
867
|
-
const totalCents = selections.reduce(
|
|
1317
|
+
const totalCents = selections.reduce(
|
|
1318
|
+
(s, sel) => s + sel.priceCents * sel.quantity,
|
|
1319
|
+
0
|
|
1320
|
+
);
|
|
868
1321
|
const saleCents = computeBundleSaleCents(totalCents, bundle.discountConfig);
|
|
869
1322
|
const savings = Math.max(0, totalCents - saleCents);
|
|
870
1323
|
if (savings <= 0) {
|
|
@@ -958,8 +1411,8 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
958
1411
|
rowsBuilt = true;
|
|
959
1412
|
list.innerHTML = "";
|
|
960
1413
|
eligible.forEach((ep) => {
|
|
961
|
-
const availableVariants = ep.variants
|
|
962
|
-
const firstAvailVariant =
|
|
1414
|
+
const availableVariants = ep.variants;
|
|
1415
|
+
const firstAvailVariant = ep.variants.find((v) => v.availableForSale) ?? ep.firstAvailableVariant ?? ep.variants[0];
|
|
963
1416
|
if (!firstAvailVariant) return;
|
|
964
1417
|
let currentVariant = firstAvailVariant;
|
|
965
1418
|
const productEl = el(
|
|
@@ -968,23 +1421,28 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
968
1421
|
{ "data-product-id": ep.product.id.replace(/^.*\//, "") }
|
|
969
1422
|
);
|
|
970
1423
|
const thumb = el("div", "lb-mix-match__modal-product-thumb");
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1424
|
+
const initialThumbVariant = ep.variants.find((v) => v.availableForSale) ?? ep.variants[0] ?? null;
|
|
1425
|
+
const initialThumbImage = initialThumbVariant?.image ?? ep.product.featuredImage ?? null;
|
|
1426
|
+
let thumbImg = null;
|
|
1427
|
+
if (initialThumbImage) {
|
|
1428
|
+
thumbImg = document.createElement("img");
|
|
1429
|
+
thumbImg.src = transformImageUrl(initialThumbImage.url, {
|
|
974
1430
|
width: THUMB_PX,
|
|
975
1431
|
height: THUMB_PX,
|
|
976
1432
|
crop: "center"
|
|
977
1433
|
});
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
thumb.appendChild(
|
|
1434
|
+
thumbImg.alt = initialThumbImage.altText ?? ep.product.title;
|
|
1435
|
+
thumbImg.width = THUMB_PX;
|
|
1436
|
+
thumbImg.height = THUMB_PX;
|
|
1437
|
+
thumbImg.loading = "lazy";
|
|
1438
|
+
thumb.appendChild(thumbImg);
|
|
983
1439
|
} else {
|
|
984
1440
|
thumb.insertAdjacentHTML("beforeend", PLACEHOLDER_THUMB_SVG2);
|
|
985
1441
|
}
|
|
986
1442
|
const countBadge = el("span", "lb-bundle-qty-badge");
|
|
987
|
-
countBadge.
|
|
1443
|
+
countBadge.textContent = String(
|
|
1444
|
+
resolveBundleQty2(bundle, ep.product.id, currentVariant.id)
|
|
1445
|
+
);
|
|
988
1446
|
thumb.appendChild(countBadge);
|
|
989
1447
|
productEl.appendChild(thumb);
|
|
990
1448
|
const info = el("div", "lb-mix-match__modal-product-info");
|
|
@@ -993,7 +1451,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
993
1451
|
info.appendChild(title);
|
|
994
1452
|
const price = el("p", "lb-mix-match__modal-product-price");
|
|
995
1453
|
price.textContent = formatCents(
|
|
996
|
-
parseCents(currentVariant.price.amount),
|
|
1454
|
+
parseCents(currentVariant.price.amount) * resolveBundleQty2(bundle, ep.product.id, currentVariant.id),
|
|
997
1455
|
currency
|
|
998
1456
|
);
|
|
999
1457
|
info.appendChild(price);
|
|
@@ -1020,28 +1478,53 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1020
1478
|
}
|
|
1021
1479
|
};
|
|
1022
1480
|
if (availableVariants.length > 1) {
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
select.setAttribute("data-variant-select", "");
|
|
1026
|
-
select.name = `lb-variant-${ep.product.id.replace(/^.*\//, "")}`;
|
|
1027
|
-
select.setAttribute(
|
|
1028
|
-
"aria-label",
|
|
1029
|
-
`Select variant for ${ep.product.title}`
|
|
1481
|
+
const optionNames = availableVariants[0].selectedOptions.map(
|
|
1482
|
+
(o) => o.name
|
|
1030
1483
|
);
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1484
|
+
const productIdTail = ep.product.id.replace(/^.*\//, "");
|
|
1485
|
+
const optionSelects = [];
|
|
1486
|
+
const resolveVariant = (values) => availableVariants.find(
|
|
1487
|
+
(v) => v.selectedOptions.length === values.length && v.selectedOptions.every((o, i) => o.value === values[i])
|
|
1488
|
+
) ?? null;
|
|
1489
|
+
const syncSelectsToVariant = (v) => {
|
|
1490
|
+
v.selectedOptions.forEach((o, i) => {
|
|
1491
|
+
const sel = optionSelects[i];
|
|
1492
|
+
if (sel && sel.value !== o.value) sel.value = o.value;
|
|
1493
|
+
});
|
|
1494
|
+
};
|
|
1495
|
+
const isValueAvailable = (optionIndex, value, selected) => availableVariants.some((v) => {
|
|
1496
|
+
if (!v.availableForSale) return false;
|
|
1497
|
+
if (v.selectedOptions[optionIndex]?.value !== value) return false;
|
|
1498
|
+
return v.selectedOptions.every(
|
|
1499
|
+
(o, i) => i === optionIndex || o.value === selected[i]
|
|
1500
|
+
);
|
|
1037
1501
|
});
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1502
|
+
const recomputeDisabled = (selected) => {
|
|
1503
|
+
optionSelects.forEach((sel, i) => {
|
|
1504
|
+
Array.from(sel.options).forEach((opt) => {
|
|
1505
|
+
opt.disabled = !isValueAvailable(i, opt.value, selected);
|
|
1506
|
+
});
|
|
1507
|
+
});
|
|
1508
|
+
};
|
|
1509
|
+
const handleChange = () => {
|
|
1510
|
+
const values = optionSelects.map((s) => s.value);
|
|
1511
|
+
const next = resolveVariant(values);
|
|
1512
|
+
if (!next) {
|
|
1513
|
+
syncSelectsToVariant(currentVariant);
|
|
1514
|
+
recomputeDisabled(
|
|
1515
|
+
currentVariant.selectedOptions.map((o) => o.value)
|
|
1516
|
+
);
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1041
1519
|
currentVariant = next;
|
|
1042
1520
|
row.variant = next;
|
|
1521
|
+
const nextQty = resolveBundleQty2(
|
|
1522
|
+
bundle,
|
|
1523
|
+
ep.product.id,
|
|
1524
|
+
currentVariant.id
|
|
1525
|
+
);
|
|
1043
1526
|
price.textContent = formatCents(
|
|
1044
|
-
parseCents(currentVariant.price.amount),
|
|
1527
|
+
parseCents(currentVariant.price.amount) * nextQty,
|
|
1045
1528
|
currency
|
|
1046
1529
|
);
|
|
1047
1530
|
const nextUnitText = formatUnitPrice(
|
|
@@ -1056,9 +1539,52 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1056
1539
|
unitPrice.textContent = "";
|
|
1057
1540
|
unitPrice.hidden = true;
|
|
1058
1541
|
}
|
|
1542
|
+
const nextImage = currentVariant.image ?? ep.product.featuredImage;
|
|
1543
|
+
if (thumbImg && nextImage) {
|
|
1544
|
+
thumbImg.src = transformImageUrl(nextImage.url, {
|
|
1545
|
+
width: THUMB_PX,
|
|
1546
|
+
height: THUMB_PX,
|
|
1547
|
+
crop: "center"
|
|
1548
|
+
});
|
|
1549
|
+
thumbImg.alt = nextImage.altText ?? ep.product.title;
|
|
1550
|
+
}
|
|
1551
|
+
recomputeDisabled(next.selectedOptions.map((o) => o.value));
|
|
1059
1552
|
rowUpdateCount();
|
|
1553
|
+
};
|
|
1554
|
+
const groupsContainer = el("div", "lb-bundle-variant-option-groups");
|
|
1555
|
+
optionNames.forEach((name, position) => {
|
|
1556
|
+
const group = el("div", "lb-bundle-variant-option-group");
|
|
1557
|
+
const label = el("span", "lb-bundle-variant-option-label");
|
|
1558
|
+
label.textContent = name;
|
|
1559
|
+
group.appendChild(label);
|
|
1560
|
+
const select = document.createElement("select");
|
|
1561
|
+
select.className = "lb-mix-match__variant-select";
|
|
1562
|
+
select.setAttribute("data-variant-option", "");
|
|
1563
|
+
select.setAttribute("data-option-position", String(position + 1));
|
|
1564
|
+
select.name = `lb-variant-${productIdTail}-${position + 1}`;
|
|
1565
|
+
select.setAttribute("aria-label", name);
|
|
1566
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1567
|
+
availableVariants.forEach((v) => {
|
|
1568
|
+
const value = v.selectedOptions[position]?.value;
|
|
1569
|
+
if (!value || seen.has(value)) return;
|
|
1570
|
+
seen.add(value);
|
|
1571
|
+
const opt = document.createElement("option");
|
|
1572
|
+
opt.value = value;
|
|
1573
|
+
opt.textContent = value;
|
|
1574
|
+
if (firstAvailVariant.selectedOptions[position]?.value === value) {
|
|
1575
|
+
opt.selected = true;
|
|
1576
|
+
}
|
|
1577
|
+
select.appendChild(opt);
|
|
1578
|
+
});
|
|
1579
|
+
select.addEventListener("change", handleChange);
|
|
1580
|
+
optionSelects.push(select);
|
|
1581
|
+
group.appendChild(select);
|
|
1582
|
+
groupsContainer.appendChild(group);
|
|
1060
1583
|
});
|
|
1061
|
-
info.appendChild(
|
|
1584
|
+
info.appendChild(groupsContainer);
|
|
1585
|
+
recomputeDisabled(
|
|
1586
|
+
firstAvailVariant.selectedOptions.map((o) => o.value)
|
|
1587
|
+
);
|
|
1062
1588
|
} else if (availableVariants.length === 1 && firstAvailVariant.title !== "Default Title") {
|
|
1063
1589
|
const variantLabel = el("span", "lb-mix-match__filled-variant");
|
|
1064
1590
|
variantLabel.textContent = firstAvailVariant.title;
|
|
@@ -1071,13 +1597,9 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1071
1597
|
}
|
|
1072
1598
|
productEl.appendChild(info);
|
|
1073
1599
|
const rowUpdateCount = () => {
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
countBadge.style.display = "";
|
|
1078
|
-
} else {
|
|
1079
|
-
countBadge.style.display = "none";
|
|
1080
|
-
}
|
|
1600
|
+
countBadge.textContent = String(
|
|
1601
|
+
resolveBundleQty2(bundle, ep.product.id, currentVariant.id)
|
|
1602
|
+
);
|
|
1081
1603
|
};
|
|
1082
1604
|
if (!ep.isOos) {
|
|
1083
1605
|
const addBtn = document.createElement("button");
|
|
@@ -1087,6 +1609,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1087
1609
|
addBtn.addEventListener("click", () => {
|
|
1088
1610
|
if (handlers.isOverMax()) return;
|
|
1089
1611
|
handlers.onAdd(ep.product, currentVariant);
|
|
1612
|
+
close();
|
|
1090
1613
|
});
|
|
1091
1614
|
productEl.appendChild(addBtn);
|
|
1092
1615
|
}
|
|
@@ -1095,6 +1618,7 @@ function renderModal(bundle, eligible, currency, handlers) {
|
|
|
1095
1618
|
productRows.push(row);
|
|
1096
1619
|
list.appendChild(productEl);
|
|
1097
1620
|
});
|
|
1621
|
+
bindAllDropdowns(list);
|
|
1098
1622
|
refreshCounts();
|
|
1099
1623
|
}
|
|
1100
1624
|
function applySearch() {
|
|
@@ -1492,6 +2016,61 @@ function clamp(n, min, max) {
|
|
|
1492
2016
|
// src/lime-bundle.ts
|
|
1493
2017
|
import { applyWidgetConfigVars } from "@lime-bundles/core";
|
|
1494
2018
|
|
|
2019
|
+
// src/utils/input-mode.ts
|
|
2020
|
+
var NAV_KEYS = /* @__PURE__ */ new Set([
|
|
2021
|
+
"Tab",
|
|
2022
|
+
"ArrowUp",
|
|
2023
|
+
"ArrowDown",
|
|
2024
|
+
"ArrowLeft",
|
|
2025
|
+
"ArrowRight",
|
|
2026
|
+
"Home",
|
|
2027
|
+
"End",
|
|
2028
|
+
"PageUp",
|
|
2029
|
+
"PageDown",
|
|
2030
|
+
"Enter",
|
|
2031
|
+
" ",
|
|
2032
|
+
"Escape"
|
|
2033
|
+
]);
|
|
2034
|
+
var targets = /* @__PURE__ */ new Set();
|
|
2035
|
+
var listenersAttached = false;
|
|
2036
|
+
function setAll(on) {
|
|
2037
|
+
const off = on === "using-mouse" ? "using-keyboard" : "using-mouse";
|
|
2038
|
+
for (const el2 of targets) {
|
|
2039
|
+
el2.classList.add(on);
|
|
2040
|
+
el2.classList.remove(off);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
function onKeyDown(e) {
|
|
2044
|
+
if (NAV_KEYS.has(e.key)) setAll("using-keyboard");
|
|
2045
|
+
}
|
|
2046
|
+
function onPointerDown() {
|
|
2047
|
+
setAll("using-mouse");
|
|
2048
|
+
}
|
|
2049
|
+
function attachListeners() {
|
|
2050
|
+
if (listenersAttached) return;
|
|
2051
|
+
listenersAttached = true;
|
|
2052
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
2053
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
2054
|
+
}
|
|
2055
|
+
function detachListeners() {
|
|
2056
|
+
if (!listenersAttached) return;
|
|
2057
|
+
listenersAttached = false;
|
|
2058
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
2059
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
2060
|
+
}
|
|
2061
|
+
function trackInputMode(target) {
|
|
2062
|
+
target.classList.add("using-mouse");
|
|
2063
|
+
target.classList.remove("using-keyboard");
|
|
2064
|
+
targets.add(target);
|
|
2065
|
+
attachListeners();
|
|
2066
|
+
return () => {
|
|
2067
|
+
targets.delete(target);
|
|
2068
|
+
target.classList.remove("using-mouse");
|
|
2069
|
+
target.classList.remove("using-keyboard");
|
|
2070
|
+
if (targets.size === 0) detachListeners();
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
|
|
1495
2074
|
// src/styles/bundle-css.ts
|
|
1496
2075
|
var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle widget types */
|
|
1497
2076
|
|
|
@@ -1505,6 +2084,11 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
1505
2084
|
--lb-thumbnail-bg: #F0F0F0;
|
|
1506
2085
|
--lb-widget-pad: 20px;
|
|
1507
2086
|
--lb-progress-color: var(--lb-primary-color);
|
|
2087
|
+
/* Cap on the per-bundle product/slot/tier list height \u2014 keeps long
|
|
2088
|
+
bundles from pushing the CTA off-screen. The list scrolls
|
|
2089
|
+
internally with the same custom 4px scrollbar as the variant
|
|
2090
|
+
dropdown when content exceeds this. */
|
|
2091
|
+
--lb-list-max-height: 360px;
|
|
1508
2092
|
|
|
1509
2093
|
font-family: inherit;
|
|
1510
2094
|
font-size: 16px;
|
|
@@ -1776,6 +2360,56 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
1776
2360
|
margin-top: 8px;
|
|
1777
2361
|
}
|
|
1778
2362
|
|
|
2363
|
+
/* Per-option variant pickers. Each option's label + select sit inside a
|
|
2364
|
+
.lb-bundle-variant-option-group (flex column, 2px gap between label and
|
|
2365
|
+
select); the groups stack inside a .lb-bundle-variant-option-groups
|
|
2366
|
+
parent (flex column, 12px gap between groups). The parent owns the top
|
|
2367
|
+
offset from the preceding unit-price line, so individual labels and
|
|
2368
|
+
selects don't carry their own vertical margins. */
|
|
2369
|
+
.lb-bundle-variant-option-groups {
|
|
2370
|
+
display: flex;
|
|
2371
|
+
flex-direction: column;
|
|
2372
|
+
gap: 12px;
|
|
2373
|
+
margin-top: 8px;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
.lb-bundle-variant-option-group {
|
|
2377
|
+
display: flex;
|
|
2378
|
+
flex-direction: column;
|
|
2379
|
+
gap: 2px;
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
.lb-bundle-variant-option-label {
|
|
2383
|
+
display: block;
|
|
2384
|
+
margin: 0;
|
|
2385
|
+
font-size: 12px;
|
|
2386
|
+
line-height: 16px;
|
|
2387
|
+
font-weight: 600;
|
|
2388
|
+
letter-spacing: 0.05em;
|
|
2389
|
+
text-transform: uppercase;
|
|
2390
|
+
color: color-mix(in srgb, var(--lb-text) 60%, transparent);
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
.lb-mix-match__modal-product-info .lb-bundle-variant-option-groups {
|
|
2394
|
+
margin-top: 4px;
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
/* Focus-ring suppression for mouse users. A small JS helper \u2014 see
|
|
2398
|
+
packages/widget/src/utils/input-mode.ts and bundle-widget.js \u2014 toggles
|
|
2399
|
+
.using-mouse / .using-keyboard on the widget root (or html in the Liquid
|
|
2400
|
+
path) based on the customer's current input device. Default is mouse, so
|
|
2401
|
+
click-to-focus doesn't leave a keyboard-style ring. The modal overlay
|
|
2402
|
+
gets its own selector because the Liquid path reparents it to body,
|
|
2403
|
+
outside the widget root. */
|
|
2404
|
+
.using-mouse .lb-bundle-widget :focus,
|
|
2405
|
+
.using-mouse .lb-bundle-widget :focus-visible,
|
|
2406
|
+
.using-mouse .lb-mix-match__modal-overlay :focus,
|
|
2407
|
+
.using-mouse .lb-mix-match__modal-overlay :focus-visible {
|
|
2408
|
+
outline: none;
|
|
2409
|
+
outline-offset: 0;
|
|
2410
|
+
box-shadow: none;
|
|
2411
|
+
}
|
|
2412
|
+
|
|
1779
2413
|
.lb-bundle-quantity {
|
|
1780
2414
|
font-size: 12px;
|
|
1781
2415
|
line-height: 16px;
|
|
@@ -2009,6 +2643,22 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
|
|
|
2009
2643
|
flex-direction: column;
|
|
2010
2644
|
gap: 0;
|
|
2011
2645
|
margin: 0;
|
|
2646
|
+
max-height: var(--lb-list-max-height);
|
|
2647
|
+
overflow-y: auto;
|
|
2648
|
+
/* Custom scrollbar \u2014 text colour at 15% opacity (thumb) and 5% (track). */
|
|
2649
|
+
scrollbar-width: thin;
|
|
2650
|
+
scrollbar-color: color-mix(in srgb, var(--lb-text) 15%, transparent)
|
|
2651
|
+
color-mix(in srgb, var(--lb-text) 2%, transparent);
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
.lb-fixed__products::-webkit-scrollbar { width: 4px; }
|
|
2655
|
+
.lb-fixed__products::-webkit-scrollbar-track {
|
|
2656
|
+
background: color-mix(in srgb, var(--lb-text) 2%, transparent);
|
|
2657
|
+
border-radius: 2px;
|
|
2658
|
+
}
|
|
2659
|
+
.lb-fixed__products::-webkit-scrollbar-thumb {
|
|
2660
|
+
background: color-mix(in srgb, var(--lb-text) 15%, transparent);
|
|
2661
|
+
border-radius: 2px;
|
|
2012
2662
|
}
|
|
2013
2663
|
|
|
2014
2664
|
/* Fixed bundles: product rows */
|
|
@@ -2033,9 +2683,10 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
|
|
|
2033
2683
|
border: none;
|
|
2034
2684
|
}
|
|
2035
2685
|
|
|
2036
|
-
/* Variant picker select \u2014 styled to match the variant badge aesthetic
|
|
2686
|
+
/* Variant picker select \u2014 styled to match the variant badge aesthetic.
|
|
2687
|
+
Sits inside .lb-bundle-variant-option-group so vertical spacing is owned
|
|
2688
|
+
by the group/groups flex gap, not the select itself. */
|
|
2037
2689
|
.lb-bundle-variant-select {
|
|
2038
|
-
margin-top: 8px;
|
|
2039
2690
|
display: inline-block;
|
|
2040
2691
|
border: var(--lb-variant-border-width) solid var(--lb-variant-border-color);
|
|
2041
2692
|
border-radius: var(--lb-variant-radius);
|
|
@@ -2052,7 +2703,8 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
|
|
|
2052
2703
|
background-repeat: no-repeat;
|
|
2053
2704
|
background-position: right 8px center;
|
|
2054
2705
|
background-size: 12px;
|
|
2055
|
-
|
|
2706
|
+
width: 50%;
|
|
2707
|
+
max-width: 50%;
|
|
2056
2708
|
}
|
|
2057
2709
|
|
|
2058
2710
|
.lb-bundle-variant-select:focus-visible {
|
|
@@ -2062,6 +2714,28 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
|
|
|
2062
2714
|
`;
|
|
2063
2715
|
var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
2064
2716
|
|
|
2717
|
+
/* === Slot list ============================================================
|
|
2718
|
+
Caps the height of the slot stack so long bundles don't push the CTA off
|
|
2719
|
+
the page. Internal scroll with the same custom 4px scrollbar as the
|
|
2720
|
+
variant dropdown panel. */
|
|
2721
|
+
.lb-mix-match__slots {
|
|
2722
|
+
max-height: var(--lb-list-max-height);
|
|
2723
|
+
overflow-y: auto;
|
|
2724
|
+
scrollbar-width: thin;
|
|
2725
|
+
scrollbar-color: color-mix(in srgb, var(--lb-text) 15%, transparent)
|
|
2726
|
+
color-mix(in srgb, var(--lb-text) 2%, transparent);
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
.lb-mix-match__slots::-webkit-scrollbar { width: 4px; }
|
|
2730
|
+
.lb-mix-match__slots::-webkit-scrollbar-track {
|
|
2731
|
+
background: color-mix(in srgb, var(--lb-text) 2%, transparent);
|
|
2732
|
+
border-radius: 2px;
|
|
2733
|
+
}
|
|
2734
|
+
.lb-mix-match__slots::-webkit-scrollbar-thumb {
|
|
2735
|
+
background: color-mix(in srgb, var(--lb-text) 15%, transparent);
|
|
2736
|
+
border-radius: 2px;
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2065
2739
|
/* === Progress Bar === */
|
|
2066
2740
|
.lb-mix-match__progress {
|
|
2067
2741
|
margin-bottom: 16px;
|
|
@@ -2459,7 +3133,6 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
2459
3133
|
|
|
2460
3134
|
.lb-mix-match__variant-select {
|
|
2461
3135
|
font-size: 12px;
|
|
2462
|
-
margin: 4px 0 0;
|
|
2463
3136
|
padding: 4px 24px 4px 8px;
|
|
2464
3137
|
border: var(--lb-picker-variant-border-width) solid var(--lb-picker-variant-border-color);
|
|
2465
3138
|
border-radius: var(--lb-picker-variant-radius);
|
|
@@ -2472,7 +3145,8 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
2472
3145
|
font-family: inherit;
|
|
2473
3146
|
min-height: 32px;
|
|
2474
3147
|
cursor: pointer;
|
|
2475
|
-
|
|
3148
|
+
width: 50%;
|
|
3149
|
+
max-width: 50%;
|
|
2476
3150
|
appearance: none;
|
|
2477
3151
|
-webkit-appearance: none;
|
|
2478
3152
|
}
|
|
@@ -2556,6 +3230,11 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
2556
3230
|
.lb-mix-match__modal-overlay--open .lb-mix-match__modal {
|
|
2557
3231
|
transform: translateY(0);
|
|
2558
3232
|
}
|
|
3233
|
+
|
|
3234
|
+
.lb-mix-match__variant-select {
|
|
3235
|
+
width: 80%;
|
|
3236
|
+
max-width: 80%;
|
|
3237
|
+
}
|
|
2559
3238
|
}
|
|
2560
3239
|
|
|
2561
3240
|
/* === Reduced Motion === */
|
|
@@ -2573,6 +3252,22 @@ var BUNDLE_VOLUME_CSS = `/* Lime Bundles \u2014 Volume / Quantity Breaks styles
|
|
|
2573
3252
|
display: flex;
|
|
2574
3253
|
flex-direction: column;
|
|
2575
3254
|
gap: 12px;
|
|
3255
|
+
max-height: var(--lb-list-max-height);
|
|
3256
|
+
overflow-y: auto;
|
|
3257
|
+
/* Custom scrollbar \u2014 text colour at 15% opacity (thumb) and 5% (track). */
|
|
3258
|
+
scrollbar-width: thin;
|
|
3259
|
+
scrollbar-color: color-mix(in srgb, var(--lb-text) 15%, transparent)
|
|
3260
|
+
color-mix(in srgb, var(--lb-text) 2%, transparent);
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
.lb-volume__tiers::-webkit-scrollbar { width: 4px; }
|
|
3264
|
+
.lb-volume__tiers::-webkit-scrollbar-track {
|
|
3265
|
+
background: color-mix(in srgb, var(--lb-text) 2%, transparent);
|
|
3266
|
+
border-radius: 2px;
|
|
3267
|
+
}
|
|
3268
|
+
.lb-volume__tiers::-webkit-scrollbar-thumb {
|
|
3269
|
+
background: color-mix(in srgb, var(--lb-text) 15%, transparent);
|
|
3270
|
+
border-radius: 2px;
|
|
2576
3271
|
}
|
|
2577
3272
|
|
|
2578
3273
|
.lb-volume__tier {
|
|
@@ -2677,6 +3372,259 @@ var BUNDLE_VOLUME_CSS = `/* Lime Bundles \u2014 Volume / Quantity Breaks styles
|
|
|
2677
3372
|
}
|
|
2678
3373
|
|
|
2679
3374
|
|
|
3375
|
+
`;
|
|
3376
|
+
var BUNDLE_DROPDOWN_CSS = `/**
|
|
3377
|
+
* Lime Bundles \u2014 Custom variant-picker dropdown styling.
|
|
3378
|
+
*
|
|
3379
|
+
* Reuses existing CSS variables: no new merchant-configurable surface.
|
|
3380
|
+
* --lb-variant-border-{width,color}, --lb-variant-radius, --lb-variant-chevron
|
|
3381
|
+
* --lb-bg, --lb-text, --lb-primary-color
|
|
3382
|
+
*
|
|
3383
|
+
* Mix-match modal context overrides via .lb-mix-match__modal scope to use
|
|
3384
|
+
* --lb-picker-variant-* and --lb-picker-bg.
|
|
3385
|
+
*/
|
|
3386
|
+
|
|
3387
|
+
/* Hide the native <select> while keeping it form-serializable and focusable
|
|
3388
|
+
programmatically. The .lb-dropdown-state marker is added by JS at bind
|
|
3389
|
+
time, so this rule matches every variant-select class (main widget,
|
|
3390
|
+
mix-match modal, future bundle types). aria-hidden + tabindex=-1
|
|
3391
|
+
(also set in JS) remove it from the accessibility tree. */
|
|
3392
|
+
.lb-dropdown-state {
|
|
3393
|
+
position: absolute !important;
|
|
3394
|
+
width: 1px !important;
|
|
3395
|
+
height: 1px !important;
|
|
3396
|
+
padding: 0 !important;
|
|
3397
|
+
margin: -1px !important;
|
|
3398
|
+
overflow: hidden !important;
|
|
3399
|
+
clip: rect(0 0 0 0) !important;
|
|
3400
|
+
white-space: nowrap !important;
|
|
3401
|
+
border: 0 !important;
|
|
3402
|
+
pointer-events: none !important;
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
/* Shell fills its parent column. */
|
|
3406
|
+
.lb-dropdown {
|
|
3407
|
+
position: relative;
|
|
3408
|
+
display: inline-block;
|
|
3409
|
+
width: 100%;
|
|
3410
|
+
max-width: 100%;
|
|
3411
|
+
font-family: inherit;
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
/* Trigger styled identically to the closed-state native select */
|
|
3415
|
+
.lb-dropdown-trigger {
|
|
3416
|
+
display: inline-flex;
|
|
3417
|
+
align-items: center;
|
|
3418
|
+
justify-content: space-between;
|
|
3419
|
+
gap: 8px;
|
|
3420
|
+
width: 100%;
|
|
3421
|
+
border: var(--lb-variant-border-width) solid var(--lb-variant-border-color);
|
|
3422
|
+
border-radius: var(--lb-variant-radius);
|
|
3423
|
+
padding: 8px 12px;
|
|
3424
|
+
font-size: 12px;
|
|
3425
|
+
line-height: 16px;
|
|
3426
|
+
color: var(--lb-text);
|
|
3427
|
+
background: var(--lb-bg);
|
|
3428
|
+
font-family: inherit;
|
|
3429
|
+
cursor: pointer;
|
|
3430
|
+
appearance: none;
|
|
3431
|
+
-webkit-appearance: none;
|
|
3432
|
+
text-align: start;
|
|
3433
|
+
transition: border-color 120ms ease;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
.lb-dropdown-trigger:focus-visible {
|
|
3437
|
+
outline: 2px solid var(--lb-primary-color);
|
|
3438
|
+
outline-offset: 2px;
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
.lb-dropdown-trigger[aria-expanded="true"] {
|
|
3442
|
+
border-color: var(--lb-text);
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
.lb-dropdown-trigger-value {
|
|
3446
|
+
flex: 1 1 auto;
|
|
3447
|
+
white-space: nowrap;
|
|
3448
|
+
overflow: hidden;
|
|
3449
|
+
text-overflow: ellipsis;
|
|
3450
|
+
text-align: start;
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
.lb-dropdown-chevron {
|
|
3454
|
+
flex: 0 0 auto;
|
|
3455
|
+
width: 12px;
|
|
3456
|
+
height: 12px;
|
|
3457
|
+
background: var(--lb-variant-chevron) center / contain no-repeat;
|
|
3458
|
+
transition: transform 120ms ease;
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
.lb-dropdown-trigger[aria-expanded="true"] .lb-dropdown-chevron {
|
|
3462
|
+
transform: rotate(180deg);
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3465
|
+
/* Popover panel \u2014 position: absolute against the .lb-dropdown shell
|
|
3466
|
+
(already position: relative). Top/left/width come from CSS so we
|
|
3467
|
+
never depend on JS having set inline coords by the time the panel
|
|
3468
|
+
becomes visible. JS only sets max-height. */
|
|
3469
|
+
.lb-dropdown-listbox {
|
|
3470
|
+
position: absolute;
|
|
3471
|
+
left: 0;
|
|
3472
|
+
/* Default to below-trigger placement so the panel doesn't overlap the
|
|
3473
|
+
trigger if data-placement is missing for any reason. The explicit
|
|
3474
|
+
[data-placement="down"|"up"] rules below override this. */
|
|
3475
|
+
top: calc(100% + 4px);
|
|
3476
|
+
width: 100%;
|
|
3477
|
+
z-index: 9999;
|
|
3478
|
+
margin: 0;
|
|
3479
|
+
padding: 4px 0;
|
|
3480
|
+
list-style: none;
|
|
3481
|
+
background: var(--lb-bg);
|
|
3482
|
+
color: var(--lb-text);
|
|
3483
|
+
border: var(--lb-variant-border-width) solid var(--lb-variant-border-color);
|
|
3484
|
+
border-radius: var(--lb-variant-radius);
|
|
3485
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
|
3486
|
+
overflow-y: auto;
|
|
3487
|
+
overflow-x: hidden;
|
|
3488
|
+
/* Custom scrollbar \u2014 text colour at 15% opacity (thumb) and 5% (track). */
|
|
3489
|
+
scrollbar-width: thin;
|
|
3490
|
+
scrollbar-color: color-mix(in srgb, var(--lb-text) 15%, transparent)
|
|
3491
|
+
color-mix(in srgb, var(--lb-text) 2%, transparent);
|
|
3492
|
+
animation: lb-dropdown-in-down 120ms ease-out;
|
|
3493
|
+
transform-origin: top center;
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
.lb-dropdown-listbox[data-placement="down"] {
|
|
3497
|
+
top: calc(100% + 4px);
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
.lb-dropdown-listbox[data-placement="up"] {
|
|
3501
|
+
top: auto;
|
|
3502
|
+
bottom: calc(100% + 4px);
|
|
3503
|
+
animation-name: lb-dropdown-in-up;
|
|
3504
|
+
transform-origin: bottom center;
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
/* When portaled out of the .lb-dropdown shell (mix-match modal context:
|
|
3508
|
+
.lb-mix-match__modal applies translateY which would otherwise trap
|
|
3509
|
+
position:fixed), switch to fixed and let JS set viewport coords. */
|
|
3510
|
+
.lb-dropdown-listbox[data-lb-dropdown-portal] {
|
|
3511
|
+
position: fixed;
|
|
3512
|
+
top: auto;
|
|
3513
|
+
left: auto;
|
|
3514
|
+
bottom: auto;
|
|
3515
|
+
width: auto;
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
/* Custom scrollbar \u2014 Webkit/Blink: exact 4px width */
|
|
3519
|
+
.lb-dropdown-listbox::-webkit-scrollbar {
|
|
3520
|
+
width: 4px;
|
|
3521
|
+
}
|
|
3522
|
+
.lb-dropdown-listbox::-webkit-scrollbar-track {
|
|
3523
|
+
background: color-mix(in srgb, var(--lb-text) 2%, transparent);
|
|
3524
|
+
border-radius: 2px;
|
|
3525
|
+
}
|
|
3526
|
+
.lb-dropdown-listbox::-webkit-scrollbar-thumb {
|
|
3527
|
+
background: color-mix(in srgb, var(--lb-text) 15%, transparent);
|
|
3528
|
+
border-radius: 2px;
|
|
3529
|
+
}
|
|
3530
|
+
|
|
3531
|
+
/* Options */
|
|
3532
|
+
.lb-dropdown-option {
|
|
3533
|
+
padding: 8px 12px;
|
|
3534
|
+
font-size: 12px;
|
|
3535
|
+
line-height: 16px;
|
|
3536
|
+
cursor: pointer;
|
|
3537
|
+
white-space: nowrap;
|
|
3538
|
+
overflow: hidden;
|
|
3539
|
+
text-overflow: ellipsis;
|
|
3540
|
+
color: var(--lb-text);
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3543
|
+
.lb-dropdown-option[aria-selected="true"] {
|
|
3544
|
+
font-weight: 600;
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
.lb-dropdown-option.is-active,
|
|
3548
|
+
.lb-dropdown-option:hover:not([aria-disabled="true"]) {
|
|
3549
|
+
background: color-mix(in srgb, var(--lb-text) 2%, transparent);
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
.lb-dropdown-option[aria-disabled="true"] {
|
|
3553
|
+
opacity: 0.4;
|
|
3554
|
+
cursor: not-allowed;
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3557
|
+
/* Animations */
|
|
3558
|
+
@keyframes lb-dropdown-in-down {
|
|
3559
|
+
from { opacity: 0; transform: translateY(-4px) scale(0.98); }
|
|
3560
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
@keyframes lb-dropdown-in-up {
|
|
3564
|
+
from { opacity: 0; transform: translateY(4px) scale(0.98); }
|
|
3565
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3569
|
+
.lb-dropdown-listbox { animation: none; }
|
|
3570
|
+
.lb-dropdown-chevron { transition: none; }
|
|
3571
|
+
.lb-dropdown-trigger { transition: none; }
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
/* Forced-colors mode (Windows high-contrast) */
|
|
3575
|
+
@media (forced-colors: active) {
|
|
3576
|
+
.lb-dropdown-trigger {
|
|
3577
|
+
border-color: ButtonBorder;
|
|
3578
|
+
color: ButtonText;
|
|
3579
|
+
background: ButtonFace;
|
|
3580
|
+
}
|
|
3581
|
+
.lb-dropdown-listbox {
|
|
3582
|
+
border-color: ButtonBorder;
|
|
3583
|
+
background: Canvas;
|
|
3584
|
+
color: CanvasText;
|
|
3585
|
+
}
|
|
3586
|
+
.lb-dropdown-option.is-active {
|
|
3587
|
+
background: Highlight;
|
|
3588
|
+
color: HighlightText;
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
/* Mix-match modal context \u2014 use picker-scoped variables.
|
|
3593
|
+
No CSS fallbacks: --lb-picker-* are always emitted by bundle-widget.liquid
|
|
3594
|
+
because WidgetConfig.parse() fully hydrates the merchant config.
|
|
3595
|
+
See docs/solutions/ui-bugs/widget-css-single-source-defaults.md. */
|
|
3596
|
+
.lb-mix-match__modal .lb-dropdown-trigger {
|
|
3597
|
+
border-color: var(--lb-picker-variant-border-color);
|
|
3598
|
+
border-width: var(--lb-picker-variant-border-width);
|
|
3599
|
+
border-radius: var(--lb-picker-variant-radius);
|
|
3600
|
+
background: var(--lb-picker-bg);
|
|
3601
|
+
color: var(--lb-picker-text);
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
.lb-mix-match__modal .lb-dropdown-chevron {
|
|
3605
|
+
background-image: var(--lb-picker-variant-chevron);
|
|
3606
|
+
}
|
|
3607
|
+
|
|
3608
|
+
/* Listbox is portaled out of the transformed .lb-mix-match__modal up to
|
|
3609
|
+
its [data-modal-overlay] parent, so picker-scoped rules anchor on the
|
|
3610
|
+
overlay attribute, not the modal class. */
|
|
3611
|
+
[data-modal-overlay] > .lb-dropdown-listbox {
|
|
3612
|
+
border-color: var(--lb-picker-variant-border-color);
|
|
3613
|
+
border-width: var(--lb-picker-variant-border-width);
|
|
3614
|
+
border-radius: var(--lb-picker-variant-radius);
|
|
3615
|
+
background: var(--lb-picker-bg);
|
|
3616
|
+
color: var(--lb-picker-text);
|
|
3617
|
+
scrollbar-color: color-mix(in srgb, var(--lb-picker-text) 15%, transparent)
|
|
3618
|
+
color-mix(in srgb, var(--lb-picker-text) 2%, transparent);
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
[data-modal-overlay] > .lb-dropdown-listbox::-webkit-scrollbar-track {
|
|
3622
|
+
background: color-mix(in srgb, var(--lb-picker-text) 2%, transparent);
|
|
3623
|
+
border-radius: 2px;
|
|
3624
|
+
}
|
|
3625
|
+
[data-modal-overlay] > .lb-dropdown-listbox::-webkit-scrollbar-thumb {
|
|
3626
|
+
background: color-mix(in srgb, var(--lb-picker-text) 15%, transparent);
|
|
3627
|
+
}
|
|
2680
3628
|
`;
|
|
2681
3629
|
var BUNDLE_SKELETON_CSS = `/* Lime Bundles \u2014 web-component loading skeleton (not mirrored to theme assets) */
|
|
2682
3630
|
|
|
@@ -3112,7 +4060,8 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
3112
4060
|
BUNDLE_BASE_CSS,
|
|
3113
4061
|
BUNDLE_FIXED_CSS,
|
|
3114
4062
|
BUNDLE_MIX_MATCH_CSS,
|
|
3115
|
-
BUNDLE_VOLUME_CSS
|
|
4063
|
+
BUNDLE_VOLUME_CSS,
|
|
4064
|
+
BUNDLE_DROPDOWN_CSS
|
|
3116
4065
|
].join("\n");
|
|
3117
4066
|
this.shadow.appendChild(style);
|
|
3118
4067
|
if (this.shopCustomCss) {
|
|
@@ -3129,6 +4078,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
3129
4078
|
container.setAttribute("data-bundle-type", bundle.bundleType);
|
|
3130
4079
|
container.setAttribute("data-bundle-gid", bundle.id);
|
|
3131
4080
|
applyWidgetConfigVars(container, bundle.widgetConfig);
|
|
4081
|
+
this.renderCleanups.push(trackInputMode(container));
|
|
3132
4082
|
const dispatch = (lines) => this.handleAddToCart(bundle, lines);
|
|
3133
4083
|
const registerCleanup = (fn) => this.renderCleanups.push(fn);
|
|
3134
4084
|
switch (bundle.bundleType) {
|
|
@@ -3230,6 +4180,7 @@ if (typeof customElements !== "undefined" && !customElements.get("lime-bundle"))
|
|
|
3230
4180
|
customElements.define("lime-bundle", LimeBundleElement);
|
|
3231
4181
|
}
|
|
3232
4182
|
export {
|
|
3233
|
-
LimeBundleElement
|
|
4183
|
+
LimeBundleElement,
|
|
4184
|
+
trackInputMode
|
|
3234
4185
|
};
|
|
3235
4186
|
//# sourceMappingURL=index.js.map
|