@recipe-book/react 0.1.0 → 0.1.2

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.js CHANGED
@@ -1,18 +1,19 @@
1
1
  "use client";
2
2
 
3
- // src/parts/image.tsx
4
- import {
5
- forwardRef
6
- } from "react";
7
-
8
3
  // src/context.tsx
9
4
  import { RecipeBookError as RecipeBookError2 } from "@recipe-book/core";
10
5
  import {
11
- createContext,
12
6
  useContext,
13
7
  useMemo as useMemo2
14
8
  } from "react";
15
9
 
10
+ // src/display-name.ts
11
+ function setDisplayName(component, name) {
12
+ const target = component;
13
+ target.displayName = name;
14
+ Object.defineProperty(target.render ?? component, "name", { value: name });
15
+ }
16
+
16
17
  // src/labels.ts
17
18
  var defaultLabels = {
18
19
  servings: "Servings",
@@ -25,6 +26,15 @@ var defaultLabels = {
25
26
  to: "to"
26
27
  };
27
28
 
29
+ // src/react.ts
30
+ import {
31
+ cloneElement,
32
+ createContext,
33
+ forwardRef,
34
+ Fragment,
35
+ isValidElement
36
+ } from "react";
37
+
28
38
  // src/state/use-recipe-state.ts
29
39
  import {
30
40
  resolveRecipe
@@ -77,14 +87,8 @@ import {
77
87
  useRef,
78
88
  useState
79
89
  } from "react";
80
- function useControllableState({
81
- prop,
82
- value,
83
- defaultValue,
84
- onChange,
85
- resetKey
86
- }) {
87
- const controlled = value !== void 0;
90
+ function useControllableState(options) {
91
+ const { value, defaultValue, onChange, resetKey } = options;
88
92
  const [internal, setInternal] = useState(defaultValue);
89
93
  const [internalResetKey, setInternalResetKey] = useState(resetKey);
90
94
  let uncontrolled = internal;
@@ -93,22 +97,27 @@ function useControllableState({
93
97
  setInternal(defaultValue);
94
98
  uncontrolled = defaultValue;
95
99
  }
96
- const current = controlled ? value : uncontrolled;
97
- const latest = useRef({ controlled, value: current, onChange });
100
+ const current = value === void 0 ? uncontrolled : value;
101
+ const latest = useRef({ value, current, onChange });
98
102
  useInsertionEffect(() => {
99
- latest.current = { controlled, value: current, onChange };
103
+ latest.current = { value, current, onChange };
100
104
  });
101
105
  const setValue = useCallback((next) => {
102
- const { controlled: controlled2, value: value2, onChange: onChange2 } = latest.current;
103
- if (Object.is(next, value2)) return;
104
- if (!controlled2) {
105
- latest.current.value = next;
106
+ const { value: value2, current: current2, onChange: onChange2 } = latest.current;
107
+ if (Object.is(next, current2)) return;
108
+ if (value2 === void 0) {
109
+ latest.current.current = next;
106
110
  setInternal(next);
107
111
  }
108
112
  onChange2?.(next);
109
113
  }, []);
110
114
  if (process.env.NODE_ENV !== "production") {
111
- useModeWarnings(prop, controlled, controlled && onChange === void 0);
115
+ const controlled = value !== void 0;
116
+ useModeWarnings(
117
+ options.prop,
118
+ controlled,
119
+ controlled && onChange === void 0
120
+ );
112
121
  }
113
122
  return [current, setValue];
114
123
  }
@@ -141,7 +150,7 @@ function useRecipeState(options) {
141
150
  assertParsedRecipe(
142
151
  recipe,
143
152
  "useRecipeState()",
144
- "Call parseRecipe() from @recipe-book/core and pass result.data."
153
+ "Call parseRecipe() from @recipe-book/react/core and pass result.data."
145
154
  );
146
155
  }, [recipe]);
147
156
  }
@@ -199,8 +208,8 @@ function useRecipeState(options) {
199
208
  },
200
209
  [updateSystem]
201
210
  );
202
- const steps = useMemo(() => numberSteps(recipe), [recipe]);
203
- const { scale, canConvert, ingredients } = useMemo(
211
+ const steps = useMemo(() => resolveRecipe(recipe).steps, [recipe]);
212
+ const resolved = useMemo(
204
213
  () => resolveRecipe(recipe, { servings, system }),
205
214
  [recipe, servings, system]
206
215
  );
@@ -209,24 +218,14 @@ function useRecipeState(options) {
209
218
  recipe,
210
219
  servings,
211
220
  setServings,
212
- scale,
221
+ scale: resolved.scale,
213
222
  system,
214
223
  setSystem,
215
- canConvert,
216
- ingredients,
224
+ canConvert: resolved.canConvert,
225
+ ingredients: resolved.ingredients,
217
226
  steps
218
227
  }),
219
- [
220
- recipe,
221
- servings,
222
- setServings,
223
- scale,
224
- system,
225
- setSystem,
226
- canConvert,
227
- ingredients,
228
- steps
229
- ]
228
+ [recipe, servings, setServings, resolved, system, setSystem, steps]
230
229
  );
231
230
  }
232
231
  function useWarning(message) {
@@ -238,27 +237,35 @@ function useWarning(message) {
238
237
  }
239
238
  }, [message]);
240
239
  }
241
- function numberSteps(recipe) {
242
- let number = 0;
243
- return recipe.steps.map((section) => ({
244
- ...section,
245
- items: section.items.map((step) => ({ ...step, number: ++number }))
246
- }));
247
- }
248
240
  function isServings(value) {
249
241
  return typeof value === "number" && Number.isFinite(value) && value > 0;
250
242
  }
251
243
  function servingsBase(recipe) {
252
244
  return [recipe.servings?.amount, recipe.yield?.amount].find(isServings) ?? 1;
253
245
  }
254
- var MIN_SCALE = 1e-6;
255
- var MAX_SCALE = 1e6;
246
+ function servingsWindow(base) {
247
+ const [mantissa, exponent] = base.toExponential().split("e");
248
+ return [
249
+ Number(`${mantissa}e${Number(exponent) - 6}`),
250
+ Number(`${mantissa}e${Number(exponent) + 6}`)
251
+ ];
252
+ }
256
253
  function isServingsFor(value, base) {
257
- return isServings(value) && value / base >= MIN_SCALE && value / base <= MAX_SCALE;
254
+ const [min, max] = servingsWindow(base);
255
+ return isServings(value) && value >= min && value <= max;
258
256
  }
259
257
  function describeRange(base) {
260
- const bound = (value) => String(Number(value.toPrecision(6)));
261
- return `a number from ${bound(base * MIN_SCALE)} to ${bound(base * MAX_SCALE)} for this recipe`;
258
+ const [min, max] = servingsWindow(base);
259
+ return `a number from ${roundInward(min, true)} to ${roundInward(max, false)} for this recipe`;
260
+ }
261
+ function roundInward(bound, up) {
262
+ const nearest = Number(bound.toPrecision(6));
263
+ if (up ? nearest >= bound : nearest <= bound) {
264
+ return nearest;
265
+ }
266
+ const [mantissa, exponent] = bound.toExponential().split("e");
267
+ const digits = Number(mantissa.replace(".", "").slice(0, 6));
268
+ return Number(`${up ? digits + 1 : digits}e${Number(exponent) - 5}`);
262
269
  }
263
270
  function isSystem(value) {
264
271
  return value === "metric" || value === "us";
@@ -312,7 +319,7 @@ function useConfig({
312
319
  }),
313
320
  [outer.labels, labels]
314
321
  ),
315
- defaultSystem: isSystem(defaultSystem) ? defaultSystem : outer.defaultSystem
322
+ defaultSystem: [defaultSystem, outer.defaultSystem].find(isSystem)
316
323
  };
317
324
  }
318
325
  var RecipeDataContext = /* @__PURE__ */ createContext(null);
@@ -342,6 +349,9 @@ function useRecipe() {
342
349
  "useRecipe() must be called inside <Recipe.Root>. To use recipe state without a Root, call useRecipeState()."
343
350
  );
344
351
  }
352
+ if (process.env.NODE_ENV !== "production") {
353
+ setDisplayName(RecipeBookProvider, "Recipe.RecipeBookProvider");
354
+ }
345
355
 
346
356
  // src/parts/image.tsx
347
357
  import { jsx as jsx2 } from "react/jsx-runtime";
@@ -352,10 +362,7 @@ var Image = /* @__PURE__ */ forwardRef(function Image2({
352
362
  ...props
353
363
  }, ref) {
354
364
  const { recipe } = useRecipeData("Recipe.Image");
355
- if (recipe.image === void 0) {
356
- return null;
357
- }
358
- return /* @__PURE__ */ jsx2(
365
+ return recipe.image !== void 0 && /* @__PURE__ */ jsx2(
359
366
  Comp,
360
367
  {
361
368
  ...props,
@@ -366,23 +373,17 @@ var Image = /* @__PURE__ */ forwardRef(function Image2({
366
373
  }
367
374
  );
368
375
  });
376
+ if (process.env.NODE_ENV !== "production") {
377
+ setDisplayName(Image, "Recipe.Image");
378
+ }
369
379
 
370
380
  // src/parts/ingredients.tsx
371
381
  import {
372
- cloneElement as cloneElement2,
373
- createContext as createContext2,
374
- forwardRef as forwardRef3,
375
382
  useMemo as useMemo3
376
383
  } from "react";
377
384
 
378
385
  // src/slot.tsx
379
- import {
380
- cloneElement,
381
- forwardRef as forwardRef2,
382
- Fragment,
383
- isValidElement,
384
- useCallback as useCallback3
385
- } from "react";
386
+ import { useCallback as useCallback3 } from "react";
386
387
  function resolveAsChild(part, tag, asChild, children, defaults) {
387
388
  if (asChild) {
388
389
  if (isValidElement(children) && children.type !== Fragment) {
@@ -400,7 +401,7 @@ function resolveAsChild(part, tag, asChild, children, defaults) {
400
401
  }
401
402
  return [tag, isEmpty(children) ? defaults : children];
402
403
  }
403
- var Slot = /* @__PURE__ */ forwardRef2(function Slot2({ children, ...props }, ref) {
404
+ var Slot = /* @__PURE__ */ forwardRef(function Slot2({ children, ...props }, ref) {
404
405
  const child = children;
405
406
  const merged = { ...props, ...child.props };
406
407
  for (const name in child.props) {
@@ -421,7 +422,7 @@ var Slot = /* @__PURE__ */ forwardRef2(function Slot2({ children, ...props }, re
421
422
  }
422
423
  }
423
424
  const childRef = Object.getOwnPropertyDescriptor(child.props, "ref")?.value ?? child.ref;
424
- merged.ref = useCallback3(
425
+ const mergedRef = useCallback3(
425
426
  (node) => {
426
427
  const refs = [ref, childRef];
427
428
  const cleanups = refs.map((each) => setRef(each, node));
@@ -439,6 +440,9 @@ var Slot = /* @__PURE__ */ forwardRef2(function Slot2({ children, ...props }, re
439
440
  },
440
441
  [ref, childRef]
441
442
  );
443
+ if (ref != null) {
444
+ merged.ref = mergedRef;
445
+ }
442
446
  return cloneElement(child, merged);
443
447
  });
444
448
  function setRef(ref, node) {
@@ -467,6 +471,9 @@ function describeChildren(children) {
467
471
  function isEmpty(children) {
468
472
  return children == null || typeof children === "boolean";
469
473
  }
474
+ if (process.env.NODE_ENV !== "production") {
475
+ setDisplayName(Slot, "Slot");
476
+ }
470
477
 
471
478
  // src/use-format-ingredient.ts
472
479
  import {
@@ -480,7 +487,7 @@ function useFormatIngredient() {
480
487
  labels: { optional }
481
488
  } = useRequired(
482
489
  RecipeDataContext,
483
- "useFormatIngredient() must be called inside <Recipe.Root>. To format ingredients without a Root, call formatIngredient() from @recipe-book/core."
490
+ "useFormatIngredient() must be called inside <Recipe.Root>. To format ingredients without a Root, call formatIngredient() from @recipe-book/react/core."
484
491
  );
485
492
  return useCallback4(
486
493
  (item) => formatLine(item, locale, optional),
@@ -489,9 +496,10 @@ function useFormatIngredient() {
489
496
  }
490
497
  function formatLine(item, locale, optional) {
491
498
  const options = { locale };
499
+ const formatted = formatIngredient(item, options);
492
500
  const quantity = lineQuantity(item);
493
501
  return {
494
- ...formatIngredient(item, options),
502
+ ...formatted,
495
503
  quantityParts: quantity ? formatQuantityToParts(quantity, options) : [],
496
504
  ...item.ingredient.optional && { optional }
497
505
  };
@@ -501,10 +509,10 @@ function lineQuantity(item) {
501
509
  }
502
510
 
503
511
  // src/parts/sections.tsx
504
- import { Fragment as Fragment2, useId } from "react";
512
+ import { useId } from "react";
505
513
  import { jsx as jsx3, jsxs } from "react/jsx-runtime";
506
514
  function renderKeyed(items, render) {
507
- return items.map((item) => /* @__PURE__ */ jsx3(Fragment2, { children: render(item) }, item.id));
515
+ return items.map((item) => /* @__PURE__ */ jsx3(Fragment, { children: render(item) }, item.id));
508
516
  }
509
517
  function SectionBlock({
510
518
  kind,
@@ -514,19 +522,16 @@ function SectionBlock({
514
522
  }) {
515
523
  const headingId = useId();
516
524
  const marker = `data-recipe-${kind}-section`;
517
- if (!title) {
518
- return /* @__PURE__ */ jsx3("div", { ...{ [marker]: "" }, children });
519
- }
520
525
  const Heading = `h${headingLevel}`;
521
- return /* @__PURE__ */ jsxs("section", { ...{ [marker]: "" }, "aria-labelledby": headingId, children: [
526
+ return title ? /* @__PURE__ */ jsxs("section", { ...{ [marker]: "" }, "aria-labelledby": headingId, children: [
522
527
  /* @__PURE__ */ jsx3(Heading, { id: headingId, children: title }),
523
528
  children
524
- ] });
529
+ ] }) : /* @__PURE__ */ jsx3("div", { ...{ [marker]: "" }, children });
525
530
  }
526
531
 
527
532
  // src/parts/ingredients.tsx
528
- import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
529
- var Ingredients = /* @__PURE__ */ forwardRef3(
533
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
534
+ var Ingredients = /* @__PURE__ */ forwardRef(
530
535
  function Ingredients2({ headingLevel = 3, children, ...props }, ref) {
531
536
  const { ingredients } = usePartState("Recipe.Ingredients");
532
537
  const renderSection = children ?? ((section) => /* @__PURE__ */ jsx4(
@@ -541,15 +546,15 @@ var Ingredients = /* @__PURE__ */ forwardRef3(
541
546
  return /* @__PURE__ */ jsx4("div", { ...props, "data-recipe-ingredients": "", ref, children: renderKeyed(ingredients, renderSection) });
542
547
  }
543
548
  );
544
- var IngredientList = /* @__PURE__ */ forwardRef3(
549
+ var IngredientList = /* @__PURE__ */ forwardRef(
545
550
  function IngredientList2({ section, children = (item) => /* @__PURE__ */ jsx4(Ingredient, { item }), ...props }, ref) {
546
551
  const { ingredients } = usePartState("Recipe.IngredientList");
547
552
  const items = section?.items ?? ingredients.flatMap((s) => s.items);
548
553
  return /* @__PURE__ */ jsx4("ul", { ...props, "data-recipe-ingredient-list": "", ref, children: renderKeyed(items, children) });
549
554
  }
550
555
  );
551
- var LineContext = /* @__PURE__ */ createContext2(null);
552
- var Ingredient = /* @__PURE__ */ forwardRef3(
556
+ var LineContext = /* @__PURE__ */ createContext(null);
557
+ var Ingredient = /* @__PURE__ */ forwardRef(
553
558
  function Ingredient2({ item, asChild = false, children, ...props }, ref) {
554
559
  const {
555
560
  labels: { optional, to },
@@ -565,7 +570,7 @@ var Ingredient = /* @__PURE__ */ forwardRef3(
565
570
  "li",
566
571
  asChild,
567
572
  children,
568
- /* @__PURE__ */ jsxs2(Fragment3, { children: [
573
+ /* @__PURE__ */ jsxs2(Fragment2, { children: [
569
574
  /* @__PURE__ */ jsx4(IngredientQuantity, {}),
570
575
  quantity && (quantity.unit === void 0 ? "\xA0" : " "),
571
576
  /* @__PURE__ */ jsx4(IngredientName, {}),
@@ -601,15 +606,12 @@ function ingredientText(name, attribute, read) {
601
606
  return null;
602
607
  }
603
608
  const [Comp, child] = resolveAsChild(part, "span", asChild, children, null);
604
- return /* @__PURE__ */ jsx4(Comp, { ...props, ...{ [attribute]: "" }, ref, children: Comp === "span" ? /* @__PURE__ */ jsxs2(Fragment3, { children: [
609
+ return /* @__PURE__ */ jsx4(Comp, { ...props, ...{ [attribute]: "" }, ref, children: Comp === "span" ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
605
610
  children,
606
611
  text
607
- ] }) : cloneElement2(child, void 0, child.props.children, text) });
608
- }
609
- if (process.env.NODE_ENV !== "production") {
610
- Object.defineProperty(IngredientText, "name", { value: name });
612
+ ] }) : cloneElement(child, void 0, child.props.children, text) });
611
613
  }
612
- return forwardRef3(IngredientText);
614
+ return forwardRef(IngredientText);
613
615
  }
614
616
  var IngredientQuantity = /* @__PURE__ */ ingredientText(
615
617
  "IngredientQuantity",
@@ -637,7 +639,7 @@ function renderQuantity(parts, to) {
637
639
  if (!separator) {
638
640
  return join(parts);
639
641
  }
640
- return /* @__PURE__ */ jsxs2(Fragment3, { children: [
642
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
641
643
  join(parts.slice(0, dash)),
642
644
  /* @__PURE__ */ jsx4("span", { "data-recipe-range-separator": "", "aria-hidden": "true", children: separator.value }),
643
645
  /* @__PURE__ */ jsx4("span", { style: hiddenStyle, children: ` ${to} ` }),
@@ -660,17 +662,25 @@ var IngredientOptional = /* @__PURE__ */ ingredientText(
660
662
  // An empty label shows only the part's own children, such as an icon.
661
663
  ({ optional }, children) => optional === "" && isEmpty(children) ? void 0 : optional
662
664
  );
665
+ if (process.env.NODE_ENV !== "production") {
666
+ setDisplayName(Ingredients, "Recipe.Ingredients");
667
+ setDisplayName(IngredientList, "Recipe.IngredientList");
668
+ setDisplayName(Ingredient, "Recipe.Ingredient");
669
+ setDisplayName(IngredientQuantity, "Recipe.IngredientQuantity");
670
+ setDisplayName(IngredientName, "Recipe.IngredientName");
671
+ setDisplayName(IngredientNote, "Recipe.IngredientNote");
672
+ setDisplayName(IngredientOptional, "Recipe.IngredientOptional");
673
+ }
663
674
 
664
675
  // src/parts/json-ld.tsx
665
676
  import { toJsonLd } from "@recipe-book/core/schema-org";
666
677
  import {
667
- forwardRef as forwardRef4,
668
678
  useEffect as useEffect3,
669
679
  useMemo as useMemo4,
670
680
  useRef as useRef3
671
681
  } from "react";
672
682
  import { jsx as jsx5 } from "react/jsx-runtime";
673
- var JsonLd = /* @__PURE__ */ forwardRef4(
683
+ var JsonLd = /* @__PURE__ */ forwardRef(
674
684
  function JsonLd2({
675
685
  servings,
676
686
  system,
@@ -682,12 +692,12 @@ var JsonLd = /* @__PURE__ */ forwardRef4(
682
692
  ...props
683
693
  }, ref) {
684
694
  const { recipe } = useRecipeData("Recipe.JsonLd");
685
- const { data, warnings } = useMemo4(
695
+ const result = useMemo4(
686
696
  () => toJsonLd(recipe, { servings, system, url, extra }),
687
697
  [recipe, servings, system, url, extra]
688
698
  );
689
699
  if (process.env.NODE_ENV !== "production") {
690
- useWarningsReport(recipe.name, warnings);
700
+ useWarningsReport(recipe.name, result.warnings);
691
701
  }
692
702
  return /* @__PURE__ */ jsx5(
693
703
  "script",
@@ -696,7 +706,7 @@ var JsonLd = /* @__PURE__ */ forwardRef4(
696
706
  type: "application/ld+json",
697
707
  "data-recipe-json-ld": "",
698
708
  dangerouslySetInnerHTML: {
699
- __html: JSON.stringify(data).replace(/</g, "\\u003c")
709
+ __html: JSON.stringify(result.data).replace(/</g, "\\u003c")
700
710
  },
701
711
  ref
702
712
  }
@@ -717,6 +727,9 @@ function useWarningsReport(name, warnings) {
717
727
  reported.current = key;
718
728
  }, [name, warnings]);
719
729
  }
730
+ if (process.env.NODE_ENV !== "production") {
731
+ setDisplayName(JsonLd, "Recipe.JsonLd");
732
+ }
720
733
 
721
734
  // src/parts/root.tsx
722
735
  import {
@@ -726,7 +739,7 @@ import {
726
739
  useRef as useRef4,
727
740
  useState as useState2
728
741
  } from "react";
729
- import { Fragment as Fragment4, jsx as jsx6 } from "react/jsx-runtime";
742
+ import { Fragment as Fragment3, jsx as jsx6 } from "react/jsx-runtime";
730
743
  function Root({
731
744
  recipe,
732
745
  parse,
@@ -741,7 +754,7 @@ function Root({
741
754
  assertParsedRecipe(
742
755
  recipe,
743
756
  "<Recipe.Root>",
744
- "Call parseRecipe() from @recipe-book/core on the server, in a loader, or at build time and pass result.data, or pass parse={parseRecipe} to parse it in the browser."
757
+ "Call parseRecipe() from @recipe-book/react/core on the server, in a loader, or at build time and pass result.data, or pass parse={parseRecipe} to parse it in the browser."
745
758
  );
746
759
  return { success: true, data: recipe, warnings: [] };
747
760
  }, [parse, recipe]);
@@ -749,7 +762,7 @@ function Root({
749
762
  useParseReport(result);
750
763
  }
751
764
  if (!result.success) {
752
- return /* @__PURE__ */ jsx6(Fragment4, { children: fallback });
765
+ return /* @__PURE__ */ jsx6(Fragment3, { children: fallback });
753
766
  }
754
767
  return /* @__PURE__ */ jsx6(RecipeProvider, { ...options, recipe: result.data, children });
755
768
  }
@@ -784,7 +797,7 @@ function useParseReport(result) {
784
797
  if (result.success) {
785
798
  const count = result.warnings.length;
786
799
  console.warn(
787
- `Recipe Book: ${quote(result.data.name)} has ${count} ${count === 1 ? "warning" : "warnings"}. It still renders, but a warning can mean a misspelled field.`,
800
+ `Recipe Book: ${quote(result.data.name)} has ${count} ${count === 1 ? "warning" : "warnings"}. It still renders. https://recipe-book.siraj.page/errors/#issue-codes gives the fix for each warning's code.`,
788
801
  result.warnings
789
802
  );
790
803
  } else {
@@ -792,6 +805,9 @@ function useParseReport(result) {
792
805
  }
793
806
  }, [result]);
794
807
  }
808
+ if (process.env.NODE_ENV !== "production") {
809
+ setDisplayName(Root, "Recipe.Root");
810
+ }
795
811
 
796
812
  // src/parts/servings.tsx
797
813
  import {
@@ -799,7 +815,6 @@ import {
799
815
  formatYield
800
816
  } from "@recipe-book/core";
801
817
  import {
802
- forwardRef as forwardRef5,
803
818
  useState as useState3
804
819
  } from "react";
805
820
 
@@ -815,7 +830,7 @@ function composeEventHandlers(theirs, ours) {
815
830
 
816
831
  // src/parts/servings.tsx
817
832
  import { jsx as jsx7 } from "react/jsx-runtime";
818
- var ServingsSelect = /* @__PURE__ */ forwardRef5(
833
+ var ServingsSelect = /* @__PURE__ */ forwardRef(
819
834
  function ServingsSelect2({ options, "aria-label": ariaLabel, onChange, ...props }, ref) {
820
835
  const { recipe, labels, locale } = useRecipeData("Recipe.ServingsSelect");
821
836
  const { servings, setServings } = usePartState("Recipe.ServingsSelect");
@@ -843,7 +858,7 @@ var ServingsSelect = /* @__PURE__ */ forwardRef5(
843
858
  );
844
859
  }
845
860
  );
846
- var ServingsInput = /* @__PURE__ */ forwardRef5(
861
+ var ServingsInput = /* @__PURE__ */ forwardRef(
847
862
  function ServingsInput2({
848
863
  min = 1,
849
864
  max,
@@ -920,6 +935,10 @@ function optionText(choice, { servings, yield: makes }, base, labels, locale) {
920
935
  }
921
936
  return `${formatServings({ amount: choice }, format)}\xD7`;
922
937
  }
938
+ if (process.env.NODE_ENV !== "production") {
939
+ setDisplayName(ServingsSelect, "Recipe.ServingsSelect");
940
+ setDisplayName(ServingsInput, "Recipe.ServingsInput");
941
+ }
923
942
 
924
943
  // src/parts/step-text.tsx
925
944
  import {
@@ -928,18 +947,13 @@ import {
928
947
  segmentStepText
929
948
  } from "@recipe-book/core";
930
949
  import {
931
- forwardRef as forwardRef7,
932
950
  useEffect as useEffect5,
933
951
  useMemo as useMemo6
934
952
  } from "react";
935
953
 
936
954
  // src/parts/steps.tsx
937
- import {
938
- createContext as createContext3,
939
- forwardRef as forwardRef6
940
- } from "react";
941
- import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
942
- var Steps = /* @__PURE__ */ forwardRef6(function Steps2({ headingLevel = 3, children, ...props }, ref) {
955
+ import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
956
+ var Steps = /* @__PURE__ */ forwardRef(function Steps2({ headingLevel = 3, children, ...props }, ref) {
943
957
  const { steps } = useRecipeData("Recipe.Steps");
944
958
  const renderSection = children ?? ((section) => /* @__PURE__ */ jsx8(
945
959
  SectionBlock,
@@ -952,7 +966,7 @@ var Steps = /* @__PURE__ */ forwardRef6(function Steps2({ headingLevel = 3, chil
952
966
  ));
953
967
  return /* @__PURE__ */ jsx8("div", { ...props, "data-recipe-steps": "", ref, children: renderKeyed(steps, renderSection) });
954
968
  });
955
- var StepList = /* @__PURE__ */ forwardRef6(
969
+ var StepList = /* @__PURE__ */ forwardRef(
956
970
  function StepList2({ section, children = (step) => /* @__PURE__ */ jsx8(Step, { item: step }), start, ...props }, ref) {
957
971
  const { steps } = useRecipeData("Recipe.StepList");
958
972
  const items = section?.items ?? steps.flatMap((s) => s.items);
@@ -968,15 +982,15 @@ var StepList = /* @__PURE__ */ forwardRef6(
968
982
  );
969
983
  }
970
984
  );
971
- var StepContext = /* @__PURE__ */ createContext3(null);
972
- var Step = /* @__PURE__ */ forwardRef6(function Step2({ item, asChild = false, children, ...props }, ref) {
985
+ var StepContext = /* @__PURE__ */ createContext(null);
986
+ var Step = /* @__PURE__ */ forwardRef(function Step2({ item, asChild = false, children, ...props }, ref) {
973
987
  useRecipeData("Recipe.Step");
974
988
  const [Comp, content] = resolveAsChild(
975
989
  "Recipe.Step",
976
990
  "li",
977
991
  asChild,
978
992
  children,
979
- /* @__PURE__ */ jsxs3(Fragment5, { children: [
993
+ /* @__PURE__ */ jsxs3(Fragment4, { children: [
980
994
  /* @__PURE__ */ jsx8(StepName, {}),
981
995
  item.name !== void 0 && " ",
982
996
  item.text
@@ -984,19 +998,25 @@ var Step = /* @__PURE__ */ forwardRef6(function Step2({ item, asChild = false, c
984
998
  );
985
999
  return /* @__PURE__ */ jsx8(StepContext.Provider, { value: item, children: /* @__PURE__ */ jsx8(Comp, { ...props, "data-recipe-step": "", ref, children: content }) });
986
1000
  });
987
- var StepName = /* @__PURE__ */ forwardRef6(
1001
+ var StepName = /* @__PURE__ */ forwardRef(
988
1002
  function StepName2(props, ref) {
989
1003
  const step = useRequired(
990
1004
  StepContext,
991
1005
  "<Recipe.StepName> must be rendered inside <Recipe.Step>."
992
1006
  );
993
- return step.name === void 0 ? null : /* @__PURE__ */ jsx8("b", { ...props, "data-recipe-name": "", ref, children: step.name });
1007
+ return step.name !== void 0 && /* @__PURE__ */ jsx8("b", { ...props, "data-recipe-name": "", ref, children: step.name });
994
1008
  }
995
1009
  );
1010
+ if (process.env.NODE_ENV !== "production") {
1011
+ setDisplayName(Steps, "Recipe.Steps");
1012
+ setDisplayName(StepList, "Recipe.StepList");
1013
+ setDisplayName(Step, "Recipe.Step");
1014
+ setDisplayName(StepName, "Recipe.StepName");
1015
+ }
996
1016
 
997
1017
  // src/parts/step-text.tsx
998
1018
  import { jsx as jsx9 } from "react/jsx-runtime";
999
- var StepText = /* @__PURE__ */ forwardRef7(
1019
+ var StepText = /* @__PURE__ */ forwardRef(
1000
1020
  function StepText2(props, ref) {
1001
1021
  const step = useRequired(
1002
1022
  StepContext,
@@ -1016,36 +1036,32 @@ function Temperature({ segment }) {
1016
1036
  const unit = usePartState("Recipe.StepText").system === "us" ? "F" : "C";
1017
1037
  const readings = [segment, ...segment.alternatives];
1018
1038
  useEffect5(reportTemperature, [reportTemperature]);
1019
- return /* @__PURE__ */ jsx9("span", { "data-recipe-temperature": "", children: [false, true].flatMap((fan) => {
1039
+ return /* @__PURE__ */ jsx9("span", { "data-recipe-temperature": "", children: [false, true].map((fan) => {
1020
1040
  const kind = readings.filter((reading2) => reading2.fan === fan);
1021
1041
  const reading = kind.find((each) => each.temperature.unit === unit) ?? kind[0];
1022
- return reading ? [
1023
- formatTemperature(
1024
- convertTemperature(reading.temperature, unit),
1025
- {
1026
- locale,
1027
- rounding: reading.temperature.unit === unit ? "none" : void 0
1028
- }
1029
- ) + (fan && labels.fan ? ` ${labels.fan}` : "")
1030
- ] : [];
1031
- }).join(" / ") });
1042
+ return reading && formatTemperature(convertTemperature(reading.temperature, unit), {
1043
+ locale,
1044
+ rounding: reading.temperature.unit === unit ? "none" : void 0
1045
+ }) + (fan && labels.fan ? ` ${labels.fan}` : "");
1046
+ }).filter(Boolean).join(" / ") });
1047
+ }
1048
+ if (process.env.NODE_ENV !== "production") {
1049
+ setDisplayName(StepText, "Recipe.StepText");
1032
1050
  }
1033
1051
 
1034
1052
  // src/parts/system-toggle.tsx
1035
1053
  import {
1036
- createContext as createContext4,
1037
- forwardRef as forwardRef8,
1038
1054
  useCallback as useCallback6,
1039
1055
  useEffect as useEffect6,
1040
1056
  useId as useId2,
1041
1057
  useMemo as useMemo7,
1042
1058
  useState as useState4
1043
1059
  } from "react";
1044
- import { Fragment as Fragment6, jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
1045
- var ToggleContext = /* @__PURE__ */ createContext4(
1060
+ import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
1061
+ var ToggleContext = /* @__PURE__ */ createContext(
1046
1062
  null
1047
1063
  );
1048
- var SystemToggle = /* @__PURE__ */ forwardRef8(
1064
+ var SystemToggle = /* @__PURE__ */ forwardRef(
1049
1065
  function SystemToggle2({ asChild = false, "aria-label": ariaLabel, children, ...props }, ref) {
1050
1066
  const { labels } = useRecipeData("Recipe.SystemToggle");
1051
1067
  const { system, setSystem, canConvert } = usePartState(
@@ -1069,7 +1085,7 @@ var SystemToggle = /* @__PURE__ */ forwardRef8(
1069
1085
  "div",
1070
1086
  asChild,
1071
1087
  children,
1072
- /* @__PURE__ */ jsxs4(Fragment6, { children: [
1088
+ /* @__PURE__ */ jsxs4(Fragment5, { children: [
1073
1089
  /* @__PURE__ */ jsx10(SystemOption, { value: "metric" }),
1074
1090
  /* @__PURE__ */ jsx10(SystemOption, { value: "us" })
1075
1091
  ] })
@@ -1089,7 +1105,7 @@ var SystemToggle = /* @__PURE__ */ forwardRef8(
1089
1105
  ) });
1090
1106
  }
1091
1107
  );
1092
- var SystemOption = /* @__PURE__ */ forwardRef8(
1108
+ var SystemOption = /* @__PURE__ */ forwardRef(
1093
1109
  function SystemOption2({
1094
1110
  value,
1095
1111
  asChild = false,
@@ -1130,6 +1146,7 @@ var SystemOption = /* @__PURE__ */ forwardRef8(
1130
1146
  return /* @__PURE__ */ jsx10(
1131
1147
  Comp,
1132
1148
  {
1149
+ autoComplete: "off",
1133
1150
  ...props,
1134
1151
  type: type ?? "button",
1135
1152
  role: "radio",
@@ -1156,6 +1173,10 @@ function moveOnArrowKey(event) {
1156
1173
  if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
1157
1174
  return;
1158
1175
  }
1176
+ const group = event.currentTarget.closest("[data-recipe-system-toggle]");
1177
+ if (group === null) {
1178
+ return;
1179
+ }
1159
1180
  const { key } = event;
1160
1181
  let step;
1161
1182
  if (key === "ArrowDown") {
@@ -1163,17 +1184,16 @@ function moveOnArrowKey(event) {
1163
1184
  } else if (key === "ArrowUp") {
1164
1185
  step = -1;
1165
1186
  } else if (key === "ArrowRight") {
1166
- step = isRtl(event.currentTarget) ? -1 : 1;
1187
+ step = isRtl(group) ? -1 : 1;
1167
1188
  } else if (key === "ArrowLeft") {
1168
- step = isRtl(event.currentTarget) ? 1 : -1;
1189
+ step = isRtl(group) ? 1 : -1;
1169
1190
  } else {
1170
1191
  return;
1171
1192
  }
1172
- const group = event.currentTarget.closest("[data-recipe-system-toggle]");
1173
1193
  const options = Array.from(
1174
- group?.querySelectorAll(
1194
+ group.querySelectorAll(
1175
1195
  "[data-recipe-system-option]:not([data-disabled])"
1176
- ) ?? []
1196
+ )
1177
1197
  );
1178
1198
  const index = options.indexOf(event.currentTarget);
1179
1199
  if (index === -1) {
@@ -1185,7 +1205,11 @@ function moveOnArrowKey(event) {
1185
1205
  next?.click();
1186
1206
  }
1187
1207
  function isRtl(element) {
1188
- return element.closest("[dir]")?.getAttribute("dir")?.toLowerCase() === "rtl";
1208
+ return getComputedStyle(element).direction === "rtl";
1209
+ }
1210
+ if (process.env.NODE_ENV !== "production") {
1211
+ setDisplayName(SystemToggle, "Recipe.SystemToggle");
1212
+ setDisplayName(SystemOption, "Recipe.SystemOption");
1189
1213
  }
1190
1214
  export {
1191
1215
  Image,