@propeller-commerce/propeller-v2-react-ui 0.27.0 → 0.29.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 CHANGED
@@ -41,6 +41,9 @@ function PropellerProvider({ value, children }) {
41
41
  function usePropellerDeps() {
42
42
  return React6.useContext(PropellerDepsContext);
43
43
  }
44
+ function useImgComponent() {
45
+ return React6.useContext(PropellerDepsContext)?.imgComponent;
46
+ }
44
47
  function useRequiredPropellerDeps() {
45
48
  const deps = React6.useContext(PropellerDepsContext);
46
49
  if (!deps) {
@@ -617,6 +620,14 @@ function useAuth(options) {
617
620
  }, [graphqlClient]);
618
621
  return { loading, error, login, magicLogin, createMagicToken, registerContact, registerCustomer, forgotPassword };
619
622
  }
623
+ var CART_NOT_FOUND = "CART_NOT_FOUND_ERROR";
624
+ function isCartNotFound(error) {
625
+ const entries = error?.errors;
626
+ if (!Array.isArray(entries)) return false;
627
+ return entries.some(
628
+ (e) => e?.extensions?.code === CART_NOT_FOUND || /cart:?\s*\S+\s+not found/i.test(e?.message ?? "")
629
+ );
630
+ }
620
631
  async function initCart(config) {
621
632
  const {
622
633
  services,
@@ -756,6 +767,11 @@ function useCart(options) {
756
767
  setCreatedCartId(c.cartId);
757
768
  cartIdRef.current = c.cartId;
758
769
  }, []);
770
+ const forgetCart = React6.useCallback(() => {
771
+ setCart(null);
772
+ setCreatedCartId("");
773
+ cartIdRef.current = "";
774
+ }, []);
759
775
  const imageSearchFilters = React6.useCallback(
760
776
  () => configuration?.imageSearchFiltersGrid ?? DEFAULT_IMAGE_SEARCH_FILTERS,
761
777
  [configuration]
@@ -839,13 +855,28 @@ function useCart(options) {
839
855
  } else return propellerV2CoreUi.err("No cart ID provided");
840
856
  }
841
857
  const service = propellerV2CoreUi.createServices(graphqlClient).cart;
842
- const resultCart = await service.addItemToCart({
843
- id: resolvedCartId,
844
- input: { productId: opts.product.productId, quantity: opts.quantity, ...opts.cluster?.clusterId !== void 0 && { clusterId: opts.cluster.clusterId }, ...childItemInputs && { childItems: childItemInputs }, ...opts.notes && { notes: opts.notes }, ...opts.price !== void 0 && { price: opts.price } },
845
- language,
846
- imageSearchFilters: imageSearchFilters(),
847
- imageVariantFilters: imageVariantFilters()
848
- });
858
+ const input = { productId: opts.product.productId, quantity: opts.quantity, ...opts.cluster?.clusterId !== void 0 && { clusterId: opts.cluster.clusterId }, ...childItemInputs && { childItems: childItemInputs }, ...opts.notes && { notes: opts.notes }, ...opts.price !== void 0 && { price: opts.price } };
859
+ let resultCart;
860
+ try {
861
+ resultCart = await service.addItemToCart({
862
+ id: resolvedCartId,
863
+ input,
864
+ language,
865
+ imageSearchFilters: imageSearchFilters(),
866
+ imageVariantFilters: imageVariantFilters()
867
+ });
868
+ } catch (e) {
869
+ if (!isCartNotFound(e) || !opts.createCart) throw e;
870
+ forgetCart();
871
+ const fresh = await resolveCart();
872
+ resultCart = await service.addItemToCart({
873
+ id: fresh.cartId,
874
+ input,
875
+ language,
876
+ imageSearchFilters: imageSearchFilters(),
877
+ imageVariantFilters: imageVariantFilters()
878
+ });
879
+ }
849
880
  rememberCart(resultCart);
850
881
  const addedItem = resultCart.items?.find((i) => i.productId === opts.product.productId) ?? null;
851
882
  opts.afterAddToCart?.(resultCart, addedItem);
@@ -857,7 +888,7 @@ function useCart(options) {
857
888
  } finally {
858
889
  setLoading(false);
859
890
  }
860
- }, [graphqlClient, cartId, language, configuration, resolveCart, rememberCart, imageSearchFilters, imageVariantFilters]);
891
+ }, [graphqlClient, cartId, language, configuration, resolveCart, rememberCart, forgetCart, imageSearchFilters, imageVariantFilters]);
861
892
  const addItems = React6.useCallback(async (items) => {
862
893
  const added = [];
863
894
  let lastCart = null;
@@ -2143,19 +2174,8 @@ function useProductInfo(options) {
2143
2174
  setLoading(true);
2144
2175
  setError(null);
2145
2176
  try {
2146
- let orderlistIds = [];
2147
- let applyOrderlists = true;
2148
- if (options.orderlistIds && options.orderlistIds.length > 0) {
2149
- orderlistIds = options.orderlistIds;
2150
- applyOrderlists = options.applyOrderlists !== false;
2151
- } else if (options.applyOrderlists === false) {
2152
- applyOrderlists = false;
2153
- } else if (options.user && options.companyId) {
2154
- const orderlistService = propellerV2CoreUi.createServices(graphqlClient).orderlist;
2155
- const searchInput = { companyIds: [options.companyId] };
2156
- const orderlists = await orderlistService.getOrderlists(searchInput);
2157
- orderlistIds = (orderlists?.items ?? []).map((ol) => ol.id);
2158
- }
2177
+ const orderlistIds = options.orderlistIds ?? [];
2178
+ const applyOrderlists = options.applyOrderlists !== false;
2159
2179
  const service = propellerV2CoreUi.createServices(graphqlClient).product;
2160
2180
  const attributeInput = buildAttributeInput2();
2161
2181
  const variables = {
@@ -2366,7 +2386,7 @@ function useProductSearch(options) {
2366
2386
  const orderlistScope = options.orderlistIds && options.orderlistIds.length > 0 ? {
2367
2387
  applyOrderlists: options.applyOrderlists !== false,
2368
2388
  orderlistIds: options.orderlistIds
2369
- } : { applyOrderlists: false };
2389
+ } : options.applyOrderlists === false ? { applyOrderlists: false } : {};
2370
2390
  const categoryProductSearchInput = {
2371
2391
  language: lang,
2372
2392
  page: pagination.currentPage,
@@ -2496,7 +2516,7 @@ function useProductSearch(options) {
2496
2516
  const orderlistScope = options.orderlistIds && options.orderlistIds.length > 0 ? {
2497
2517
  applyOrderlists: options.applyOrderlists !== false,
2498
2518
  orderlistIds: options.orderlistIds
2499
- } : { applyOrderlists: false };
2519
+ } : options.applyOrderlists === false ? { applyOrderlists: false } : {};
2500
2520
  const userId = resolveListingUserId(options.user, configuration);
2501
2521
  const contactId = options.user && "contactId" in options.user ? options.user.contactId : void 0;
2502
2522
  const customerId = options.user && "customerId" in options.user ? options.user.customerId : void 0;
@@ -2672,7 +2692,7 @@ function useQuickOrder(options) {
2672
2692
  if (!catId) return [];
2673
2693
  try {
2674
2694
  const service = propellerV2CoreUi.createServices(graphqlClient).category;
2675
- const orderlistScope = orderlistIds && orderlistIds.length > 0 ? { applyOrderlists: applyOrderlists !== false, orderlistIds } : { applyOrderlists: false };
2695
+ const orderlistScope = orderlistIds && orderlistIds.length > 0 ? { applyOrderlists: applyOrderlists !== false, orderlistIds } : applyOrderlists === false ? { applyOrderlists: false } : {};
2676
2696
  const userId = resolveListingUserId(user, configuration);
2677
2697
  const contactId = user && "contactId" in user ? user.contactId : void 0;
2678
2698
  const customerId = user && "customerId" in user ? user.customerId : void 0;
@@ -3281,28 +3301,33 @@ function useProductSlider(options) {
3281
3301
  }
3282
3302
  }
3283
3303
  if (!productIds.length && !clusterIds.length) return items;
3304
+ const catId = configuration.baseCategoryId;
3305
+ if (!catId) return items;
3284
3306
  const userId = resolveUserId();
3285
3307
  const companyId = resolveCompanyId();
3286
- const searchInput = {
3308
+ const categoryProductSearchInput = {
3287
3309
  ...productIds.length && { productIds },
3288
3310
  ...clusterIds.length && { clusterIds },
3289
3311
  language,
3290
3312
  page: 1,
3291
3313
  offset: 50,
3292
3314
  statuses: [propellerSdkV2.ProductStatus.A, propellerSdkV2.ProductStatus.P, propellerSdkV2.ProductStatus.T, propellerSdkV2.ProductStatus.S],
3315
+ hidden: false,
3293
3316
  ...userId !== void 0 && { userId },
3294
3317
  ...companyId !== void 0 && { companyId }
3295
3318
  };
3296
3319
  const filterAvailableAttributeInput = { isSearchable: true };
3297
3320
  const variables = {
3298
- input: searchInput,
3321
+ categoryId: catId,
3322
+ language,
3323
+ categoryProductSearchInput,
3324
+ filterAvailableAttributeInput,
3299
3325
  imageSearchFilters: configuration.imageSearchFiltersGrid,
3300
- imageVariantFilters: configuration.imageVariantFiltersMedium,
3301
- filterAvailableAttributeInput
3326
+ imageVariantFilters: configuration.imageVariantFiltersMedium
3302
3327
  };
3303
3328
  try {
3304
- const response = await propellerV2CoreUi.createServices(graphqlClient).product.getProducts(variables);
3305
- const resolved = response?.items ?? [];
3329
+ const response = await propellerV2CoreUi.createServices(graphqlClient).category.getCategory(variables);
3330
+ const resolved = response?.products?.items ?? [];
3306
3331
  const allowed = /* @__PURE__ */ new Set();
3307
3332
  for (const r of resolved) {
3308
3333
  const id = r.productId ?? r.clusterId;
@@ -3358,10 +3383,17 @@ function useProductSlider(options) {
3358
3383
  setLoading(true);
3359
3384
  setError(null);
3360
3385
  try {
3361
- const service = propellerV2CoreUi.createServices(graphqlClient).product;
3362
- const searchInput = {
3363
- productIds,
3364
- clusterIds,
3386
+ const catId = configuration.baseCategoryId;
3387
+ if (!catId) {
3388
+ setProducts([]);
3389
+ return;
3390
+ }
3391
+ const service = propellerV2CoreUi.createServices(graphqlClient).category;
3392
+ const userId = resolveUserId();
3393
+ const companyId = resolveCompanyId();
3394
+ const categoryProductSearchInput = {
3395
+ ...productIds.length && { productIds },
3396
+ ...clusterIds.length && { clusterIds },
3365
3397
  language,
3366
3398
  page: 1,
3367
3399
  offset: 50,
@@ -3370,17 +3402,24 @@ function useProductSlider(options) {
3370
3402
  propellerSdkV2.ProductStatus.P,
3371
3403
  propellerSdkV2.ProductStatus.T,
3372
3404
  propellerSdkV2.ProductStatus.S
3373
- ]
3405
+ ],
3406
+ hidden: false,
3407
+ ...userId !== void 0 && { userId },
3408
+ ...companyId !== void 0 && { companyId }
3374
3409
  };
3375
3410
  const filterAvailableAttributeInput = { isSearchable: true };
3376
3411
  const variables = {
3377
- input: searchInput,
3412
+ categoryId: catId,
3413
+ language,
3414
+ categoryProductSearchInput,
3415
+ priceCalculateProductInput: buildPriceInput(),
3416
+ filterAvailableAttributeInput,
3378
3417
  imageSearchFilters: configuration.imageSearchFiltersGrid,
3379
- imageVariantFilters: configuration.imageVariantFiltersMedium,
3380
- filterAvailableAttributeInput
3418
+ imageVariantFilters: configuration.imageVariantFiltersMedium
3381
3419
  };
3382
- const response = await service.getProducts(variables);
3383
- setProducts(response?.items ?? []);
3420
+ const response = await service.getCategory(variables);
3421
+ const productsResponse = response?.products;
3422
+ setProducts(productsResponse?.items ?? []);
3384
3423
  } catch (e) {
3385
3424
  setError(e instanceof Error ? e.message : "Failed to fetch products");
3386
3425
  setProducts([]);
@@ -4696,8 +4735,25 @@ function ActionCode(rawProps) {
4696
4735
  ] });
4697
4736
  }
4698
4737
  var ActionCode_default = ActionCode;
4738
+ function PropellerImg({ as: Injected, ...props }) {
4739
+ if (Injected) return /* @__PURE__ */ jsxRuntime.jsx(Injected, { ...props });
4740
+ const { src, alt, className, width, height, loading, onClick } = props;
4741
+ return /* @__PURE__ */ jsxRuntime.jsx(
4742
+ "img",
4743
+ {
4744
+ src,
4745
+ alt,
4746
+ className,
4747
+ width,
4748
+ height,
4749
+ loading,
4750
+ onClick
4751
+ }
4752
+ );
4753
+ }
4699
4754
  function CartBonusItems(rawProps) {
4700
4755
  const props = useInfraProps(rawProps);
4756
+ const Img = useImgComponent();
4701
4757
  const items = props.bonusItems ?? props.cart?.bonusItems ?? [];
4702
4758
  if (items.length === 0) return null;
4703
4759
  const currency = props.currency ?? "\u20AC";
@@ -4715,20 +4771,21 @@ function CartBonusItems(rawProps) {
4715
4771
  {
4716
4772
  className: "propeller-cart-bonus-item flex items-center gap-4 bg-card p-4 rounded-container shadow-sm border border-border",
4717
4773
  children: [
4718
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-cart-bonus-item__media w-20 h-20 flex-shrink-0 bg-surface-hover rounded-control overflow-hidden flex items-center justify-center", children: img ? /* @__PURE__ */ jsxRuntime.jsx("img", { className: "propeller-cart-bonus-item__image w-full h-full object-contain p-1", src: img, alt: name }) : /* @__PURE__ */ jsxRuntime.jsx(
4774
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-cart-bonus-item__media w-20 h-20 flex-shrink-0 bg-surface-hover rounded-control overflow-hidden flex items-center justify-center", children: img ? /* @__PURE__ */ jsxRuntime.jsx(PropellerImg, { as: Img, className: "propeller-cart-bonus-item__image w-full h-full object-contain p-1", src: img, alt: name, loading: "lazy" }) : /* @__PURE__ */ jsxRuntime.jsx(
4719
4775
  "svg",
4720
4776
  {
4777
+ "aria-hidden": "true",
4721
4778
  fill: "none",
4722
4779
  viewBox: "0 0 24 24",
4723
4780
  stroke: "currentColor",
4724
- className: "propeller-cart-bonus-item__image-placeholder w-8 h-8 text-foreground-subtle",
4725
- strokeWidth: 1.5,
4781
+ className: "propeller-cart-bonus-item__image-placeholder w-10 h-10 text-foreground-subtle",
4782
+ strokeWidth: 1,
4726
4783
  children: /* @__PURE__ */ jsxRuntime.jsx(
4727
4784
  "path",
4728
4785
  {
4729
4786
  strokeLinecap: "round",
4730
4787
  strokeLinejoin: "round",
4731
- d: "M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909M3.75 21h16.5A2.25 2.25 0 0022.5 18.75V5.25A2.25 2.25 0 0020.25 3H3.75A2.25 2.25 0 001.5 5.25v13.5A2.25 2.25 0 003.75 21z"
4788
+ d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
4732
4789
  }
4733
4790
  )
4734
4791
  }
@@ -4773,6 +4830,7 @@ function mergeGrouped(p) {
4773
4830
  }
4774
4831
  function AddToCart(rawProps) {
4775
4832
  const props = mergeGrouped(useInfraProps(rawProps));
4833
+ const Img = useImgComponent();
4776
4834
  const { cart, loading, checkoutAllowed, addItem: addItem2, getMinQuantity, getStep } = useCart({
4777
4835
  graphqlClient: props.graphqlClient,
4778
4836
  user: props.user ?? null,
@@ -4805,10 +4863,7 @@ function AddToCart(rawProps) {
4805
4863
  setToastVisible(false);
4806
4864
  }
4807
4865
  function snapQuantity(value) {
4808
- const min = getMinQuantity(props.product);
4809
- const step = getStep(props.product);
4810
- if (value <= min) return min;
4811
- return Math.round((value - min) / step) * step + min;
4866
+ return propellerV2CoreUi.resolveOrderableQuantity(value, getMinQuantity(props.product), getStep(props.product));
4812
4867
  }
4813
4868
  function increment() {
4814
4869
  setQuantityDraft(null);
@@ -5129,27 +5184,30 @@ function AddToCart(rawProps) {
5129
5184
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "propeller-add-to-cart__modal-body px-6 py-5", children: [
5130
5185
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "propeller-add-to-cart__modal-product flex items-start gap-4", children: [
5131
5186
  !!getModalImageUrl() ? /* @__PURE__ */ jsxRuntime.jsx(
5132
- "img",
5187
+ PropellerImg,
5133
5188
  {
5189
+ as: Img,
5134
5190
  className: "propeller-add-to-cart__modal-image w-16 h-16 object-contain rounded border border-border-subtle flex-shrink-0",
5135
5191
  src: getModalImageUrl(),
5136
- alt: getModalName()
5192
+ alt: getModalName(),
5193
+ loading: "lazy"
5137
5194
  }
5138
5195
  ) : null,
5139
5196
  !getModalImageUrl() ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-add-to-cart__modal-image-placeholder w-16 h-16 flex items-center justify-center rounded border border-border-subtle flex-shrink-0 bg-surface-hover", children: /* @__PURE__ */ jsxRuntime.jsx(
5140
5197
  "svg",
5141
5198
  {
5199
+ "aria-hidden": "true",
5142
5200
  fill: "none",
5143
5201
  viewBox: "0 0 24 24",
5144
5202
  stroke: "currentColor",
5145
5203
  className: "w-8 h-8 text-foreground-subtle",
5146
- strokeWidth: 1.5,
5204
+ strokeWidth: 1,
5147
5205
  children: /* @__PURE__ */ jsxRuntime.jsx(
5148
5206
  "path",
5149
5207
  {
5150
5208
  strokeLinecap: "round",
5151
5209
  strokeLinejoin: "round",
5152
- d: "M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909M3.75 21h16.5A2.25 2.25 0 0022.5 18.75V5.25A2.25 2.25 0 0020.25 3H3.75A2.25 2.25 0 001.5 5.25v13.5A2.25 2.25 0 003.75 21z"
5210
+ d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
5153
5211
  }
5154
5212
  )
5155
5213
  }
@@ -6914,6 +6972,7 @@ function CartItem(rawProps) {
6914
6972
  companyId: props.companyId
6915
6973
  });
6916
6974
  const [quantity, setQuantity] = React6.useState(() => props.cartItem.quantity || 1);
6975
+ const [quantityDraft, setQuantityDraft] = React6.useState(() => null);
6917
6976
  const [notes, setNotes] = React6.useState(() => props.cartItem.notes || "");
6918
6977
  const [loading, setLoading] = React6.useState(() => false);
6919
6978
  const [deleting, setDeleting] = React6.useState(() => false);
@@ -7037,6 +7096,34 @@ function CartItem(rawProps) {
7037
7096
  setLoading(false);
7038
7097
  }
7039
7098
  }
7099
+ function handleQuantityDecrement() {
7100
+ setQuantityDraft(null);
7101
+ const orderable = propellerV2CoreUi.resolveOrderableQuantity(quantity, minQuantity, step);
7102
+ if (orderable !== quantity) {
7103
+ handleQuantityChange(orderable);
7104
+ return;
7105
+ }
7106
+ if (quantity - step >= minQuantity) handleQuantityChange(quantity - step);
7107
+ }
7108
+ function handleQuantityIncrement() {
7109
+ setQuantityDraft(null);
7110
+ const orderable = propellerV2CoreUi.resolveOrderableQuantity(quantity, minQuantity, step);
7111
+ handleQuantityChange(orderable === quantity ? quantity + step : orderable);
7112
+ }
7113
+ function handleQuantityInput(raw) {
7114
+ setQuantityDraft(raw);
7115
+ const val = parseInt(raw, 10);
7116
+ if (!isNaN(val) && val >= minQuantity) {
7117
+ handleQuantityChange(propellerV2CoreUi.resolveOrderableQuantity(val, minQuantity, step));
7118
+ }
7119
+ }
7120
+ function handleQuantityBlur() {
7121
+ if (quantityDraft === null) return;
7122
+ const val = parseInt(quantityDraft, 10);
7123
+ setQuantityDraft(null);
7124
+ const orderable = propellerV2CoreUi.resolveOrderableQuantity(val, minQuantity, step);
7125
+ if (orderable !== quantity) handleQuantityChange(orderable);
7126
+ }
7040
7127
  function handleNoteChange(note) {
7041
7128
  setNotes(note);
7042
7129
  if (props.onNoteChange) {
@@ -7139,7 +7226,8 @@ function CartItem(rawProps) {
7139
7226
  deleting,
7140
7227
  crossupsells,
7141
7228
  visibleCrossupsells: getVisibleCrossupsells(),
7142
- addingCrossupsellId
7229
+ addingCrossupsellId,
7230
+ quantityDraft
7143
7231
  },
7144
7232
  helpers: {
7145
7233
  getBundleItemName,
@@ -7152,6 +7240,10 @@ function CartItem(rawProps) {
7152
7240
  },
7153
7241
  handlers: {
7154
7242
  onQuantityChange: handleQuantityChange,
7243
+ onQuantityDecrement: handleQuantityDecrement,
7244
+ onQuantityIncrement: handleQuantityIncrement,
7245
+ onQuantityInput: handleQuantityInput,
7246
+ onQuantityBlur: handleQuantityBlur,
7155
7247
  onNoteChange: handleNoteChange,
7156
7248
  onDelete: handleDelete,
7157
7249
  onTitleClick: (e) => props.onTitleClick?.(e, props.cartItem),
@@ -7209,29 +7301,33 @@ function CartItem(rawProps) {
7209
7301
  }
7210
7302
  function CartItemImage(props = {}) {
7211
7303
  const { derived } = useCartItemContext();
7304
+ const Img = useImgComponent();
7212
7305
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: props.className ?? "propeller-cart-item__media w-20 h-20 md:w-24 md:h-24 flex-shrink-0 bg-surface-hover flex items-center justify-center overflow-hidden relative rounded-control", children: [
7213
7306
  !!derived.imageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7214
- "img",
7307
+ PropellerImg,
7215
7308
  {
7309
+ as: Img,
7216
7310
  className: "propeller-cart-item__image w-full h-full object-contain p-1",
7217
7311
  src: derived.imageUrl,
7218
- alt: derived.name
7312
+ alt: derived.name,
7313
+ loading: "lazy"
7219
7314
  }
7220
7315
  ) : null,
7221
7316
  !derived.imageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7222
7317
  "svg",
7223
7318
  {
7319
+ "aria-hidden": "true",
7224
7320
  fill: "none",
7225
7321
  viewBox: "0 0 24 24",
7226
7322
  stroke: "currentColor",
7227
- className: "propeller-cart-item__image-placeholder w-8 h-8 text-foreground-subtle",
7228
- strokeWidth: 1.5,
7323
+ className: "propeller-cart-item__image-placeholder w-10 h-10 md:w-12 md:h-12 text-foreground-subtle",
7324
+ strokeWidth: 1,
7229
7325
  children: /* @__PURE__ */ jsxRuntime.jsx(
7230
7326
  "path",
7231
7327
  {
7232
7328
  strokeLinecap: "round",
7233
7329
  strokeLinejoin: "round",
7234
- d: "M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909M3.75 21h16.5A2.25 2.25 0 0022.5 18.75V5.25A2.25 2.25 0 0020.25 3H3.75A2.25 2.25 0 001.5 5.25v13.5A2.25 2.25 0 003.75 21z"
7330
+ d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
7235
7331
  }
7236
7332
  )
7237
7333
  }
@@ -7352,6 +7448,7 @@ function CartItemNotes(props = {}) {
7352
7448
  }
7353
7449
  function CartItemCrossupsells(props = {}) {
7354
7450
  const { resolved, state, helpers, handlers } = useCartItemContext();
7451
+ const Img = useImgComponent();
7355
7452
  if (state.visibleCrossupsells.length === 0) return null;
7356
7453
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: props.className ?? "propeller-cart-item__crossupsells mt-3 pt-3 border-t border-border", children: [
7357
7454
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "propeller-cart-item__crossupsells-label text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2", children: propellerV2CoreUi.getLabel(resolved.labels, "crossupsellTitle", "You might also like") }),
@@ -7368,11 +7465,13 @@ function CartItemCrossupsells(props = {}) {
7368
7465
  onClick: (e) => handlers.onCrossupsellClick(item, e),
7369
7466
  children: [
7370
7467
  !!helpers.getCrossupsellImageUrl(item) ? /* @__PURE__ */ jsxRuntime.jsx(
7371
- "img",
7468
+ PropellerImg,
7372
7469
  {
7470
+ as: Img,
7373
7471
  className: "propeller-cart-item__crossupsell-image w-10 h-10 object-contain rounded flex-shrink-0",
7374
7472
  src: helpers.getCrossupsellImageUrl(item),
7375
- alt: helpers.getCrossupsellName(item)
7473
+ alt: helpers.getCrossupsellName(item),
7474
+ loading: "lazy"
7376
7475
  }
7377
7476
  ) : null,
7378
7477
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
@@ -7459,7 +7558,7 @@ function CartItemQuantity(props = {}) {
7459
7558
  {
7460
7559
  type: "button",
7461
7560
  className: "propeller-cart-item__decrement px-2.5 h-full text-muted-foreground hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed transition-colors rounded-l-control select-none",
7462
- onClick: () => handlers.onQuantityChange(derived.quantity - derived.step),
7561
+ onClick: handlers.onQuantityDecrement,
7463
7562
  disabled: derived.quantity <= derived.minQuantity || state.loading,
7464
7563
  children: "-"
7465
7564
  }
@@ -7471,13 +7570,9 @@ function CartItemQuantity(props = {}) {
7471
7570
  className: "propeller-cart-item__quantity w-10 text-center text-sm bg-transparent border-x border-input h-full focus:ring-0 focus:outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none",
7472
7571
  min: derived.minQuantity,
7473
7572
  step: derived.step,
7474
- value: derived.quantity,
7475
- onChange: (e) => {
7476
- const val = parseInt(e.target.value, 10);
7477
- if (!isNaN(val) && val >= derived.minQuantity) {
7478
- handlers.onQuantityChange(Math.round((val - derived.minQuantity) / derived.step) * derived.step + derived.minQuantity);
7479
- }
7480
- }
7573
+ value: state.quantityDraft ?? derived.quantity,
7574
+ onChange: (e) => handlers.onQuantityInput(e.target.value),
7575
+ onBlur: handlers.onQuantityBlur
7481
7576
  }
7482
7577
  ),
7483
7578
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -7485,7 +7580,7 @@ function CartItemQuantity(props = {}) {
7485
7580
  {
7486
7581
  type: "button",
7487
7582
  className: "propeller-cart-item__increment px-2.5 h-full text-muted-foreground hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed transition-colors rounded-r-control select-none",
7488
- onClick: () => handlers.onQuantityChange(derived.quantity + derived.step),
7583
+ onClick: handlers.onQuantityIncrement,
7489
7584
  disabled: state.loading,
7490
7585
  children: "+"
7491
7586
  }
@@ -7499,13 +7594,9 @@ function CartItemQuantity(props = {}) {
7499
7594
  className: props.className ?? "propeller-cart-item__quantity w-14 h-9 text-center text-sm border border-input rounded-control focus:ring-2 focus:ring-primary focus:border-transparent [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none",
7500
7595
  min: derived.minQuantity,
7501
7596
  step: derived.step,
7502
- value: derived.quantity,
7503
- onChange: (e) => {
7504
- const val = parseInt(e.target.value, 10);
7505
- if (!isNaN(val) && val >= derived.minQuantity) {
7506
- handlers.onQuantityChange(Math.round((val - derived.minQuantity) / derived.step) * derived.step + derived.minQuantity);
7507
- }
7508
- }
7597
+ value: state.quantityDraft ?? derived.quantity,
7598
+ onChange: (e) => handlers.onQuantityInput(e.target.value),
7599
+ onBlur: handlers.onQuantityBlur
7509
7600
  }
7510
7601
  );
7511
7602
  }
@@ -7801,7 +7892,7 @@ function CartIconAndSidebar(rawProps) {
7801
7892
  includeTax: useTax,
7802
7893
  className: "propeller-cart-icon__item",
7803
7894
  children: [
7804
- /* @__PURE__ */ jsxRuntime.jsx(CartItem_default.Image, { className: "w-20 h-20 flex-shrink-0 bg-surface-hover rounded-control overflow-hidden border border-border-subtle" }),
7895
+ /* @__PURE__ */ jsxRuntime.jsx(CartItem_default.Image, { className: "w-20 h-20 flex-shrink-0 bg-surface-hover rounded-control overflow-hidden border border-border-subtle flex items-center justify-center relative" }),
7805
7896
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex flex-col justify-between py-1", children: [
7806
7897
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
7807
7898
  /* @__PURE__ */ jsxRuntime.jsx(CartItem_default.Title, { className: "propeller-cart-icon__item-name text-sm font-medium hover:text-primary line-clamp-2" }),
@@ -8568,6 +8659,7 @@ function ClusterCardImage(props) {
8568
8659
  const showFavorite = props.showFavorite ?? !!resolved.enableAddFavorite;
8569
8660
  const isRow = derived.isRow;
8570
8661
  const InjectedImage = resolved.imageComponent;
8662
+ const Img = useImgComponent();
8571
8663
  if (InjectedImage) {
8572
8664
  return /* @__PURE__ */ jsxRuntime.jsx(
8573
8665
  InjectedImage,
@@ -8586,13 +8678,15 @@ function ClusterCardImage(props) {
8586
8678
  className: props.className ?? `propeller-cluster-card__media relative overflow-hidden bg-surface-hover ${isRow ? "w-20 h-20 flex-shrink-0 p-2" : "aspect-[4/3] sm:aspect-square p-2 sm:p-4"}`,
8587
8679
  children: [
8588
8680
  /* @__PURE__ */ jsxRuntime.jsx("a", { className: "block h-full w-full", href: derived.clusterUrl, onClick: handleClusterClick, children: derived.imageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
8589
- "img",
8681
+ PropellerImg,
8590
8682
  {
8683
+ as: Img,
8591
8684
  className: "propeller-cluster-card__image h-full w-full object-contain transition-transform duration-300 group-hover:scale-105",
8592
8685
  src: derived.imageUrl,
8593
- alt: derived.name
8686
+ alt: derived.name,
8687
+ loading: "lazy"
8594
8688
  }
8595
- ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-cluster-card__image-placeholder flex h-full w-full items-center justify-center text-foreground-subtle", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", className: "h-16 w-16", children: /* @__PURE__ */ jsxRuntime.jsx(
8689
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-cluster-card__image-placeholder flex h-full w-full items-center justify-center text-foreground-subtle", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { "aria-hidden": "true", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", className: "h-16 w-16", children: /* @__PURE__ */ jsxRuntime.jsx(
8596
8690
  "path",
8597
8691
  {
8598
8692
  strokeLinecap: "round",
@@ -8810,6 +8904,7 @@ function getMachineImageUrl(machine) {
8810
8904
  }
8811
8905
  function MachineCard(props) {
8812
8906
  const { machine, href, language } = props;
8907
+ const Img = useImgComponent();
8813
8908
  const Wrapper = href ? "a" : "div";
8814
8909
  const showImage = props.showImage ?? true;
8815
8910
  const showDescription = props.showDescription ?? false;
@@ -8828,8 +8923,9 @@ function MachineCard(props) {
8828
8923
  className: "flex h-full flex-col focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
8829
8924
  children: [
8830
8925
  showImage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-machine-card__image aspect-square w-full overflow-hidden bg-background-subtle", children: imageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
8831
- "img",
8926
+ PropellerImg,
8832
8927
  {
8928
+ as: Img,
8833
8929
  src: imageUrl,
8834
8930
  alt: name,
8835
8931
  loading: "lazy",
@@ -8846,6 +8942,7 @@ function MachineCard(props) {
8846
8942
  children: /* @__PURE__ */ jsxRuntime.jsx(
8847
8943
  "svg",
8848
8944
  {
8945
+ "aria-hidden": "true",
8849
8946
  className: "h-12 w-12",
8850
8947
  fill: "none",
8851
8948
  stroke: "currentColor",
@@ -9150,6 +9247,7 @@ function ProductCardImage(props) {
9150
9247
  const showFavorite = props.showFavorite ?? !!resolved.enableAddFavorite;
9151
9248
  const isRow = derived.isRow;
9152
9249
  const InjectedImage = resolved.imageComponent;
9250
+ const Img = useImgComponent();
9153
9251
  if (InjectedImage) {
9154
9252
  return /* @__PURE__ */ jsxRuntime.jsx(
9155
9253
  InjectedImage,
@@ -9168,13 +9266,15 @@ function ProductCardImage(props) {
9168
9266
  className: props.className ?? `propeller-product-card__media relative overflow-hidden bg-surface-hover ${isRow ? "w-20 h-20 flex-shrink-0 p-2" : "aspect-[4/3] sm:aspect-square p-2 sm:p-4"}`,
9169
9267
  children: [
9170
9268
  /* @__PURE__ */ jsxRuntime.jsx("a", { className: "block h-full w-full", href: derived.productUrl, onClick: handleProductClick, children: derived.imageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
9171
- "img",
9269
+ PropellerImg,
9172
9270
  {
9271
+ as: Img,
9173
9272
  className: "propeller-product-card__image h-full w-full object-contain transition-transform duration-300 group-hover:scale-105",
9174
9273
  src: derived.imageUrl,
9175
- alt: derived.name
9274
+ alt: derived.name,
9275
+ loading: "lazy"
9176
9276
  }
9177
- ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-product-card__image-placeholder flex h-full w-full items-center justify-center text-foreground-subtle", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", className: "h-16 w-16", children: /* @__PURE__ */ jsxRuntime.jsx(
9277
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-product-card__image-placeholder flex h-full w-full items-center justify-center text-foreground-subtle", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { "aria-hidden": "true", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", className: "h-16 w-16", children: /* @__PURE__ */ jsxRuntime.jsx(
9178
9278
  "path",
9179
9279
  {
9180
9280
  strokeLinecap: "round",
@@ -11029,6 +11129,7 @@ function MachinePartsSearch(props) {
11029
11129
  ] });
11030
11130
  }
11031
11131
  function ClusterConfigurator(props) {
11132
+ const Img = useImgComponent();
11032
11133
  const { settingsWithValues, handleAttributeSelect, initFromProduct } = useClusterConfigurator({
11033
11134
  products: props.products,
11034
11135
  config: props.config,
@@ -11119,7 +11220,7 @@ function ClusterConfigurator(props) {
11119
11220
  "data-selected": setting.selectedValue === val ? "true" : "false",
11120
11221
  className: `propeller-cluster-configurator__image-swatch relative w-16 h-16 rounded-control border-2 overflow-hidden transition-all ${setting.disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"} ${setting.selectedValue === val ? "border-secondary ring-2 ring-secondary/30 ring-offset-1" : "border-border hover:border-secondary/30"}`,
11121
11222
  children: [
11122
- /* @__PURE__ */ jsxRuntime.jsx("img", { className: "propeller-cluster-configurator__image w-full h-full object-cover", src: val, alt: val }),
11223
+ /* @__PURE__ */ jsxRuntime.jsx(PropellerImg, { as: Img, className: "propeller-cluster-configurator__image w-full h-full object-cover", src: val, alt: val, loading: "lazy" }),
11123
11224
  setting.selectedValue === val ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-cluster-configurator__image-check absolute inset-0 bg-secondary bg-opacity-20 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
11124
11225
  "svg",
11125
11226
  {
@@ -11219,6 +11320,7 @@ function getProductImageUrl4(product) {
11219
11320
  }
11220
11321
  function ClusterOptions(rawProps) {
11221
11322
  const props = useInfraProps(rawProps);
11323
+ const Img = useImgComponent();
11222
11324
  const [selectedProductIds, setSelectedProductIds] = React6.useState({});
11223
11325
  function getOptionsForRender() {
11224
11326
  const language = props.language;
@@ -11305,20 +11407,23 @@ function ClusterOptions(rawProps) {
11305
11407
  option.hasError ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "propeller-cluster-options__error mt-1 text-xs text-destructive", children: propellerV2CoreUi.getLabel(props.labels, "requiredError", "This option is required") }) : null,
11306
11408
  option.hasSelection ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "propeller-cluster-options__preview mt-3 flex items-center gap-3 rounded-container border border-border-subtle bg-surface-hover p-3", children: [
11307
11409
  !!option.previewImageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11308
- "img",
11410
+ PropellerImg,
11309
11411
  {
11412
+ as: Img,
11310
11413
  className: "propeller-cluster-options__preview-image h-12 w-12 flex-shrink-0 rounded border border-border-subtle bg-card object-contain",
11311
11414
  src: option.previewImageUrl,
11312
- alt: option.previewName
11415
+ alt: option.previewName,
11416
+ loading: "lazy"
11313
11417
  }
11314
11418
  ) : null,
11315
11419
  !option.previewImageUrl ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-cluster-options__preview-image-placeholder flex h-12 w-12 flex-shrink-0 items-center justify-center rounded border border-border bg-surface-hover", children: /* @__PURE__ */ jsxRuntime.jsx(
11316
11420
  "svg",
11317
11421
  {
11422
+ "aria-hidden": "true",
11318
11423
  fill: "none",
11319
11424
  stroke: "currentColor",
11320
11425
  viewBox: "0 0 24 24",
11321
- className: "h-5 w-5 text-foreground-subtle",
11426
+ className: "h-6 w-6 text-foreground-subtle",
11322
11427
  children: /* @__PURE__ */ jsxRuntime.jsx(
11323
11428
  "path",
11324
11429
  {
@@ -11802,19 +11907,22 @@ function FavoriteListItem(rawProps) {
11802
11907
  }
11803
11908
  function FavoriteListItemImage(props) {
11804
11909
  const { derived, handleItemClick, resolved } = useFavoriteListItemContext();
11910
+ const Img = useImgComponent();
11805
11911
  const { imageUrl, name, productUrl } = derived;
11806
11912
  const linkable = resolved.titleLinkable !== false;
11807
11913
  const wrapperClass = props.className ?? "propeller-favorite-list-item__media relative w-16 h-16 flex-shrink-0 overflow-hidden rounded-control bg-surface-hover p-1";
11808
11914
  const inner = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11809
11915
  !!imageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11810
- "img",
11916
+ PropellerImg,
11811
11917
  {
11918
+ as: Img,
11812
11919
  className: "propeller-favorite-list-item__image h-full w-full object-contain",
11813
11920
  src: imageUrl,
11814
- alt: name
11921
+ alt: name,
11922
+ loading: "lazy"
11815
11923
  }
11816
11924
  ) : null,
11817
- !imageUrl ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-favorite-list-item__image-placeholder flex h-full w-full items-center justify-center text-foreground-subtle", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", className: "h-8 w-8", children: /* @__PURE__ */ jsxRuntime.jsx(
11925
+ !imageUrl ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-favorite-list-item__image-placeholder flex h-full w-full items-center justify-center text-foreground-subtle", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { "aria-hidden": "true", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", className: "h-10 w-10", children: /* @__PURE__ */ jsxRuntime.jsx(
11818
11926
  "path",
11819
11927
  {
11820
11928
  strokeLinecap: "round",
@@ -12012,6 +12120,7 @@ FavoriteListItemExport.Actions = FavoriteListItemActions;
12012
12120
  var FavoriteListItem_default = FavoriteListItemExport;
12013
12121
  function FavoriteListDetails(rawProps) {
12014
12122
  const props = useInfraProps(rawProps);
12123
+ const Img = useImgComponent();
12015
12124
  const FavoriteListItemImpl = props.favoriteListItemComponent ?? FavoriteListItem_default;
12016
12125
  const GridPaginationImpl = props.gridPaginationComponent ?? GridPagination_default;
12017
12126
  const { addToList } = useFavorites({
@@ -12593,11 +12702,13 @@ function FavoriteListDetails(rawProps) {
12593
12702
  "data-adding": isAdding ? "true" : "false",
12594
12703
  children: [
12595
12704
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-favorite-list-details__search-item-media h-14 w-14 flex-shrink-0 rounded-control bg-surface-hover overflow-hidden flex items-center justify-center", children: getSearchItemImage(item) ? /* @__PURE__ */ jsxRuntime.jsx(
12596
- "img",
12705
+ PropellerImg,
12597
12706
  {
12707
+ as: Img,
12598
12708
  src: getSearchItemImage(item),
12599
12709
  alt: getSearchItemName(item),
12600
- className: "h-full w-full object-contain"
12710
+ className: "h-full w-full object-contain",
12711
+ loading: "lazy"
12601
12712
  }
12602
12713
  ) : /* @__PURE__ */ jsxRuntime.jsxs(
12603
12714
  "svg",
@@ -13305,6 +13416,7 @@ function getBundleNonLeaders(item) {
13305
13416
  }
13306
13417
  function ItemsOverview(rawProps) {
13307
13418
  const props = useInfraProps(rawProps);
13419
+ const Img = useImgComponent();
13308
13420
  const language = props.language;
13309
13421
  const useTax = !!props.includeTax;
13310
13422
  const containerClass = props.itemsOverviewContainerClass || "cart-items-overview";
@@ -13363,26 +13475,29 @@ function ItemsOverview(rawProps) {
13363
13475
  "data-bundle": isBundle ? "true" : "false",
13364
13476
  children: [
13365
13477
  showImage ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-items-overview__item-media w-16 h-16 flex-shrink-0 bg-surface-hover rounded-control overflow-hidden border border-border-subtle flex items-center justify-center", children: getItemImageUrl(item) ? /* @__PURE__ */ jsxRuntime.jsx(
13366
- "img",
13478
+ PropellerImg,
13367
13479
  {
13480
+ as: Img,
13368
13481
  className: "propeller-items-overview__item-image w-full h-full object-contain p-1.5",
13369
13482
  src: getItemImageUrl(item),
13370
- alt: getItemName(item, language)
13483
+ alt: getItemName(item, language),
13484
+ loading: "lazy"
13371
13485
  }
13372
13486
  ) : /* @__PURE__ */ jsxRuntime.jsx(
13373
13487
  "svg",
13374
13488
  {
13489
+ "aria-hidden": "true",
13375
13490
  fill: "none",
13376
13491
  viewBox: "0 0 24 24",
13377
13492
  stroke: "currentColor",
13378
- strokeWidth: "1.5",
13379
- className: "propeller-items-overview__item-image-placeholder w-6 h-6 text-foreground-subtle",
13493
+ strokeWidth: "1",
13494
+ className: "propeller-items-overview__item-image-placeholder w-8 h-8 text-foreground-subtle",
13380
13495
  children: /* @__PURE__ */ jsxRuntime.jsx(
13381
13496
  "path",
13382
13497
  {
13383
13498
  strokeLinecap: "round",
13384
13499
  strokeLinejoin: "round",
13385
- d: "M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909M3.75 21h16.5A2.25 2.25 0 0022.5 18.75V5.25A2.25 2.25 0 0020.25 3H3.75A2.25 2.25 0 001.5 5.25v13.5A2.25 2.25 0 003.75 21z"
13500
+ d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
13386
13501
  }
13387
13502
  )
13388
13503
  }
@@ -14006,11 +14121,13 @@ function OrderItemCard(props) {
14006
14121
  className: `propeller-order-item-card__cell propeller-order-item-card__cell--product ${isChildItem ? "px-6 py-2 pl-28" : "px-6 py-4"}`,
14007
14122
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
14008
14123
  showImage ? productImage ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-order-item-card__media relative w-16 h-16 flex-shrink-0 rounded overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
14009
- "img",
14124
+ PropellerImg,
14010
14125
  {
14126
+ as: props.imgComponent,
14011
14127
  className: "propeller-order-item-card__image object-cover w-full h-full",
14012
14128
  src: productImage,
14013
- alt: productName
14129
+ alt: productName,
14130
+ loading: "lazy"
14014
14131
  }
14015
14132
  ) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-order-item-card__image-placeholder w-16 h-16 bg-surface-hover rounded flex items-center justify-center text-foreground-subtle text-xs", children: propellerV2CoreUi.getLabel(props.labels, "noImage", "No Img") }) : null,
14016
14133
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
@@ -14737,10 +14854,6 @@ function RequestPriceButton(props) {
14737
14854
  }
14738
14855
  var RequestPriceButton_default = RequestPriceButton;
14739
14856
  var ROW_SEQ = 0;
14740
- function snapToStep(value, min, step) {
14741
- if (!Number.isFinite(value) || value <= min) return min;
14742
- return Math.round((value - min) / (step || 1)) * (step || 1) + min;
14743
- }
14744
14857
  function blankRow() {
14745
14858
  ROW_SEQ += 1;
14746
14859
  return {
@@ -14777,6 +14890,7 @@ function QuickOrderInner(props) {
14777
14890
  className,
14778
14891
  labels
14779
14892
  } = props;
14893
+ const Img = useImgComponent();
14780
14894
  const user = props.user ?? null;
14781
14895
  const graphqlClient = props.graphqlClient;
14782
14896
  const displayPrice = (n) => props.formatPrice ? props.formatPrice(n) : propellerV2CoreUi.formatPrice(n, { symbol: currency, locale: propellerV2CoreUi.localeForLanguage(props.language) });
@@ -14859,7 +14973,7 @@ function QuickOrderInner(props) {
14859
14973
  const n = parseInt(raw, 10);
14860
14974
  setRows(
14861
14975
  (prev) => prev.map(
14862
- (r) => r.key === key ? { ...r, quantity: snapToStep(n, r.minQuantity, r.step) } : r
14976
+ (r) => r.key === key ? { ...r, quantity: propellerV2CoreUi.resolveOrderableQuantity(n, r.minQuantity, r.step) } : r
14863
14977
  )
14864
14978
  );
14865
14979
  },
@@ -15008,7 +15122,7 @@ function QuickOrderInner(props) {
15008
15122
  children: [
15009
15123
  m.imageUrl ? (
15010
15124
  // eslint-disable-next-line @next/next/no-img-element
15011
- /* @__PURE__ */ jsxRuntime.jsx("img", { src: m.imageUrl, alt: "", width: 32, height: 32, className: "rounded object-cover" })
15125
+ /* @__PURE__ */ jsxRuntime.jsx(PropellerImg, { as: Img, src: m.imageUrl, alt: "", width: 32, height: 32, className: "rounded object-cover", loading: "lazy" })
15012
15126
  ) : null,
15013
15127
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex-1", children: [
15014
15128
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-foreground", children: m.name }),
@@ -15673,6 +15787,7 @@ function getProductName4(product, language) {
15673
15787
  }
15674
15788
  function ProductBundles(rawProps) {
15675
15789
  const props = useInfraProps(rawProps);
15790
+ const Img = useImgComponent();
15676
15791
  const language = props.language;
15677
15792
  const currencySymbol = props.currency ?? "\u20AC";
15678
15793
  const money = (value) => propellerV2CoreUi.formatPrice(value, { symbol: currencySymbol, locale: propellerV2CoreUi.localeForLanguage(props.language) });
@@ -15808,11 +15923,13 @@ function ProductBundles(rawProps) {
15808
15923
  ) }) : null,
15809
15924
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center text-center w-40", children: [
15810
15925
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-product-bundles__item-media w-32 h-32 bg-surface-hover rounded-container overflow-hidden flex-shrink-0 mb-2", children: getProductImage(item.product) ? /* @__PURE__ */ jsxRuntime.jsx(
15811
- "img",
15926
+ PropellerImg,
15812
15927
  {
15928
+ as: Img,
15813
15929
  className: "propeller-product-bundles__item-image w-full h-full object-contain p-2",
15814
15930
  src: getProductImage(item.product),
15815
- alt: getProductName4(item.product, language)
15931
+ alt: getProductName4(item.product, language),
15932
+ loading: "lazy"
15816
15933
  }
15817
15934
  ) : null }),
15818
15935
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-product-bundles__item-name text-sm font-medium text-muted-foreground leading-tight mb-1", children: getProductName4(item.product, language) || "Product " + item.productId }),
@@ -15977,26 +16094,29 @@ function ProductBundles(rawProps) {
15977
16094
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "propeller-product-bundles__modal-body px-6 py-5", children: [
15978
16095
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "propeller-product-bundles__modal-product flex items-start gap-4", children: [
15979
16096
  lastAddedBundle && lastAddedBundle.items && lastAddedBundle.items.length > 0 && getProductImage(lastAddedBundle.items[0].product) ? /* @__PURE__ */ jsxRuntime.jsx(
15980
- "img",
16097
+ PropellerImg,
15981
16098
  {
16099
+ as: Img,
15982
16100
  className: "propeller-product-bundles__modal-image w-16 h-16 object-contain rounded border border-border-subtle flex-shrink-0",
15983
16101
  src: lastAddedBundle?.items?.[0] ? getProductImage(lastAddedBundle.items[0].product) : "",
15984
- alt: lastAddedBundle?.name || "Bundle"
16102
+ alt: lastAddedBundle?.name || "Bundle",
16103
+ loading: "lazy"
15985
16104
  }
15986
16105
  ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-product-bundles__modal-image-placeholder w-16 h-16 flex items-center justify-center rounded border border-border-subtle flex-shrink-0 bg-surface-hover", children: /* @__PURE__ */ jsxRuntime.jsx(
15987
16106
  "svg",
15988
16107
  {
16108
+ "aria-hidden": "true",
15989
16109
  fill: "none",
15990
16110
  viewBox: "0 0 24 24",
15991
16111
  stroke: "currentColor",
15992
16112
  className: "w-8 h-8 text-foreground-subtle",
15993
- strokeWidth: 1.5,
16113
+ strokeWidth: 1,
15994
16114
  children: /* @__PURE__ */ jsxRuntime.jsx(
15995
16115
  "path",
15996
16116
  {
15997
16117
  strokeLinecap: "round",
15998
16118
  strokeLinejoin: "round",
15999
- d: "M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909M3.75 21h16.5A2.25 2.25 0 0022.5 18.75V5.25A2.25 2.25 0 0020.25 3H3.75A2.25 2.25 0 001.5 5.25v13.5A2.25 2.25 0 003.75 21z"
16119
+ d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
16000
16120
  }
16001
16121
  )
16002
16122
  }
@@ -16161,6 +16281,7 @@ function ProductDownloads(props) {
16161
16281
  }
16162
16282
  var ProductDownloads_default = ProductDownloads;
16163
16283
  function ProductGallery(props) {
16284
+ const Img = useImgComponent();
16164
16285
  const [selectedIndex, setSelectedIndex] = React6.useState(0);
16165
16286
  const [lightboxOpen, setLightboxOpen] = React6.useState(false);
16166
16287
  const images = props.images || [];
@@ -16198,11 +16319,13 @@ function ProductGallery(props) {
16198
16319
  }
16199
16320
  ) }) : null,
16200
16321
  images.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
16201
- "img",
16322
+ PropellerImg,
16202
16323
  {
16324
+ as: Img,
16203
16325
  alt: propellerV2CoreUi.getLabel(props.labels, "productImageAlt", "Product image"),
16204
16326
  src: mainImage,
16205
16327
  onClick: openLightbox,
16328
+ loading: "eager",
16206
16329
  className: `h-full w-full object-contain p-8 transition-transform duration-200 ${props.enableZoom !== false ? "cursor-zoom-in hover:scale-105" : ""}`
16207
16330
  }
16208
16331
  ) : null
@@ -16214,11 +16337,13 @@ function ProductGallery(props) {
16214
16337
  onClick: () => setSelectedIndex(index),
16215
16338
  className: `propeller-product-gallery__thumbnail relative flex-shrink-0 w-20 h-20 rounded-container border-2 overflow-hidden transition-all bg-card ${selectedIndex === index ? "border-primary ring-2 ring-primary/20" : "border-transparent hover:border-border"}`,
16216
16339
  children: /* @__PURE__ */ jsxRuntime.jsx(
16217
- "img",
16340
+ PropellerImg,
16218
16341
  {
16342
+ as: Img,
16219
16343
  className: "w-full h-full object-contain p-1",
16220
16344
  src: img,
16221
- alt: `${propellerV2CoreUi.getLabel(props.labels, "productImageAlt", "Product image")} ${index + 1}`
16345
+ alt: `${propellerV2CoreUi.getLabel(props.labels, "productImageAlt", "Product image")} ${index + 1}`,
16346
+ loading: "lazy"
16222
16347
  }
16223
16348
  )
16224
16349
  },
@@ -16273,12 +16398,13 @@ function ProductGallery(props) {
16273
16398
  }
16274
16399
  ) : null,
16275
16400
  /* @__PURE__ */ jsxRuntime.jsx(
16276
- "img",
16401
+ PropellerImg,
16277
16402
  {
16403
+ as: Img,
16278
16404
  alt: propellerV2CoreUi.getLabel(props.labels, "productImageFullscreenAlt", "Product image fullscreen"),
16279
16405
  className: "max-h-full max-w-full object-contain rounded-lg",
16280
16406
  src: mainImage,
16281
- onClick: (e) => e.stopPropagation()
16407
+ onClick: (e) => e.stopPropagation?.()
16282
16408
  }
16283
16409
  ),
16284
16410
  hasThumbnails ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -16332,6 +16458,7 @@ function pickImageUrl(mediaItems, target) {
16332
16458
  }
16333
16459
  function DefaultProductImage(props) {
16334
16460
  const { product, cluster, className } = props;
16461
+ const Img = useImgComponent();
16335
16462
  const targetLanguage = props.language ?? "NL";
16336
16463
  const mediaItems = (() => {
16337
16464
  const target = product ?? cluster?.defaultProduct ?? cluster;
@@ -16366,8 +16493,9 @@ function DefaultProductImage(props) {
16366
16493
  );
16367
16494
  }
16368
16495
  return /* @__PURE__ */ jsxRuntime.jsx(
16369
- "img",
16496
+ PropellerImg,
16370
16497
  {
16498
+ as: Img,
16371
16499
  src: url,
16372
16500
  alt: altText,
16373
16501
  className: className ?? "h-full w-full object-contain",
@@ -19276,6 +19404,7 @@ function mapToSearchBarResult(item, language, urls) {
19276
19404
  }
19277
19405
  function SearchBar(rawProps) {
19278
19406
  const props = useInfraProps(rawProps);
19407
+ const Img = useImgComponent();
19279
19408
  const [showDropdown, setShowDropdown] = React6.useState(false);
19280
19409
  const [localTerm, setLocalTerm] = React6.useState("");
19281
19410
  const containerRef = React6.useRef(null);
@@ -19441,11 +19570,13 @@ function SearchBar(rawProps) {
19441
19570
  ...resultProps,
19442
19571
  children: [
19443
19572
  result.imageUrl || noImageUrl() ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-search-bar__result-media relative w-16 h-16 flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
19444
- "img",
19573
+ PropellerImg,
19445
19574
  {
19575
+ as: Img,
19446
19576
  className: "propeller-search-bar__result-image w-full h-full object-contain",
19447
19577
  src: result.imageUrl || noImageUrl(),
19448
- alt: result.name
19578
+ alt: result.name,
19579
+ loading: "lazy"
19449
19580
  }
19450
19581
  ) }) : null,
19451
19582
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
@@ -19682,6 +19813,7 @@ exports.ProductSpecifications = ProductSpecifications_default;
19682
19813
  exports.ProductTabs = ProductTabs_default;
19683
19814
  exports.ProductVideos = ProductVideos_default;
19684
19815
  exports.PropellerDepsProvider = PropellerDepsProvider;
19816
+ exports.PropellerImg = PropellerImg;
19685
19817
  exports.PropellerProvider = PropellerProvider;
19686
19818
  exports.PurchaseAuthorizationConfigurator = PurchaseAuthorizationConfigurator_default;
19687
19819
  exports.PurchaseAuthorizationRequests = PurchaseAuthorizationRequests_default;
@@ -19707,6 +19839,7 @@ exports.useCheckout = useCheckout;
19707
19839
  exports.useClusterConfigurator = useClusterConfigurator;
19708
19840
  exports.useCompany = useCompany;
19709
19841
  exports.useFavorites = useFavorites;
19842
+ exports.useImgComponent = useImgComponent;
19710
19843
  exports.useInfraProps = useInfraProps;
19711
19844
  exports.useMachines = useMachines;
19712
19845
  exports.useMenu = useMenu;