@lime-bundles/widget 4.5.0 → 4.6.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/README.md +5 -2
- package/dist/index.cjs +259 -48
- package/dist/index.d.cts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +261 -49
- package/dist/lime-bundle.global.js +196 -56
- package/dist/lime-bundle.global.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -109,17 +109,31 @@ declare class LimeBundleElement extends HTMLElement {
|
|
|
109
109
|
private get country();
|
|
110
110
|
private get language();
|
|
111
111
|
private get marketId();
|
|
112
|
+
/**
|
|
113
|
+
* Store→presentment currency rate. Merchant-configured amounts
|
|
114
|
+
* (fixed_amount / flat_price values, volume tier amounts) are stored in
|
|
115
|
+
* the shop's store currency; set this to the buyer's currency rate so
|
|
116
|
+
* quoted sale prices match what the checkout's discount function
|
|
117
|
+
* charges. Anything unparseable or non-positive falls back to 1 —
|
|
118
|
+
* no conversion — via `normalizeCurrencyRate`.
|
|
119
|
+
*/
|
|
120
|
+
private get currencyRate();
|
|
112
121
|
/**
|
|
113
122
|
* Returns the @inContext-wrapped query when any context field is set;
|
|
114
123
|
* otherwise the plain query. Keeps responses publicly cacheable when
|
|
115
124
|
* no buyer/country/language is set.
|
|
116
125
|
*/
|
|
117
126
|
private wrapQueryForContext;
|
|
127
|
+
/** Buyer resolved once per fetch so the market gate can read the
|
|
128
|
+
* company location without invoking a callback resolver twice. */
|
|
129
|
+
private resolvedBuyer;
|
|
118
130
|
/**
|
|
119
|
-
* Returns true if this bundle should be hidden for the current
|
|
131
|
+
* Returns true if this bundle should be hidden for the current buyer.
|
|
120
132
|
* For "all" bundles, always returns false (visible). For "specific"
|
|
121
|
-
* bundles,
|
|
122
|
-
*
|
|
133
|
+
* bundles, visibility needs a matching signal: the `country` attribute
|
|
134
|
+
* against the bundle's projected country codes (retail markets), the
|
|
135
|
+
* resolved buyer's companyLocationId against its projected company
|
|
136
|
+
* locations (B2B markets), or a legacy `market-id` match.
|
|
123
137
|
*/
|
|
124
138
|
private isMarketHidden;
|
|
125
139
|
private fetchBundle;
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
CART_LINES_REMOVE_MUTATION,
|
|
10
10
|
SHOP_SETTINGS_QUERY,
|
|
11
11
|
parseMetaobjectBundle,
|
|
12
|
+
convertBundleToPresentment,
|
|
12
13
|
observeImpression,
|
|
13
14
|
reportImpression,
|
|
14
15
|
reportAddToCart,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
sanitizeCustomCss,
|
|
17
18
|
hasInContext,
|
|
18
19
|
withInContext,
|
|
19
|
-
|
|
20
|
+
isVisibleToBuyer,
|
|
20
21
|
getVisitorId,
|
|
21
22
|
resolveAbTests
|
|
22
23
|
} from "@lime-bundles/core";
|
|
@@ -3582,6 +3583,8 @@ function mergeCartLines(selections) {
|
|
|
3582
3583
|
var ALL_TYPES2 = "__all__";
|
|
3583
3584
|
var CLOSE_TRANSITION_MS2 = 300;
|
|
3584
3585
|
var SEARCH_DEBOUNCE_MS2 = 200;
|
|
3586
|
+
var STEP_CASCADE_MS = 500;
|
|
3587
|
+
var AUTO_ADVANCE_HOLD_MS = 160;
|
|
3585
3588
|
function createWizard(deps) {
|
|
3586
3589
|
const { container, steps, selections, t } = deps;
|
|
3587
3590
|
const overlay = container.querySelector("[data-modal-overlay]");
|
|
@@ -3606,6 +3609,8 @@ function createWizard(deps) {
|
|
|
3606
3609
|
let modalOpen = false;
|
|
3607
3610
|
let stepIndex = 0;
|
|
3608
3611
|
let rows = [];
|
|
3612
|
+
let cascadeTimer = null;
|
|
3613
|
+
let autoAdvanceTimer = null;
|
|
3609
3614
|
const productsFor = (i) => steps[i].eligibleProducts || [];
|
|
3610
3615
|
const rowCapacity = {
|
|
3611
3616
|
remainingSpots: () => stepHeadroom(selections, steps, stepIndex),
|
|
@@ -3706,7 +3711,13 @@ function createWizard(deps) {
|
|
|
3706
3711
|
if (!deps.autoAdvance) return;
|
|
3707
3712
|
if (stepIndex >= steps.length - 1) return;
|
|
3708
3713
|
if (stepHeadroom(selections, steps, stepIndex) !== 0) return;
|
|
3709
|
-
|
|
3714
|
+
const from = stepIndex;
|
|
3715
|
+
if (autoAdvanceTimer !== null) clearTimeout(autoAdvanceTimer);
|
|
3716
|
+
autoAdvanceTimer = setTimeout(() => {
|
|
3717
|
+
autoAdvanceTimer = null;
|
|
3718
|
+
if (!modalOpen) return;
|
|
3719
|
+
goToStep(from + 1);
|
|
3720
|
+
}, AUTO_ADVANCE_HOLD_MS);
|
|
3710
3721
|
}
|
|
3711
3722
|
function buildProductList(forStep) {
|
|
3712
3723
|
if (!modalList) return;
|
|
@@ -3894,7 +3905,29 @@ function createWizard(deps) {
|
|
|
3894
3905
|
});
|
|
3895
3906
|
}
|
|
3896
3907
|
function goToStep(i, opts = {}) {
|
|
3897
|
-
|
|
3908
|
+
const target = Math.max(0, Math.min(steps.length - 1, i));
|
|
3909
|
+
if (autoAdvanceTimer !== null) {
|
|
3910
|
+
clearTimeout(autoAdvanceTimer);
|
|
3911
|
+
autoAdvanceTimer = null;
|
|
3912
|
+
}
|
|
3913
|
+
if (modalDialog) {
|
|
3914
|
+
if (cascadeTimer !== null) clearTimeout(cascadeTimer);
|
|
3915
|
+
cascadeTimer = null;
|
|
3916
|
+
modalDialog.removeAttribute("data-step-transition");
|
|
3917
|
+
if (opts.animate !== false && target !== stepIndex) {
|
|
3918
|
+
void modalDialog.offsetHeight;
|
|
3919
|
+
modalDialog.setAttribute(
|
|
3920
|
+
"data-step-transition",
|
|
3921
|
+
target > stepIndex ? "forward" : "back"
|
|
3922
|
+
);
|
|
3923
|
+
const dialog = modalDialog;
|
|
3924
|
+
cascadeTimer = setTimeout(() => {
|
|
3925
|
+
cascadeTimer = null;
|
|
3926
|
+
dialog.removeAttribute("data-step-transition");
|
|
3927
|
+
}, STEP_CASCADE_MS);
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
stepIndex = target;
|
|
3898
3931
|
buildProductList(stepIndex);
|
|
3899
3932
|
buildFilters(stepIndex);
|
|
3900
3933
|
activeType = ALL_TYPES2;
|
|
@@ -3919,7 +3952,10 @@ function createWizard(deps) {
|
|
|
3919
3952
|
function open(atStep) {
|
|
3920
3953
|
if (!overlay || modalOpen) return;
|
|
3921
3954
|
modalOpen = true;
|
|
3922
|
-
goToStep(typeof atStep === "number" ? atStep : 0, {
|
|
3955
|
+
goToStep(typeof atStep === "number" ? atStep : 0, {
|
|
3956
|
+
skipFocus: true,
|
|
3957
|
+
animate: false
|
|
3958
|
+
});
|
|
3923
3959
|
overlay.style.display = "";
|
|
3924
3960
|
void overlay.offsetHeight;
|
|
3925
3961
|
overlay.classList.add("lb-mix-match__modal-overlay--open");
|
|
@@ -3932,6 +3968,14 @@ function createWizard(deps) {
|
|
|
3932
3968
|
function close() {
|
|
3933
3969
|
if (!overlay || !modalOpen) return;
|
|
3934
3970
|
modalOpen = false;
|
|
3971
|
+
if (autoAdvanceTimer !== null) {
|
|
3972
|
+
clearTimeout(autoAdvanceTimer);
|
|
3973
|
+
autoAdvanceTimer = null;
|
|
3974
|
+
}
|
|
3975
|
+
if (cascadeTimer !== null) {
|
|
3976
|
+
clearTimeout(cascadeTimer);
|
|
3977
|
+
cascadeTimer = null;
|
|
3978
|
+
}
|
|
3935
3979
|
overlay.classList.remove("lb-mix-match__modal-overlay--open");
|
|
3936
3980
|
unlock();
|
|
3937
3981
|
setTimeout(() => {
|
|
@@ -4319,11 +4363,18 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
4319
4363
|
--lb-list-max-height: 360px;
|
|
4320
4364
|
/* Depth of the soft fade at a scroll list's clipped edge. */
|
|
4321
4365
|
--lb-edge-fade-size: 20px;
|
|
4322
|
-
/* Derived "small" corner radius for
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4366
|
+
/* Derived "small" corner radius for chips and badges \u2014 the low-stock badge,
|
|
4367
|
+
the volume tier badge, the multi-step count chip. Controls and media
|
|
4368
|
+
(thumbnails, selects, steppers, buttons) take the full --lb-radius: they
|
|
4369
|
+
are the elements the merchant is looking at when they pick a preset, and
|
|
4370
|
+
the picker's own help text already promises they follow it.
|
|
4371
|
+
|
|
4372
|
+
Proportional rather than a fixed subtraction. At "subtle" (6px) minus-4px
|
|
4373
|
+
left 2px, which reads as square \u2014 the merchant saw a picker set to Subtle
|
|
4374
|
+
render with sharp corners (issue #363). Two thirds holds the proportion at
|
|
4375
|
+
every preset: none 0, subtle 4px, rounded 8px, round 12px. No max() needed,
|
|
4376
|
+
since 0 \xD7 \u2154 is 0. */
|
|
4377
|
+
--lb-radius-sm: calc(var(--lb-radius) * 2 / 3);
|
|
4327
4378
|
|
|
4328
4379
|
font-family: inherit;
|
|
4329
4380
|
font-size: 16px;
|
|
@@ -4487,7 +4538,7 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
4487
4538
|
ratio. Default keeps the historical 1:1 behaviour. */
|
|
4488
4539
|
aspect-ratio: var(--lb-thumbnail-aspect-ratio, 1 / 1);
|
|
4489
4540
|
background: var(--lb-thumbnail-bg);
|
|
4490
|
-
border-radius: var(--lb-radius
|
|
4541
|
+
border-radius: var(--lb-radius);
|
|
4491
4542
|
overflow: hidden;
|
|
4492
4543
|
display: flex;
|
|
4493
4544
|
align-items: center;
|
|
@@ -5037,12 +5088,12 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
|
|
|
5037
5088
|
.lb-fixed .lb-bundle-thumbnail {
|
|
5038
5089
|
width: 60px;
|
|
5039
5090
|
min-width: 60px;
|
|
5040
|
-
border-radius: var(--lb-radius
|
|
5091
|
+
border-radius: var(--lb-radius);
|
|
5041
5092
|
box-sizing: border-box;
|
|
5042
5093
|
}
|
|
5043
5094
|
|
|
5044
5095
|
.lb-fixed .lb-bundle-thumbnail img {
|
|
5045
|
-
border-radius: var(--lb-radius
|
|
5096
|
+
border-radius: var(--lb-radius);
|
|
5046
5097
|
border: none;
|
|
5047
5098
|
}
|
|
5048
5099
|
|
|
@@ -5059,7 +5110,7 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
|
|
|
5059
5110
|
display: var(--lb-product-qty-chip-display, inline-flex);
|
|
5060
5111
|
align-items: center;
|
|
5061
5112
|
justify-content: center;
|
|
5062
|
-
border-radius: var(--lb-radius
|
|
5113
|
+
border-radius: var(--lb-radius);
|
|
5063
5114
|
background: color-mix(in srgb, var(--lb-text) 7%, transparent);
|
|
5064
5115
|
color: color-mix(in srgb, var(--lb-text) 75%, transparent);
|
|
5065
5116
|
font-size: 13px;
|
|
@@ -5075,7 +5126,7 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
|
|
|
5075
5126
|
.lb-bundle-variant-select {
|
|
5076
5127
|
display: inline-block;
|
|
5077
5128
|
border: var(--lb-border-width) solid var(--lb-border);
|
|
5078
|
-
border-radius: var(--lb-radius
|
|
5129
|
+
border-radius: var(--lb-radius);
|
|
5079
5130
|
padding: 8px 32px 8px 12px;
|
|
5080
5131
|
font-size: 12px;
|
|
5081
5132
|
line-height: 16px;
|
|
@@ -5223,12 +5274,12 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5223
5274
|
position: relative;
|
|
5224
5275
|
width: 60px;
|
|
5225
5276
|
min-width: 60px;
|
|
5226
|
-
border-radius: var(--lb-radius
|
|
5277
|
+
border-radius: var(--lb-radius);
|
|
5227
5278
|
box-sizing: border-box;
|
|
5228
5279
|
}
|
|
5229
5280
|
|
|
5230
5281
|
.lb-mix-match .lb-bundle-thumbnail img {
|
|
5231
|
-
border-radius: var(--lb-radius
|
|
5282
|
+
border-radius: var(--lb-radius);
|
|
5232
5283
|
border: none;
|
|
5233
5284
|
}
|
|
5234
5285
|
|
|
@@ -5394,14 +5445,26 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5394
5445
|
color: inherit;
|
|
5395
5446
|
}
|
|
5396
5447
|
|
|
5397
|
-
/* Derived "small" radius for the picker's
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5448
|
+
/* Derived "small" radius for the picker's chips and badges \u2014 the progress
|
|
5449
|
+
segments, the "Added" badge, the close button, the filter pills. The
|
|
5450
|
+
picker-scoped mirror of --lb-radius-sm, and proportional for the same reason
|
|
5451
|
+
(see bundle-base.css).
|
|
5452
|
+
|
|
5453
|
+
Everything the merchant would call a control or an image \u2014 the modal, search,
|
|
5454
|
+
product tiles, thumbnails, variant dropdown, quantity stepper and the
|
|
5455
|
+
buttons \u2014 takes the full --lb-picker-radius instead. That is what the
|
|
5456
|
+
editor's own Borders copy promises them, and the reduced tier had it
|
|
5457
|
+
rendering square at the Subtle preset (issue #363).
|
|
5458
|
+
|
|
5459
|
+
Declared on BOTH hosts the modal can live under. On the storefront it portals
|
|
5460
|
+
to [data-modal-overlay], which the variant listbox portals up to as well. In
|
|
5461
|
+
the admin widget editor there is no overlay at all \u2014 the preview renders the
|
|
5462
|
+
modal directly under .lb-bundle-widget \u2014 so scoping this to the overlay alone
|
|
5463
|
+
left the variable undefined there and every control using it fell back to
|
|
5464
|
+
square corners while the modal and tiles rounded correctly (issue #363). */
|
|
5465
|
+
.lb-bundle-widget,
|
|
5403
5466
|
[data-modal-overlay] {
|
|
5404
|
-
--lb-picker-radius-sm:
|
|
5467
|
+
--lb-picker-radius-sm: calc(var(--lb-picker-radius) * 2 / 3);
|
|
5405
5468
|
}
|
|
5406
5469
|
|
|
5407
5470
|
/* === Modal Overlay === */
|
|
@@ -5498,7 +5561,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5498
5561
|
border: none;
|
|
5499
5562
|
cursor: pointer;
|
|
5500
5563
|
color: var(--lb-picker-text);
|
|
5501
|
-
border-radius:
|
|
5564
|
+
border-radius: var(--lb-picker-radius-sm);
|
|
5502
5565
|
padding: 0;
|
|
5503
5566
|
margin: -12px -12px 0 0;
|
|
5504
5567
|
transition: background 0.25s ease;
|
|
@@ -5528,7 +5591,9 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5528
5591
|
.lb-mix-match__filter {
|
|
5529
5592
|
padding: 6px 14px;
|
|
5530
5593
|
border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
|
|
5531
|
-
|
|
5594
|
+
/* Pills followed the merchant's picker radius as of #363 \u2014 a fixed 999px
|
|
5595
|
+
read as a stray rounded shape in a picker set to None or Subtle. */
|
|
5596
|
+
border-radius: var(--lb-picker-radius-sm);
|
|
5532
5597
|
background: transparent;
|
|
5533
5598
|
color: var(--lb-picker-text);
|
|
5534
5599
|
font-family: inherit;
|
|
@@ -5582,7 +5647,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5582
5647
|
width: 100%;
|
|
5583
5648
|
padding: 12px 40px 12px 44px;
|
|
5584
5649
|
border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
|
|
5585
|
-
border-radius: var(--lb-picker-radius
|
|
5650
|
+
border-radius: var(--lb-picker-radius);
|
|
5586
5651
|
font-size: 16px;
|
|
5587
5652
|
line-height: 20px;
|
|
5588
5653
|
color: var(--lb-picker-text);
|
|
@@ -5682,7 +5747,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5682
5747
|
independent from the main widget's thumbnailRatio so a merchant can
|
|
5683
5748
|
e.g. show tall picker thumbs with square main thumbs. */
|
|
5684
5749
|
aspect-ratio: var(--lb-picker-thumbnail-aspect-ratio, 1 / 1);
|
|
5685
|
-
border-radius: var(--lb-picker-radius
|
|
5750
|
+
border-radius: var(--lb-picker-radius);
|
|
5686
5751
|
box-sizing: border-box;
|
|
5687
5752
|
overflow: hidden;
|
|
5688
5753
|
background: var(--lb-thumbnail-bg);
|
|
@@ -5695,7 +5760,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5695
5760
|
to auto/contain so the image renders uncropped at intrinsic ratio. */
|
|
5696
5761
|
height: var(--lb-picker-thumbnail-img-height, 100%);
|
|
5697
5762
|
object-fit: var(--lb-picker-thumbnail-img-fit, cover);
|
|
5698
|
-
border-radius: var(--lb-picker-radius
|
|
5763
|
+
border-radius: var(--lb-picker-radius);
|
|
5699
5764
|
}
|
|
5700
5765
|
|
|
5701
5766
|
.lb-mix-match__modal-product-info {
|
|
@@ -5770,7 +5835,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5770
5835
|
font-size: 12px;
|
|
5771
5836
|
padding: 4px 24px 4px 8px;
|
|
5772
5837
|
border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
|
|
5773
|
-
border-radius: var(--lb-picker-radius
|
|
5838
|
+
border-radius: var(--lb-picker-radius);
|
|
5774
5839
|
background-color: var(--lb-picker-bg);
|
|
5775
5840
|
background-image: var(--lb-picker-variant-chevron);
|
|
5776
5841
|
background-repeat: no-repeat;
|
|
@@ -5821,7 +5886,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5821
5886
|
width: 100%;
|
|
5822
5887
|
flex-shrink: 0;
|
|
5823
5888
|
border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
|
|
5824
|
-
border-radius: var(--lb-picker-radius
|
|
5889
|
+
border-radius: var(--lb-picker-radius);
|
|
5825
5890
|
background-color: var(--lb-picker-bg);
|
|
5826
5891
|
overflow: hidden;
|
|
5827
5892
|
box-sizing: border-box;
|
|
@@ -5898,7 +5963,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5898
5963
|
background: var(--lb-picker-add-bg);
|
|
5899
5964
|
color: var(--lb-picker-add-label);
|
|
5900
5965
|
border: var(--lb-picker-add-border-width) solid var(--lb-picker-add-border-color);
|
|
5901
|
-
border-radius: var(--lb-picker-radius
|
|
5966
|
+
border-radius: var(--lb-picker-radius);
|
|
5902
5967
|
box-sizing: border-box;
|
|
5903
5968
|
font-size: 12px;
|
|
5904
5969
|
font-weight: 600;
|
|
@@ -6039,7 +6104,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
6039
6104
|
background: var(--lb-picker-add-bg);
|
|
6040
6105
|
color: var(--lb-picker-add-label);
|
|
6041
6106
|
border: var(--lb-picker-add-border-width) solid var(--lb-picker-add-border-color);
|
|
6042
|
-
border-radius: var(--lb-picker-radius
|
|
6107
|
+
border-radius: var(--lb-picker-radius);
|
|
6043
6108
|
box-sizing: border-box;
|
|
6044
6109
|
font-family: inherit;
|
|
6045
6110
|
font-size: 14px;
|
|
@@ -6088,7 +6153,9 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
6088
6153
|
.lb-mix-match__modal {
|
|
6089
6154
|
max-width: 100%;
|
|
6090
6155
|
max-height: 90vh;
|
|
6091
|
-
|
|
6156
|
+
/* Bottom sheet: only the top corners round, but they round to whatever the
|
|
6157
|
+
merchant picked rather than a fixed 16px (issue #363). */
|
|
6158
|
+
border-radius: var(--lb-picker-radius) var(--lb-picker-radius) 0 0;
|
|
6092
6159
|
transform: translateY(100%);
|
|
6093
6160
|
}
|
|
6094
6161
|
|
|
@@ -6355,12 +6422,12 @@ var BUNDLE_BOGO_CSS = `/* Lime Bundles \u2014 BOGO (Buy X Get Y) bundle styles *
|
|
|
6355
6422
|
.lb-bogo .lb-bundle-thumbnail {
|
|
6356
6423
|
width: 60px;
|
|
6357
6424
|
min-width: 60px;
|
|
6358
|
-
border-radius: var(--lb-radius
|
|
6425
|
+
border-radius: var(--lb-radius);
|
|
6359
6426
|
box-sizing: border-box;
|
|
6360
6427
|
}
|
|
6361
6428
|
|
|
6362
6429
|
.lb-bogo .lb-bundle-thumbnail img {
|
|
6363
|
-
border-radius: var(--lb-radius
|
|
6430
|
+
border-radius: var(--lb-radius);
|
|
6364
6431
|
border: none;
|
|
6365
6432
|
}
|
|
6366
6433
|
|
|
@@ -6481,6 +6548,13 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6481
6548
|
cursor: pointer;
|
|
6482
6549
|
box-sizing: border-box;
|
|
6483
6550
|
transition: background-color 0.25s ease;
|
|
6551
|
+
/* Deliberately no border-radius. This row paints the checklist hairline as
|
|
6552
|
+
its own border-top (see the run rule above), and a radius tapers that
|
|
6553
|
+
border into the corner arcs \u2014 the dividers came out visibly curved at the
|
|
6554
|
+
"round" preset (issue #363). A full-bleed list row is meant to be square
|
|
6555
|
+
anyway; the hover tint below spans the card width, so it has no corner to
|
|
6556
|
+
round against. The :focus-visible radius is fine: an outline is a
|
|
6557
|
+
separate paint and never touches the border. */
|
|
6484
6558
|
}
|
|
6485
6559
|
|
|
6486
6560
|
.lb-multi-step__step-row:hover {
|
|
@@ -6628,7 +6702,7 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6628
6702
|
padding: 10px 24px;
|
|
6629
6703
|
background: none;
|
|
6630
6704
|
border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
|
|
6631
|
-
border-radius: var(--lb-picker-radius
|
|
6705
|
+
border-radius: var(--lb-picker-radius);
|
|
6632
6706
|
box-sizing: border-box;
|
|
6633
6707
|
color: var(--lb-picker-text);
|
|
6634
6708
|
font-family: inherit;
|
|
@@ -6646,6 +6720,108 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6646
6720
|
outline: 2px solid var(--lb-primary-color);
|
|
6647
6721
|
outline-offset: 2px;
|
|
6648
6722
|
}
|
|
6723
|
+
|
|
6724
|
+
/* \u2500\u2500 Step-change transition \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6725
|
+
Moving between steps swaps the whole step-scoped region in a single
|
|
6726
|
+
paint, which reads as nothing happening (or as a glitch) rather than
|
|
6727
|
+
as arriving somewhere new \u2014 and worst of all under auto-advance,
|
|
6728
|
+
where the shopper never clicked anything. So the step assembles
|
|
6729
|
+
instead of arriving whole: the step label leads, the filter pills
|
|
6730
|
+
follow, then the product cards one after another. Staging is what
|
|
6731
|
+
makes the change legible; a uniform slide of everything at once only
|
|
6732
|
+
wobbles the card.
|
|
6733
|
+
|
|
6734
|
+
The wizard stamps data-step-transition="forward" | "back" on the
|
|
6735
|
+
dialog on every step change (manual Next/Back and auto-advance
|
|
6736
|
+
alike, never on open, where the modal entrance already carries the
|
|
6737
|
+
motion), and clears it again once the cascade has played \u2014 see
|
|
6738
|
+
STEP_CASCADE_MS in multi-step/wizard.ts. That clear is load-bearing,
|
|
6739
|
+
not tidiness: search and filter hide cards with .lb-hidden
|
|
6740
|
+
(display: none), and restoring display restarts a card's animation,
|
|
6741
|
+
so a live attribute would replay the whole cascade on every
|
|
6742
|
+
keystroke that reveals a card.
|
|
6743
|
+
|
|
6744
|
+
Two elements deliberately hold still. The footer, because Back/Next
|
|
6745
|
+
are the shopper's anchor while everything above them moves. And the
|
|
6746
|
+
progress rail, because it is the one thing that answers "where am I
|
|
6747
|
+
now" \u2014 animating the shopper's only orientation cue is what made the
|
|
6748
|
+
previous version hard to read. Its segment fill carries its own
|
|
6749
|
+
state change already. */
|
|
6750
|
+
@keyframes lb-multi-step-cascade-fwd {
|
|
6751
|
+
from {
|
|
6752
|
+
opacity: 0;
|
|
6753
|
+
transform: translateY(10px);
|
|
6754
|
+
}
|
|
6755
|
+
}
|
|
6756
|
+
|
|
6757
|
+
@keyframes lb-multi-step-cascade-back {
|
|
6758
|
+
from {
|
|
6759
|
+
opacity: 0;
|
|
6760
|
+
transform: translateY(-10px);
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
|
|
6764
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-subtitle {
|
|
6765
|
+
animation: lb-multi-step-cascade-fwd 0.2s ease-out both;
|
|
6766
|
+
}
|
|
6767
|
+
|
|
6768
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__filters {
|
|
6769
|
+
animation: lb-multi-step-cascade-fwd 0.2s ease-out 0.05s both;
|
|
6770
|
+
}
|
|
6771
|
+
|
|
6772
|
+
/* Cards stagger 30ms apart, but only the first four: a step can hold
|
|
6773
|
+
dozens of products and the two-column grid shows about four at a
|
|
6774
|
+
time, so past that the stagger is below the fold and would only add
|
|
6775
|
+
lag. Everything from the fifth on shares the fourth's landing. */
|
|
6776
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-list > * {
|
|
6777
|
+
animation: lb-multi-step-cascade-fwd 0.22s ease-out 0.2s both;
|
|
6778
|
+
}
|
|
6779
|
+
|
|
6780
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-list > :nth-child(1) {
|
|
6781
|
+
animation-delay: 0.09s;
|
|
6782
|
+
}
|
|
6783
|
+
|
|
6784
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-list > :nth-child(2) {
|
|
6785
|
+
animation-delay: 0.12s;
|
|
6786
|
+
}
|
|
6787
|
+
|
|
6788
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-list > :nth-child(3) {
|
|
6789
|
+
animation-delay: 0.15s;
|
|
6790
|
+
}
|
|
6791
|
+
|
|
6792
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-list > :nth-child(4) {
|
|
6793
|
+
animation-delay: 0.18s;
|
|
6794
|
+
}
|
|
6795
|
+
|
|
6796
|
+
/* Going back runs the same cascade in the same order \u2014 only the offset
|
|
6797
|
+
flips, so the shopper still reads which way they moved. Overrides
|
|
6798
|
+
the name alone; the staggered delays above must survive. */
|
|
6799
|
+
.lb-mix-match__modal[data-step-transition="back"] .lb-mix-match__modal-subtitle,
|
|
6800
|
+
.lb-mix-match__modal[data-step-transition="back"] .lb-mix-match__filters,
|
|
6801
|
+
.lb-mix-match__modal[data-step-transition="back"] .lb-mix-match__modal-list > * {
|
|
6802
|
+
animation-name: lb-multi-step-cascade-back;
|
|
6803
|
+
}
|
|
6804
|
+
|
|
6805
|
+
@keyframes lb-multi-step-cascade-fade {
|
|
6806
|
+
from {
|
|
6807
|
+
opacity: 0;
|
|
6808
|
+
}
|
|
6809
|
+
}
|
|
6810
|
+
|
|
6811
|
+
/* Reduced motion keeps the fade and drops the travel and the stagger:
|
|
6812
|
+
the step change still has to be perceptible, which is the whole
|
|
6813
|
+
point of this block. The card selector is :nth-child(n) rather than
|
|
6814
|
+
* on purpose \u2014 a plain * loses to the :nth-child(1..4) delays above
|
|
6815
|
+
on specificity, which would leave the stagger running with nothing
|
|
6816
|
+
moving. :nth-child(n) matches every child at equal specificity and
|
|
6817
|
+
wins on order. */
|
|
6818
|
+
@media (prefers-reduced-motion: reduce) {
|
|
6819
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-subtitle,
|
|
6820
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__filters,
|
|
6821
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-list > :nth-child(n) {
|
|
6822
|
+
animation: lb-multi-step-cascade-fade 0.15s ease-out both;
|
|
6823
|
+
}
|
|
6824
|
+
}
|
|
6649
6825
|
`;
|
|
6650
6826
|
var BUNDLE_DROPDOWN_CSS = `/**
|
|
6651
6827
|
* Lime Bundles \u2014 Custom variant-picker dropdown styling.
|
|
@@ -6693,7 +6869,7 @@ var BUNDLE_DROPDOWN_CSS = `/**
|
|
|
6693
6869
|
gap: 8px;
|
|
6694
6870
|
width: 100%;
|
|
6695
6871
|
border: var(--lb-border-width) solid var(--lb-border);
|
|
6696
|
-
border-radius: var(--lb-radius
|
|
6872
|
+
border-radius: var(--lb-radius);
|
|
6697
6873
|
padding: 8px 12px;
|
|
6698
6874
|
font-size: 12px;
|
|
6699
6875
|
line-height: 16px;
|
|
@@ -6756,7 +6932,7 @@ var BUNDLE_DROPDOWN_CSS = `/**
|
|
|
6756
6932
|
background: var(--lb-bg);
|
|
6757
6933
|
color: var(--lb-text);
|
|
6758
6934
|
border: var(--lb-border-width) solid var(--lb-border);
|
|
6759
|
-
border-radius: var(--lb-radius
|
|
6935
|
+
border-radius: var(--lb-radius);
|
|
6760
6936
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
|
6761
6937
|
overflow-y: auto;
|
|
6762
6938
|
overflow-x: hidden;
|
|
@@ -6817,6 +6993,10 @@ var BUNDLE_DROPDOWN_CSS = `/**
|
|
|
6817
6993
|
overflow: hidden;
|
|
6818
6994
|
text-overflow: ellipsis;
|
|
6819
6995
|
color: var(--lb-text);
|
|
6996
|
+
/* The active/hover tint below fills the option, so it rounds with the rest
|
|
6997
|
+
of the widget rather than squaring off against the listbox corner it sits
|
|
6998
|
+
in (issue #363). */
|
|
6999
|
+
border-radius: var(--lb-radius-sm);
|
|
6820
7000
|
}
|
|
6821
7001
|
|
|
6822
7002
|
.lb-dropdown-option[aria-selected="true"] {
|
|
@@ -6875,7 +7055,7 @@ var BUNDLE_DROPDOWN_CSS = `/**
|
|
|
6875
7055
|
.lb-mix-match__modal .lb-dropdown-trigger {
|
|
6876
7056
|
border-color: var(--lb-picker-border-color);
|
|
6877
7057
|
border-width: var(--lb-picker-border-width);
|
|
6878
|
-
border-radius: var(--lb-picker-radius
|
|
7058
|
+
border-radius: var(--lb-picker-radius);
|
|
6879
7059
|
background: var(--lb-picker-bg);
|
|
6880
7060
|
color: var(--lb-picker-text);
|
|
6881
7061
|
/* Match the picker qty stepper + Add button height (border-box so 32px is
|
|
@@ -6896,13 +7076,17 @@ var BUNDLE_DROPDOWN_CSS = `/**
|
|
|
6896
7076
|
[data-modal-overlay] > .lb-dropdown-listbox {
|
|
6897
7077
|
border-color: var(--lb-picker-border-color);
|
|
6898
7078
|
border-width: var(--lb-picker-border-width);
|
|
6899
|
-
border-radius: var(--lb-picker-radius
|
|
7079
|
+
border-radius: var(--lb-picker-radius);
|
|
6900
7080
|
background: var(--lb-picker-bg);
|
|
6901
7081
|
color: var(--lb-picker-text);
|
|
6902
7082
|
scrollbar-color: color-mix(in srgb, var(--lb-picker-text) 15%, transparent)
|
|
6903
7083
|
color-mix(in srgb, var(--lb-picker-text) 2%, transparent);
|
|
6904
7084
|
}
|
|
6905
7085
|
|
|
7086
|
+
[data-modal-overlay] > .lb-dropdown-listbox .lb-dropdown-option {
|
|
7087
|
+
border-radius: var(--lb-picker-radius-sm);
|
|
7088
|
+
}
|
|
7089
|
+
|
|
6906
7090
|
[data-modal-overlay] > .lb-dropdown-listbox::-webkit-scrollbar-track {
|
|
6907
7091
|
background: color-mix(in srgb, var(--lb-picker-text) 2%, transparent);
|
|
6908
7092
|
border-radius: 2px;
|
|
@@ -7037,7 +7221,8 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7037
7221
|
"locale",
|
|
7038
7222
|
"country",
|
|
7039
7223
|
"language",
|
|
7040
|
-
"market-id"
|
|
7224
|
+
"market-id",
|
|
7225
|
+
"currency-rate"
|
|
7041
7226
|
];
|
|
7042
7227
|
/**
|
|
7043
7228
|
* B2B buyer identity. Set programmatically — `element.buyer = {...}` or
|
|
@@ -7092,7 +7277,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7092
7277
|
}
|
|
7093
7278
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
7094
7279
|
if (oldValue === newValue || !this.isConnected) return;
|
|
7095
|
-
if (name === "bundle-gid" || name === "product-handle" || name === "product-id" || name === "shop-domain" || name === "storefront-token") {
|
|
7280
|
+
if (name === "bundle-gid" || name === "product-handle" || name === "product-id" || name === "shop-domain" || name === "storefront-token" || name === "currency-rate") {
|
|
7096
7281
|
if (this.shopDomain && this.storefrontToken) {
|
|
7097
7282
|
this.fetchBundle();
|
|
7098
7283
|
}
|
|
@@ -7135,6 +7320,17 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7135
7320
|
get marketId() {
|
|
7136
7321
|
return this.getAttribute("market-id") ?? void 0;
|
|
7137
7322
|
}
|
|
7323
|
+
/**
|
|
7324
|
+
* Store→presentment currency rate. Merchant-configured amounts
|
|
7325
|
+
* (fixed_amount / flat_price values, volume tier amounts) are stored in
|
|
7326
|
+
* the shop's store currency; set this to the buyer's currency rate so
|
|
7327
|
+
* quoted sale prices match what the checkout's discount function
|
|
7328
|
+
* charges. Anything unparseable or non-positive falls back to 1 —
|
|
7329
|
+
* no conversion — via `normalizeCurrencyRate`.
|
|
7330
|
+
*/
|
|
7331
|
+
get currencyRate() {
|
|
7332
|
+
return this.getAttribute("currency-rate") ?? void 0;
|
|
7333
|
+
}
|
|
7138
7334
|
/**
|
|
7139
7335
|
* Returns the @inContext-wrapped query when any context field is set;
|
|
7140
7336
|
* otherwise the plain query. Keeps responses publicly cacheable when
|
|
@@ -7149,14 +7345,23 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7149
7345
|
buyer: this.buyer
|
|
7150
7346
|
}) ? withInContext(query) : query;
|
|
7151
7347
|
}
|
|
7348
|
+
/** Buyer resolved once per fetch so the market gate can read the
|
|
7349
|
+
* company location without invoking a callback resolver twice. */
|
|
7350
|
+
resolvedBuyer = void 0;
|
|
7152
7351
|
/**
|
|
7153
|
-
* Returns true if this bundle should be hidden for the current
|
|
7352
|
+
* Returns true if this bundle should be hidden for the current buyer.
|
|
7154
7353
|
* For "all" bundles, always returns false (visible). For "specific"
|
|
7155
|
-
* bundles,
|
|
7156
|
-
*
|
|
7354
|
+
* bundles, visibility needs a matching signal: the `country` attribute
|
|
7355
|
+
* against the bundle's projected country codes (retail markets), the
|
|
7356
|
+
* resolved buyer's companyLocationId against its projected company
|
|
7357
|
+
* locations (B2B markets), or a legacy `market-id` match.
|
|
7157
7358
|
*/
|
|
7158
7359
|
isMarketHidden(bundle) {
|
|
7159
|
-
return !
|
|
7360
|
+
return !isVisibleToBuyer(bundle, {
|
|
7361
|
+
marketId: this.marketId,
|
|
7362
|
+
countryCode: this.country,
|
|
7363
|
+
companyLocationId: this.resolvedBuyer?.companyLocationId
|
|
7364
|
+
});
|
|
7160
7365
|
}
|
|
7161
7366
|
async fetchBundle() {
|
|
7162
7367
|
if (!this.shopDomain || !this.storefrontToken) {
|
|
@@ -7169,12 +7374,17 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7169
7374
|
const controller = new AbortController();
|
|
7170
7375
|
this.abortController = controller;
|
|
7171
7376
|
this.renderLoading();
|
|
7377
|
+
try {
|
|
7378
|
+
this.resolvedBuyer = typeof this.buyer === "function" ? await this.buyer() : this.buyer;
|
|
7379
|
+
} catch {
|
|
7380
|
+
this.resolvedBuyer = void 0;
|
|
7381
|
+
}
|
|
7172
7382
|
const client = createStorefrontClient({
|
|
7173
7383
|
shopDomain: this.shopDomain,
|
|
7174
7384
|
accessToken: this.storefrontToken,
|
|
7175
7385
|
country: this.country,
|
|
7176
7386
|
language: this.language,
|
|
7177
|
-
buyer: this.
|
|
7387
|
+
buyer: this.resolvedBuyer
|
|
7178
7388
|
});
|
|
7179
7389
|
try {
|
|
7180
7390
|
let bundlePromise;
|
|
@@ -7239,7 +7449,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7239
7449
|
data.metaobject.id,
|
|
7240
7450
|
data.metaobject.fields
|
|
7241
7451
|
);
|
|
7242
|
-
this.bundles = parsed && !this.isMarketHidden(parsed) ? [parsed] : [];
|
|
7452
|
+
this.bundles = parsed && !this.isMarketHidden(parsed) ? [convertBundleToPresentment(parsed, this.currencyRate)] : [];
|
|
7243
7453
|
}
|
|
7244
7454
|
async fetchProductBundles(client, signal, productHandle) {
|
|
7245
7455
|
const data = await client.query(
|
|
@@ -7255,7 +7465,9 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7255
7465
|
const bundles = [];
|
|
7256
7466
|
for (const ref of refs) {
|
|
7257
7467
|
const parsed = parseMetaobjectBundle(ref.id, ref.fields);
|
|
7258
|
-
if (parsed && !this.isMarketHidden(parsed))
|
|
7468
|
+
if (parsed && !this.isMarketHidden(parsed)) {
|
|
7469
|
+
bundles.push(convertBundleToPresentment(parsed, this.currencyRate));
|
|
7470
|
+
}
|
|
7259
7471
|
}
|
|
7260
7472
|
this.bundles = resolveAbTests(bundles, getVisitorId());
|
|
7261
7473
|
}
|