@tmlmt/cooklang-parser 3.0.0-alpha.21 → 3.0.0-alpha.22

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
@@ -509,7 +509,7 @@ interface IngredientExtras {
509
509
  * Used if: the ingredient is a recipe
510
510
  *
511
511
  * @example
512
- * ```cooklang
512
+ * ```yaml
513
513
  * Take @./essentials/doughs/pizza dough{1} out of the freezer and let it unfreeze overnight
514
514
  * ```
515
515
  * Would lead to:
@@ -541,9 +541,12 @@ interface AlternativeIngredientRef {
541
541
  interface IngredientQuantityGroup extends QuantityWithPlainUnit {
542
542
  /**
543
543
  * References to alternative ingredients for this quantity group.
544
+ * Each inner array represents one alternative choice option (subgroup).
545
+ * Items within the same inner array are combined with "+" (AND),
546
+ * while different inner arrays represent "or" alternatives.
544
547
  * If undefined, this group has no alternatives.
545
548
  */
546
- alternatives?: AlternativeIngredientRef[];
549
+ alternatives?: AlternativeIngredientRef[][];
547
550
  }
548
551
  /**
549
552
  * Represents an AND group of quantities when primary units are incompatible but equivalents can be summed.
@@ -557,9 +560,12 @@ interface IngredientQuantityAndGroup extends FlatAndGroup<QuantityWithPlainUnit>
557
560
  equivalents?: QuantityWithPlainUnit[];
558
561
  /**
559
562
  * References to alternative ingredients for this quantity group.
563
+ * Each inner array represents one alternative choice option (subgroup).
564
+ * Items within the same inner array are combined with "+" (AND),
565
+ * while different inner arrays represent "or" alternatives.
560
566
  * If undefined, this group has no alternatives.
561
567
  */
562
- alternatives?: AlternativeIngredientRef[];
568
+ alternatives?: AlternativeIngredientRef[][];
563
569
  }
564
570
  /**
565
571
  * Represents an ingredient in a recipe.
@@ -1760,15 +1766,15 @@ declare function renderFractionAsVulgar(num: number, den: number): string;
1760
1766
  * Format a numeric value (decimal or fraction) to a string.
1761
1767
  *
1762
1768
  * @param value - The decimal or fraction value to format
1763
- * @param useVulgar - Whether to use Unicode vulgar fraction characters (default: false)
1769
+ * @param useVulgar - Whether to use Unicode vulgar fraction characters (default: true)
1764
1770
  * @returns The formatted string representation
1765
1771
  * @category Helpers
1766
1772
  *
1767
1773
  * @example
1768
1774
  * ```typescript
1769
1775
  * formatNumericValue({ type: "decimal", decimal: 1.5 }); // "1.5"
1770
- * formatNumericValue({ type: "fraction", num: 1, den: 2 }); // "1/2"
1771
- * formatNumericValue({ type: "fraction", num: 1, den: 2 }, true); // "½"
1776
+ * formatNumericValue({ type: "fraction", num: 1, den: 2 }); // "½"
1777
+ * formatNumericValue({ type: "fraction", num: 1, den: 2 }, false); // "1/2"
1772
1778
  * formatNumericValue({ type: "fraction", num: 5, den: 4 }, true); // "1¼"
1773
1779
  * ```
1774
1780
  */
@@ -1899,7 +1905,7 @@ declare function isGroupedItem(item: IngredientItem): boolean;
1899
1905
  * for (const item of step.items) {
1900
1906
  * if (item.type === 'ingredient') {
1901
1907
  * item.alternatives.forEach((alt, idx) => {
1902
- * const isSelected = isAlternativeSelected(item, idx, recipe, choices);
1908
+ * const isSelected = isAlternativeSelected(recipe, choices, item, idx);
1903
1909
  * // Render differently based on isSelected
1904
1910
  * });
1905
1911
  * }
@@ -2033,15 +2039,18 @@ declare function isSimpleGroup(entry: IngredientQuantityGroup | IngredientQuanti
2033
2039
  * for (const entry of ingredient.quantities) {
2034
2040
  * if (hasAlternatives(entry)) {
2035
2041
  * // entry.alternatives is available and non-empty
2036
- * for (const alt of entry.alternatives) {
2037
- * console.log(`Alternative ingredient index: ${alt.index}`);
2042
+ * for (const subgroup of entry.alternatives) {
2043
+ * // Each subgroup is one "or" choice; items within are combined with "+"
2044
+ * for (const alt of subgroup) {
2045
+ * console.log(`Alternative ingredient index: ${alt.index}`);
2046
+ * }
2038
2047
  * }
2039
2048
  * }
2040
2049
  * }
2041
2050
  * ```
2042
2051
  */
2043
2052
  declare function hasAlternatives(entry: IngredientQuantityGroup | IngredientQuantityAndGroup): entry is (IngredientQuantityGroup | IngredientQuantityAndGroup) & {
2044
- alternatives: AlternativeIngredientRef[];
2053
+ alternatives: AlternativeIngredientRef[][];
2045
2054
  };
2046
2055
 
2047
2056
  /**
package/dist/index.d.ts CHANGED
@@ -509,7 +509,7 @@ interface IngredientExtras {
509
509
  * Used if: the ingredient is a recipe
510
510
  *
511
511
  * @example
512
- * ```cooklang
512
+ * ```yaml
513
513
  * Take @./essentials/doughs/pizza dough{1} out of the freezer and let it unfreeze overnight
514
514
  * ```
515
515
  * Would lead to:
@@ -541,9 +541,12 @@ interface AlternativeIngredientRef {
541
541
  interface IngredientQuantityGroup extends QuantityWithPlainUnit {
542
542
  /**
543
543
  * References to alternative ingredients for this quantity group.
544
+ * Each inner array represents one alternative choice option (subgroup).
545
+ * Items within the same inner array are combined with "+" (AND),
546
+ * while different inner arrays represent "or" alternatives.
544
547
  * If undefined, this group has no alternatives.
545
548
  */
546
- alternatives?: AlternativeIngredientRef[];
549
+ alternatives?: AlternativeIngredientRef[][];
547
550
  }
548
551
  /**
549
552
  * Represents an AND group of quantities when primary units are incompatible but equivalents can be summed.
@@ -557,9 +560,12 @@ interface IngredientQuantityAndGroup extends FlatAndGroup<QuantityWithPlainUnit>
557
560
  equivalents?: QuantityWithPlainUnit[];
558
561
  /**
559
562
  * References to alternative ingredients for this quantity group.
563
+ * Each inner array represents one alternative choice option (subgroup).
564
+ * Items within the same inner array are combined with "+" (AND),
565
+ * while different inner arrays represent "or" alternatives.
560
566
  * If undefined, this group has no alternatives.
561
567
  */
562
- alternatives?: AlternativeIngredientRef[];
568
+ alternatives?: AlternativeIngredientRef[][];
563
569
  }
564
570
  /**
565
571
  * Represents an ingredient in a recipe.
@@ -1760,15 +1766,15 @@ declare function renderFractionAsVulgar(num: number, den: number): string;
1760
1766
  * Format a numeric value (decimal or fraction) to a string.
1761
1767
  *
1762
1768
  * @param value - The decimal or fraction value to format
1763
- * @param useVulgar - Whether to use Unicode vulgar fraction characters (default: false)
1769
+ * @param useVulgar - Whether to use Unicode vulgar fraction characters (default: true)
1764
1770
  * @returns The formatted string representation
1765
1771
  * @category Helpers
1766
1772
  *
1767
1773
  * @example
1768
1774
  * ```typescript
1769
1775
  * formatNumericValue({ type: "decimal", decimal: 1.5 }); // "1.5"
1770
- * formatNumericValue({ type: "fraction", num: 1, den: 2 }); // "1/2"
1771
- * formatNumericValue({ type: "fraction", num: 1, den: 2 }, true); // "½"
1776
+ * formatNumericValue({ type: "fraction", num: 1, den: 2 }); // "½"
1777
+ * formatNumericValue({ type: "fraction", num: 1, den: 2 }, false); // "1/2"
1772
1778
  * formatNumericValue({ type: "fraction", num: 5, den: 4 }, true); // "1¼"
1773
1779
  * ```
1774
1780
  */
@@ -1899,7 +1905,7 @@ declare function isGroupedItem(item: IngredientItem): boolean;
1899
1905
  * for (const item of step.items) {
1900
1906
  * if (item.type === 'ingredient') {
1901
1907
  * item.alternatives.forEach((alt, idx) => {
1902
- * const isSelected = isAlternativeSelected(item, idx, recipe, choices);
1908
+ * const isSelected = isAlternativeSelected(recipe, choices, item, idx);
1903
1909
  * // Render differently based on isSelected
1904
1910
  * });
1905
1911
  * }
@@ -2033,15 +2039,18 @@ declare function isSimpleGroup(entry: IngredientQuantityGroup | IngredientQuanti
2033
2039
  * for (const entry of ingredient.quantities) {
2034
2040
  * if (hasAlternatives(entry)) {
2035
2041
  * // entry.alternatives is available and non-empty
2036
- * for (const alt of entry.alternatives) {
2037
- * console.log(`Alternative ingredient index: ${alt.index}`);
2042
+ * for (const subgroup of entry.alternatives) {
2043
+ * // Each subgroup is one "or" choice; items within are combined with "+"
2044
+ * for (const alt of subgroup) {
2045
+ * console.log(`Alternative ingredient index: ${alt.index}`);
2046
+ * }
2038
2047
  * }
2039
2048
  * }
2040
2049
  * }
2041
2050
  * ```
2042
2051
  */
2043
2052
  declare function hasAlternatives(entry: IngredientQuantityGroup | IngredientQuantityAndGroup): entry is (IngredientQuantityGroup | IngredientQuantityAndGroup) & {
2044
- alternatives: AlternativeIngredientRef[];
2053
+ alternatives: AlternativeIngredientRef[][];
2045
2054
  };
2046
2055
 
2047
2056
  /**
package/dist/index.js CHANGED
@@ -2033,7 +2033,7 @@ function unionOfSets(s1, s2) {
2033
2033
  }
2034
2034
  function getAlternativeSignature(alternatives) {
2035
2035
  if (!alternatives || alternatives.length === 0) return null;
2036
- return alternatives.map((a2) => a2.index).sort((a2, b) => a2 - b).join(",");
2036
+ return alternatives.flat().map((a2) => a2.index).sort((a2, b) => a2 - b).join(",");
2037
2037
  }
2038
2038
 
2039
2039
  // src/classes/pantry.ts
@@ -3346,7 +3346,7 @@ var _Recipe = class _Recipe {
3346
3346
  const currentSubgroupIdx = groupSubgroups.findIndex(
3347
3347
  (sg) => sg.some((alt) => alt.itemId === item.id)
3348
3348
  );
3349
- alternativeRefs = groupSubgroups.filter((_, idx) => idx !== currentSubgroupIdx).flatMap(
3349
+ alternativeRefs = groupSubgroups.filter((_, idx) => idx !== currentSubgroupIdx).map(
3350
3350
  (subgroup) => subgroup.map((otherAlt) => {
3351
3351
  const ref = {
3352
3352
  index: otherAlt.index
@@ -3385,7 +3385,7 @@ var _Recipe = class _Recipe {
3385
3385
  };
3386
3386
  ref.quantities = [altQty];
3387
3387
  }
3388
- return ref;
3388
+ return [ref];
3389
3389
  });
3390
3390
  }
3391
3391
  const altIndices = getAlternativeSignature(alternativeRefs) ?? "";
@@ -3406,28 +3406,36 @@ var _Recipe = class _Recipe {
3406
3406
  if (!groupsForIng.has(signature)) {
3407
3407
  groupsForIng.set(signature, {
3408
3408
  quantities: [],
3409
- alternativeQuantities: /* @__PURE__ */ new Map()
3409
+ alternativeQuantities: /* @__PURE__ */ new Map(),
3410
+ alternativeSubgroups: []
3410
3411
  });
3411
3412
  }
3412
3413
  const group = groupsForIng.get(signature);
3413
3414
  group.quantities.push(quantityEntry);
3414
- for (const ref of alternativeRefs ?? []) {
3415
- if (!group.alternativeQuantities.has(ref.index)) {
3416
- group.alternativeQuantities.set(ref.index, []);
3417
- }
3418
- for (const altQty of ref.quantities ?? []) {
3419
- const extended = toExtendedUnit({
3420
- quantity: altQty.quantity,
3421
- unit: altQty.unit
3422
- });
3423
- if (altQty.equivalents?.length) {
3424
- const eqEntries = [
3425
- extended,
3426
- ...altQty.equivalents.map((eq) => toExtendedUnit(eq))
3427
- ];
3428
- group.alternativeQuantities.get(ref.index).push({ or: eqEntries });
3429
- } else {
3430
- group.alternativeQuantities.get(ref.index).push(extended);
3415
+ if (alternativeRefs && alternativeRefs.length > 0 && group.alternativeSubgroups.length === 0) {
3416
+ group.alternativeSubgroups = alternativeRefs.map(
3417
+ (subgroup) => subgroup.map((ref) => ref.index)
3418
+ );
3419
+ }
3420
+ for (const subgroup of alternativeRefs ?? []) {
3421
+ for (const ref of subgroup) {
3422
+ if (!group.alternativeQuantities.has(ref.index)) {
3423
+ group.alternativeQuantities.set(ref.index, []);
3424
+ }
3425
+ for (const altQty of ref.quantities ?? []) {
3426
+ const extended = toExtendedUnit({
3427
+ quantity: altQty.quantity,
3428
+ unit: altQty.unit
3429
+ });
3430
+ if (altQty.equivalents?.length) {
3431
+ const eqEntries = [
3432
+ extended,
3433
+ ...altQty.equivalents.map((eq) => toExtendedUnit(eq))
3434
+ ];
3435
+ group.alternativeQuantities.get(ref.index).push({ or: eqEntries });
3436
+ } else {
3437
+ group.alternativeQuantities.get(ref.index).push(extended);
3438
+ }
3431
3439
  }
3432
3440
  }
3433
3441
  }
@@ -3550,17 +3558,25 @@ var _Recipe = class _Recipe {
3550
3558
  this.unitSystem
3551
3559
  );
3552
3560
  const flattened = flattenPlainUnitGroup(summed);
3553
- const alternatives = group.alternativeQuantities.size > 0 ? [...group.alternativeQuantities].map(([altIdx, altQtys]) => ({
3554
- index: altIdx,
3555
- ...altQtys.length > 0 && {
3556
- quantities: flattenPlainUnitGroup(
3557
- addEquivalentsAndSimplify(altQtys, this.unitSystem)
3558
- ).flatMap(
3559
- /* v8 ignore next -- item.and branch requires complex nested AND-with-equivalents structure */
3560
- (item) => "quantity" in item ? [item] : item.and
3561
- )
3562
- }
3563
- })) : void 0;
3561
+ let alternatives;
3562
+ if (group.alternativeSubgroups.length > 0) {
3563
+ alternatives = group.alternativeSubgroups.map(
3564
+ (subgroupIndices) => subgroupIndices.map((altIdx) => {
3565
+ const altQtys = group.alternativeQuantities.get(altIdx);
3566
+ return {
3567
+ index: altIdx,
3568
+ ...altQtys.length > 0 && {
3569
+ quantities: flattenPlainUnitGroup(
3570
+ addEquivalentsAndSimplify(altQtys, this.unitSystem)
3571
+ ).flatMap(
3572
+ /* v8 ignore next -- item.and branch requires complex nested AND-with-equivalents structure */
3573
+ (item) => "quantity" in item ? [item] : item.and
3574
+ )
3575
+ }
3576
+ };
3577
+ })
3578
+ );
3579
+ }
3564
3580
  for (const gq of flattened) {
3565
3581
  if ("and" in gq) {
3566
3582
  quantityGroups.push({