@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/README.md
CHANGED
|
@@ -62,9 +62,12 @@ document.querySelector("lime-bundle").addEventListener(
|
|
|
62
62
|
| `app-url` | | Enables impression + add-to-cart analytics when set. |
|
|
63
63
|
| `analytics` | | `"false"` suppresses analytics even when `app-url` is set. |
|
|
64
64
|
| `locale` | | BCP-47 tag forwarded to the Storefront API. |
|
|
65
|
-
| `country` | | ISO-3166 alpha-2 (e.g. `"US"`). Adds `@inContext(country:)` to Storefront queries — Markets pricing + currency. |
|
|
65
|
+
| `country` | | ISO-3166 alpha-2 (e.g. `"US"`). Adds `@inContext(country:)` to Storefront queries — Markets pricing + currency — and matches market-restricted bundles' projected country codes. |
|
|
66
66
|
| `language` | | ISO-639-1 (e.g. `"EN"`). Adds `@inContext(language:)`. |
|
|
67
|
-
| `market-id` | | Current visitor's Market GID.
|
|
67
|
+
| `market-id` | | Current visitor's Market GID. Legacy market-gate signal; prefer `country` (retail) and the `buyer` property (B2B) — a B2B buyer's company-location market has no storefront market context. |
|
|
68
|
+
| `currency-rate` | | Store→presentment currency rate. Merchant-configured amounts (fixed-amount discounts, flat prices, volume tier amounts) are stored in the shop's store currency; on a "local currencies" market, pass the buyer's rate (on a Liquid-adjacent page, `window.Shopify.currency.rate`) so quoted sale prices match what checkout charges. Defaults to `1` (no conversion); invalid or non-positive values also fall back to `1`. Percentages are unaffected. |
|
|
69
|
+
|
|
70
|
+
A market-restricted bundle renders when any signal matches: `country` vs the bundle's projected country codes, the resolved `buyer` property's `companyLocationId` vs its projected company locations, or `market-id` vs its market allow-list.
|
|
68
71
|
|
|
69
72
|
**Product resolution** (when `bundle-gid` is absent): explicit `product-handle` → `<meta name="shopify:product-handle">` → `/products/<handle>` URL segment.
|
|
70
73
|
|
package/dist/index.cjs
CHANGED
|
@@ -3574,6 +3574,8 @@ function mergeCartLines(selections) {
|
|
|
3574
3574
|
var ALL_TYPES2 = "__all__";
|
|
3575
3575
|
var CLOSE_TRANSITION_MS2 = 300;
|
|
3576
3576
|
var SEARCH_DEBOUNCE_MS2 = 200;
|
|
3577
|
+
var STEP_CASCADE_MS = 500;
|
|
3578
|
+
var AUTO_ADVANCE_HOLD_MS = 160;
|
|
3577
3579
|
function createWizard(deps) {
|
|
3578
3580
|
const { container, steps, selections, t } = deps;
|
|
3579
3581
|
const overlay = container.querySelector("[data-modal-overlay]");
|
|
@@ -3598,6 +3600,8 @@ function createWizard(deps) {
|
|
|
3598
3600
|
let modalOpen = false;
|
|
3599
3601
|
let stepIndex = 0;
|
|
3600
3602
|
let rows = [];
|
|
3603
|
+
let cascadeTimer = null;
|
|
3604
|
+
let autoAdvanceTimer = null;
|
|
3601
3605
|
const productsFor = (i) => steps[i].eligibleProducts || [];
|
|
3602
3606
|
const rowCapacity = {
|
|
3603
3607
|
remainingSpots: () => stepHeadroom(selections, steps, stepIndex),
|
|
@@ -3698,7 +3702,13 @@ function createWizard(deps) {
|
|
|
3698
3702
|
if (!deps.autoAdvance) return;
|
|
3699
3703
|
if (stepIndex >= steps.length - 1) return;
|
|
3700
3704
|
if (stepHeadroom(selections, steps, stepIndex) !== 0) return;
|
|
3701
|
-
|
|
3705
|
+
const from = stepIndex;
|
|
3706
|
+
if (autoAdvanceTimer !== null) clearTimeout(autoAdvanceTimer);
|
|
3707
|
+
autoAdvanceTimer = setTimeout(() => {
|
|
3708
|
+
autoAdvanceTimer = null;
|
|
3709
|
+
if (!modalOpen) return;
|
|
3710
|
+
goToStep(from + 1);
|
|
3711
|
+
}, AUTO_ADVANCE_HOLD_MS);
|
|
3702
3712
|
}
|
|
3703
3713
|
function buildProductList(forStep) {
|
|
3704
3714
|
if (!modalList) return;
|
|
@@ -3886,7 +3896,29 @@ function createWizard(deps) {
|
|
|
3886
3896
|
});
|
|
3887
3897
|
}
|
|
3888
3898
|
function goToStep(i, opts = {}) {
|
|
3889
|
-
|
|
3899
|
+
const target = Math.max(0, Math.min(steps.length - 1, i));
|
|
3900
|
+
if (autoAdvanceTimer !== null) {
|
|
3901
|
+
clearTimeout(autoAdvanceTimer);
|
|
3902
|
+
autoAdvanceTimer = null;
|
|
3903
|
+
}
|
|
3904
|
+
if (modalDialog) {
|
|
3905
|
+
if (cascadeTimer !== null) clearTimeout(cascadeTimer);
|
|
3906
|
+
cascadeTimer = null;
|
|
3907
|
+
modalDialog.removeAttribute("data-step-transition");
|
|
3908
|
+
if (opts.animate !== false && target !== stepIndex) {
|
|
3909
|
+
void modalDialog.offsetHeight;
|
|
3910
|
+
modalDialog.setAttribute(
|
|
3911
|
+
"data-step-transition",
|
|
3912
|
+
target > stepIndex ? "forward" : "back"
|
|
3913
|
+
);
|
|
3914
|
+
const dialog = modalDialog;
|
|
3915
|
+
cascadeTimer = setTimeout(() => {
|
|
3916
|
+
cascadeTimer = null;
|
|
3917
|
+
dialog.removeAttribute("data-step-transition");
|
|
3918
|
+
}, STEP_CASCADE_MS);
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
stepIndex = target;
|
|
3890
3922
|
buildProductList(stepIndex);
|
|
3891
3923
|
buildFilters(stepIndex);
|
|
3892
3924
|
activeType = ALL_TYPES2;
|
|
@@ -3911,7 +3943,10 @@ function createWizard(deps) {
|
|
|
3911
3943
|
function open(atStep) {
|
|
3912
3944
|
if (!overlay || modalOpen) return;
|
|
3913
3945
|
modalOpen = true;
|
|
3914
|
-
goToStep(typeof atStep === "number" ? atStep : 0, {
|
|
3946
|
+
goToStep(typeof atStep === "number" ? atStep : 0, {
|
|
3947
|
+
skipFocus: true,
|
|
3948
|
+
animate: false
|
|
3949
|
+
});
|
|
3915
3950
|
overlay.style.display = "";
|
|
3916
3951
|
void overlay.offsetHeight;
|
|
3917
3952
|
overlay.classList.add("lb-mix-match__modal-overlay--open");
|
|
@@ -3924,6 +3959,14 @@ function createWizard(deps) {
|
|
|
3924
3959
|
function close() {
|
|
3925
3960
|
if (!overlay || !modalOpen) return;
|
|
3926
3961
|
modalOpen = false;
|
|
3962
|
+
if (autoAdvanceTimer !== null) {
|
|
3963
|
+
clearTimeout(autoAdvanceTimer);
|
|
3964
|
+
autoAdvanceTimer = null;
|
|
3965
|
+
}
|
|
3966
|
+
if (cascadeTimer !== null) {
|
|
3967
|
+
clearTimeout(cascadeTimer);
|
|
3968
|
+
cascadeTimer = null;
|
|
3969
|
+
}
|
|
3927
3970
|
overlay.classList.remove("lb-mix-match__modal-overlay--open");
|
|
3928
3971
|
unlock();
|
|
3929
3972
|
setTimeout(() => {
|
|
@@ -4311,11 +4354,18 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
4311
4354
|
--lb-list-max-height: 360px;
|
|
4312
4355
|
/* Depth of the soft fade at a scroll list's clipped edge. */
|
|
4313
4356
|
--lb-edge-fade-size: 20px;
|
|
4314
|
-
/* Derived "small" corner radius for
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4357
|
+
/* Derived "small" corner radius for chips and badges \u2014 the low-stock badge,
|
|
4358
|
+
the volume tier badge, the multi-step count chip. Controls and media
|
|
4359
|
+
(thumbnails, selects, steppers, buttons) take the full --lb-radius: they
|
|
4360
|
+
are the elements the merchant is looking at when they pick a preset, and
|
|
4361
|
+
the picker's own help text already promises they follow it.
|
|
4362
|
+
|
|
4363
|
+
Proportional rather than a fixed subtraction. At "subtle" (6px) minus-4px
|
|
4364
|
+
left 2px, which reads as square \u2014 the merchant saw a picker set to Subtle
|
|
4365
|
+
render with sharp corners (issue #363). Two thirds holds the proportion at
|
|
4366
|
+
every preset: none 0, subtle 4px, rounded 8px, round 12px. No max() needed,
|
|
4367
|
+
since 0 \xD7 \u2154 is 0. */
|
|
4368
|
+
--lb-radius-sm: calc(var(--lb-radius) * 2 / 3);
|
|
4319
4369
|
|
|
4320
4370
|
font-family: inherit;
|
|
4321
4371
|
font-size: 16px;
|
|
@@ -4479,7 +4529,7 @@ var BUNDLE_BASE_CSS = `/* Lime Bundles \u2014 shared base styles for all bundle
|
|
|
4479
4529
|
ratio. Default keeps the historical 1:1 behaviour. */
|
|
4480
4530
|
aspect-ratio: var(--lb-thumbnail-aspect-ratio, 1 / 1);
|
|
4481
4531
|
background: var(--lb-thumbnail-bg);
|
|
4482
|
-
border-radius: var(--lb-radius
|
|
4532
|
+
border-radius: var(--lb-radius);
|
|
4483
4533
|
overflow: hidden;
|
|
4484
4534
|
display: flex;
|
|
4485
4535
|
align-items: center;
|
|
@@ -5029,12 +5079,12 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
|
|
|
5029
5079
|
.lb-fixed .lb-bundle-thumbnail {
|
|
5030
5080
|
width: 60px;
|
|
5031
5081
|
min-width: 60px;
|
|
5032
|
-
border-radius: var(--lb-radius
|
|
5082
|
+
border-radius: var(--lb-radius);
|
|
5033
5083
|
box-sizing: border-box;
|
|
5034
5084
|
}
|
|
5035
5085
|
|
|
5036
5086
|
.lb-fixed .lb-bundle-thumbnail img {
|
|
5037
|
-
border-radius: var(--lb-radius
|
|
5087
|
+
border-radius: var(--lb-radius);
|
|
5038
5088
|
border: none;
|
|
5039
5089
|
}
|
|
5040
5090
|
|
|
@@ -5051,7 +5101,7 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
|
|
|
5051
5101
|
display: var(--lb-product-qty-chip-display, inline-flex);
|
|
5052
5102
|
align-items: center;
|
|
5053
5103
|
justify-content: center;
|
|
5054
|
-
border-radius: var(--lb-radius
|
|
5104
|
+
border-radius: var(--lb-radius);
|
|
5055
5105
|
background: color-mix(in srgb, var(--lb-text) 7%, transparent);
|
|
5056
5106
|
color: color-mix(in srgb, var(--lb-text) 75%, transparent);
|
|
5057
5107
|
font-size: 13px;
|
|
@@ -5067,7 +5117,7 @@ var BUNDLE_FIXED_CSS = `/* Lime Bundles \u2014 Fixed bundle styles */
|
|
|
5067
5117
|
.lb-bundle-variant-select {
|
|
5068
5118
|
display: inline-block;
|
|
5069
5119
|
border: var(--lb-border-width) solid var(--lb-border);
|
|
5070
|
-
border-radius: var(--lb-radius
|
|
5120
|
+
border-radius: var(--lb-radius);
|
|
5071
5121
|
padding: 8px 32px 8px 12px;
|
|
5072
5122
|
font-size: 12px;
|
|
5073
5123
|
line-height: 16px;
|
|
@@ -5215,12 +5265,12 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5215
5265
|
position: relative;
|
|
5216
5266
|
width: 60px;
|
|
5217
5267
|
min-width: 60px;
|
|
5218
|
-
border-radius: var(--lb-radius
|
|
5268
|
+
border-radius: var(--lb-radius);
|
|
5219
5269
|
box-sizing: border-box;
|
|
5220
5270
|
}
|
|
5221
5271
|
|
|
5222
5272
|
.lb-mix-match .lb-bundle-thumbnail img {
|
|
5223
|
-
border-radius: var(--lb-radius
|
|
5273
|
+
border-radius: var(--lb-radius);
|
|
5224
5274
|
border: none;
|
|
5225
5275
|
}
|
|
5226
5276
|
|
|
@@ -5386,14 +5436,26 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5386
5436
|
color: inherit;
|
|
5387
5437
|
}
|
|
5388
5438
|
|
|
5389
|
-
/* Derived "small" radius for the picker's
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5439
|
+
/* Derived "small" radius for the picker's chips and badges \u2014 the progress
|
|
5440
|
+
segments, the "Added" badge, the close button, the filter pills. The
|
|
5441
|
+
picker-scoped mirror of --lb-radius-sm, and proportional for the same reason
|
|
5442
|
+
(see bundle-base.css).
|
|
5443
|
+
|
|
5444
|
+
Everything the merchant would call a control or an image \u2014 the modal, search,
|
|
5445
|
+
product tiles, thumbnails, variant dropdown, quantity stepper and the
|
|
5446
|
+
buttons \u2014 takes the full --lb-picker-radius instead. That is what the
|
|
5447
|
+
editor's own Borders copy promises them, and the reduced tier had it
|
|
5448
|
+
rendering square at the Subtle preset (issue #363).
|
|
5449
|
+
|
|
5450
|
+
Declared on BOTH hosts the modal can live under. On the storefront it portals
|
|
5451
|
+
to [data-modal-overlay], which the variant listbox portals up to as well. In
|
|
5452
|
+
the admin widget editor there is no overlay at all \u2014 the preview renders the
|
|
5453
|
+
modal directly under .lb-bundle-widget \u2014 so scoping this to the overlay alone
|
|
5454
|
+
left the variable undefined there and every control using it fell back to
|
|
5455
|
+
square corners while the modal and tiles rounded correctly (issue #363). */
|
|
5456
|
+
.lb-bundle-widget,
|
|
5395
5457
|
[data-modal-overlay] {
|
|
5396
|
-
--lb-picker-radius-sm:
|
|
5458
|
+
--lb-picker-radius-sm: calc(var(--lb-picker-radius) * 2 / 3);
|
|
5397
5459
|
}
|
|
5398
5460
|
|
|
5399
5461
|
/* === Modal Overlay === */
|
|
@@ -5490,7 +5552,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5490
5552
|
border: none;
|
|
5491
5553
|
cursor: pointer;
|
|
5492
5554
|
color: var(--lb-picker-text);
|
|
5493
|
-
border-radius:
|
|
5555
|
+
border-radius: var(--lb-picker-radius-sm);
|
|
5494
5556
|
padding: 0;
|
|
5495
5557
|
margin: -12px -12px 0 0;
|
|
5496
5558
|
transition: background 0.25s ease;
|
|
@@ -5520,7 +5582,9 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5520
5582
|
.lb-mix-match__filter {
|
|
5521
5583
|
padding: 6px 14px;
|
|
5522
5584
|
border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
|
|
5523
|
-
|
|
5585
|
+
/* Pills followed the merchant's picker radius as of #363 \u2014 a fixed 999px
|
|
5586
|
+
read as a stray rounded shape in a picker set to None or Subtle. */
|
|
5587
|
+
border-radius: var(--lb-picker-radius-sm);
|
|
5524
5588
|
background: transparent;
|
|
5525
5589
|
color: var(--lb-picker-text);
|
|
5526
5590
|
font-family: inherit;
|
|
@@ -5574,7 +5638,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5574
5638
|
width: 100%;
|
|
5575
5639
|
padding: 12px 40px 12px 44px;
|
|
5576
5640
|
border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
|
|
5577
|
-
border-radius: var(--lb-picker-radius
|
|
5641
|
+
border-radius: var(--lb-picker-radius);
|
|
5578
5642
|
font-size: 16px;
|
|
5579
5643
|
line-height: 20px;
|
|
5580
5644
|
color: var(--lb-picker-text);
|
|
@@ -5674,7 +5738,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5674
5738
|
independent from the main widget's thumbnailRatio so a merchant can
|
|
5675
5739
|
e.g. show tall picker thumbs with square main thumbs. */
|
|
5676
5740
|
aspect-ratio: var(--lb-picker-thumbnail-aspect-ratio, 1 / 1);
|
|
5677
|
-
border-radius: var(--lb-picker-radius
|
|
5741
|
+
border-radius: var(--lb-picker-radius);
|
|
5678
5742
|
box-sizing: border-box;
|
|
5679
5743
|
overflow: hidden;
|
|
5680
5744
|
background: var(--lb-thumbnail-bg);
|
|
@@ -5687,7 +5751,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5687
5751
|
to auto/contain so the image renders uncropped at intrinsic ratio. */
|
|
5688
5752
|
height: var(--lb-picker-thumbnail-img-height, 100%);
|
|
5689
5753
|
object-fit: var(--lb-picker-thumbnail-img-fit, cover);
|
|
5690
|
-
border-radius: var(--lb-picker-radius
|
|
5754
|
+
border-radius: var(--lb-picker-radius);
|
|
5691
5755
|
}
|
|
5692
5756
|
|
|
5693
5757
|
.lb-mix-match__modal-product-info {
|
|
@@ -5762,7 +5826,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5762
5826
|
font-size: 12px;
|
|
5763
5827
|
padding: 4px 24px 4px 8px;
|
|
5764
5828
|
border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
|
|
5765
|
-
border-radius: var(--lb-picker-radius
|
|
5829
|
+
border-radius: var(--lb-picker-radius);
|
|
5766
5830
|
background-color: var(--lb-picker-bg);
|
|
5767
5831
|
background-image: var(--lb-picker-variant-chevron);
|
|
5768
5832
|
background-repeat: no-repeat;
|
|
@@ -5813,7 +5877,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5813
5877
|
width: 100%;
|
|
5814
5878
|
flex-shrink: 0;
|
|
5815
5879
|
border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
|
|
5816
|
-
border-radius: var(--lb-picker-radius
|
|
5880
|
+
border-radius: var(--lb-picker-radius);
|
|
5817
5881
|
background-color: var(--lb-picker-bg);
|
|
5818
5882
|
overflow: hidden;
|
|
5819
5883
|
box-sizing: border-box;
|
|
@@ -5890,7 +5954,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
5890
5954
|
background: var(--lb-picker-add-bg);
|
|
5891
5955
|
color: var(--lb-picker-add-label);
|
|
5892
5956
|
border: var(--lb-picker-add-border-width) solid var(--lb-picker-add-border-color);
|
|
5893
|
-
border-radius: var(--lb-picker-radius
|
|
5957
|
+
border-radius: var(--lb-picker-radius);
|
|
5894
5958
|
box-sizing: border-box;
|
|
5895
5959
|
font-size: 12px;
|
|
5896
5960
|
font-weight: 600;
|
|
@@ -6031,7 +6095,7 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
6031
6095
|
background: var(--lb-picker-add-bg);
|
|
6032
6096
|
color: var(--lb-picker-add-label);
|
|
6033
6097
|
border: var(--lb-picker-add-border-width) solid var(--lb-picker-add-border-color);
|
|
6034
|
-
border-radius: var(--lb-picker-radius
|
|
6098
|
+
border-radius: var(--lb-picker-radius);
|
|
6035
6099
|
box-sizing: border-box;
|
|
6036
6100
|
font-family: inherit;
|
|
6037
6101
|
font-size: 14px;
|
|
@@ -6080,7 +6144,9 @@ var BUNDLE_MIX_MATCH_CSS = `/* Lime Bundles \u2014 Mix & Match styles */
|
|
|
6080
6144
|
.lb-mix-match__modal {
|
|
6081
6145
|
max-width: 100%;
|
|
6082
6146
|
max-height: 90vh;
|
|
6083
|
-
|
|
6147
|
+
/* Bottom sheet: only the top corners round, but they round to whatever the
|
|
6148
|
+
merchant picked rather than a fixed 16px (issue #363). */
|
|
6149
|
+
border-radius: var(--lb-picker-radius) var(--lb-picker-radius) 0 0;
|
|
6084
6150
|
transform: translateY(100%);
|
|
6085
6151
|
}
|
|
6086
6152
|
|
|
@@ -6347,12 +6413,12 @@ var BUNDLE_BOGO_CSS = `/* Lime Bundles \u2014 BOGO (Buy X Get Y) bundle styles *
|
|
|
6347
6413
|
.lb-bogo .lb-bundle-thumbnail {
|
|
6348
6414
|
width: 60px;
|
|
6349
6415
|
min-width: 60px;
|
|
6350
|
-
border-radius: var(--lb-radius
|
|
6416
|
+
border-radius: var(--lb-radius);
|
|
6351
6417
|
box-sizing: border-box;
|
|
6352
6418
|
}
|
|
6353
6419
|
|
|
6354
6420
|
.lb-bogo .lb-bundle-thumbnail img {
|
|
6355
|
-
border-radius: var(--lb-radius
|
|
6421
|
+
border-radius: var(--lb-radius);
|
|
6356
6422
|
border: none;
|
|
6357
6423
|
}
|
|
6358
6424
|
|
|
@@ -6473,6 +6539,13 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6473
6539
|
cursor: pointer;
|
|
6474
6540
|
box-sizing: border-box;
|
|
6475
6541
|
transition: background-color 0.25s ease;
|
|
6542
|
+
/* Deliberately no border-radius. This row paints the checklist hairline as
|
|
6543
|
+
its own border-top (see the run rule above), and a radius tapers that
|
|
6544
|
+
border into the corner arcs \u2014 the dividers came out visibly curved at the
|
|
6545
|
+
"round" preset (issue #363). A full-bleed list row is meant to be square
|
|
6546
|
+
anyway; the hover tint below spans the card width, so it has no corner to
|
|
6547
|
+
round against. The :focus-visible radius is fine: an outline is a
|
|
6548
|
+
separate paint and never touches the border. */
|
|
6476
6549
|
}
|
|
6477
6550
|
|
|
6478
6551
|
.lb-multi-step__step-row:hover {
|
|
@@ -6620,7 +6693,7 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6620
6693
|
padding: 10px 24px;
|
|
6621
6694
|
background: none;
|
|
6622
6695
|
border: var(--lb-picker-border-width) solid var(--lb-picker-border-color);
|
|
6623
|
-
border-radius: var(--lb-picker-radius
|
|
6696
|
+
border-radius: var(--lb-picker-radius);
|
|
6624
6697
|
box-sizing: border-box;
|
|
6625
6698
|
color: var(--lb-picker-text);
|
|
6626
6699
|
font-family: inherit;
|
|
@@ -6638,6 +6711,108 @@ var BUNDLE_MULTI_STEP_CSS = `/**
|
|
|
6638
6711
|
outline: 2px solid var(--lb-primary-color);
|
|
6639
6712
|
outline-offset: 2px;
|
|
6640
6713
|
}
|
|
6714
|
+
|
|
6715
|
+
/* \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
|
|
6716
|
+
Moving between steps swaps the whole step-scoped region in a single
|
|
6717
|
+
paint, which reads as nothing happening (or as a glitch) rather than
|
|
6718
|
+
as arriving somewhere new \u2014 and worst of all under auto-advance,
|
|
6719
|
+
where the shopper never clicked anything. So the step assembles
|
|
6720
|
+
instead of arriving whole: the step label leads, the filter pills
|
|
6721
|
+
follow, then the product cards one after another. Staging is what
|
|
6722
|
+
makes the change legible; a uniform slide of everything at once only
|
|
6723
|
+
wobbles the card.
|
|
6724
|
+
|
|
6725
|
+
The wizard stamps data-step-transition="forward" | "back" on the
|
|
6726
|
+
dialog on every step change (manual Next/Back and auto-advance
|
|
6727
|
+
alike, never on open, where the modal entrance already carries the
|
|
6728
|
+
motion), and clears it again once the cascade has played \u2014 see
|
|
6729
|
+
STEP_CASCADE_MS in multi-step/wizard.ts. That clear is load-bearing,
|
|
6730
|
+
not tidiness: search and filter hide cards with .lb-hidden
|
|
6731
|
+
(display: none), and restoring display restarts a card's animation,
|
|
6732
|
+
so a live attribute would replay the whole cascade on every
|
|
6733
|
+
keystroke that reveals a card.
|
|
6734
|
+
|
|
6735
|
+
Two elements deliberately hold still. The footer, because Back/Next
|
|
6736
|
+
are the shopper's anchor while everything above them moves. And the
|
|
6737
|
+
progress rail, because it is the one thing that answers "where am I
|
|
6738
|
+
now" \u2014 animating the shopper's only orientation cue is what made the
|
|
6739
|
+
previous version hard to read. Its segment fill carries its own
|
|
6740
|
+
state change already. */
|
|
6741
|
+
@keyframes lb-multi-step-cascade-fwd {
|
|
6742
|
+
from {
|
|
6743
|
+
opacity: 0;
|
|
6744
|
+
transform: translateY(10px);
|
|
6745
|
+
}
|
|
6746
|
+
}
|
|
6747
|
+
|
|
6748
|
+
@keyframes lb-multi-step-cascade-back {
|
|
6749
|
+
from {
|
|
6750
|
+
opacity: 0;
|
|
6751
|
+
transform: translateY(-10px);
|
|
6752
|
+
}
|
|
6753
|
+
}
|
|
6754
|
+
|
|
6755
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-subtitle {
|
|
6756
|
+
animation: lb-multi-step-cascade-fwd 0.2s ease-out both;
|
|
6757
|
+
}
|
|
6758
|
+
|
|
6759
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__filters {
|
|
6760
|
+
animation: lb-multi-step-cascade-fwd 0.2s ease-out 0.05s both;
|
|
6761
|
+
}
|
|
6762
|
+
|
|
6763
|
+
/* Cards stagger 30ms apart, but only the first four: a step can hold
|
|
6764
|
+
dozens of products and the two-column grid shows about four at a
|
|
6765
|
+
time, so past that the stagger is below the fold and would only add
|
|
6766
|
+
lag. Everything from the fifth on shares the fourth's landing. */
|
|
6767
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-list > * {
|
|
6768
|
+
animation: lb-multi-step-cascade-fwd 0.22s ease-out 0.2s both;
|
|
6769
|
+
}
|
|
6770
|
+
|
|
6771
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-list > :nth-child(1) {
|
|
6772
|
+
animation-delay: 0.09s;
|
|
6773
|
+
}
|
|
6774
|
+
|
|
6775
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-list > :nth-child(2) {
|
|
6776
|
+
animation-delay: 0.12s;
|
|
6777
|
+
}
|
|
6778
|
+
|
|
6779
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-list > :nth-child(3) {
|
|
6780
|
+
animation-delay: 0.15s;
|
|
6781
|
+
}
|
|
6782
|
+
|
|
6783
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-list > :nth-child(4) {
|
|
6784
|
+
animation-delay: 0.18s;
|
|
6785
|
+
}
|
|
6786
|
+
|
|
6787
|
+
/* Going back runs the same cascade in the same order \u2014 only the offset
|
|
6788
|
+
flips, so the shopper still reads which way they moved. Overrides
|
|
6789
|
+
the name alone; the staggered delays above must survive. */
|
|
6790
|
+
.lb-mix-match__modal[data-step-transition="back"] .lb-mix-match__modal-subtitle,
|
|
6791
|
+
.lb-mix-match__modal[data-step-transition="back"] .lb-mix-match__filters,
|
|
6792
|
+
.lb-mix-match__modal[data-step-transition="back"] .lb-mix-match__modal-list > * {
|
|
6793
|
+
animation-name: lb-multi-step-cascade-back;
|
|
6794
|
+
}
|
|
6795
|
+
|
|
6796
|
+
@keyframes lb-multi-step-cascade-fade {
|
|
6797
|
+
from {
|
|
6798
|
+
opacity: 0;
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
|
|
6802
|
+
/* Reduced motion keeps the fade and drops the travel and the stagger:
|
|
6803
|
+
the step change still has to be perceptible, which is the whole
|
|
6804
|
+
point of this block. The card selector is :nth-child(n) rather than
|
|
6805
|
+
* on purpose \u2014 a plain * loses to the :nth-child(1..4) delays above
|
|
6806
|
+
on specificity, which would leave the stagger running with nothing
|
|
6807
|
+
moving. :nth-child(n) matches every child at equal specificity and
|
|
6808
|
+
wins on order. */
|
|
6809
|
+
@media (prefers-reduced-motion: reduce) {
|
|
6810
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-subtitle,
|
|
6811
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__filters,
|
|
6812
|
+
.lb-mix-match__modal[data-step-transition] .lb-mix-match__modal-list > :nth-child(n) {
|
|
6813
|
+
animation: lb-multi-step-cascade-fade 0.15s ease-out both;
|
|
6814
|
+
}
|
|
6815
|
+
}
|
|
6641
6816
|
`;
|
|
6642
6817
|
var BUNDLE_DROPDOWN_CSS = `/**
|
|
6643
6818
|
* Lime Bundles \u2014 Custom variant-picker dropdown styling.
|
|
@@ -6685,7 +6860,7 @@ var BUNDLE_DROPDOWN_CSS = `/**
|
|
|
6685
6860
|
gap: 8px;
|
|
6686
6861
|
width: 100%;
|
|
6687
6862
|
border: var(--lb-border-width) solid var(--lb-border);
|
|
6688
|
-
border-radius: var(--lb-radius
|
|
6863
|
+
border-radius: var(--lb-radius);
|
|
6689
6864
|
padding: 8px 12px;
|
|
6690
6865
|
font-size: 12px;
|
|
6691
6866
|
line-height: 16px;
|
|
@@ -6748,7 +6923,7 @@ var BUNDLE_DROPDOWN_CSS = `/**
|
|
|
6748
6923
|
background: var(--lb-bg);
|
|
6749
6924
|
color: var(--lb-text);
|
|
6750
6925
|
border: var(--lb-border-width) solid var(--lb-border);
|
|
6751
|
-
border-radius: var(--lb-radius
|
|
6926
|
+
border-radius: var(--lb-radius);
|
|
6752
6927
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
|
6753
6928
|
overflow-y: auto;
|
|
6754
6929
|
overflow-x: hidden;
|
|
@@ -6809,6 +6984,10 @@ var BUNDLE_DROPDOWN_CSS = `/**
|
|
|
6809
6984
|
overflow: hidden;
|
|
6810
6985
|
text-overflow: ellipsis;
|
|
6811
6986
|
color: var(--lb-text);
|
|
6987
|
+
/* The active/hover tint below fills the option, so it rounds with the rest
|
|
6988
|
+
of the widget rather than squaring off against the listbox corner it sits
|
|
6989
|
+
in (issue #363). */
|
|
6990
|
+
border-radius: var(--lb-radius-sm);
|
|
6812
6991
|
}
|
|
6813
6992
|
|
|
6814
6993
|
.lb-dropdown-option[aria-selected="true"] {
|
|
@@ -6867,7 +7046,7 @@ var BUNDLE_DROPDOWN_CSS = `/**
|
|
|
6867
7046
|
.lb-mix-match__modal .lb-dropdown-trigger {
|
|
6868
7047
|
border-color: var(--lb-picker-border-color);
|
|
6869
7048
|
border-width: var(--lb-picker-border-width);
|
|
6870
|
-
border-radius: var(--lb-picker-radius
|
|
7049
|
+
border-radius: var(--lb-picker-radius);
|
|
6871
7050
|
background: var(--lb-picker-bg);
|
|
6872
7051
|
color: var(--lb-picker-text);
|
|
6873
7052
|
/* Match the picker qty stepper + Add button height (border-box so 32px is
|
|
@@ -6888,13 +7067,17 @@ var BUNDLE_DROPDOWN_CSS = `/**
|
|
|
6888
7067
|
[data-modal-overlay] > .lb-dropdown-listbox {
|
|
6889
7068
|
border-color: var(--lb-picker-border-color);
|
|
6890
7069
|
border-width: var(--lb-picker-border-width);
|
|
6891
|
-
border-radius: var(--lb-picker-radius
|
|
7070
|
+
border-radius: var(--lb-picker-radius);
|
|
6892
7071
|
background: var(--lb-picker-bg);
|
|
6893
7072
|
color: var(--lb-picker-text);
|
|
6894
7073
|
scrollbar-color: color-mix(in srgb, var(--lb-picker-text) 15%, transparent)
|
|
6895
7074
|
color-mix(in srgb, var(--lb-picker-text) 2%, transparent);
|
|
6896
7075
|
}
|
|
6897
7076
|
|
|
7077
|
+
[data-modal-overlay] > .lb-dropdown-listbox .lb-dropdown-option {
|
|
7078
|
+
border-radius: var(--lb-picker-radius-sm);
|
|
7079
|
+
}
|
|
7080
|
+
|
|
6898
7081
|
[data-modal-overlay] > .lb-dropdown-listbox::-webkit-scrollbar-track {
|
|
6899
7082
|
background: color-mix(in srgb, var(--lb-picker-text) 2%, transparent);
|
|
6900
7083
|
border-radius: 2px;
|
|
@@ -7029,7 +7212,8 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7029
7212
|
"locale",
|
|
7030
7213
|
"country",
|
|
7031
7214
|
"language",
|
|
7032
|
-
"market-id"
|
|
7215
|
+
"market-id",
|
|
7216
|
+
"currency-rate"
|
|
7033
7217
|
];
|
|
7034
7218
|
/**
|
|
7035
7219
|
* B2B buyer identity. Set programmatically — `element.buyer = {...}` or
|
|
@@ -7084,7 +7268,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7084
7268
|
}
|
|
7085
7269
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
7086
7270
|
if (oldValue === newValue || !this.isConnected) return;
|
|
7087
|
-
if (name === "bundle-gid" || name === "product-handle" || name === "product-id" || name === "shop-domain" || name === "storefront-token") {
|
|
7271
|
+
if (name === "bundle-gid" || name === "product-handle" || name === "product-id" || name === "shop-domain" || name === "storefront-token" || name === "currency-rate") {
|
|
7088
7272
|
if (this.shopDomain && this.storefrontToken) {
|
|
7089
7273
|
this.fetchBundle();
|
|
7090
7274
|
}
|
|
@@ -7127,6 +7311,17 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7127
7311
|
get marketId() {
|
|
7128
7312
|
return this.getAttribute("market-id") ?? void 0;
|
|
7129
7313
|
}
|
|
7314
|
+
/**
|
|
7315
|
+
* Store→presentment currency rate. Merchant-configured amounts
|
|
7316
|
+
* (fixed_amount / flat_price values, volume tier amounts) are stored in
|
|
7317
|
+
* the shop's store currency; set this to the buyer's currency rate so
|
|
7318
|
+
* quoted sale prices match what the checkout's discount function
|
|
7319
|
+
* charges. Anything unparseable or non-positive falls back to 1 —
|
|
7320
|
+
* no conversion — via `normalizeCurrencyRate`.
|
|
7321
|
+
*/
|
|
7322
|
+
get currencyRate() {
|
|
7323
|
+
return this.getAttribute("currency-rate") ?? void 0;
|
|
7324
|
+
}
|
|
7130
7325
|
/**
|
|
7131
7326
|
* Returns the @inContext-wrapped query when any context field is set;
|
|
7132
7327
|
* otherwise the plain query. Keeps responses publicly cacheable when
|
|
@@ -7141,14 +7336,23 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7141
7336
|
buyer: this.buyer
|
|
7142
7337
|
}) ? (0, import_core6.withInContext)(query) : query;
|
|
7143
7338
|
}
|
|
7339
|
+
/** Buyer resolved once per fetch so the market gate can read the
|
|
7340
|
+
* company location without invoking a callback resolver twice. */
|
|
7341
|
+
resolvedBuyer = void 0;
|
|
7144
7342
|
/**
|
|
7145
|
-
* Returns true if this bundle should be hidden for the current
|
|
7343
|
+
* Returns true if this bundle should be hidden for the current buyer.
|
|
7146
7344
|
* For "all" bundles, always returns false (visible). For "specific"
|
|
7147
|
-
* bundles,
|
|
7148
|
-
*
|
|
7345
|
+
* bundles, visibility needs a matching signal: the `country` attribute
|
|
7346
|
+
* against the bundle's projected country codes (retail markets), the
|
|
7347
|
+
* resolved buyer's companyLocationId against its projected company
|
|
7348
|
+
* locations (B2B markets), or a legacy `market-id` match.
|
|
7149
7349
|
*/
|
|
7150
7350
|
isMarketHidden(bundle) {
|
|
7151
|
-
return !(0, import_core6.
|
|
7351
|
+
return !(0, import_core6.isVisibleToBuyer)(bundle, {
|
|
7352
|
+
marketId: this.marketId,
|
|
7353
|
+
countryCode: this.country,
|
|
7354
|
+
companyLocationId: this.resolvedBuyer?.companyLocationId
|
|
7355
|
+
});
|
|
7152
7356
|
}
|
|
7153
7357
|
async fetchBundle() {
|
|
7154
7358
|
if (!this.shopDomain || !this.storefrontToken) {
|
|
@@ -7161,12 +7365,17 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7161
7365
|
const controller = new AbortController();
|
|
7162
7366
|
this.abortController = controller;
|
|
7163
7367
|
this.renderLoading();
|
|
7368
|
+
try {
|
|
7369
|
+
this.resolvedBuyer = typeof this.buyer === "function" ? await this.buyer() : this.buyer;
|
|
7370
|
+
} catch {
|
|
7371
|
+
this.resolvedBuyer = void 0;
|
|
7372
|
+
}
|
|
7164
7373
|
const client = (0, import_core6.createStorefrontClient)({
|
|
7165
7374
|
shopDomain: this.shopDomain,
|
|
7166
7375
|
accessToken: this.storefrontToken,
|
|
7167
7376
|
country: this.country,
|
|
7168
7377
|
language: this.language,
|
|
7169
|
-
buyer: this.
|
|
7378
|
+
buyer: this.resolvedBuyer
|
|
7170
7379
|
});
|
|
7171
7380
|
try {
|
|
7172
7381
|
let bundlePromise;
|
|
@@ -7231,7 +7440,7 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7231
7440
|
data.metaobject.id,
|
|
7232
7441
|
data.metaobject.fields
|
|
7233
7442
|
);
|
|
7234
|
-
this.bundles = parsed && !this.isMarketHidden(parsed) ? [parsed] : [];
|
|
7443
|
+
this.bundles = parsed && !this.isMarketHidden(parsed) ? [(0, import_core6.convertBundleToPresentment)(parsed, this.currencyRate)] : [];
|
|
7235
7444
|
}
|
|
7236
7445
|
async fetchProductBundles(client, signal, productHandle) {
|
|
7237
7446
|
const data = await client.query(
|
|
@@ -7247,7 +7456,9 @@ var LimeBundleElement = class extends HTMLElement {
|
|
|
7247
7456
|
const bundles = [];
|
|
7248
7457
|
for (const ref of refs) {
|
|
7249
7458
|
const parsed = (0, import_core6.parseMetaobjectBundle)(ref.id, ref.fields);
|
|
7250
|
-
if (parsed && !this.isMarketHidden(parsed))
|
|
7459
|
+
if (parsed && !this.isMarketHidden(parsed)) {
|
|
7460
|
+
bundles.push((0, import_core6.convertBundleToPresentment)(parsed, this.currencyRate));
|
|
7461
|
+
}
|
|
7251
7462
|
}
|
|
7252
7463
|
this.bundles = (0, import_core6.resolveAbTests)(bundles, (0, import_core6.getVisitorId)());
|
|
7253
7464
|
}
|
package/dist/index.d.cts
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;
|