@propeller-commerce/propeller-v2-react-ui 0.28.0 → 0.30.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.d.cts CHANGED
@@ -1381,9 +1381,7 @@ interface UseProductInfoReturn {
1381
1381
  *
1382
1382
  * @remarks
1383
1383
  * GraphQL integration: services are built per-call via `createServices(graphqlClient)`.
1384
- * `fetchProduct` runs two sequential calls — first `services.orderlist.getOrderlists()`
1385
- * (`OrderlistService`, only when a user + company are present, to resolve price-tier
1386
- * orderlist ids), then `services.product.getProduct()` (`ProductService`) with
1384
+ * `fetchProduct` calls `services.product.getProduct()` (`ProductService`) with
1387
1385
  * `priceCalculateProductInput` / `userBulkPriceProductInput` for per-user pricing.
1388
1386
  * `fetchCluster` runs `services.cluster.getClusterConfig()` then
1389
1387
  * `services.cluster.getCluster()` (`ClusterService`), passing the config-derived
@@ -1918,8 +1916,8 @@ declare function useMachines(options: UseMachinesOptions): UseMachinesReturn;
1918
1916
  *
1919
1917
  * Responsibilities:
1920
1918
  * - fetchCrossupsells: CrossupsellService with priceCalculateProductInput + extract productTo/clusterTo
1921
- * - fetchProducts: ProductService.getProducts() batch call (NOT per-item getProduct())
1922
- * with statuses filter and filterAvailableAttributeInput
1919
+ * - fetchProducts: one `category.products` batch call over `baseCategoryId`
1920
+ * (the flat `products` resolver ignores orderlist scoping), priced per user
1923
1921
  * - Scroll position tracking for responsive sliding
1924
1922
  */
1925
1923
 
@@ -1949,6 +1947,8 @@ interface UseProductSliderOptions {
1949
1947
  /** The channel's anonymous user, seeded by the host. Scopes logged-out
1950
1948
  * listings exactly like the SSR seed does. */
1951
1949
  anonymousUserId?: number;
1950
+ /** Catalog root. Required for orderlist-scoped fetching — see the remarks. */
1951
+ baseCategoryId?: number;
1952
1952
  imageSearchFiltersGrid?: MediaImageProductSearchInput;
1953
1953
  imageVariantFiltersMedium?: TransformationsInput;
1954
1954
  };
package/dist/index.d.ts CHANGED
@@ -1381,9 +1381,7 @@ interface UseProductInfoReturn {
1381
1381
  *
1382
1382
  * @remarks
1383
1383
  * GraphQL integration: services are built per-call via `createServices(graphqlClient)`.
1384
- * `fetchProduct` runs two sequential calls — first `services.orderlist.getOrderlists()`
1385
- * (`OrderlistService`, only when a user + company are present, to resolve price-tier
1386
- * orderlist ids), then `services.product.getProduct()` (`ProductService`) with
1384
+ * `fetchProduct` calls `services.product.getProduct()` (`ProductService`) with
1387
1385
  * `priceCalculateProductInput` / `userBulkPriceProductInput` for per-user pricing.
1388
1386
  * `fetchCluster` runs `services.cluster.getClusterConfig()` then
1389
1387
  * `services.cluster.getCluster()` (`ClusterService`), passing the config-derived
@@ -1918,8 +1916,8 @@ declare function useMachines(options: UseMachinesOptions): UseMachinesReturn;
1918
1916
  *
1919
1917
  * Responsibilities:
1920
1918
  * - fetchCrossupsells: CrossupsellService with priceCalculateProductInput + extract productTo/clusterTo
1921
- * - fetchProducts: ProductService.getProducts() batch call (NOT per-item getProduct())
1922
- * with statuses filter and filterAvailableAttributeInput
1919
+ * - fetchProducts: one `category.products` batch call over `baseCategoryId`
1920
+ * (the flat `products` resolver ignores orderlist scoping), priced per user
1923
1921
  * - Scroll position tracking for responsive sliding
1924
1922
  */
1925
1923
 
@@ -1949,6 +1947,8 @@ interface UseProductSliderOptions {
1949
1947
  /** The channel's anonymous user, seeded by the host. Scopes logged-out
1950
1948
  * listings exactly like the SSR seed does. */
1951
1949
  anonymousUserId?: number;
1950
+ /** Catalog root. Required for orderlist-scoped fetching — see the remarks. */
1951
+ baseCategoryId?: number;
1952
1952
  imageSearchFiltersGrid?: MediaImageProductSearchInput;
1953
1953
  imageVariantFiltersMedium?: TransformationsInput;
1954
1954
  };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { getLabel, getLocalizedValue, formatPrice, localeForLanguage, getClusterSku, getClusterImageUrl, isContentHidden, getProductSku, getProductImageUrl, createServices, isCheckoutAllowed, err, ok, formatSurcharge, deriveUserMode, isContact, isCustomer, getAddresses, attributeNameMatches, getAttributeDisplayName, filterProductsBySelections, extractAttributeValues, buildInventoryFilter, findPurchaserPac, isOverAuthorizationLimit, MIN_STOCK_THRESHOLD, getNettedBonusItems, getLanguageString, collectAttributeValues, getCountryName, resolveLanguageEntry, getLanguageUri } from '@propeller-commerce/propeller-v2-core-ui';
2
+ import { getLabel, getLocalizedValue, formatPrice, localeForLanguage, getClusterSku, getClusterImageUrl, isContentHidden, getProductSku, getProductImageUrl, createServices, isCheckoutAllowed, err, ok, formatSurcharge, deriveUserMode, isContact, isCustomer, getAddresses, resolveOrderableQuantity, attributeNameMatches, getAttributeDisplayName, filterProductsBySelections, extractAttributeValues, buildInventoryFilter, findPurchaserPac, isOverAuthorizationLimit, MIN_STOCK_THRESHOLD, getNettedBonusItems, getLanguageString, collectAttributeValues, getCountryName, resolveLanguageEntry, getLanguageUri } from '@propeller-commerce/propeller-v2-core-ui';
3
3
  export * from '@propeller-commerce/propeller-v2-core-ui';
4
4
  import * as React6 from 'react';
5
5
  import { createContext, useState, useContext, useRef, useCallback, useEffect, useMemo, useSyncExternalStore } from 'react';
@@ -600,6 +600,14 @@ function useAuth(options) {
600
600
  }, [graphqlClient]);
601
601
  return { loading, error, login, magicLogin, createMagicToken, registerContact, registerCustomer, forgotPassword };
602
602
  }
603
+ var CART_NOT_FOUND = "CART_NOT_FOUND_ERROR";
604
+ function isCartNotFound(error) {
605
+ const entries = error?.errors;
606
+ if (!Array.isArray(entries)) return false;
607
+ return entries.some(
608
+ (e) => e?.extensions?.code === CART_NOT_FOUND || /cart:?\s*\S+\s+not found/i.test(e?.message ?? "")
609
+ );
610
+ }
603
611
  async function initCart(config) {
604
612
  const {
605
613
  services,
@@ -739,6 +747,11 @@ function useCart(options) {
739
747
  setCreatedCartId(c.cartId);
740
748
  cartIdRef.current = c.cartId;
741
749
  }, []);
750
+ const forgetCart = useCallback(() => {
751
+ setCart(null);
752
+ setCreatedCartId("");
753
+ cartIdRef.current = "";
754
+ }, []);
742
755
  const imageSearchFilters = useCallback(
743
756
  () => configuration?.imageSearchFiltersGrid ?? DEFAULT_IMAGE_SEARCH_FILTERS,
744
757
  [configuration]
@@ -822,13 +835,28 @@ function useCart(options) {
822
835
  } else return err("No cart ID provided");
823
836
  }
824
837
  const service = createServices(graphqlClient).cart;
825
- const resultCart = await service.addItemToCart({
826
- id: resolvedCartId,
827
- 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 } },
828
- language,
829
- imageSearchFilters: imageSearchFilters(),
830
- imageVariantFilters: imageVariantFilters()
831
- });
838
+ 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 } };
839
+ let resultCart;
840
+ try {
841
+ resultCart = await service.addItemToCart({
842
+ id: resolvedCartId,
843
+ input,
844
+ language,
845
+ imageSearchFilters: imageSearchFilters(),
846
+ imageVariantFilters: imageVariantFilters()
847
+ });
848
+ } catch (e) {
849
+ if (!isCartNotFound(e) || !opts.createCart) throw e;
850
+ forgetCart();
851
+ const fresh = await resolveCart();
852
+ resultCart = await service.addItemToCart({
853
+ id: fresh.cartId,
854
+ input,
855
+ language,
856
+ imageSearchFilters: imageSearchFilters(),
857
+ imageVariantFilters: imageVariantFilters()
858
+ });
859
+ }
832
860
  rememberCart(resultCart);
833
861
  const addedItem = resultCart.items?.find((i) => i.productId === opts.product.productId) ?? null;
834
862
  opts.afterAddToCart?.(resultCart, addedItem);
@@ -840,7 +868,7 @@ function useCart(options) {
840
868
  } finally {
841
869
  setLoading(false);
842
870
  }
843
- }, [graphqlClient, cartId, language, configuration, resolveCart, rememberCart, imageSearchFilters, imageVariantFilters]);
871
+ }, [graphqlClient, cartId, language, configuration, resolveCart, rememberCart, forgetCart, imageSearchFilters, imageVariantFilters]);
844
872
  const addItems = useCallback(async (items) => {
845
873
  const added = [];
846
874
  let lastCart = null;
@@ -2126,19 +2154,8 @@ function useProductInfo(options) {
2126
2154
  setLoading(true);
2127
2155
  setError(null);
2128
2156
  try {
2129
- let orderlistIds = [];
2130
- let applyOrderlists = true;
2131
- if (options.orderlistIds && options.orderlistIds.length > 0) {
2132
- orderlistIds = options.orderlistIds;
2133
- applyOrderlists = options.applyOrderlists !== false;
2134
- } else if (options.applyOrderlists === false) {
2135
- applyOrderlists = false;
2136
- } else if (options.user && options.companyId) {
2137
- const orderlistService = createServices(graphqlClient).orderlist;
2138
- const searchInput = { companyIds: [options.companyId] };
2139
- const orderlists = await orderlistService.getOrderlists(searchInput);
2140
- orderlistIds = (orderlists?.items ?? []).map((ol) => ol.id);
2141
- }
2157
+ const orderlistIds = options.orderlistIds ?? [];
2158
+ const applyOrderlists = options.applyOrderlists !== false;
2142
2159
  const service = createServices(graphqlClient).product;
2143
2160
  const attributeInput = buildAttributeInput2();
2144
2161
  const variables = {
@@ -2349,7 +2366,7 @@ function useProductSearch(options) {
2349
2366
  const orderlistScope = options.orderlistIds && options.orderlistIds.length > 0 ? {
2350
2367
  applyOrderlists: options.applyOrderlists !== false,
2351
2368
  orderlistIds: options.orderlistIds
2352
- } : { applyOrderlists: false };
2369
+ } : options.applyOrderlists === false ? { applyOrderlists: false } : {};
2353
2370
  const categoryProductSearchInput = {
2354
2371
  language: lang,
2355
2372
  page: pagination.currentPage,
@@ -2479,7 +2496,7 @@ function useProductSearch(options) {
2479
2496
  const orderlistScope = options.orderlistIds && options.orderlistIds.length > 0 ? {
2480
2497
  applyOrderlists: options.applyOrderlists !== false,
2481
2498
  orderlistIds: options.orderlistIds
2482
- } : { applyOrderlists: false };
2499
+ } : options.applyOrderlists === false ? { applyOrderlists: false } : {};
2483
2500
  const userId = resolveListingUserId(options.user, configuration);
2484
2501
  const contactId = options.user && "contactId" in options.user ? options.user.contactId : void 0;
2485
2502
  const customerId = options.user && "customerId" in options.user ? options.user.customerId : void 0;
@@ -2655,7 +2672,7 @@ function useQuickOrder(options) {
2655
2672
  if (!catId) return [];
2656
2673
  try {
2657
2674
  const service = createServices(graphqlClient).category;
2658
- const orderlistScope = orderlistIds && orderlistIds.length > 0 ? { applyOrderlists: applyOrderlists !== false, orderlistIds } : { applyOrderlists: false };
2675
+ const orderlistScope = orderlistIds && orderlistIds.length > 0 ? { applyOrderlists: applyOrderlists !== false, orderlistIds } : applyOrderlists === false ? { applyOrderlists: false } : {};
2659
2676
  const userId = resolveListingUserId(user, configuration);
2660
2677
  const contactId = user && "contactId" in user ? user.contactId : void 0;
2661
2678
  const customerId = user && "customerId" in user ? user.customerId : void 0;
@@ -3264,28 +3281,33 @@ function useProductSlider(options) {
3264
3281
  }
3265
3282
  }
3266
3283
  if (!productIds.length && !clusterIds.length) return items;
3284
+ const catId = configuration.baseCategoryId;
3285
+ if (!catId) return items;
3267
3286
  const userId = resolveUserId();
3268
3287
  const companyId = resolveCompanyId();
3269
- const searchInput = {
3288
+ const categoryProductSearchInput = {
3270
3289
  ...productIds.length && { productIds },
3271
3290
  ...clusterIds.length && { clusterIds },
3272
3291
  language,
3273
3292
  page: 1,
3274
3293
  offset: 50,
3275
3294
  statuses: [ProductStatus.A, ProductStatus.P, ProductStatus.T, ProductStatus.S],
3295
+ hidden: false,
3276
3296
  ...userId !== void 0 && { userId },
3277
3297
  ...companyId !== void 0 && { companyId }
3278
3298
  };
3279
3299
  const filterAvailableAttributeInput = { isSearchable: true };
3280
3300
  const variables = {
3281
- input: searchInput,
3301
+ categoryId: catId,
3302
+ language,
3303
+ categoryProductSearchInput,
3304
+ filterAvailableAttributeInput,
3282
3305
  imageSearchFilters: configuration.imageSearchFiltersGrid,
3283
- imageVariantFilters: configuration.imageVariantFiltersMedium,
3284
- filterAvailableAttributeInput
3306
+ imageVariantFilters: configuration.imageVariantFiltersMedium
3285
3307
  };
3286
3308
  try {
3287
- const response = await createServices(graphqlClient).product.getProducts(variables);
3288
- const resolved = response?.items ?? [];
3309
+ const response = await createServices(graphqlClient).category.getCategory(variables);
3310
+ const resolved = response?.products?.items ?? [];
3289
3311
  const allowed = /* @__PURE__ */ new Set();
3290
3312
  for (const r of resolved) {
3291
3313
  const id = r.productId ?? r.clusterId;
@@ -3341,10 +3363,17 @@ function useProductSlider(options) {
3341
3363
  setLoading(true);
3342
3364
  setError(null);
3343
3365
  try {
3344
- const service = createServices(graphqlClient).product;
3345
- const searchInput = {
3346
- productIds,
3347
- clusterIds,
3366
+ const catId = configuration.baseCategoryId;
3367
+ if (!catId) {
3368
+ setProducts([]);
3369
+ return;
3370
+ }
3371
+ const service = createServices(graphqlClient).category;
3372
+ const userId = resolveUserId();
3373
+ const companyId = resolveCompanyId();
3374
+ const categoryProductSearchInput = {
3375
+ ...productIds.length && { productIds },
3376
+ ...clusterIds.length && { clusterIds },
3348
3377
  language,
3349
3378
  page: 1,
3350
3379
  offset: 50,
@@ -3353,17 +3382,24 @@ function useProductSlider(options) {
3353
3382
  ProductStatus.P,
3354
3383
  ProductStatus.T,
3355
3384
  ProductStatus.S
3356
- ]
3385
+ ],
3386
+ hidden: false,
3387
+ ...userId !== void 0 && { userId },
3388
+ ...companyId !== void 0 && { companyId }
3357
3389
  };
3358
3390
  const filterAvailableAttributeInput = { isSearchable: true };
3359
3391
  const variables = {
3360
- input: searchInput,
3392
+ categoryId: catId,
3393
+ language,
3394
+ categoryProductSearchInput,
3395
+ priceCalculateProductInput: buildPriceInput(),
3396
+ filterAvailableAttributeInput,
3361
3397
  imageSearchFilters: configuration.imageSearchFiltersGrid,
3362
- imageVariantFilters: configuration.imageVariantFiltersMedium,
3363
- filterAvailableAttributeInput
3398
+ imageVariantFilters: configuration.imageVariantFiltersMedium
3364
3399
  };
3365
- const response = await service.getProducts(variables);
3366
- setProducts(response?.items ?? []);
3400
+ const response = await service.getCategory(variables);
3401
+ const productsResponse = response?.products;
3402
+ setProducts(productsResponse?.items ?? []);
3367
3403
  } catch (e) {
3368
3404
  setError(e instanceof Error ? e.message : "Failed to fetch products");
3369
3405
  setProducts([]);
@@ -4807,10 +4843,7 @@ function AddToCart(rawProps) {
4807
4843
  setToastVisible(false);
4808
4844
  }
4809
4845
  function snapQuantity(value) {
4810
- const min = getMinQuantity(props.product);
4811
- const step = getStep(props.product);
4812
- if (value <= min) return min;
4813
- return Math.round((value - min) / step) * step + min;
4846
+ return resolveOrderableQuantity(value, getMinQuantity(props.product), getStep(props.product));
4814
4847
  }
4815
4848
  function increment() {
4816
4849
  setQuantityDraft(null);
@@ -6919,6 +6952,7 @@ function CartItem(rawProps) {
6919
6952
  companyId: props.companyId
6920
6953
  });
6921
6954
  const [quantity, setQuantity] = useState(() => props.cartItem.quantity || 1);
6955
+ const [quantityDraft, setQuantityDraft] = useState(() => null);
6922
6956
  const [notes, setNotes] = useState(() => props.cartItem.notes || "");
6923
6957
  const [loading, setLoading] = useState(() => false);
6924
6958
  const [deleting, setDeleting] = useState(() => false);
@@ -7042,6 +7076,34 @@ function CartItem(rawProps) {
7042
7076
  setLoading(false);
7043
7077
  }
7044
7078
  }
7079
+ function handleQuantityDecrement() {
7080
+ setQuantityDraft(null);
7081
+ const orderable = resolveOrderableQuantity(quantity, minQuantity, step);
7082
+ if (orderable !== quantity) {
7083
+ handleQuantityChange(orderable);
7084
+ return;
7085
+ }
7086
+ if (quantity - step >= minQuantity) handleQuantityChange(quantity - step);
7087
+ }
7088
+ function handleQuantityIncrement() {
7089
+ setQuantityDraft(null);
7090
+ const orderable = resolveOrderableQuantity(quantity, minQuantity, step);
7091
+ handleQuantityChange(orderable === quantity ? quantity + step : orderable);
7092
+ }
7093
+ function handleQuantityInput(raw) {
7094
+ setQuantityDraft(raw);
7095
+ const val = parseInt(raw, 10);
7096
+ if (!isNaN(val) && val >= minQuantity) {
7097
+ handleQuantityChange(resolveOrderableQuantity(val, minQuantity, step));
7098
+ }
7099
+ }
7100
+ function handleQuantityBlur() {
7101
+ if (quantityDraft === null) return;
7102
+ const val = parseInt(quantityDraft, 10);
7103
+ setQuantityDraft(null);
7104
+ const orderable = resolveOrderableQuantity(val, minQuantity, step);
7105
+ if (orderable !== quantity) handleQuantityChange(orderable);
7106
+ }
7045
7107
  function handleNoteChange(note) {
7046
7108
  setNotes(note);
7047
7109
  if (props.onNoteChange) {
@@ -7144,7 +7206,8 @@ function CartItem(rawProps) {
7144
7206
  deleting,
7145
7207
  crossupsells,
7146
7208
  visibleCrossupsells: getVisibleCrossupsells(),
7147
- addingCrossupsellId
7209
+ addingCrossupsellId,
7210
+ quantityDraft
7148
7211
  },
7149
7212
  helpers: {
7150
7213
  getBundleItemName,
@@ -7157,6 +7220,10 @@ function CartItem(rawProps) {
7157
7220
  },
7158
7221
  handlers: {
7159
7222
  onQuantityChange: handleQuantityChange,
7223
+ onQuantityDecrement: handleQuantityDecrement,
7224
+ onQuantityIncrement: handleQuantityIncrement,
7225
+ onQuantityInput: handleQuantityInput,
7226
+ onQuantityBlur: handleQuantityBlur,
7160
7227
  onNoteChange: handleNoteChange,
7161
7228
  onDelete: handleDelete,
7162
7229
  onTitleClick: (e) => props.onTitleClick?.(e, props.cartItem),
@@ -7471,7 +7538,7 @@ function CartItemQuantity(props = {}) {
7471
7538
  {
7472
7539
  type: "button",
7473
7540
  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",
7474
- onClick: () => handlers.onQuantityChange(derived.quantity - derived.step),
7541
+ onClick: handlers.onQuantityDecrement,
7475
7542
  disabled: derived.quantity <= derived.minQuantity || state.loading,
7476
7543
  children: "-"
7477
7544
  }
@@ -7483,13 +7550,9 @@ function CartItemQuantity(props = {}) {
7483
7550
  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",
7484
7551
  min: derived.minQuantity,
7485
7552
  step: derived.step,
7486
- value: derived.quantity,
7487
- onChange: (e) => {
7488
- const val = parseInt(e.target.value, 10);
7489
- if (!isNaN(val) && val >= derived.minQuantity) {
7490
- handlers.onQuantityChange(Math.round((val - derived.minQuantity) / derived.step) * derived.step + derived.minQuantity);
7491
- }
7492
- }
7553
+ value: state.quantityDraft ?? derived.quantity,
7554
+ onChange: (e) => handlers.onQuantityInput(e.target.value),
7555
+ onBlur: handlers.onQuantityBlur
7493
7556
  }
7494
7557
  ),
7495
7558
  /* @__PURE__ */ jsx(
@@ -7497,7 +7560,7 @@ function CartItemQuantity(props = {}) {
7497
7560
  {
7498
7561
  type: "button",
7499
7562
  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",
7500
- onClick: () => handlers.onQuantityChange(derived.quantity + derived.step),
7563
+ onClick: handlers.onQuantityIncrement,
7501
7564
  disabled: state.loading,
7502
7565
  children: "+"
7503
7566
  }
@@ -7511,13 +7574,9 @@ function CartItemQuantity(props = {}) {
7511
7574
  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",
7512
7575
  min: derived.minQuantity,
7513
7576
  step: derived.step,
7514
- value: derived.quantity,
7515
- onChange: (e) => {
7516
- const val = parseInt(e.target.value, 10);
7517
- if (!isNaN(val) && val >= derived.minQuantity) {
7518
- handlers.onQuantityChange(Math.round((val - derived.minQuantity) / derived.step) * derived.step + derived.minQuantity);
7519
- }
7520
- }
7577
+ value: state.quantityDraft ?? derived.quantity,
7578
+ onChange: (e) => handlers.onQuantityInput(e.target.value),
7579
+ onBlur: handlers.onQuantityBlur
7521
7580
  }
7522
7581
  );
7523
7582
  }
@@ -14775,10 +14834,6 @@ function RequestPriceButton(props) {
14775
14834
  }
14776
14835
  var RequestPriceButton_default = RequestPriceButton;
14777
14836
  var ROW_SEQ = 0;
14778
- function snapToStep(value, min, step) {
14779
- if (!Number.isFinite(value) || value <= min) return min;
14780
- return Math.round((value - min) / (step || 1)) * (step || 1) + min;
14781
- }
14782
14837
  function blankRow() {
14783
14838
  ROW_SEQ += 1;
14784
14839
  return {
@@ -14898,7 +14953,7 @@ function QuickOrderInner(props) {
14898
14953
  const n = parseInt(raw, 10);
14899
14954
  setRows(
14900
14955
  (prev) => prev.map(
14901
- (r) => r.key === key ? { ...r, quantity: snapToStep(n, r.minQuantity, r.step) } : r
14956
+ (r) => r.key === key ? { ...r, quantity: resolveOrderableQuantity(n, r.minQuantity, r.step) } : r
14902
14957
  )
14903
14958
  );
14904
14959
  },
@@ -19469,7 +19524,7 @@ function SearchBar(rawProps) {
19469
19524
  "propeller-search-bar__input w-full pl-10 pr-10 py-2 bg-card text-foreground border border-input rounded-container focus:outline-none focus:ring-2 focus:ring-secondary placeholder:text-muted-foreground",
19470
19525
  props.inputClassName
19471
19526
  ),
19472
- placeholder: props.placeholder || "Search products...",
19527
+ placeholder: props.placeholder || getLabel(props.labels, "placeholder", "Search products..."),
19473
19528
  value: localTerm,
19474
19529
  onChange: (e) => handleInputChange(e.target.value)
19475
19530
  }