@recipe-book/react 0.0.3 → 0.1.1
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/CHANGELOG.md +36 -0
- package/LICENSE +21 -0
- package/README.md +48 -15
- package/core/package.json +5 -0
- package/core/schema-org/package.json +5 -0
- package/dist/core/schema-org.cjs +25 -0
- package/dist/core/schema-org.cjs.map +1 -0
- package/dist/core/schema-org.d.cts +1 -0
- package/dist/core/schema-org.d.ts +1 -0
- package/dist/core/schema-org.js +3 -0
- package/dist/core/schema-org.js.map +1 -0
- package/dist/core.cjs +25 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +1 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +3 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +1224 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1276 -0
- package/dist/index.d.ts +1276 -10
- package/dist/index.js +1207 -2
- package/dist/index.js.map +1 -0
- package/package.json +71 -15
- package/dist/chunk-2S2Z4HLL.mjs +0 -46
- package/dist/chunk-2VMRVR3X.mjs +0 -1
- package/dist/chunk-36H6LM2B.mjs +0 -14
- package/dist/chunk-4IWVZEXX.mjs +0 -20
- package/dist/chunk-4XBZKTOL.mjs +0 -1
- package/dist/chunk-6AGILC25.mjs +0 -20
- package/dist/chunk-6AXGEB5P.mjs +0 -14
- package/dist/chunk-6JEDN4VR.mjs +0 -20
- package/dist/chunk-6KLLMLNF.mjs +0 -33
- package/dist/chunk-6LTLY5K7.mjs +0 -20
- package/dist/chunk-6RA4EVIO.mjs +0 -46
- package/dist/chunk-75DF3E6L.mjs +0 -50
- package/dist/chunk-7BWMULSB.mjs +0 -55
- package/dist/chunk-7PPB3PM6.mjs +0 -40
- package/dist/chunk-7SBP7DC5.mjs +0 -42
- package/dist/chunk-7XJBP24U.mjs +0 -20
- package/dist/chunk-A5JAKCMG.mjs +0 -44
- package/dist/chunk-ATOAYKI3.mjs +0 -26
- package/dist/chunk-B5IXKKFC.mjs +0 -38
- package/dist/chunk-BEMMFA52.mjs +0 -49
- package/dist/chunk-BOG5DA6M.mjs +0 -38
- package/dist/chunk-BW4DYFBO.mjs +0 -36
- package/dist/chunk-CDQFOSWR.mjs +0 -34
- package/dist/chunk-CRQVP6PW.mjs +0 -20
- package/dist/chunk-CY27OHOG.mjs +0 -1
- package/dist/chunk-DACFJWGN.mjs +0 -14
- package/dist/chunk-DE5GUZKO.mjs +0 -46
- package/dist/chunk-DNZAFS5G.mjs +0 -48
- package/dist/chunk-DS3O5PAR.mjs +0 -20
- package/dist/chunk-ENGJXHYU.mjs +0 -42
- package/dist/chunk-FIFGRJKD.mjs +0 -26
- package/dist/chunk-FWOMIZEF.mjs +0 -46
- package/dist/chunk-G7XD4VBW.mjs +0 -44
- package/dist/chunk-GBU6MVZL.mjs +0 -14
- package/dist/chunk-GM5F7IUT.mjs +0 -2
- package/dist/chunk-GSGRXRJP.mjs +0 -38
- package/dist/chunk-GZRNU24M.mjs +0 -46
- package/dist/chunk-HNDXCSZY.mjs +0 -44
- package/dist/chunk-HXTUDCO2.mjs +0 -49
- package/dist/chunk-I5TR6PEA.mjs +0 -0
- package/dist/chunk-IXRYSEMS.mjs +0 -30
- package/dist/chunk-J5Q2Z2JG.mjs +0 -38
- package/dist/chunk-JBAAWEV6.mjs +0 -1
- package/dist/chunk-JCPPNN26.mjs +0 -44
- package/dist/chunk-JR3OTSZB.mjs +0 -32
- package/dist/chunk-JWS5RTU3.mjs +0 -46
- package/dist/chunk-K6EZ773Q.mjs +0 -26
- package/dist/chunk-KNXMYQVO.mjs +0 -36
- package/dist/chunk-KRUZ7OA7.mjs +0 -44
- package/dist/chunk-KXCIJBR6.mjs +0 -14
- package/dist/chunk-M5D3UUZF.mjs +0 -19
- package/dist/chunk-MASAOP7L.mjs +0 -55
- package/dist/chunk-MGBEDCLT.mjs +0 -34
- package/dist/chunk-MKBCBPVR.mjs +0 -53
- package/dist/chunk-N53TONLF.mjs +0 -39
- package/dist/chunk-NYDGFRPM.mjs +0 -46
- package/dist/chunk-OPOABKD6.mjs +0 -23
- package/dist/chunk-OX6ITS5C.mjs +0 -20
- package/dist/chunk-PAAVW47I.mjs +0 -50
- package/dist/chunk-PAF5HSSV.mjs +0 -53
- package/dist/chunk-PBENQR7Y.mjs +0 -1
- package/dist/chunk-PH3LQIHZ.mjs +0 -26
- package/dist/chunk-PIB3RAOO.mjs +0 -24
- package/dist/chunk-PIXLJD7Y.mjs +0 -37
- package/dist/chunk-PTS3BZHK.mjs +0 -1
- package/dist/chunk-Q6I5J3IJ.mjs +0 -1
- package/dist/chunk-QOCKR2HO.mjs +0 -1
- package/dist/chunk-QOZCSS3B.mjs +0 -44
- package/dist/chunk-QSFPUODQ.mjs +0 -34
- package/dist/chunk-RY7QUBJI.mjs +0 -34
- package/dist/chunk-RZJRUG2I.mjs +0 -55
- package/dist/chunk-S3GGSQB5.mjs +0 -1
- package/dist/chunk-S3HRT5Z5.mjs +0 -14
- package/dist/chunk-SB7HBCCZ.mjs +0 -49
- package/dist/chunk-SEXTT3NJ.mjs +0 -25
- package/dist/chunk-SKEQCOPX.mjs +0 -43
- package/dist/chunk-TLBRVWD2.mjs +0 -1
- package/dist/chunk-TOOCHDUY.mjs +0 -36
- package/dist/chunk-TSKANLT4.mjs +0 -37
- package/dist/chunk-UAMZEZUI.mjs +0 -36
- package/dist/chunk-UQJ4XNWN.mjs +0 -26
- package/dist/chunk-VDSUUXCH.mjs +0 -34
- package/dist/chunk-VO7RY2ZK.mjs +0 -44
- package/dist/chunk-WJKDFBJ2.mjs +0 -34
- package/dist/chunk-WTC7PY7U.mjs +0 -54
- package/dist/chunk-WXDV7CMB.mjs +0 -14
- package/dist/chunk-X5FG3X6T.mjs +0 -1
- package/dist/chunk-X66DY4AZ.mjs +0 -1
- package/dist/chunk-XXSJXKWQ.mjs +0 -44
- package/dist/chunk-YZMVAQQG.mjs +0 -1
- package/dist/chunk-ZCXPZU7W.mjs +0 -20
- package/dist/chunk-ZJ4PNVYO.mjs +0 -52
- package/dist/chunk-ZU4QJLEY.mjs +0 -53
- package/dist/components/ingredients/ingredients-list.d.mts +0 -13
- package/dist/components/ingredients/ingredients-list.d.ts +0 -13
- package/dist/components/ingredients/ingredients-list.js +0 -2
- package/dist/components/ingredients/ingredients-list.mjs +0 -1
- package/dist/components/instructions/instruction-list.d.mts +0 -13
- package/dist/components/instructions/instruction-list.d.ts +0 -13
- package/dist/components/instructions/instruction-list.js +0 -1
- package/dist/components/instructions/instruction-list.mjs +0 -1
- package/dist/components/layouts/layout-default.d.mts +0 -7
- package/dist/components/layouts/layout-default.d.ts +0 -7
- package/dist/components/layouts/layout-default.js +0 -1
- package/dist/components/layouts/layout-default.mjs +0 -1
- package/dist/components/layouts/layout-types.d.mts +0 -8
- package/dist/components/layouts/layout-types.d.ts +0 -8
- package/dist/components/layouts/layout-types.js +0 -1
- package/dist/components/layouts/layout-types.mjs +0 -0
- package/dist/components/measurement-select.d.mts +0 -13
- package/dist/components/measurement-select.d.ts +0 -13
- package/dist/components/measurement-select.js +0 -1
- package/dist/components/measurement-select.mjs +0 -1
- package/dist/components/root.d.mts +0 -13
- package/dist/components/root.d.ts +0 -13
- package/dist/components/root.js +0 -1
- package/dist/components/root.mjs +0 -1
- package/dist/components/servings-select.d.mts +0 -11
- package/dist/components/servings-select.d.ts +0 -11
- package/dist/components/servings-select.js +0 -1
- package/dist/components/servings-select.mjs +0 -1
- package/dist/components/thumbnail.d.mts +0 -18
- package/dist/components/thumbnail.d.ts +0 -18
- package/dist/components/thumbnail.js +0 -1
- package/dist/components/thumbnail.mjs +0 -1
- package/dist/global-context.d.mts +0 -29
- package/dist/global-context.d.ts +0 -29
- package/dist/global-context.js +0 -1
- package/dist/global-context.mjs +0 -1
- package/dist/hooks/use-controllable.d.mts +0 -15
- package/dist/hooks/use-controllable.d.ts +0 -15
- package/dist/hooks/use-controllable.js +0 -1
- package/dist/hooks/use-controllable.mjs +0 -1
- package/dist/index.d.mts +0 -10
- package/dist/index.mjs +0 -1
- package/dist/recipe-context.d.mts +0 -29
- package/dist/recipe-context.d.ts +0 -29
- package/dist/recipe-context.js +0 -1
- package/dist/recipe-context.mjs +0 -1
- package/dist/types-from-core.d.mts +0 -1
- package/dist/types-from-core.d.ts +0 -1
- package/dist/types-from-core.js +0 -1
- package/dist/types-from-core.mjs +0 -1
package/dist/index.js
CHANGED
|
@@ -1,2 +1,1207 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/context.tsx
|
|
4
|
+
import { RecipeBookError as RecipeBookError2 } from "@recipe-book/core";
|
|
5
|
+
import {
|
|
6
|
+
useContext,
|
|
7
|
+
useMemo as useMemo2
|
|
8
|
+
} from "react";
|
|
9
|
+
|
|
10
|
+
// src/labels.ts
|
|
11
|
+
var defaultLabels = {
|
|
12
|
+
servings: "Servings",
|
|
13
|
+
servingUnit: { one: "serving", other: "servings" },
|
|
14
|
+
system: "Measurement system",
|
|
15
|
+
metric: "Metric",
|
|
16
|
+
us: "US",
|
|
17
|
+
fan: "fan",
|
|
18
|
+
optional: "(optional)",
|
|
19
|
+
to: "to"
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/react.ts
|
|
23
|
+
import {
|
|
24
|
+
cloneElement,
|
|
25
|
+
createContext,
|
|
26
|
+
forwardRef,
|
|
27
|
+
Fragment,
|
|
28
|
+
isValidElement
|
|
29
|
+
} from "react";
|
|
30
|
+
|
|
31
|
+
// src/state/use-recipe-state.ts
|
|
32
|
+
import {
|
|
33
|
+
resolveRecipe
|
|
34
|
+
} from "@recipe-book/core";
|
|
35
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo, useRef as useRef2 } from "react";
|
|
36
|
+
|
|
37
|
+
// src/is-parsed-recipe.ts
|
|
38
|
+
import { RecipeBookError } from "@recipe-book/core";
|
|
39
|
+
function assertParsedRecipe(value, caller, fix) {
|
|
40
|
+
if (isFields(value) && typeof value.id === "string" && typeof value.name === "string" && isFields(value.times) && Array.isArray(value.times.custom) && Array.isArray(value.notes) && Array.isArray(value.equipment) && (value.servings === void 0 || isFields(value.servings) && typeof value.servings.amount === "number") && (value.yield === void 0 || isFields(value.yield) && typeof value.yield.amount === "number" && typeof value.yield.unit === "string") && isSections(
|
|
41
|
+
value.ingredients,
|
|
42
|
+
(item) => (
|
|
43
|
+
// Parts format each line with formatIngredient, which checks the name.
|
|
44
|
+
(item.quantity === void 0 || isFields(item.quantity) && (typeof item.quantity.amount === "number" || isFields(item.quantity.amount))) && Array.isArray(item.equivalents) && typeof item.optional === "boolean"
|
|
45
|
+
)
|
|
46
|
+
) && isSections(value.steps, (item) => typeof item.text === "string")) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
throw new RecipeBookError(
|
|
50
|
+
"invalid_argument",
|
|
51
|
+
`${caller}: recipe must be a parsed recipe, received ${value === void 0 || value === null ? String(value) : Array.isArray(value) ? "an array" : typeof value === "object" ? "an object that parseRecipe hasn't normalized" : `a ${typeof value}`}. ${fix}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
function isSections(value, isItem) {
|
|
55
|
+
return Array.isArray(value) && value.every(
|
|
56
|
+
(section) => isFields(section) && typeof section.id === "string" && Array.isArray(section.items) && section.items.every(
|
|
57
|
+
(item) => isFields(item) && typeof item.id === "string" && isItem(item)
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
function isFields(value) {
|
|
62
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/quote.ts
|
|
66
|
+
var UNESCAPED_BY_JSON = /[\x80-\x9f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g;
|
|
67
|
+
function quote(text) {
|
|
68
|
+
const shown = text.length > 60 ? `${text.slice(0, 59).replace(/[\ud800-\udbff]$/, "")}\u2026` : text;
|
|
69
|
+
return JSON.stringify(shown).replace(
|
|
70
|
+
UNESCAPED_BY_JSON,
|
|
71
|
+
(char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/state/use-controllable-state.ts
|
|
76
|
+
import {
|
|
77
|
+
useCallback,
|
|
78
|
+
useEffect,
|
|
79
|
+
useInsertionEffect,
|
|
80
|
+
useRef,
|
|
81
|
+
useState
|
|
82
|
+
} from "react";
|
|
83
|
+
function useControllableState(options) {
|
|
84
|
+
const { value, defaultValue, onChange, resetKey } = options;
|
|
85
|
+
const [internal, setInternal] = useState(defaultValue);
|
|
86
|
+
const [internalResetKey, setInternalResetKey] = useState(resetKey);
|
|
87
|
+
let uncontrolled = internal;
|
|
88
|
+
if (!Object.is(resetKey, internalResetKey)) {
|
|
89
|
+
setInternalResetKey(resetKey);
|
|
90
|
+
setInternal(defaultValue);
|
|
91
|
+
uncontrolled = defaultValue;
|
|
92
|
+
}
|
|
93
|
+
const current = value === void 0 ? uncontrolled : value;
|
|
94
|
+
const latest = useRef({ value, current, onChange });
|
|
95
|
+
useInsertionEffect(() => {
|
|
96
|
+
latest.current = { value, current, onChange };
|
|
97
|
+
});
|
|
98
|
+
const setValue = useCallback((next) => {
|
|
99
|
+
const { value: value2, current: current2, onChange: onChange2 } = latest.current;
|
|
100
|
+
if (Object.is(next, current2)) return;
|
|
101
|
+
if (value2 === void 0) {
|
|
102
|
+
latest.current.current = next;
|
|
103
|
+
setInternal(next);
|
|
104
|
+
}
|
|
105
|
+
onChange2?.(next);
|
|
106
|
+
}, []);
|
|
107
|
+
if (process.env.NODE_ENV !== "production") {
|
|
108
|
+
const controlled = value !== void 0;
|
|
109
|
+
useModeWarnings(
|
|
110
|
+
options.prop,
|
|
111
|
+
controlled,
|
|
112
|
+
controlled && onChange === void 0
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return [current, setValue];
|
|
116
|
+
}
|
|
117
|
+
function useModeWarnings(prop, controlled, readOnly) {
|
|
118
|
+
const initiallyControlled = useRef(controlled);
|
|
119
|
+
const warned2 = useRef({ readOnly: false, modeSwitch: false });
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
const name = prop.charAt(0).toUpperCase() + prop.slice(1);
|
|
122
|
+
if (readOnly && !warned2.current.readOnly) {
|
|
123
|
+
warned2.current.readOnly = true;
|
|
124
|
+
console.warn(
|
|
125
|
+
`Recipe Book: \`${prop}\` was passed without \`on${name}Change\`, so it is read-only. Pass \`on${name}Change\` to update it, or \`default${name}\` instead of \`${prop}\` for uncontrolled state.`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (controlled !== initiallyControlled.current && !warned2.current.modeSwitch) {
|
|
129
|
+
warned2.current.modeSwitch = true;
|
|
130
|
+
const [from, to] = controlled ? ["undefined to a value", "uncontrolled to controlled"] : ["a value to undefined", "controlled to uncontrolled"];
|
|
131
|
+
console.warn(
|
|
132
|
+
`Recipe Book: \`${prop}\` changed from ${from}, which switches it from ${to}. Decide between controlled and uncontrolled for the component's lifetime: pass \`${prop}\` on every render (never undefined), or pass only \`default${name}\`.`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}, [prop, controlled, readOnly]);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/state/use-recipe-state.ts
|
|
139
|
+
function useRecipeState(options) {
|
|
140
|
+
const { recipe } = options;
|
|
141
|
+
if (process.env.NODE_ENV !== "production") {
|
|
142
|
+
useMemo(() => {
|
|
143
|
+
assertParsedRecipe(
|
|
144
|
+
recipe,
|
|
145
|
+
"useRecipeState()",
|
|
146
|
+
"Call parseRecipe() from @recipe-book/react/core and pass result.data."
|
|
147
|
+
);
|
|
148
|
+
}, [recipe]);
|
|
149
|
+
}
|
|
150
|
+
const base = servingsBase(recipe);
|
|
151
|
+
const defaultServings = isServingsFor(options.defaultServings, base) ? options.defaultServings : base;
|
|
152
|
+
const defaultSystem = [options.defaultSystem, recipe.system].find(isSystem) ?? "metric";
|
|
153
|
+
const [rawServings, updateServings] = useControllableState({
|
|
154
|
+
prop: "servings",
|
|
155
|
+
value: options.servings,
|
|
156
|
+
defaultValue: defaultServings,
|
|
157
|
+
onChange: options.onServingsChange,
|
|
158
|
+
resetKey: recipe.id
|
|
159
|
+
});
|
|
160
|
+
const [rawSystem, updateSystem] = useControllableState({
|
|
161
|
+
prop: "system",
|
|
162
|
+
value: options.system,
|
|
163
|
+
defaultValue: defaultSystem,
|
|
164
|
+
onChange: options.onSystemChange,
|
|
165
|
+
resetKey: recipe.id
|
|
166
|
+
});
|
|
167
|
+
const servings = isServingsFor(rawServings, base) ? rawServings : defaultServings;
|
|
168
|
+
const system = isSystem(rawSystem) ? rawSystem : defaultSystem;
|
|
169
|
+
if (process.env.NODE_ENV !== "production") {
|
|
170
|
+
useWarning(
|
|
171
|
+
options.defaultServings !== void 0 && options.defaultServings !== defaultServings && `Recipe Book: \`defaultServings\` must be ${describeRange(base)}, received ${describe(options.defaultServings)}. Using ${defaultServings} instead.`
|
|
172
|
+
);
|
|
173
|
+
useWarning(
|
|
174
|
+
servings !== rawServings && `Recipe Book: \`servings\` must be ${describeRange(base)}, received ${describe(rawServings)}. Showing ${servings} instead.`
|
|
175
|
+
);
|
|
176
|
+
useWarning(
|
|
177
|
+
system !== rawSystem && `Recipe Book: \`system\` must be "metric" or "us", received ${describe(rawSystem)}. Showing "${system}" instead.`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
const setServings = useCallback2(
|
|
181
|
+
(next) => {
|
|
182
|
+
if (isServingsFor(next, base)) {
|
|
183
|
+
updateServings(next);
|
|
184
|
+
} else if (process.env.NODE_ENV !== "production") {
|
|
185
|
+
console.warn(
|
|
186
|
+
`Recipe Book: setServings() expects ${describeRange(base)}, but received ${describe(next)}. The value was ignored.`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
[updateServings, base]
|
|
191
|
+
);
|
|
192
|
+
const setSystem = useCallback2(
|
|
193
|
+
(next) => {
|
|
194
|
+
if (isSystem(next)) {
|
|
195
|
+
updateSystem(next);
|
|
196
|
+
} else if (process.env.NODE_ENV !== "production") {
|
|
197
|
+
console.warn(
|
|
198
|
+
`Recipe Book: setSystem() expects "metric" or "us", but received ${describe(next)}. The value was ignored.`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
[updateSystem]
|
|
203
|
+
);
|
|
204
|
+
const steps = useMemo(() => resolveRecipe(recipe).steps, [recipe]);
|
|
205
|
+
const resolved = useMemo(
|
|
206
|
+
() => resolveRecipe(recipe, { servings, system }),
|
|
207
|
+
[recipe, servings, system]
|
|
208
|
+
);
|
|
209
|
+
return useMemo(
|
|
210
|
+
() => ({
|
|
211
|
+
recipe,
|
|
212
|
+
servings,
|
|
213
|
+
setServings,
|
|
214
|
+
scale: resolved.scale,
|
|
215
|
+
system,
|
|
216
|
+
setSystem,
|
|
217
|
+
canConvert: resolved.canConvert,
|
|
218
|
+
ingredients: resolved.ingredients,
|
|
219
|
+
steps
|
|
220
|
+
}),
|
|
221
|
+
[recipe, servings, setServings, resolved, system, setSystem, steps]
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
function useWarning(message) {
|
|
225
|
+
const warned2 = useRef2(false);
|
|
226
|
+
useEffect2(() => {
|
|
227
|
+
if (message !== warned2.current) {
|
|
228
|
+
warned2.current = message;
|
|
229
|
+
if (message) console.warn(message);
|
|
230
|
+
}
|
|
231
|
+
}, [message]);
|
|
232
|
+
}
|
|
233
|
+
function isServings(value) {
|
|
234
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
235
|
+
}
|
|
236
|
+
function servingsBase(recipe) {
|
|
237
|
+
return [recipe.servings?.amount, recipe.yield?.amount].find(isServings) ?? 1;
|
|
238
|
+
}
|
|
239
|
+
function servingsWindow(base) {
|
|
240
|
+
const [mantissa, exponent] = base.toExponential().split("e");
|
|
241
|
+
return [
|
|
242
|
+
Number(`${mantissa}e${Number(exponent) - 6}`),
|
|
243
|
+
Number(`${mantissa}e${Number(exponent) + 6}`)
|
|
244
|
+
];
|
|
245
|
+
}
|
|
246
|
+
function isServingsFor(value, base) {
|
|
247
|
+
const [min, max] = servingsWindow(base);
|
|
248
|
+
return isServings(value) && value >= min && value <= max;
|
|
249
|
+
}
|
|
250
|
+
function describeRange(base) {
|
|
251
|
+
const [min, max] = servingsWindow(base);
|
|
252
|
+
return `a number from ${roundInward(min, true)} to ${roundInward(max, false)} for this recipe`;
|
|
253
|
+
}
|
|
254
|
+
function roundInward(bound, up) {
|
|
255
|
+
const nearest = Number(bound.toPrecision(6));
|
|
256
|
+
if (up ? nearest >= bound : nearest <= bound) {
|
|
257
|
+
return nearest;
|
|
258
|
+
}
|
|
259
|
+
const [mantissa, exponent] = bound.toExponential().split("e");
|
|
260
|
+
const digits = Number(mantissa.replace(".", "").slice(0, 6));
|
|
261
|
+
return Number(`${up ? digits + 1 : digits}e${Number(exponent) - 5}`);
|
|
262
|
+
}
|
|
263
|
+
function isSystem(value) {
|
|
264
|
+
return value === "metric" || value === "us";
|
|
265
|
+
}
|
|
266
|
+
function describe(value) {
|
|
267
|
+
if (typeof value === "string") {
|
|
268
|
+
return quote(value);
|
|
269
|
+
}
|
|
270
|
+
if (typeof value === "function") {
|
|
271
|
+
return "a function";
|
|
272
|
+
}
|
|
273
|
+
if (value === null || typeof value !== "object") {
|
|
274
|
+
return quote(String(value)).slice(1, -1);
|
|
275
|
+
}
|
|
276
|
+
return Array.isArray(value) ? "an array" : "an object";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/context.tsx
|
|
280
|
+
import { jsx } from "react/jsx-runtime";
|
|
281
|
+
var ConfigContext = /* @__PURE__ */ createContext({
|
|
282
|
+
locale: "en-US",
|
|
283
|
+
labels: defaultLabels
|
|
284
|
+
});
|
|
285
|
+
function RecipeBookProvider({
|
|
286
|
+
children,
|
|
287
|
+
...config
|
|
288
|
+
}) {
|
|
289
|
+
const { locale, labels, defaultSystem } = useConfig(config);
|
|
290
|
+
const value = useMemo2(
|
|
291
|
+
() => ({ locale, labels, defaultSystem }),
|
|
292
|
+
[locale, labels, defaultSystem]
|
|
293
|
+
);
|
|
294
|
+
return /* @__PURE__ */ jsx(ConfigContext.Provider, { value, children });
|
|
295
|
+
}
|
|
296
|
+
function useConfig({
|
|
297
|
+
locale,
|
|
298
|
+
labels,
|
|
299
|
+
defaultSystem
|
|
300
|
+
}) {
|
|
301
|
+
const outer = useContext(ConfigContext);
|
|
302
|
+
return {
|
|
303
|
+
locale: locale ?? outer.locale,
|
|
304
|
+
labels: useMemo2(
|
|
305
|
+
() => ({
|
|
306
|
+
...outer.labels,
|
|
307
|
+
...Object.fromEntries(
|
|
308
|
+
Object.entries({ ...labels }).filter(
|
|
309
|
+
([, value]) => value !== void 0
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
}),
|
|
313
|
+
[outer.labels, labels]
|
|
314
|
+
),
|
|
315
|
+
defaultSystem: [defaultSystem, outer.defaultSystem].find(isSystem)
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
var RecipeDataContext = /* @__PURE__ */ createContext(null);
|
|
319
|
+
var RecipeStateContext = /* @__PURE__ */ createContext(null);
|
|
320
|
+
function useRequired(context, message) {
|
|
321
|
+
const value = useContext(context);
|
|
322
|
+
if (!value) {
|
|
323
|
+
throw new RecipeBookError2("missing_parent", message);
|
|
324
|
+
}
|
|
325
|
+
return value;
|
|
326
|
+
}
|
|
327
|
+
function useRecipeData(part) {
|
|
328
|
+
return useRequired(
|
|
329
|
+
RecipeDataContext,
|
|
330
|
+
`<${part}> must be rendered inside <Recipe.Root>.`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
function usePartState(part) {
|
|
334
|
+
return useRequired(
|
|
335
|
+
RecipeStateContext,
|
|
336
|
+
`<${part}> must be rendered inside <Recipe.Root>.`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
function useRecipe() {
|
|
340
|
+
return useRequired(
|
|
341
|
+
RecipeStateContext,
|
|
342
|
+
"useRecipe() must be called inside <Recipe.Root>. To use recipe state without a Root, call useRecipeState()."
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/parts/image.tsx
|
|
347
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
348
|
+
var Image = /* @__PURE__ */ forwardRef(function Image2({
|
|
349
|
+
as: Comp = "img",
|
|
350
|
+
src,
|
|
351
|
+
alt,
|
|
352
|
+
...props
|
|
353
|
+
}, ref) {
|
|
354
|
+
const { recipe } = useRecipeData("Recipe.Image");
|
|
355
|
+
return recipe.image !== void 0 && /* @__PURE__ */ jsx2(
|
|
356
|
+
Comp,
|
|
357
|
+
{
|
|
358
|
+
...props,
|
|
359
|
+
src: src ?? recipe.image,
|
|
360
|
+
alt: alt ?? recipe.name,
|
|
361
|
+
"data-recipe-image": "",
|
|
362
|
+
ref
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// src/parts/ingredients.tsx
|
|
368
|
+
import {
|
|
369
|
+
useMemo as useMemo3
|
|
370
|
+
} from "react";
|
|
371
|
+
|
|
372
|
+
// src/slot.tsx
|
|
373
|
+
import { useCallback as useCallback3 } from "react";
|
|
374
|
+
function resolveAsChild(part, tag, asChild, children, defaults) {
|
|
375
|
+
if (asChild) {
|
|
376
|
+
if (isValidElement(children) && children.type !== Fragment) {
|
|
377
|
+
return [
|
|
378
|
+
Slot,
|
|
379
|
+
isEmpty(children.props.children) ? cloneElement(children, void 0, defaults) : children
|
|
380
|
+
];
|
|
381
|
+
}
|
|
382
|
+
if (process.env.NODE_ENV !== "production" && !warned.has(part)) {
|
|
383
|
+
warned.add(part);
|
|
384
|
+
console.warn(
|
|
385
|
+
`Recipe Book: <${part}> has asChild but no single element child, received ${describeChildren(children)}, so it renders ${tag === "li" ? "an" : "a"} <${tag}>. Pass one element, such as <${tag}>.`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return [tag, isEmpty(children) ? defaults : children];
|
|
390
|
+
}
|
|
391
|
+
var Slot = /* @__PURE__ */ forwardRef(function Slot2({ children, ...props }, ref) {
|
|
392
|
+
const child = children;
|
|
393
|
+
const merged = { ...props, ...child.props };
|
|
394
|
+
for (const name in child.props) {
|
|
395
|
+
const ours = props[name];
|
|
396
|
+
const theirs = child.props[name];
|
|
397
|
+
if (name === "tabIndex" && ours !== void 0) {
|
|
398
|
+
merged[name] = ours;
|
|
399
|
+
} else if (name === "className") {
|
|
400
|
+
merged[name] = [ours, theirs].filter(Boolean).join(" ") || void 0;
|
|
401
|
+
} else if (name === "style") {
|
|
402
|
+
merged[name] = { ...ours, ...theirs };
|
|
403
|
+
} else if (/^on/.test(name) && typeof ours === "function") {
|
|
404
|
+
merged[name] = typeof theirs === "function" ? (...args) => {
|
|
405
|
+
const [first, second] = [theirs, ours];
|
|
406
|
+
first(...args);
|
|
407
|
+
second(...args);
|
|
408
|
+
} : ours;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const childRef = Object.getOwnPropertyDescriptor(child.props, "ref")?.value ?? child.ref;
|
|
412
|
+
merged.ref = useCallback3(
|
|
413
|
+
(node) => {
|
|
414
|
+
const refs = [ref, childRef];
|
|
415
|
+
const cleanups = refs.map((each) => setRef(each, node));
|
|
416
|
+
if (cleanups.some((cleanup) => typeof cleanup === "function")) {
|
|
417
|
+
return () => refs.forEach((each, index) => {
|
|
418
|
+
const cleanup = cleanups[index];
|
|
419
|
+
if (typeof cleanup === "function") {
|
|
420
|
+
cleanup();
|
|
421
|
+
} else {
|
|
422
|
+
setRef(each, null);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
return void 0;
|
|
427
|
+
},
|
|
428
|
+
[ref, childRef]
|
|
429
|
+
);
|
|
430
|
+
return cloneElement(child, merged);
|
|
431
|
+
});
|
|
432
|
+
function setRef(ref, node) {
|
|
433
|
+
if (typeof ref === "function") {
|
|
434
|
+
return ref(node);
|
|
435
|
+
}
|
|
436
|
+
if (ref) {
|
|
437
|
+
ref.current = node;
|
|
438
|
+
}
|
|
439
|
+
return void 0;
|
|
440
|
+
}
|
|
441
|
+
var warned = /* @__PURE__ */ new Set();
|
|
442
|
+
function describeChildren(children) {
|
|
443
|
+
if (isValidElement(children)) {
|
|
444
|
+
return "a Fragment";
|
|
445
|
+
}
|
|
446
|
+
if (Array.isArray(children)) {
|
|
447
|
+
const count = children.length;
|
|
448
|
+
return `an array of ${count} ${count === 1 ? "child" : "children"}`;
|
|
449
|
+
}
|
|
450
|
+
if (typeof children === "string" || typeof children === "number") {
|
|
451
|
+
return `text ${quote(String(children))}`;
|
|
452
|
+
}
|
|
453
|
+
return isEmpty(children) ? "no children" : "a child that is not an element";
|
|
454
|
+
}
|
|
455
|
+
function isEmpty(children) {
|
|
456
|
+
return children == null || typeof children === "boolean";
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/use-format-ingredient.ts
|
|
460
|
+
import {
|
|
461
|
+
formatIngredient,
|
|
462
|
+
formatQuantityToParts
|
|
463
|
+
} from "@recipe-book/core";
|
|
464
|
+
import { useCallback as useCallback4 } from "react";
|
|
465
|
+
function useFormatIngredient() {
|
|
466
|
+
const {
|
|
467
|
+
locale,
|
|
468
|
+
labels: { optional }
|
|
469
|
+
} = useRequired(
|
|
470
|
+
RecipeDataContext,
|
|
471
|
+
"useFormatIngredient() must be called inside <Recipe.Root>. To format ingredients without a Root, call formatIngredient() from @recipe-book/react/core."
|
|
472
|
+
);
|
|
473
|
+
return useCallback4(
|
|
474
|
+
(item) => formatLine(item, locale, optional),
|
|
475
|
+
[locale, optional]
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
function formatLine(item, locale, optional) {
|
|
479
|
+
const options = { locale };
|
|
480
|
+
const formatted = formatIngredient(item, options);
|
|
481
|
+
const quantity = lineQuantity(item);
|
|
482
|
+
return {
|
|
483
|
+
...formatted,
|
|
484
|
+
quantityParts: quantity ? formatQuantityToParts(quantity, options) : [],
|
|
485
|
+
...item.ingredient.optional && { optional }
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function lineQuantity(item) {
|
|
489
|
+
return item.quantity ?? item.ingredient.quantity;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/parts/sections.tsx
|
|
493
|
+
import { useId } from "react";
|
|
494
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
495
|
+
function renderKeyed(items, render) {
|
|
496
|
+
return items.map((item) => /* @__PURE__ */ jsx3(Fragment, { children: render(item) }, item.id));
|
|
497
|
+
}
|
|
498
|
+
function SectionBlock({
|
|
499
|
+
kind,
|
|
500
|
+
title,
|
|
501
|
+
headingLevel,
|
|
502
|
+
children
|
|
503
|
+
}) {
|
|
504
|
+
const headingId = useId();
|
|
505
|
+
const marker = `data-recipe-${kind}-section`;
|
|
506
|
+
const Heading = `h${headingLevel}`;
|
|
507
|
+
return title ? /* @__PURE__ */ jsxs("section", { ...{ [marker]: "" }, "aria-labelledby": headingId, children: [
|
|
508
|
+
/* @__PURE__ */ jsx3(Heading, { id: headingId, children: title }),
|
|
509
|
+
children
|
|
510
|
+
] }) : /* @__PURE__ */ jsx3("div", { ...{ [marker]: "" }, children });
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/parts/ingredients.tsx
|
|
514
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
515
|
+
var Ingredients = /* @__PURE__ */ forwardRef(
|
|
516
|
+
function Ingredients2({ headingLevel = 3, children, ...props }, ref) {
|
|
517
|
+
const { ingredients } = usePartState("Recipe.Ingredients");
|
|
518
|
+
const renderSection = children ?? ((section) => /* @__PURE__ */ jsx4(
|
|
519
|
+
SectionBlock,
|
|
520
|
+
{
|
|
521
|
+
kind: "ingredient",
|
|
522
|
+
title: section.title,
|
|
523
|
+
headingLevel,
|
|
524
|
+
children: /* @__PURE__ */ jsx4(IngredientList, { section })
|
|
525
|
+
}
|
|
526
|
+
));
|
|
527
|
+
return /* @__PURE__ */ jsx4("div", { ...props, "data-recipe-ingredients": "", ref, children: renderKeyed(ingredients, renderSection) });
|
|
528
|
+
}
|
|
529
|
+
);
|
|
530
|
+
var IngredientList = /* @__PURE__ */ forwardRef(
|
|
531
|
+
function IngredientList2({ section, children = (item) => /* @__PURE__ */ jsx4(Ingredient, { item }), ...props }, ref) {
|
|
532
|
+
const { ingredients } = usePartState("Recipe.IngredientList");
|
|
533
|
+
const items = section?.items ?? ingredients.flatMap((s) => s.items);
|
|
534
|
+
return /* @__PURE__ */ jsx4("ul", { ...props, "data-recipe-ingredient-list": "", ref, children: renderKeyed(items, children) });
|
|
535
|
+
}
|
|
536
|
+
);
|
|
537
|
+
var LineContext = /* @__PURE__ */ createContext(null);
|
|
538
|
+
var Ingredient = /* @__PURE__ */ forwardRef(
|
|
539
|
+
function Ingredient2({ item, asChild = false, children, ...props }, ref) {
|
|
540
|
+
const {
|
|
541
|
+
labels: { optional, to },
|
|
542
|
+
locale
|
|
543
|
+
} = useRecipeData("Recipe.Ingredient");
|
|
544
|
+
const line = useMemo3(
|
|
545
|
+
() => ({ ...formatLine(item, locale, optional), to }),
|
|
546
|
+
[item, locale, optional, to]
|
|
547
|
+
);
|
|
548
|
+
const quantity = lineQuantity(item);
|
|
549
|
+
const [Comp, content] = resolveAsChild(
|
|
550
|
+
"Recipe.Ingredient",
|
|
551
|
+
"li",
|
|
552
|
+
asChild,
|
|
553
|
+
children,
|
|
554
|
+
/* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
555
|
+
/* @__PURE__ */ jsx4(IngredientQuantity, {}),
|
|
556
|
+
quantity && (quantity.unit === void 0 ? "\xA0" : " "),
|
|
557
|
+
/* @__PURE__ */ jsx4(IngredientName, {}),
|
|
558
|
+
line.note !== void 0 && ", ",
|
|
559
|
+
/* @__PURE__ */ jsx4(IngredientNote, {}),
|
|
560
|
+
line.optional && " ",
|
|
561
|
+
/* @__PURE__ */ jsx4(IngredientOptional, {})
|
|
562
|
+
] })
|
|
563
|
+
);
|
|
564
|
+
return /* @__PURE__ */ jsx4(LineContext.Provider, { value: line, children: /* @__PURE__ */ jsx4(
|
|
565
|
+
Comp,
|
|
566
|
+
{
|
|
567
|
+
...props,
|
|
568
|
+
"data-recipe-ingredient": "",
|
|
569
|
+
"data-origin": quantity && item.origin,
|
|
570
|
+
"data-unquantified": quantity ? void 0 : "",
|
|
571
|
+
"data-optional": item.ingredient.optional ? "" : void 0,
|
|
572
|
+
ref,
|
|
573
|
+
children: content
|
|
574
|
+
}
|
|
575
|
+
) });
|
|
576
|
+
}
|
|
577
|
+
);
|
|
578
|
+
function ingredientText(name, attribute, read) {
|
|
579
|
+
const part = `Recipe.${name}`;
|
|
580
|
+
function IngredientText({ asChild = false, children, ...props }, ref) {
|
|
581
|
+
const line = useRequired(
|
|
582
|
+
LineContext,
|
|
583
|
+
`<${part}> must be rendered inside <Recipe.Ingredient>.`
|
|
584
|
+
);
|
|
585
|
+
const text = read(line, children);
|
|
586
|
+
if (text === void 0) {
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
const [Comp, child] = resolveAsChild(part, "span", asChild, children, null);
|
|
590
|
+
return /* @__PURE__ */ jsx4(Comp, { ...props, ...{ [attribute]: "" }, ref, children: Comp === "span" ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
591
|
+
children,
|
|
592
|
+
text
|
|
593
|
+
] }) : cloneElement(child, void 0, child.props.children, text) });
|
|
594
|
+
}
|
|
595
|
+
if (process.env.NODE_ENV !== "production") {
|
|
596
|
+
Object.defineProperty(IngredientText, "name", { value: name });
|
|
597
|
+
}
|
|
598
|
+
return forwardRef(IngredientText);
|
|
599
|
+
}
|
|
600
|
+
var IngredientQuantity = /* @__PURE__ */ ingredientText(
|
|
601
|
+
"IngredientQuantity",
|
|
602
|
+
"data-recipe-quantity",
|
|
603
|
+
({ quantity, quantityParts, to }) => quantity === void 0 ? void 0 : renderQuantity(quantityParts, to)
|
|
604
|
+
);
|
|
605
|
+
var hiddenStyle = {
|
|
606
|
+
position: "absolute",
|
|
607
|
+
border: 0,
|
|
608
|
+
width: 1,
|
|
609
|
+
height: 1,
|
|
610
|
+
padding: 0,
|
|
611
|
+
margin: -1,
|
|
612
|
+
overflow: "hidden",
|
|
613
|
+
clip: "rect(0, 0, 0, 0)",
|
|
614
|
+
whiteSpace: "nowrap",
|
|
615
|
+
wordWrap: "normal",
|
|
616
|
+
userSelect: "none",
|
|
617
|
+
WebkitUserSelect: "none"
|
|
618
|
+
};
|
|
619
|
+
function renderQuantity(parts, to) {
|
|
620
|
+
const join = (from) => from.map((part) => part.value).join("");
|
|
621
|
+
const dash = parts.findIndex((part) => part.type === "rangeSeparator");
|
|
622
|
+
const separator = parts[dash];
|
|
623
|
+
if (!separator) {
|
|
624
|
+
return join(parts);
|
|
625
|
+
}
|
|
626
|
+
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
627
|
+
join(parts.slice(0, dash)),
|
|
628
|
+
/* @__PURE__ */ jsx4("span", { "data-recipe-range-separator": "", "aria-hidden": "true", children: separator.value }),
|
|
629
|
+
/* @__PURE__ */ jsx4("span", { style: hiddenStyle, children: ` ${to} ` }),
|
|
630
|
+
join(parts.slice(dash + 1))
|
|
631
|
+
] });
|
|
632
|
+
}
|
|
633
|
+
var IngredientName = /* @__PURE__ */ ingredientText(
|
|
634
|
+
"IngredientName",
|
|
635
|
+
"data-recipe-name",
|
|
636
|
+
({ name }) => name
|
|
637
|
+
);
|
|
638
|
+
var IngredientNote = /* @__PURE__ */ ingredientText(
|
|
639
|
+
"IngredientNote",
|
|
640
|
+
"data-recipe-note",
|
|
641
|
+
({ note }) => note
|
|
642
|
+
);
|
|
643
|
+
var IngredientOptional = /* @__PURE__ */ ingredientText(
|
|
644
|
+
"IngredientOptional",
|
|
645
|
+
"data-recipe-optional",
|
|
646
|
+
// An empty label shows only the part's own children, such as an icon.
|
|
647
|
+
({ optional }, children) => optional === "" && isEmpty(children) ? void 0 : optional
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
// src/parts/json-ld.tsx
|
|
651
|
+
import { toJsonLd } from "@recipe-book/core/schema-org";
|
|
652
|
+
import {
|
|
653
|
+
useEffect as useEffect3,
|
|
654
|
+
useMemo as useMemo4,
|
|
655
|
+
useRef as useRef3
|
|
656
|
+
} from "react";
|
|
657
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
658
|
+
var JsonLd = /* @__PURE__ */ forwardRef(
|
|
659
|
+
function JsonLd2({
|
|
660
|
+
servings,
|
|
661
|
+
system,
|
|
662
|
+
url,
|
|
663
|
+
extra,
|
|
664
|
+
// Off the props type, but a JavaScript caller can still pass them.
|
|
665
|
+
children,
|
|
666
|
+
src,
|
|
667
|
+
...props
|
|
668
|
+
}, ref) {
|
|
669
|
+
const { recipe } = useRecipeData("Recipe.JsonLd");
|
|
670
|
+
const result = useMemo4(
|
|
671
|
+
() => toJsonLd(recipe, { servings, system, url, extra }),
|
|
672
|
+
[recipe, servings, system, url, extra]
|
|
673
|
+
);
|
|
674
|
+
if (process.env.NODE_ENV !== "production") {
|
|
675
|
+
useWarningsReport(recipe.name, result.warnings);
|
|
676
|
+
}
|
|
677
|
+
return /* @__PURE__ */ jsx5(
|
|
678
|
+
"script",
|
|
679
|
+
{
|
|
680
|
+
...props,
|
|
681
|
+
type: "application/ld+json",
|
|
682
|
+
"data-recipe-json-ld": "",
|
|
683
|
+
dangerouslySetInnerHTML: {
|
|
684
|
+
__html: JSON.stringify(result.data).replace(/</g, "\\u003c")
|
|
685
|
+
},
|
|
686
|
+
ref
|
|
687
|
+
}
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
);
|
|
691
|
+
function useWarningsReport(name, warnings) {
|
|
692
|
+
const reported = useRef3(void 0);
|
|
693
|
+
useEffect3(() => {
|
|
694
|
+
const key = JSON.stringify(warnings);
|
|
695
|
+
const count = warnings.length;
|
|
696
|
+
if (count > 0 && key !== reported.current) {
|
|
697
|
+
console.warn(
|
|
698
|
+
`Recipe Book: <Recipe.JsonLd> for ${quote(name)} has ${count} ${count === 1 ? "warning" : "warnings"}. The script still renders, but without the fields they name.`,
|
|
699
|
+
warnings
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
reported.current = key;
|
|
703
|
+
}, [name, warnings]);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// src/parts/root.tsx
|
|
707
|
+
import {
|
|
708
|
+
useCallback as useCallback5,
|
|
709
|
+
useEffect as useEffect4,
|
|
710
|
+
useMemo as useMemo5,
|
|
711
|
+
useRef as useRef4,
|
|
712
|
+
useState as useState2
|
|
713
|
+
} from "react";
|
|
714
|
+
import { Fragment as Fragment3, jsx as jsx6 } from "react/jsx-runtime";
|
|
715
|
+
function Root({
|
|
716
|
+
recipe,
|
|
717
|
+
parse,
|
|
718
|
+
fallback = null,
|
|
719
|
+
children,
|
|
720
|
+
...options
|
|
721
|
+
}) {
|
|
722
|
+
const result = useMemo5(() => {
|
|
723
|
+
if (parse !== void 0) {
|
|
724
|
+
return parse(recipe);
|
|
725
|
+
}
|
|
726
|
+
assertParsedRecipe(
|
|
727
|
+
recipe,
|
|
728
|
+
"<Recipe.Root>",
|
|
729
|
+
"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."
|
|
730
|
+
);
|
|
731
|
+
return { success: true, data: recipe, warnings: [] };
|
|
732
|
+
}, [parse, recipe]);
|
|
733
|
+
if (process.env.NODE_ENV !== "production") {
|
|
734
|
+
useParseReport(result);
|
|
735
|
+
}
|
|
736
|
+
if (!result.success) {
|
|
737
|
+
return /* @__PURE__ */ jsx6(Fragment3, { children: fallback });
|
|
738
|
+
}
|
|
739
|
+
return /* @__PURE__ */ jsx6(RecipeProvider, { ...options, recipe: result.data, children });
|
|
740
|
+
}
|
|
741
|
+
function RecipeProvider({ children, ...options }) {
|
|
742
|
+
const { locale, labels, defaultSystem } = useConfig(options);
|
|
743
|
+
const state = useRecipeState({ ...options, defaultSystem });
|
|
744
|
+
const [temperatures, setTemperatures] = useState2(0);
|
|
745
|
+
const reportTemperature = useCallback5(() => {
|
|
746
|
+
setTemperatures((count) => count + 1);
|
|
747
|
+
return () => setTemperatures((count) => count - 1);
|
|
748
|
+
}, []);
|
|
749
|
+
const canConvert = state.canConvert || temperatures > 0;
|
|
750
|
+
const value = useMemo5(() => ({ ...state, canConvert }), [state, canConvert]);
|
|
751
|
+
const { recipe, steps } = state;
|
|
752
|
+
const data = useMemo5(
|
|
753
|
+
() => ({ recipe, steps, labels, locale, reportTemperature }),
|
|
754
|
+
[recipe, steps, labels, locale, reportTemperature]
|
|
755
|
+
);
|
|
756
|
+
return /* @__PURE__ */ jsx6(RecipeDataContext.Provider, { value: data, children: /* @__PURE__ */ jsx6(RecipeStateContext.Provider, { value, children }) });
|
|
757
|
+
}
|
|
758
|
+
function useParseReport(result) {
|
|
759
|
+
const reported = useRef4(void 0);
|
|
760
|
+
useEffect4(() => {
|
|
761
|
+
const key = result.success ? result.warnings.length > 0 ? `warnings ${JSON.stringify(result.warnings)}` : void 0 : `error ${JSON.stringify([result.error.message, result.error.issues])}`;
|
|
762
|
+
if (key === reported.current) {
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
reported.current = key;
|
|
766
|
+
if (key === void 0) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
if (result.success) {
|
|
770
|
+
const count = result.warnings.length;
|
|
771
|
+
console.warn(
|
|
772
|
+
`Recipe Book: ${quote(result.data.name)} has ${count} ${count === 1 ? "warning" : "warnings"}. It still renders, but a warning can mean a misspelled field.`,
|
|
773
|
+
result.warnings
|
|
774
|
+
);
|
|
775
|
+
} else {
|
|
776
|
+
console.error(result.error);
|
|
777
|
+
}
|
|
778
|
+
}, [result]);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/parts/servings.tsx
|
|
782
|
+
import {
|
|
783
|
+
formatServings,
|
|
784
|
+
formatYield
|
|
785
|
+
} from "@recipe-book/core";
|
|
786
|
+
import {
|
|
787
|
+
useState as useState3
|
|
788
|
+
} from "react";
|
|
789
|
+
|
|
790
|
+
// src/compose-event-handlers.ts
|
|
791
|
+
function composeEventHandlers(theirs, ours) {
|
|
792
|
+
return (event) => {
|
|
793
|
+
theirs?.(event);
|
|
794
|
+
if (!event.defaultPrevented) {
|
|
795
|
+
ours(event);
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// src/parts/servings.tsx
|
|
801
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
802
|
+
var ServingsSelect = /* @__PURE__ */ forwardRef(
|
|
803
|
+
function ServingsSelect2({ options, "aria-label": ariaLabel, onChange, ...props }, ref) {
|
|
804
|
+
const { recipe, labels, locale } = useRecipeData("Recipe.ServingsSelect");
|
|
805
|
+
const { servings, setServings } = usePartState("Recipe.ServingsSelect");
|
|
806
|
+
const base = servingsBase(recipe);
|
|
807
|
+
const choices = [
|
|
808
|
+
.../* @__PURE__ */ new Set([
|
|
809
|
+
...(options ?? [0.5, 1, 2, 3].map((factor) => Math.max(1, Math.round(base * factor)))).filter((choice) => isServingsFor(choice, base)),
|
|
810
|
+
servings
|
|
811
|
+
])
|
|
812
|
+
].sort((a, b) => a - b);
|
|
813
|
+
return /* @__PURE__ */ jsx7(
|
|
814
|
+
"select",
|
|
815
|
+
{
|
|
816
|
+
autoComplete: "off",
|
|
817
|
+
...props,
|
|
818
|
+
"aria-label": (ariaLabel ?? labels.servings) || void 0,
|
|
819
|
+
value: servings,
|
|
820
|
+
"data-recipe-servings-select": "",
|
|
821
|
+
ref,
|
|
822
|
+
onChange: composeEventHandlers(onChange, (event) => {
|
|
823
|
+
setServings(Number(event.target.value));
|
|
824
|
+
}),
|
|
825
|
+
children: choices.map((choice) => /* @__PURE__ */ jsx7("option", { value: choice, children: optionText(choice, recipe, base, labels, locale) }, choice))
|
|
826
|
+
}
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
);
|
|
830
|
+
var ServingsInput = /* @__PURE__ */ forwardRef(
|
|
831
|
+
function ServingsInput2({
|
|
832
|
+
min = 1,
|
|
833
|
+
max,
|
|
834
|
+
step,
|
|
835
|
+
inputMode,
|
|
836
|
+
"aria-label": ariaLabel,
|
|
837
|
+
onChange,
|
|
838
|
+
onBlur,
|
|
839
|
+
...props
|
|
840
|
+
}, ref) {
|
|
841
|
+
const { recipe, labels } = useRecipeData("Recipe.ServingsInput");
|
|
842
|
+
const { servings, setServings } = usePartState("Recipe.ServingsInput");
|
|
843
|
+
const base = servingsBase(recipe);
|
|
844
|
+
const [draft, setDraft] = useState3({ text: String(servings), servings });
|
|
845
|
+
if (draft.servings !== servings) {
|
|
846
|
+
setDraft({
|
|
847
|
+
text: Number(draft.text) === servings ? draft.text : String(servings),
|
|
848
|
+
servings
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
const whole = Number.isInteger(min) && Number.isInteger(step ?? 1);
|
|
852
|
+
return /* @__PURE__ */ jsx7(
|
|
853
|
+
"input",
|
|
854
|
+
{
|
|
855
|
+
autoComplete: "off",
|
|
856
|
+
...props,
|
|
857
|
+
type: "number",
|
|
858
|
+
inputMode: inputMode ?? (whole ? "numeric" : "decimal"),
|
|
859
|
+
"aria-label": (ariaLabel ?? labels.servings) || void 0,
|
|
860
|
+
min,
|
|
861
|
+
max,
|
|
862
|
+
step,
|
|
863
|
+
value: draft.text,
|
|
864
|
+
"data-recipe-servings-input": "",
|
|
865
|
+
ref,
|
|
866
|
+
onChange: composeEventHandlers(onChange, (event) => {
|
|
867
|
+
const text = event.target.value;
|
|
868
|
+
const next = Number(text);
|
|
869
|
+
setDraft({ text, servings });
|
|
870
|
+
if (isServingsFor(next, base) && next >= min && (max === void 0 || next <= max)) {
|
|
871
|
+
const steps = step === "any" ? 0 : (next - min) / (step && step > 0 ? step : 1);
|
|
872
|
+
if (Math.abs(steps - Math.round(steps)) <= (Math.round(steps) && 1e-15 * (steps + steps / (1 - min / next)))) {
|
|
873
|
+
setServings(next);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}),
|
|
877
|
+
onBlur: composeEventHandlers(onBlur, () => {
|
|
878
|
+
setDraft({ text: String(servings), servings });
|
|
879
|
+
})
|
|
880
|
+
}
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
);
|
|
884
|
+
function optionText(choice, { servings, yield: makes }, base, labels, locale) {
|
|
885
|
+
const format = {
|
|
886
|
+
locale,
|
|
887
|
+
rounding: Number.isInteger(choice * 4) ? "kitchen" : "none"
|
|
888
|
+
};
|
|
889
|
+
if (base === servings?.amount) {
|
|
890
|
+
return formatYield(
|
|
891
|
+
{
|
|
892
|
+
amount: choice,
|
|
893
|
+
unit: labels.servingUnit.one,
|
|
894
|
+
plural: labels.servingUnit.other
|
|
895
|
+
},
|
|
896
|
+
format
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
if (base === makes?.amount) {
|
|
900
|
+
return formatYield(
|
|
901
|
+
{ amount: choice, unit: makes.unit, plural: makes.plural },
|
|
902
|
+
format
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
return `${formatServings({ amount: choice }, format)}\xD7`;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// src/parts/step-text.tsx
|
|
909
|
+
import {
|
|
910
|
+
convertTemperature,
|
|
911
|
+
formatTemperature,
|
|
912
|
+
segmentStepText
|
|
913
|
+
} from "@recipe-book/core";
|
|
914
|
+
import {
|
|
915
|
+
useEffect as useEffect5,
|
|
916
|
+
useMemo as useMemo6
|
|
917
|
+
} from "react";
|
|
918
|
+
|
|
919
|
+
// src/parts/steps.tsx
|
|
920
|
+
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
921
|
+
var Steps = /* @__PURE__ */ forwardRef(function Steps2({ headingLevel = 3, children, ...props }, ref) {
|
|
922
|
+
const { steps } = useRecipeData("Recipe.Steps");
|
|
923
|
+
const renderSection = children ?? ((section) => /* @__PURE__ */ jsx8(
|
|
924
|
+
SectionBlock,
|
|
925
|
+
{
|
|
926
|
+
kind: "step",
|
|
927
|
+
title: section.title,
|
|
928
|
+
headingLevel,
|
|
929
|
+
children: /* @__PURE__ */ jsx8(StepList, { section })
|
|
930
|
+
}
|
|
931
|
+
));
|
|
932
|
+
return /* @__PURE__ */ jsx8("div", { ...props, "data-recipe-steps": "", ref, children: renderKeyed(steps, renderSection) });
|
|
933
|
+
});
|
|
934
|
+
var StepList = /* @__PURE__ */ forwardRef(
|
|
935
|
+
function StepList2({ section, children = (step) => /* @__PURE__ */ jsx8(Step, { item: step }), start, ...props }, ref) {
|
|
936
|
+
const { steps } = useRecipeData("Recipe.StepList");
|
|
937
|
+
const items = section?.items ?? steps.flatMap((s) => s.items);
|
|
938
|
+
return /* @__PURE__ */ jsx8(
|
|
939
|
+
"ol",
|
|
940
|
+
{
|
|
941
|
+
...props,
|
|
942
|
+
start: start ?? items[0]?.number,
|
|
943
|
+
"data-recipe-step-list": "",
|
|
944
|
+
ref,
|
|
945
|
+
children: renderKeyed(items, children)
|
|
946
|
+
}
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
);
|
|
950
|
+
var StepContext = /* @__PURE__ */ createContext(null);
|
|
951
|
+
var Step = /* @__PURE__ */ forwardRef(function Step2({ item, asChild = false, children, ...props }, ref) {
|
|
952
|
+
useRecipeData("Recipe.Step");
|
|
953
|
+
const [Comp, content] = resolveAsChild(
|
|
954
|
+
"Recipe.Step",
|
|
955
|
+
"li",
|
|
956
|
+
asChild,
|
|
957
|
+
children,
|
|
958
|
+
/* @__PURE__ */ jsxs3(Fragment4, { children: [
|
|
959
|
+
/* @__PURE__ */ jsx8(StepName, {}),
|
|
960
|
+
item.name !== void 0 && " ",
|
|
961
|
+
item.text
|
|
962
|
+
] })
|
|
963
|
+
);
|
|
964
|
+
return /* @__PURE__ */ jsx8(StepContext.Provider, { value: item, children: /* @__PURE__ */ jsx8(Comp, { ...props, "data-recipe-step": "", ref, children: content }) });
|
|
965
|
+
});
|
|
966
|
+
var StepName = /* @__PURE__ */ forwardRef(
|
|
967
|
+
function StepName2(props, ref) {
|
|
968
|
+
const step = useRequired(
|
|
969
|
+
StepContext,
|
|
970
|
+
"<Recipe.StepName> must be rendered inside <Recipe.Step>."
|
|
971
|
+
);
|
|
972
|
+
return step.name === void 0 ? null : /* @__PURE__ */ jsx8("b", { ...props, "data-recipe-name": "", ref, children: step.name });
|
|
973
|
+
}
|
|
974
|
+
);
|
|
975
|
+
|
|
976
|
+
// src/parts/step-text.tsx
|
|
977
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
978
|
+
var StepText = /* @__PURE__ */ forwardRef(
|
|
979
|
+
function StepText2(props, ref) {
|
|
980
|
+
const step = useRequired(
|
|
981
|
+
StepContext,
|
|
982
|
+
"<Recipe.StepText> must be rendered inside <Recipe.Step>."
|
|
983
|
+
);
|
|
984
|
+
const segments = useMemo6(() => segmentStepText(step.text), [step.text]);
|
|
985
|
+
return /* @__PURE__ */ jsx9("span", { ...props, "data-recipe-step-text": "", ref, children: segments.map(
|
|
986
|
+
(segment, index) => segment.type === "temperature" ? /* @__PURE__ */ jsx9(Temperature, { segment }, index) : (
|
|
987
|
+
// Core may add segment types in a minor release; show those as authored.
|
|
988
|
+
segment.text
|
|
989
|
+
)
|
|
990
|
+
) });
|
|
991
|
+
}
|
|
992
|
+
);
|
|
993
|
+
function Temperature({ segment }) {
|
|
994
|
+
const { labels, locale, reportTemperature } = useRecipeData("Recipe.StepText");
|
|
995
|
+
const unit = usePartState("Recipe.StepText").system === "us" ? "F" : "C";
|
|
996
|
+
const readings = [segment, ...segment.alternatives];
|
|
997
|
+
useEffect5(reportTemperature, [reportTemperature]);
|
|
998
|
+
return /* @__PURE__ */ jsx9("span", { "data-recipe-temperature": "", children: [false, true].map((fan) => {
|
|
999
|
+
const kind = readings.filter((reading2) => reading2.fan === fan);
|
|
1000
|
+
const reading = kind.find((each) => each.temperature.unit === unit) ?? kind[0];
|
|
1001
|
+
return reading && formatTemperature(convertTemperature(reading.temperature, unit), {
|
|
1002
|
+
locale,
|
|
1003
|
+
rounding: reading.temperature.unit === unit ? "none" : void 0
|
|
1004
|
+
}) + (fan && labels.fan ? ` ${labels.fan}` : "");
|
|
1005
|
+
}).filter(Boolean).join(" / ") });
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// src/parts/system-toggle.tsx
|
|
1009
|
+
import {
|
|
1010
|
+
useCallback as useCallback6,
|
|
1011
|
+
useEffect as useEffect6,
|
|
1012
|
+
useId as useId2,
|
|
1013
|
+
useMemo as useMemo7,
|
|
1014
|
+
useState as useState4
|
|
1015
|
+
} from "react";
|
|
1016
|
+
import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1017
|
+
var ToggleContext = /* @__PURE__ */ createContext(
|
|
1018
|
+
null
|
|
1019
|
+
);
|
|
1020
|
+
var SystemToggle = /* @__PURE__ */ forwardRef(
|
|
1021
|
+
function SystemToggle2({ asChild = false, "aria-label": ariaLabel, children, ...props }, ref) {
|
|
1022
|
+
const { labels } = useRecipeData("Recipe.SystemToggle");
|
|
1023
|
+
const { system, setSystem, canConvert } = usePartState(
|
|
1024
|
+
"Recipe.SystemToggle"
|
|
1025
|
+
);
|
|
1026
|
+
const disabled = !canConvert;
|
|
1027
|
+
const [offers, setOffers] = useState4([]);
|
|
1028
|
+
const [chosen, choose] = useState4();
|
|
1029
|
+
const offer = useCallback6((id, value) => {
|
|
1030
|
+
const entry = { id, value };
|
|
1031
|
+
setOffers((current) => [...current, entry]);
|
|
1032
|
+
return () => setOffers((current) => current.filter((other) => other !== entry));
|
|
1033
|
+
}, []);
|
|
1034
|
+
const tabStop = (offers.find((entry) => entry.value === system && entry.id === chosen) ?? offers.find((entry) => entry.value === system) ?? offers[0])?.id;
|
|
1035
|
+
const context = useMemo7(
|
|
1036
|
+
() => ({ system, setSystem, disabled, tabStop, offer, choose }),
|
|
1037
|
+
[system, setSystem, disabled, tabStop, offer]
|
|
1038
|
+
);
|
|
1039
|
+
const [Comp, content] = resolveAsChild(
|
|
1040
|
+
"Recipe.SystemToggle",
|
|
1041
|
+
"div",
|
|
1042
|
+
asChild,
|
|
1043
|
+
children,
|
|
1044
|
+
/* @__PURE__ */ jsxs4(Fragment5, { children: [
|
|
1045
|
+
/* @__PURE__ */ jsx10(SystemOption, { value: "metric" }),
|
|
1046
|
+
/* @__PURE__ */ jsx10(SystemOption, { value: "us" })
|
|
1047
|
+
] })
|
|
1048
|
+
);
|
|
1049
|
+
return /* @__PURE__ */ jsx10(ToggleContext.Provider, { value: context, children: /* @__PURE__ */ jsx10(
|
|
1050
|
+
Comp,
|
|
1051
|
+
{
|
|
1052
|
+
...props,
|
|
1053
|
+
"aria-label": ariaLabel ?? (props["aria-labelledby"] === void 0 ? labels.system : void 0),
|
|
1054
|
+
role: "radiogroup",
|
|
1055
|
+
"aria-disabled": disabled || void 0,
|
|
1056
|
+
"data-recipe-system-toggle": "",
|
|
1057
|
+
"data-disabled": disabled ? "" : void 0,
|
|
1058
|
+
ref,
|
|
1059
|
+
children: content
|
|
1060
|
+
}
|
|
1061
|
+
) });
|
|
1062
|
+
}
|
|
1063
|
+
);
|
|
1064
|
+
var SystemOption = /* @__PURE__ */ forwardRef(
|
|
1065
|
+
function SystemOption2({
|
|
1066
|
+
value,
|
|
1067
|
+
asChild = false,
|
|
1068
|
+
type,
|
|
1069
|
+
disabled: ownDisabled = false,
|
|
1070
|
+
children,
|
|
1071
|
+
onClick,
|
|
1072
|
+
onKeyDown,
|
|
1073
|
+
...props
|
|
1074
|
+
}, ref) {
|
|
1075
|
+
const {
|
|
1076
|
+
system,
|
|
1077
|
+
setSystem,
|
|
1078
|
+
disabled: groupDisabled,
|
|
1079
|
+
tabStop,
|
|
1080
|
+
offer,
|
|
1081
|
+
choose
|
|
1082
|
+
} = useRequired(
|
|
1083
|
+
ToggleContext,
|
|
1084
|
+
"<Recipe.SystemOption> must be rendered inside <Recipe.SystemToggle>."
|
|
1085
|
+
);
|
|
1086
|
+
const { labels } = useRecipeData("Recipe.SystemOption");
|
|
1087
|
+
const id = useId2();
|
|
1088
|
+
const disabled = groupDisabled || ownDisabled;
|
|
1089
|
+
const checked = value === system;
|
|
1090
|
+
useEffect6(
|
|
1091
|
+
() => disabled ? void 0 : offer(id, value),
|
|
1092
|
+
[disabled, offer, id, value]
|
|
1093
|
+
);
|
|
1094
|
+
const tabbable = !disabled && (tabStop === void 0 ? checked : tabStop === id);
|
|
1095
|
+
const [Comp, content] = resolveAsChild(
|
|
1096
|
+
"Recipe.SystemOption",
|
|
1097
|
+
"button",
|
|
1098
|
+
asChild,
|
|
1099
|
+
children,
|
|
1100
|
+
labels[value]
|
|
1101
|
+
);
|
|
1102
|
+
return /* @__PURE__ */ jsx10(
|
|
1103
|
+
Comp,
|
|
1104
|
+
{
|
|
1105
|
+
autoComplete: "off",
|
|
1106
|
+
...props,
|
|
1107
|
+
type: type ?? "button",
|
|
1108
|
+
role: "radio",
|
|
1109
|
+
"aria-checked": checked,
|
|
1110
|
+
tabIndex: tabbable ? 0 : -1,
|
|
1111
|
+
disabled,
|
|
1112
|
+
"data-recipe-system-option": "",
|
|
1113
|
+
"data-state": checked ? "checked" : "unchecked",
|
|
1114
|
+
"data-disabled": disabled ? "" : void 0,
|
|
1115
|
+
ref,
|
|
1116
|
+
onClick: composeEventHandlers(onClick, () => {
|
|
1117
|
+
if (!disabled) {
|
|
1118
|
+
setSystem(value);
|
|
1119
|
+
choose(id);
|
|
1120
|
+
}
|
|
1121
|
+
}),
|
|
1122
|
+
onKeyDown: composeEventHandlers(onKeyDown, moveOnArrowKey),
|
|
1123
|
+
children: content
|
|
1124
|
+
}
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
);
|
|
1128
|
+
function moveOnArrowKey(event) {
|
|
1129
|
+
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
const { key } = event;
|
|
1133
|
+
let step;
|
|
1134
|
+
if (key === "ArrowDown") {
|
|
1135
|
+
step = 1;
|
|
1136
|
+
} else if (key === "ArrowUp") {
|
|
1137
|
+
step = -1;
|
|
1138
|
+
} else if (key === "ArrowRight") {
|
|
1139
|
+
step = isRtl(event.currentTarget) ? -1 : 1;
|
|
1140
|
+
} else if (key === "ArrowLeft") {
|
|
1141
|
+
step = isRtl(event.currentTarget) ? 1 : -1;
|
|
1142
|
+
} else {
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
const group = event.currentTarget.closest("[data-recipe-system-toggle]");
|
|
1146
|
+
const options = Array.from(
|
|
1147
|
+
group?.querySelectorAll(
|
|
1148
|
+
"[data-recipe-system-option]:not([data-disabled])"
|
|
1149
|
+
) ?? []
|
|
1150
|
+
);
|
|
1151
|
+
const index = options.indexOf(event.currentTarget);
|
|
1152
|
+
if (index === -1) {
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
event.preventDefault();
|
|
1156
|
+
const next = options[(index + step + options.length) % options.length];
|
|
1157
|
+
next?.focus();
|
|
1158
|
+
next?.click();
|
|
1159
|
+
}
|
|
1160
|
+
function isRtl(element) {
|
|
1161
|
+
return element.closest("[dir]")?.getAttribute("dir")?.toLowerCase() === "rtl";
|
|
1162
|
+
}
|
|
1163
|
+
export {
|
|
1164
|
+
Image,
|
|
1165
|
+
Ingredient,
|
|
1166
|
+
IngredientList,
|
|
1167
|
+
IngredientName,
|
|
1168
|
+
IngredientNote,
|
|
1169
|
+
IngredientOptional,
|
|
1170
|
+
IngredientQuantity,
|
|
1171
|
+
Ingredients,
|
|
1172
|
+
JsonLd,
|
|
1173
|
+
RecipeBookProvider,
|
|
1174
|
+
Image as RecipeImage,
|
|
1175
|
+
Ingredient as RecipeIngredient,
|
|
1176
|
+
IngredientList as RecipeIngredientList,
|
|
1177
|
+
IngredientName as RecipeIngredientName,
|
|
1178
|
+
IngredientNote as RecipeIngredientNote,
|
|
1179
|
+
IngredientOptional as RecipeIngredientOptional,
|
|
1180
|
+
IngredientQuantity as RecipeIngredientQuantity,
|
|
1181
|
+
Ingredients as RecipeIngredients,
|
|
1182
|
+
JsonLd as RecipeJsonLd,
|
|
1183
|
+
Root as RecipeRoot,
|
|
1184
|
+
ServingsInput as RecipeServingsInput,
|
|
1185
|
+
ServingsSelect as RecipeServingsSelect,
|
|
1186
|
+
Step as RecipeStep,
|
|
1187
|
+
StepList as RecipeStepList,
|
|
1188
|
+
StepName as RecipeStepName,
|
|
1189
|
+
StepText as RecipeStepText,
|
|
1190
|
+
Steps as RecipeSteps,
|
|
1191
|
+
SystemOption as RecipeSystemOption,
|
|
1192
|
+
SystemToggle as RecipeSystemToggle,
|
|
1193
|
+
Root,
|
|
1194
|
+
ServingsInput,
|
|
1195
|
+
ServingsSelect,
|
|
1196
|
+
Step,
|
|
1197
|
+
StepList,
|
|
1198
|
+
StepName,
|
|
1199
|
+
StepText,
|
|
1200
|
+
Steps,
|
|
1201
|
+
SystemOption,
|
|
1202
|
+
SystemToggle,
|
|
1203
|
+
useFormatIngredient,
|
|
1204
|
+
useRecipe,
|
|
1205
|
+
useRecipeState
|
|
1206
|
+
};
|
|
1207
|
+
//# sourceMappingURL=index.js.map
|