codexly-ui 0.10.67 → 0.10.69

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.
@@ -14444,7 +14444,11 @@ class ClxProductDetailComponent {
14444
14444
  _carousel = viewChild(ClxCarouselComponent, ...(ngDevMode ? [{ debugName: "_carousel" }] : /* istanbul ignore next */ []));
14445
14445
  quantity = signal(1, ...(ngDevMode ? [{ debugName: "quantity" }] : /* istanbul ignore next */ []));
14446
14446
  activeTab = signal('general', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
14447
+ /** Only holds specs the visitor has explicitly clicked — starts empty, not pre-filled with the
14448
+ * initial variation's specs, so a spec option can be toggled off (click again to deselect)
14449
+ * without fighting a "fully selected" starting state that never lets go of its last value. */
14447
14450
  selectedSpecs = signal({}, ...(ngDevMode ? [{ debugName: "selectedSpecs" }] : /* istanbul ignore next */ []));
14451
+ _initialVariation = signal(null, ...(ngDevMode ? [{ debugName: "_initialVariation" }] : /* istanbul ignore next */ []));
14448
14452
  /** Same card treatment as clx-product (bg-clx-surface + shadow-sm) — keeps the gallery/info
14449
14453
  * panels visually consistent with the rest of the e-commerce card set. */
14450
14454
  _panelClass = 'bg-clx-surface rounded-2xl shadow-sm p-4';
@@ -14453,14 +14457,30 @@ class ClxProductDetailComponent {
14453
14457
  { id: 'specs', label: 'Especificaciones' },
14454
14458
  { id: 'description', label: 'Descripción' },
14455
14459
  ];
14460
+ /** Tracks which product the spec selection was initialized for — the init effect below must run
14461
+ * once per actual product change, not every time initialVariationId() changes. A host app that
14462
+ * syncs its own ?variant= URL param from the (variationChange) output (see codexly-store's
14463
+ * product-detail page) feeds that id back into this same input; without this guard, every spec
14464
+ * click would round-trip through the host and re-trigger initialization, silently overwriting
14465
+ * the visitor's in-progress selection with the just-resolved variation's full spec set. */
14466
+ _initializedForProductId = null;
14456
14467
  constructor() {
14457
14468
  effect(() => {
14458
14469
  const p = this.product();
14459
14470
  if (!p?.variations?.length)
14460
14471
  return;
14472
+ if (this._initializedForProductId === p.product_id)
14473
+ return;
14474
+ this._initializedForProductId = p.product_id;
14461
14475
  const initialId = this.initialVariationId();
14462
14476
  const initial = (initialId && p.variations.find(v => v.id === initialId)) || p.variations[0];
14463
- this.selectedSpecs.set({ ...initial.specs });
14477
+ this._initialVariation.set(initial);
14478
+ // Pre-select the full spec combination only when the landing variation has stock — every
14479
+ // other combination stays reachable by changing any one spec. When it's sold out, starting
14480
+ // fully selected would fix both Color and Talla to a combination that can never resolve to
14481
+ // an in-stock variation, hiding every other option (see isOptionAvailable's fallback) with
14482
+ // no selected value left to toggle off — so start empty and let the visitor pick from scratch.
14483
+ this.selectedSpecs.set(initial.in_stock ? { ...initial.specs } : {});
14464
14484
  });
14465
14485
  effect(() => {
14466
14486
  const v = this.activeVariation();
@@ -14468,12 +14488,22 @@ class ClxProductDetailComponent {
14468
14488
  this.variationChange.emit(v);
14469
14489
  });
14470
14490
  }
14491
+ /** With no specs selected (fresh load, or every spec toggled back off) this is the variation
14492
+ * the visitor originally landed on. Otherwise, resolves the variation matching every currently
14493
+ * selected spec — with a partial selection (some spec types still untouched), any variation
14494
+ * whose specs are a superset of the selection counts as a match, since the visitor hasn't
14495
+ * narrowed those other spec types down to one value yet. */
14471
14496
  activeVariation = computed(() => {
14472
14497
  const p = this.product();
14473
14498
  if (!p?.variations?.length)
14474
14499
  return null;
14475
14500
  const sel = this.selectedSpecs();
14476
- return p.variations.find(v => Object.entries(v.specs).every(([typeId, optId]) => sel[typeId] === optId)) ?? p.variations[0];
14501
+ if (Object.keys(sel).length === 0)
14502
+ return this._initialVariation() ?? p.variations[0];
14503
+ const matches = p.variations.filter(v => Object.entries(sel).every(([typeId, optId]) => v.specs[typeId] === optId));
14504
+ // With a partial selection, several variations can match (e.g. only Color chosen) — prefer
14505
+ // one with stock so the price/gallery/CTA reflect a purchasable option by default.
14506
+ return matches.find(v => v.in_stock) ?? matches[0] ?? this._initialVariation() ?? p.variations[0];
14477
14507
  }, ...(ngDevMode ? [{ debugName: "activeVariation" }] : /* istanbul ignore next */ []));
14478
14508
  finalPrice = computed(() => {
14479
14509
  const v = this.activeVariation();
@@ -14512,8 +14542,19 @@ class ClxProductDetailComponent {
14512
14542
  }
14513
14543
  return variations.some(v => v.specs[specTypeId] === specOptionId && v.in_stock && matchesCrossSelection(v));
14514
14544
  }
14545
+ /** Clicking an already-selected option deselects it (toggle) instead of being a no-op — the only
14546
+ * way to escape a narrowed-down selection and see all options again, per the "hide unavailable
14547
+ * options" design: there's no separate disabled state to click through, so toggling off is the
14548
+ * sole path back. Once every spec is toggled off, activeVariation() falls back to the variation
14549
+ * the visitor originally landed on (see _initialVariation). */
14515
14550
  selectSpec(specTypeId, specOptionId) {
14516
- this.selectedSpecs.update(s => ({ ...s, [specTypeId]: specOptionId }));
14551
+ this.selectedSpecs.update(s => {
14552
+ if (s[specTypeId] === specOptionId) {
14553
+ const { [specTypeId]: _removed, ...rest } = s;
14554
+ return rest;
14555
+ }
14556
+ return { ...s, [specTypeId]: specOptionId };
14557
+ });
14517
14558
  this._carousel()?.goTo(0);
14518
14559
  }
14519
14560
  isSpecSelected(specTypeId, specOptionId) {