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