@lynstack/recipe 1.0.0 → 1.1.0

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/README.md CHANGED
@@ -53,8 +53,14 @@ text({ size: "lg", muted: true });
53
53
  // => { color: "black", fontSize: 24, opacity: 0.6, fontWeight: 300 }
54
54
  ```
55
55
 
56
+ Pass the same kind to `createSlotRecipeKind` to create slot recipes, which
57
+ map variants to the results of several elements, such as a card's root and
58
+ title.
59
+
56
60
  See [Recipe kinds](https://lynstack.github.io/recipe/recipe/recipe-kinds/)
57
- for what each function of a kind does, and
61
+ for what each function of a kind does,
62
+ [Slot recipes](https://lynstack.github.io/recipe/recipe/slot-recipes/) for
63
+ slot recipes, and
58
64
  [Practices](https://lynstack.github.io/recipe/recipe/practices/) for how to
59
65
  write kinds that stay fast.
60
66
 
package/dist/index.d.ts CHANGED
@@ -51,11 +51,31 @@ type CompoundCondition<Variants> = { readonly [Name in keyof Variants]?: Variant
51
51
  type RecipeFunction<Props, Result> = Partial<Props> extends Props ? (props?: Props) => Result : (props: Props) => Result;
52
52
  type KeyName<Key> = Key extends string ? Key : Key extends number ? `${Key}` : never;
53
53
  /**
54
- * The name of each variant in a selection, as a string.
54
+ * The name of each variant in a selection, as a string, as a recipe lists
55
+ * it in `variantKeys`.
55
56
  *
56
- * @typeParam Selection - The variants a selector accepts.
57
+ * @typeParam Selection - The variants a recipe accepts.
57
58
  */
58
59
  type VariantKey<Selection> = KeyName<keyof Selection>;
60
+ /**
61
+ * The variants a recipe accepts. Use it to type the props of a component
62
+ * built on a recipe.
63
+ *
64
+ * @typeParam Recipe - The type of a recipe, such as one created by the
65
+ * function that `createRecipeKind` returns.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const box = styleRecipe({
70
+ * variants: { size: { sm: { padding: 4 }, md: { padding: 8 } } },
71
+ * defaultVariants: { size: "md" },
72
+ * });
73
+ *
74
+ * type BoxVariants = VariantsOf<typeof box>;
75
+ * // => { readonly size?: "sm" | "md" | undefined }
76
+ * ```
77
+ */
78
+ type VariantsOf<Recipe extends (props: never) => unknown> = Simplify<NonNullable<Parameters<Recipe>[0]>>;
59
79
  //#endregion
60
80
  //#region src/variants.d.ts
61
81
  /**
@@ -249,5 +269,155 @@ type CreateKindRecipe<Value, Result> = <const Variants extends KindVariants<Valu
249
269
  */
250
270
  declare function createRecipeKind<Value, Accumulator, Result = Accumulator>(kind: RecipeKind<Value, Accumulator, Result>): CreateKindRecipe<Value, Result>;
251
271
  //#endregion
252
- export { type CompoundCondition, type CreateKindRecipe, type DefaultVariants, type KindCompoundVariant, type KindRecipe, type KindRecipeConfig, type KindVariants, type RecipeFunction, type RecipeKind, type VariantOption, type VariantSelection, createRecipeKind };
272
+ //#region src/slot-recipe-kind.d.ts
273
+ /**
274
+ * Values for some of a slot recipe's slots, keyed by slot name.
275
+ *
276
+ * @typeParam Slot - The names of the slots.
277
+ * @typeParam Value - The value of a slot.
278
+ */
279
+ type SlotValues<Slot extends string, Value> = Readonly<Partial<Record<Slot, Value | undefined>>>;
280
+ /**
281
+ * The variants of a {@link KindSlotRecipeConfig}: for each variant name,
282
+ * the values of each slot for each of its options.
283
+ *
284
+ * @typeParam Value - The value of a slot.
285
+ */
286
+ type KindSlotVariants<Value> = KindVariants<SlotValues<string, Value>>;
287
+ /**
288
+ * Rejects the slots of each option's values that `Slot` does not name,
289
+ * unless the option's slot names are not known at compile time.
290
+ */
291
+ type NoUnknownSlots<Variants, Slot extends string> = { readonly [Name in keyof Variants]: { readonly [Option in keyof Variants[Name]]: string extends keyof Variants[Name][Option] ? unknown : Readonly<Partial<Record<Exclude<keyof Variants[Name][Option], Slot>, never>>>; }; };
292
+ /**
293
+ * Values added to some slots when several variants have particular options
294
+ * at the same time.
295
+ *
296
+ * @typeParam Variants - The variant definitions of the slot recipe.
297
+ * @typeParam Slot - The names of the slots.
298
+ * @typeParam Value - The value of a slot.
299
+ */
300
+ interface KindSlotCompoundVariant<Variants, Slot extends string, Value> {
301
+ /**
302
+ * The options that must all be selected for
303
+ * {@link KindSlotCompoundVariant.value} to apply.
304
+ */
305
+ readonly variants: KindCompoundCondition<Variants>;
306
+ /** The value added to each slot when the condition matches. */
307
+ readonly value: SlotValues<Slot, Value>;
308
+ }
309
+ /**
310
+ * The configuration of a slot recipe made from a {@link RecipeKind}.
311
+ *
312
+ * @typeParam Slot - The names of the slots.
313
+ * @typeParam Value - The value of a slot.
314
+ * @typeParam Variants - The variant definitions, keyed by variant name.
315
+ * @typeParam DefaultedName - The names of the variants that have a default.
316
+ */
317
+ interface KindSlotRecipeConfig<Slot extends string, Value, Variants extends KindSlotVariants<Value>, DefaultedName extends keyof Variants> {
318
+ /** The names of the slots, in the order of the recipe's result. */
319
+ readonly slots: readonly Slot[];
320
+ /** The value of each slot that the values of every selection are added to. */
321
+ readonly base?: SlotValues<NoInfer<Slot>, Value> | undefined;
322
+ /** For each variant name, the values of each slot for each of its options. */
323
+ readonly variants: Variants & NoUnknownSlots<Variants, NoInfer<Slot>>;
324
+ /**
325
+ * Values added to some slots when several variants have particular
326
+ * options at the same time, applied in order after the values of the
327
+ * variants' options.
328
+ */
329
+ readonly compoundVariants?: readonly KindSlotCompoundVariant<NoInfer<Variants>, NoInfer<Slot>, Value>[] | undefined;
330
+ /** The option each variant uses when the recipe is called without it. */
331
+ readonly defaultVariants?: KindDefaultVariants<Variants, DefaultedName> | undefined;
332
+ }
333
+ /**
334
+ * Creates slot recipes of one kind, the function that
335
+ * {@link createSlotRecipeKind} returns.
336
+ *
337
+ * @typeParam Value - The value of a slot.
338
+ * @typeParam Result - What a recipe of this kind returns for each slot.
339
+ * @param config - The slots, base values, variants, compound variants, and
340
+ * default variants of the slot recipe.
341
+ * @returns The slot recipe.
342
+ */
343
+ type CreateKindSlotRecipe<Value, Result> = <const Slot extends string, const Variants extends KindSlotVariants<Value>, const DefaultedName extends keyof Variants = never>(config: KindSlotRecipeConfig<Slot, Value, Variants, DefaultedName>) => KindRecipe<KindSelection<Variants, DefaultedName>, Readonly<Record<Slot, Result>>>;
344
+ /**
345
+ * Creates a kind of slot recipe from how it turns the values of each slot
346
+ * into that slot's result, and returns the function that creates slot
347
+ * recipes of that kind. A slot recipe maps a selection of variants to the
348
+ * result of each of several slots, such as the class names or the styles of
349
+ * the elements of a component, as `sva` of `@lynstack/class-recipe` does.
350
+ *
351
+ * @remarks
352
+ * A slot recipe reduces the values of each slot as a recipe of the same
353
+ * kind from {@link createRecipeKind} would: `kind.initial` returns the
354
+ * accumulator for the slot's base value, `kind.reduce` adds the slot's
355
+ * value of each variant's selected option, in the order of `variants`, and
356
+ * of each matching compound variant, in the order of `compoundVariants`,
357
+ * and `kind.finish` turns the accumulator into the slot's result. A slot
358
+ * without values gets the result of its accumulator for an `undefined`
359
+ * base, so every slot is in the result.
360
+ *
361
+ * The result is a frozen object of each slot's result, keyed by slot name
362
+ * in the order of `slots`. With the cache, a slot recipe builds it once for
363
+ * each declared selection and returns the same object for the same
364
+ * variants. Variants, default variants, boolean variants, and undeclared
365
+ * options behave as in {@link createRecipeKind}, and the recipe's
366
+ * `variantKeys` property lists the names of its variants.
367
+ *
368
+ * @typeParam Value - The value of a slot, inferred from the `value`
369
+ * parameter of `kind.reduce` or the `base` parameter of `kind.initial`.
370
+ * @typeParam Accumulator - What the values of a slot are reduced to,
371
+ * inferred from `kind.initial`.
372
+ * @typeParam Result - What a slot recipe returns for each slot, inferred
373
+ * from `kind.finish`, or the accumulator without it.
374
+ * @param kind - How a slot recipe turns the values of each slot into its
375
+ * result, and whether it caches its results. The same kind can create
376
+ * recipes with {@link createRecipeKind}.
377
+ * @returns The function that creates slot recipes of this kind.
378
+ *
379
+ * @example
380
+ * ```ts
381
+ * type Style = Readonly<Record<string, string | number>>;
382
+ *
383
+ * const styleKind = {
384
+ * initial: (base?: Style): Record<string, string | number> => ({ ...base }),
385
+ * reduce: (style: Record<string, string | number>, value: Style) =>
386
+ * Object.assign(style, value),
387
+ * finish: (style: Record<string, string | number>): Style =>
388
+ * Object.freeze(style),
389
+ * };
390
+ *
391
+ * const slotStyleRecipe = createSlotRecipeKind(styleKind);
392
+ *
393
+ * const card = slotStyleRecipe({
394
+ * slots: ["root", "title"],
395
+ * base: { root: { padding: 16 }, title: { fontSize: 18 } },
396
+ * variants: {
397
+ * tone: {
398
+ * light: { root: { backgroundColor: "white" } },
399
+ * dark: { root: { backgroundColor: "black" }, title: { color: "white" } },
400
+ * },
401
+ * },
402
+ * compoundVariants: [
403
+ * { variants: { tone: "dark" }, value: { title: { fontWeight: 600 } } },
404
+ * ],
405
+ * defaultVariants: { tone: "light" },
406
+ * });
407
+ *
408
+ * card();
409
+ * // => { root: { padding: 16, backgroundColor: "white" }, title: { fontSize: 18 } }
410
+ *
411
+ * card({ tone: "dark" });
412
+ * // => {
413
+ * // root: { padding: 16, backgroundColor: "black" },
414
+ * // title: { fontSize: 18, color: "white", fontWeight: 600 },
415
+ * // }
416
+ *
417
+ * card.variantKeys; // => ["tone"]
418
+ * ```
419
+ */
420
+ declare function createSlotRecipeKind<Value, Accumulator, Result = Accumulator>(kind: RecipeKind<Value, Accumulator, Result>): CreateKindSlotRecipe<Value, Result>;
421
+ //#endregion
422
+ export { type CompoundCondition, type CreateKindRecipe, type CreateKindSlotRecipe, type DefaultVariants, type KindCompoundVariant, type KindRecipe, type KindRecipeConfig, type KindSlotCompoundVariant, type KindSlotRecipeConfig, type KindSlotVariants, type KindVariants, type RecipeFunction, type RecipeKind, type SlotValues, type VariantKey, type VariantOption, type VariantSelection, type VariantsOf, createRecipeKind, createSlotRecipeKind };
253
423
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -148,6 +148,91 @@ function createRecipeKind(kind) {
148
148
  };
149
149
  }
150
150
  //#endregion
151
- export { createRecipeKind };
151
+ //#region src/slots.ts
152
+ function valueOfSlot(values, slot) {
153
+ return values !== void 0 && Object.hasOwn(values, slot) ? values[slot] : void 0;
154
+ }
155
+ /** Returns the entries of `values`, or undefined when no slot has one. */
156
+ function entriesOf(slots, values) {
157
+ const slotIndexes = [];
158
+ const slotValues = [];
159
+ for (const [index, slot] of slots.entries()) {
160
+ const value = valueOfSlot(values, slot);
161
+ if (value !== void 0) {
162
+ slotIndexes.push(index);
163
+ slotValues.push(value);
164
+ }
165
+ }
166
+ return slotIndexes.length === 0 ? void 0 : {
167
+ slots: slotIndexes,
168
+ values: slotValues
169
+ };
170
+ }
171
+ /** Turns the slot values of each option and compound variant into entries. */
172
+ function compileEntries(compiled, slots) {
173
+ return {
174
+ ...compiled,
175
+ compounds: compiled.compounds.map((compound) => ({
176
+ conditions: compound.conditions,
177
+ value: entriesOf(slots, compound.value)
178
+ })).filter((compound) => compound.value !== void 0),
179
+ valuesByIndex: compiled.valuesByIndex.map((valuesOfVariant) => valuesOfVariant.map((values) => entriesOf(slots, values)))
180
+ };
181
+ }
182
+ /** Reduces the values of `added` into the accumulators of their slots. */
183
+ function addEntries(accumulators, added, reduce) {
184
+ const { slots, values } = added;
185
+ for (let entry = 0; entry < slots.length; entry += 1) {
186
+ const slot = slots[entry] ?? 0;
187
+ accumulators[slot] = reduce(accumulators[slot], values[entry]);
188
+ }
189
+ }
190
+ /** Returns the frozen result of each slot, keyed by slot name. */
191
+ function resultsBySlot(names, accumulators, finish) {
192
+ const results = {};
193
+ for (let slot = 0; slot < names.length; slot += 1) results[names[slot] ?? ""] = finish(accumulators[slot]);
194
+ return Object.freeze(results);
195
+ }
196
+ const asResult = (accumulator) => accumulator;
197
+ /**
198
+ * Returns the function that builds the result of a selection for each slot
199
+ * of a slot recipe: each slot reduces its own values with `kind`, and the
200
+ * results are frozen in an object keyed by slot name.
201
+ *
202
+ * It reduces the values in a loop of its own instead of through
203
+ * `reduceValues`, which recipes share, so that its calls to `kind.reduce`
204
+ * see only the reducers of slot recipes and can be inlined.
205
+ */
206
+ function createSlotsBuilder(kind, compiled, config) {
207
+ const { initial, reduce, finish = asResult } = kind;
208
+ const names = [...config.slots];
209
+ const bases = names.map((slot) => valueOfSlot(config.base, slot));
210
+ const { valuesByIndex, compounds } = compileEntries(compiled, names);
211
+ return (indexes) => {
212
+ const accumulators = bases.map((base) => initial(base));
213
+ for (let variant = 0; variant < valuesByIndex.length; variant += 1) {
214
+ const added = valuesByIndex[variant]?.[indexes[variant] ?? 0];
215
+ if (added !== void 0) addEntries(accumulators, added, reduce);
216
+ }
217
+ for (const compound of compounds) if (compound.value !== void 0 && matches(compound, indexes)) addEntries(accumulators, compound.value, reduce);
218
+ return resultsBySlot(names, accumulators, finish);
219
+ };
220
+ }
221
+ //#endregion
222
+ //#region src/slot-recipe-kind.ts
223
+ function createSlotRecipeKind(kind) {
224
+ const options = { cache: kind.cache ?? true };
225
+ return (config) => {
226
+ const compiled = compileVariants({
227
+ compoundVariants: config.compoundVariants ?? [],
228
+ defaultVariants: config.defaultVariants ?? {},
229
+ noValue: void 0,
230
+ variants: config.variants
231
+ });
232
+ return withVariantKeys(createSelector(compiled, createSlotsBuilder(kind, compiled, config), options), compiled);
233
+ };
234
+ }
235
+ //#endregion
236
+ export { createRecipeKind, createSlotRecipeKind };
152
237
 
153
238
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/variants.ts","../src/selector.ts","../src/reduce-values.ts","../src/recipe-kind.ts"],"sourcesContent":["/**\n * Variant definitions in the loose shape the runtime works with: for each\n * variant name, the value of each of its options.\n */\ntype LooseVariants<Value> = Readonly<\n Record<string, Readonly<Record<string, Value>>>\n>;\n\n/** A selection in the loose shape the runtime works with. */\ntype SelectedVariants = Readonly<Record<string, unknown>>;\n\n/** A compound variant in the loose shape the runtime works with. */\ninterface LooseCompoundVariant<Value> {\n readonly variants: SelectedVariants;\n readonly value: Value;\n}\n\n/** The variant's index and the option indexes that a condition matches. */\ntype CompoundCondition = readonly [number, readonly number[]];\n\n/** A compound variant prepared for matching. */\ninterface Compound<Value> {\n readonly conditions: readonly CompoundCondition[];\n readonly value: Value;\n}\n\n/** What {@link compileVariants} prepares. */\ninterface VariantsConfig<Value> {\n readonly variants: LooseVariants<Value>;\n readonly defaultVariants: SelectedVariants;\n readonly compoundVariants: readonly LooseCompoundVariant<Value>[];\n /**\n * The value of a variant without a selected option, and of a boolean\n * option that the variant does not declare.\n */\n readonly noValue: Value;\n}\n\n/**\n * Variants prepared for selecting values. Each variant numbers its options\n * from 1, and 0 stands for no option, so a selection is a list of option\n * indexes. Read together as the digits of a mixed-radix number, they form\n * the selection's key.\n */\ninterface CompiledVariants<Value> {\n readonly names: readonly string[];\n readonly defaultOptions: readonly (string | undefined)[];\n readonly indexByOption: readonly ReadonlyMap<string, number>[];\n /** The value of each variant's options, by option index. */\n readonly valuesByIndex: readonly (readonly Value[])[];\n readonly strides: readonly number[];\n readonly compounds: readonly Compound<Value>[];\n /** Whether every selection's key is a safe integer. */\n readonly isCacheable: boolean;\n}\n\n/** The option index of a variant without a selected option. */\nconst noOption = 0;\n\n/** The key of a selection with an option that a variant does not declare. */\nconst undeclared = -1;\n\nconst noProps: SelectedVariants = Object.freeze({});\n\n/**\n * Prepares variants for selecting values. A variant that declares an option\n * named `\"true\"` or `\"false\"` also declares the other one, with\n * `config.noValue`, and a variant whose only options are those defaults to\n * `\"false\"`. A compound variant that names an undeclared variant or option,\n * or lists no option for a variant, never matches and is left out.\n */\nfunction compileVariants<Value>(\n config: VariantsConfig<Value>,\n): CompiledVariants<Value> {\n const names = Object.keys(config.variants);\n const optionValues = names.map((name) =>\n withBooleanOptions(config.variants[name] ?? {}, config.noValue),\n );\n const indexByOption: readonly ReadonlyMap<string, number>[] =\n optionValues.map(\n (valuesByOption) =>\n new Map(\n Object.keys(valuesByOption).map((option, index) => [\n option,\n index + 1,\n ]),\n ),\n );\n const radixes = indexByOption.map((indexes) => indexes.size + 1);\n\n return {\n compounds: compileCompounds(config.compoundVariants, names, indexByOption),\n defaultOptions: names.map(\n (name, index) =>\n toOptionName(config.defaultVariants[name]) ??\n (isBooleanVariant(indexByOption[index]) ? \"false\" : undefined),\n ),\n indexByOption,\n isCacheable: product(radixes) <= Number.MAX_SAFE_INTEGER,\n names,\n strides: radixes.map((_radix, index) => product(radixes.slice(0, index))),\n valuesByIndex: valuesByIndexOf(optionValues, config.noValue),\n };\n}\n\n/**\n * Writes the option index of each variant for `selected` into `indexes`\n * and returns the selection's key, or {@link undeclared} when an option is\n * not declared.\n */\nfunction select(\n compiled: CompiledVariants<unknown>,\n selected: SelectedVariants,\n indexes: Int32Array,\n): number {\n let key = 0;\n for (let variant = 0; variant < compiled.names.length; variant += 1) {\n const index = selectIndex(compiled, selected, variant);\n indexes[variant] = index ?? noOption;\n key =\n index === undefined || key === undeclared\n ? undeclared\n : key + index * (compiled.strides[variant] ?? 0);\n }\n return key;\n}\n\nfunction selectIndex(\n compiled: CompiledVariants<unknown>,\n selected: SelectedVariants,\n variant: number,\n): number | undefined {\n const option =\n toOptionName(selected[compiled.names[variant] ?? \"\"]) ??\n compiled.defaultOptions[variant];\n return option === undefined\n ? noOption\n : compiled.indexByOption[variant]?.get(option);\n}\n\nfunction valuesByIndexOf<Value>(\n optionValues: readonly Readonly<Record<string, Value>>[],\n noValue: Value,\n): Value[][] {\n const valuesByIndex: Value[][] = [];\n for (const valuesByOption of optionValues) {\n valuesByIndex.push([noValue, ...Object.values(valuesByOption)]);\n }\n return valuesByIndex;\n}\n\nfunction product(numbers: readonly number[]): number {\n return numbers.reduce((result, number) => result * number, 1);\n}\n\nfunction withBooleanOptions<Value>(\n valuesByOption: Readonly<Record<string, Value>>,\n noValue: Value,\n): Readonly<Record<string, Value>> {\n const optionNames = Object.keys(valuesByOption);\n if (!optionNames.some((option) => isBooleanName(option))) {\n return valuesByOption;\n }\n return { false: noValue, true: noValue, ...valuesByOption };\n}\n\nfunction compileCompounds<Value>(\n compoundVariants: readonly LooseCompoundVariant<Value>[],\n names: readonly string[],\n indexByOption: readonly ReadonlyMap<string, number>[],\n): Compound<Value>[] {\n return compoundVariants.flatMap(({ variants, value }) => {\n const conditions = Object.keys(variants)\n .filter((name) => variants[name] !== undefined)\n .map((name) =>\n compileCondition(indexByOption, names.indexOf(name), variants[name]),\n );\n return conditions.every(([, indexes]) => indexes.length > 0)\n ? [{ conditions, value }]\n : [];\n });\n}\n\nfunction compileCondition(\n indexByOption: readonly ReadonlyMap<string, number>[],\n variant: number,\n value: unknown,\n): CompoundCondition {\n const indexes = indexByOption[variant];\n return [\n variant,\n toOptionNames(value)\n .map((option) => indexes?.get(option))\n .filter((index) => index !== undefined),\n ];\n}\n\nfunction isBooleanVariant(\n indexByOption: ReadonlyMap<string, number> | undefined,\n): boolean {\n return (\n indexByOption !== undefined &&\n indexByOption.size > 0 &&\n [...indexByOption.keys()].every((option) => isBooleanName(option))\n );\n}\n\nfunction toOptionName(value: unknown): string | undefined {\n if (typeof value === \"string\") {\n return value;\n }\n return typeof value === \"number\" || typeof value === \"boolean\"\n ? String(value)\n : undefined;\n}\n\nfunction toOptionNames(value: unknown): readonly string[] {\n const values: readonly unknown[] = Array.isArray(value) ? value : [value];\n return values\n .map((option) => toOptionName(option))\n .filter((option) => option !== undefined);\n}\n\n/** A recipe function with the names of its variants. */\ntype WithVariantKeys<Recipe> = Recipe & {\n readonly variantKeys: readonly string[];\n};\n\n/** Adds the names of the compiled variants to `recipe` as `variantKeys`. */\nfunction withVariantKeys<Recipe extends object>(\n recipe: Recipe,\n compiled: CompiledVariants<unknown>,\n): WithVariantKeys<Recipe> {\n return Object.assign(recipe, {\n variantKeys: Object.freeze([...compiled.names]),\n });\n}\n\nfunction isBooleanName(option: string): boolean {\n return option === \"true\" || option === \"false\";\n}\n\nexport {\n compileVariants,\n noOption,\n noProps,\n select,\n undeclared,\n withVariantKeys,\n};\nexport type {\n CompiledVariants,\n Compound,\n LooseCompoundVariant,\n LooseVariants,\n SelectedVariants,\n WithVariantKeys,\n};\n","import type { CompiledVariants, SelectedVariants } from \"./variants.js\";\nimport { noProps, select, undeclared } from \"./variants.js\";\n\n/** How a selector computes its results. */\ninterface SelectorOptions {\n /** Whether to cache the result of each declared selection. */\n readonly cache: boolean;\n}\n\n/**\n * Returns a function that builds the result of a selection from the option\n * index of each variant, and treats a missing selection as an empty one.\n * With `options.cache`, it builds the result of each declared selection\n * once and caches it by the selection's key.\n */\nfunction createSelector<Result>(\n compiled: CompiledVariants<unknown>,\n build: (indexes: Int32Array) => Result,\n options: SelectorOptions,\n): (selected?: SelectedVariants | null) => Result {\n const indexes = new Int32Array(compiled.names.length);\n const results =\n options.cache && compiled.isCacheable\n ? new Map<number, Result>()\n : undefined;\n\n return (selected) => {\n const key = select(compiled, selected ?? noProps, indexes);\n const cachedResult = key === undeclared ? undefined : results?.get(key);\n if (cachedResult !== undefined) {\n return cachedResult;\n }\n const result = build(indexes);\n if (key !== undeclared) {\n results?.set(key, result);\n }\n return result;\n };\n}\n\nexport { createSelector };\nexport type { SelectorOptions };\n","import type { CompiledVariants, Compound } from \"./variants.js\";\nimport { noOption } from \"./variants.js\";\n\n/** Whether every condition of `compound` matches the selected indexes. */\nfunction matches(compound: Compound<unknown>, indexes: Int32Array): boolean {\n for (const [variant, matchingIndexes] of compound.conditions) {\n if (!matchingIndexes.includes(indexes[variant] ?? noOption)) {\n return false;\n }\n }\n return true;\n}\n\n/** Reduces the values of a selection to an accumulator. */\ninterface Reducer<Value, Accumulator> {\n /** Returns the accumulator to start from. */\n readonly initial: () => Accumulator;\n /** Adds a value to the accumulator and returns the accumulator. */\n readonly reduce: (accumulator: Accumulator, value: Value) => Accumulator;\n}\n\n/**\n * Reduces the values that apply to a selection, in order of precedence: the\n * value of each variant's option, then the value of each matching compound\n * variant. A value that is undefined adds nothing.\n */\nfunction reduceValues<Value, Accumulator>(\n compiled: CompiledVariants<Value | undefined>,\n indexes: Int32Array,\n reducer: Reducer<Value, Accumulator>,\n): Accumulator {\n const { reduce } = reducer;\n let accumulator = reducer.initial();\n for (let variant = 0; variant < compiled.valuesByIndex.length; variant += 1) {\n const value =\n compiled.valuesByIndex[variant]?.[indexes[variant] ?? noOption];\n if (value !== undefined) {\n accumulator = reduce(accumulator, value);\n }\n }\n for (const compound of compiled.compounds) {\n if (compound.value !== undefined && matches(compound, indexes)) {\n accumulator = reduce(accumulator, compound.value);\n }\n }\n return accumulator;\n}\n\nexport { reduceValues };\nexport type { Reducer };\n","import type {\n CompoundCondition,\n DefaultVariants,\n RecipeFunction,\n VariantKey,\n VariantSelection,\n} from \"./types.js\";\nimport type {\n LooseVariants,\n SelectedVariants,\n WithVariantKeys,\n} from \"./variants.js\";\nimport { compileVariants, withVariantKeys } from \"./variants.js\";\nimport { createSelector } from \"./selector.js\";\nimport { reduceValues } from \"./reduce-values.js\";\n\n/**\n * How a kind of recipe turns the values of a selection into its result,\n * such as by joining class names or merging style objects.\n *\n * @typeParam Value - The value of an option.\n * @typeParam Accumulator - What the values of a selection are reduced to.\n * @typeParam Result - What a recipe of this kind returns.\n */\ninterface RecipeKind<Value, Accumulator, Result> {\n /**\n * Returns the accumulator that the values of a selection are reduced\n * into, starting from the recipe's `base`, which is `undefined` when the\n * recipe has none. It is called for each result a recipe builds, so it\n * can return a new object each time.\n */\n readonly initial: (base: Value | undefined) => Accumulator;\n /**\n * Adds a value to the accumulator and returns the accumulator. It is\n * called with the values that apply to a selection, in order of\n * precedence: the value of each variant's selected option, in the order\n * of `variants`, then the value of each matching compound variant, in the\n * order of `compoundVariants`. An option or compound variant whose value\n * is `undefined` adds nothing.\n */\n readonly reduce: (accumulator: Accumulator, value: Value) => Accumulator;\n /**\n * Turns the accumulator into the result, for example by freezing it.\n * Without it, the result is the accumulator.\n */\n readonly finish?: ((accumulator: Accumulator) => Result) | undefined;\n /**\n * Whether a recipe builds the result of each declared selection once and\n * returns it again for the same selection. Defaults to `true`.\n */\n readonly cache?: boolean | undefined;\n}\n\n/**\n * Any selection, for variants whose names are not known at compile time,\n * such as `Record<string, Record<string, string>>`.\n */\ntype AnySelection = Readonly<Record<string, unknown>>;\n\n/** The selection of a recipe, or any selection for unknown variant names. */\ntype KindSelection<\n Variants,\n DefaultedName extends keyof Variants,\n> = string extends keyof Variants\n ? AnySelection\n : VariantSelection<Variants, DefaultedName>;\n\n/** The condition of a compound variant, or any for unknown variant names. */\ntype KindCompoundCondition<Variants> = string extends keyof Variants\n ? AnySelection\n : CompoundCondition<Variants>;\n\n/** The default variants of a recipe, or any for unknown variant names. */\ntype KindDefaultVariants<\n Variants,\n DefaultedName extends keyof Variants,\n> = string extends keyof Variants\n ? AnySelection\n : DefaultVariants<Variants, DefaultedName>;\n\n/**\n * The variants of a {@link KindRecipeConfig}: for each variant name, the\n * value of each of its options.\n *\n * @typeParam Value - The value of an option.\n */\ntype KindVariants<Value> = LooseVariants<Value>;\n\n/**\n * A value added when several variants have particular options at the same\n * time.\n *\n * @typeParam Variants - The variant definitions of the recipe.\n * @typeParam Value - The value of an option.\n */\ninterface KindCompoundVariant<Variants, Value> {\n /**\n * The options that must all be selected for\n * {@link KindCompoundVariant.value} to apply.\n */\n readonly variants: KindCompoundCondition<Variants>;\n /** The value added when the condition matches. */\n readonly value: Value;\n}\n\n/**\n * The configuration of a recipe made from a {@link RecipeKind}.\n *\n * @typeParam Value - The value of an option.\n * @typeParam Variants - The variant definitions, keyed by variant name.\n * @typeParam DefaultedName - The names of the variants that have a default.\n */\ninterface KindRecipeConfig<\n Value,\n Variants extends KindVariants<Value>,\n DefaultedName extends keyof Variants,\n> {\n /** The value that the values of every selection are added to. */\n readonly base?: Value | undefined;\n /** For each variant name, the value of each of its options. */\n readonly variants: Variants;\n /**\n * Values added when several variants have particular options at the same\n * time, applied in order after the values of the variants' options.\n */\n readonly compoundVariants?:\n readonly KindCompoundVariant<NoInfer<Variants>, Value>[] | undefined;\n /** The option each variant uses when the recipe is called without it. */\n readonly defaultVariants?:\n KindDefaultVariants<Variants, DefaultedName> | undefined;\n}\n\n/**\n * A recipe made from a {@link RecipeKind}: a function that returns the\n * result of a selection of variants, with the names of those variants in\n * `variantKeys`.\n *\n * @typeParam Selection - The variants the recipe accepts.\n * @typeParam Result - What the recipe returns.\n */\ntype KindRecipe<Selection, Result> = RecipeFunction<Selection, Result> & {\n /**\n * The names of the recipe's variants, in the order of\n * `Object.keys(config.variants)`.\n */\n readonly variantKeys: readonly VariantKey<Selection>[];\n};\n\n/**\n * Creates recipes of one kind, the function that {@link createRecipeKind}\n * returns.\n *\n * @typeParam Value - The value of an option.\n * @typeParam Result - What a recipe of this kind returns.\n * @param config - The base value, variants, compound variants, and default\n * variants of the recipe.\n * @returns The recipe.\n */\ntype CreateKindRecipe<Value, Result> = <\n const Variants extends KindVariants<Value>,\n const DefaultedName extends keyof Variants = never,\n>(\n config: KindRecipeConfig<Value, Variants, DefaultedName>,\n) => KindRecipe<KindSelection<Variants, DefaultedName>, Result>;\n\ninterface LooseRecipeKind {\n readonly initial: (base: unknown) => unknown;\n readonly reduce: (accumulator: unknown, value: unknown) => unknown;\n readonly finish?: ((accumulator: unknown) => unknown) | undefined;\n readonly cache?: boolean | undefined;\n}\n\ninterface LooseKindRecipeConfig {\n readonly base?: unknown;\n readonly variants: KindVariants<unknown>;\n readonly compoundVariants?:\n | readonly {\n readonly variants: SelectedVariants;\n readonly value: unknown;\n }[]\n | undefined;\n readonly defaultVariants?: SelectedVariants | undefined;\n}\n\ntype LooseKindRecipe = WithVariantKeys<\n (selection?: SelectedVariants | null) => unknown\n>;\n\n/**\n * Creates a kind of recipe from how it turns the values of a selection into\n * its result, and returns the function that creates recipes of that kind.\n * A recipe maps a selection of variants to a result, as `cva` of\n * `@lynstack/class-recipe` does for class names, for values of any type,\n * such as style objects.\n *\n * @remarks\n * For each selection, a recipe reduces the value of each variant's selected\n * option and the value of each matching compound variant, with\n * `kind.reduce`, into the accumulator that `kind.initial` returns for the\n * recipe's base, then passes it to `kind.finish`. A variant whose only\n * options are `\"true\"` and `\"false\"` defaults to `false`, an option that the\n * config does not declare adds no value, and properties of the selection\n * that are not variants are ignored. A recipe's `variantKeys` property lists\n * the names of its variants.\n *\n * With the cache, a recipe builds the result of each declared selection\n * once, and calling it again with the same variants returns the same\n * result. Freeze an object result in `kind.finish` so that callers cannot\n * change a result that later calls share. A selection with an undeclared\n * option is built on every call.\n *\n * When the variant names are not known at compile time, as in a library\n * that passes on a config it received, a recipe accepts any selection.\n *\n * @typeParam Value - The value of an option, inferred from the `value`\n * parameter of `kind.reduce` or the `base` parameter of `kind.initial`.\n * @typeParam Accumulator - What the values are reduced to, inferred from\n * `kind.initial`.\n * @typeParam Result - What a recipe returns, inferred from `kind.finish`,\n * or the accumulator without it.\n * @param kind - How a recipe turns the values of a selection into its\n * result, and whether it caches the result.\n * @returns The function that creates recipes of this kind.\n *\n * @example\n * ```ts\n * type Style = Readonly<Record<string, string | number>>;\n *\n * const styleRecipe = createRecipeKind({\n * initial: (base?: Style): Record<string, string | number> => ({ ...base }),\n * reduce: (style, value: Style) => Object.assign(style, value),\n * finish: (style): Style => Object.freeze(style),\n * });\n *\n * const text = styleRecipe({\n * base: { color: \"black\" },\n * variants: {\n * size: { sm: { fontSize: 12 }, lg: { fontSize: 24 } },\n * muted: { true: { opacity: 0.6 } },\n * },\n * compoundVariants: [\n * { variants: { size: \"lg\", muted: true }, value: { fontWeight: 300 } },\n * ],\n * defaultVariants: { size: \"sm\" },\n * });\n *\n * text(); // => { color: \"black\", fontSize: 12 }\n *\n * text({ size: \"lg\", muted: true });\n * // => { color: \"black\", fontSize: 24, opacity: 0.6, fontWeight: 300 }\n *\n * text.variantKeys; // => [\"size\", \"muted\"]\n * ```\n */\nfunction createRecipeKind<Value, Accumulator, Result = Accumulator>(\n kind: RecipeKind<Value, Accumulator, Result>,\n): CreateKindRecipe<Value, Result>;\n\nfunction createRecipeKind(kind: LooseRecipeKind): unknown {\n const { initial, reduce, finish } = kind;\n const options = { cache: kind.cache ?? true };\n\n return (config: LooseKindRecipeConfig): LooseKindRecipe => {\n const { base } = config;\n const compiled = compileVariants<unknown>({\n compoundVariants: config.compoundVariants ?? [],\n defaultVariants: config.defaultVariants ?? {},\n noValue: undefined,\n variants: config.variants,\n });\n const reducer = { initial: (): unknown => initial(base), reduce };\n const build =\n finish === undefined\n ? (indexes: Int32Array): unknown =>\n reduceValues(compiled, indexes, reducer)\n : (indexes: Int32Array): unknown =>\n finish(reduceValues(compiled, indexes, reducer));\n return withVariantKeys(createSelector(compiled, build, options), compiled);\n };\n}\n\nexport { createRecipeKind };\nexport type {\n CreateKindRecipe,\n KindCompoundVariant,\n KindRecipe,\n KindRecipeConfig,\n KindVariants,\n RecipeKind,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/variants.ts","../src/selector.ts","../src/reduce-values.ts","../src/recipe-kind.ts","../src/slots.ts","../src/slot-recipe-kind.ts"],"sourcesContent":["/**\n * Variant definitions in the loose shape the runtime works with: for each\n * variant name, the value of each of its options.\n */\ntype LooseVariants<Value> = Readonly<\n Record<string, Readonly<Record<string, Value>>>\n>;\n\n/** A selection in the loose shape the runtime works with. */\ntype SelectedVariants = Readonly<Record<string, unknown>>;\n\n/** A compound variant in the loose shape the runtime works with. */\ninterface LooseCompoundVariant<Value> {\n readonly variants: SelectedVariants;\n readonly value: Value;\n}\n\n/** The variant's index and the option indexes that a condition matches. */\ntype CompoundCondition = readonly [number, readonly number[]];\n\n/** A compound variant prepared for matching. */\ninterface Compound<Value> {\n readonly conditions: readonly CompoundCondition[];\n readonly value: Value;\n}\n\n/** What {@link compileVariants} prepares. */\ninterface VariantsConfig<Value> {\n readonly variants: LooseVariants<Value>;\n readonly defaultVariants: SelectedVariants;\n readonly compoundVariants: readonly LooseCompoundVariant<Value>[];\n /**\n * The value of a variant without a selected option, and of a boolean\n * option that the variant does not declare.\n */\n readonly noValue: Value;\n}\n\n/**\n * Variants prepared for selecting values. Each variant numbers its options\n * from 1, and 0 stands for no option, so a selection is a list of option\n * indexes. Read together as the digits of a mixed-radix number, they form\n * the selection's key.\n */\ninterface CompiledVariants<Value> {\n readonly names: readonly string[];\n readonly defaultOptions: readonly (string | undefined)[];\n readonly indexByOption: readonly ReadonlyMap<string, number>[];\n /** The value of each variant's options, by option index. */\n readonly valuesByIndex: readonly (readonly Value[])[];\n readonly strides: readonly number[];\n readonly compounds: readonly Compound<Value>[];\n /** Whether every selection's key is a safe integer. */\n readonly isCacheable: boolean;\n}\n\n/** The option index of a variant without a selected option. */\nconst noOption = 0;\n\n/** The key of a selection with an option that a variant does not declare. */\nconst undeclared = -1;\n\nconst noProps: SelectedVariants = Object.freeze({});\n\n/**\n * Prepares variants for selecting values. A variant that declares an option\n * named `\"true\"` or `\"false\"` also declares the other one, with\n * `config.noValue`, and a variant whose only options are those defaults to\n * `\"false\"`. A compound variant that names an undeclared variant or option,\n * or lists no option for a variant, never matches and is left out.\n */\nfunction compileVariants<Value>(\n config: VariantsConfig<Value>,\n): CompiledVariants<Value> {\n const names = Object.keys(config.variants);\n const optionValues = names.map((name) =>\n withBooleanOptions(config.variants[name] ?? {}, config.noValue),\n );\n const indexByOption: readonly ReadonlyMap<string, number>[] =\n optionValues.map(\n (valuesByOption) =>\n new Map(\n Object.keys(valuesByOption).map((option, index) => [\n option,\n index + 1,\n ]),\n ),\n );\n const radixes = indexByOption.map((indexes) => indexes.size + 1);\n\n return {\n compounds: compileCompounds(config.compoundVariants, names, indexByOption),\n defaultOptions: names.map(\n (name, index) =>\n toOptionName(config.defaultVariants[name]) ??\n (isBooleanVariant(indexByOption[index]) ? \"false\" : undefined),\n ),\n indexByOption,\n isCacheable: product(radixes) <= Number.MAX_SAFE_INTEGER,\n names,\n strides: radixes.map((_radix, index) => product(radixes.slice(0, index))),\n valuesByIndex: valuesByIndexOf(optionValues, config.noValue),\n };\n}\n\n/**\n * Writes the option index of each variant for `selected` into `indexes`\n * and returns the selection's key, or {@link undeclared} when an option is\n * not declared.\n */\nfunction select(\n compiled: CompiledVariants<unknown>,\n selected: SelectedVariants,\n indexes: Int32Array,\n): number {\n let key = 0;\n for (let variant = 0; variant < compiled.names.length; variant += 1) {\n const index = selectIndex(compiled, selected, variant);\n indexes[variant] = index ?? noOption;\n key =\n index === undefined || key === undeclared\n ? undeclared\n : key + index * (compiled.strides[variant] ?? 0);\n }\n return key;\n}\n\nfunction selectIndex(\n compiled: CompiledVariants<unknown>,\n selected: SelectedVariants,\n variant: number,\n): number | undefined {\n const option =\n toOptionName(selected[compiled.names[variant] ?? \"\"]) ??\n compiled.defaultOptions[variant];\n return option === undefined\n ? noOption\n : compiled.indexByOption[variant]?.get(option);\n}\n\nfunction valuesByIndexOf<Value>(\n optionValues: readonly Readonly<Record<string, Value>>[],\n noValue: Value,\n): Value[][] {\n const valuesByIndex: Value[][] = [];\n for (const valuesByOption of optionValues) {\n valuesByIndex.push([noValue, ...Object.values(valuesByOption)]);\n }\n return valuesByIndex;\n}\n\nfunction product(numbers: readonly number[]): number {\n return numbers.reduce((result, number) => result * number, 1);\n}\n\nfunction withBooleanOptions<Value>(\n valuesByOption: Readonly<Record<string, Value>>,\n noValue: Value,\n): Readonly<Record<string, Value>> {\n const optionNames = Object.keys(valuesByOption);\n if (!optionNames.some((option) => isBooleanName(option))) {\n return valuesByOption;\n }\n return { false: noValue, true: noValue, ...valuesByOption };\n}\n\nfunction compileCompounds<Value>(\n compoundVariants: readonly LooseCompoundVariant<Value>[],\n names: readonly string[],\n indexByOption: readonly ReadonlyMap<string, number>[],\n): Compound<Value>[] {\n return compoundVariants.flatMap(({ variants, value }) => {\n const conditions = Object.keys(variants)\n .filter((name) => variants[name] !== undefined)\n .map((name) =>\n compileCondition(indexByOption, names.indexOf(name), variants[name]),\n );\n return conditions.every(([, indexes]) => indexes.length > 0)\n ? [{ conditions, value }]\n : [];\n });\n}\n\nfunction compileCondition(\n indexByOption: readonly ReadonlyMap<string, number>[],\n variant: number,\n value: unknown,\n): CompoundCondition {\n const indexes = indexByOption[variant];\n return [\n variant,\n toOptionNames(value)\n .map((option) => indexes?.get(option))\n .filter((index) => index !== undefined),\n ];\n}\n\nfunction isBooleanVariant(\n indexByOption: ReadonlyMap<string, number> | undefined,\n): boolean {\n return (\n indexByOption !== undefined &&\n indexByOption.size > 0 &&\n [...indexByOption.keys()].every((option) => isBooleanName(option))\n );\n}\n\nfunction toOptionName(value: unknown): string | undefined {\n if (typeof value === \"string\") {\n return value;\n }\n return typeof value === \"number\" || typeof value === \"boolean\"\n ? String(value)\n : undefined;\n}\n\nfunction toOptionNames(value: unknown): readonly string[] {\n const values: readonly unknown[] = Array.isArray(value) ? value : [value];\n return values\n .map((option) => toOptionName(option))\n .filter((option) => option !== undefined);\n}\n\n/** A recipe function with the names of its variants. */\ntype WithVariantKeys<Recipe> = Recipe & {\n readonly variantKeys: readonly string[];\n};\n\n/** Adds the names of the compiled variants to `recipe` as `variantKeys`. */\nfunction withVariantKeys<Recipe extends object>(\n recipe: Recipe,\n compiled: CompiledVariants<unknown>,\n): WithVariantKeys<Recipe> {\n return Object.assign(recipe, {\n variantKeys: Object.freeze([...compiled.names]),\n });\n}\n\nfunction isBooleanName(option: string): boolean {\n return option === \"true\" || option === \"false\";\n}\n\nexport {\n compileVariants,\n noOption,\n noProps,\n select,\n undeclared,\n withVariantKeys,\n};\nexport type {\n CompiledVariants,\n Compound,\n LooseCompoundVariant,\n LooseVariants,\n SelectedVariants,\n WithVariantKeys,\n};\n","import type { CompiledVariants, SelectedVariants } from \"./variants.js\";\nimport { noProps, select, undeclared } from \"./variants.js\";\n\n/** How a selector computes its results. */\ninterface SelectorOptions {\n /** Whether to cache the result of each declared selection. */\n readonly cache: boolean;\n}\n\n/**\n * Returns a function that builds the result of a selection from the option\n * index of each variant, and treats a missing selection as an empty one.\n * With `options.cache`, it builds the result of each declared selection\n * once and caches it by the selection's key.\n */\nfunction createSelector<Result>(\n compiled: CompiledVariants<unknown>,\n build: (indexes: Int32Array) => Result,\n options: SelectorOptions,\n): (selected?: SelectedVariants | null) => Result {\n const indexes = new Int32Array(compiled.names.length);\n const results =\n options.cache && compiled.isCacheable\n ? new Map<number, Result>()\n : undefined;\n\n return (selected) => {\n const key = select(compiled, selected ?? noProps, indexes);\n const cachedResult = key === undeclared ? undefined : results?.get(key);\n if (cachedResult !== undefined) {\n return cachedResult;\n }\n const result = build(indexes);\n if (key !== undeclared) {\n results?.set(key, result);\n }\n return result;\n };\n}\n\nexport { createSelector };\nexport type { SelectorOptions };\n","import type { CompiledVariants, Compound } from \"./variants.js\";\nimport { noOption } from \"./variants.js\";\n\n/** Whether every condition of `compound` matches the selected indexes. */\nfunction matches(compound: Compound<unknown>, indexes: Int32Array): boolean {\n for (const [variant, matchingIndexes] of compound.conditions) {\n if (!matchingIndexes.includes(indexes[variant] ?? noOption)) {\n return false;\n }\n }\n return true;\n}\n\n/** Reduces the values of a selection to an accumulator. */\ninterface Reducer<Value, Accumulator> {\n /** Returns the accumulator to start from. */\n readonly initial: () => Accumulator;\n /** Adds a value to the accumulator and returns the accumulator. */\n readonly reduce: (accumulator: Accumulator, value: Value) => Accumulator;\n}\n\n/**\n * Reduces the values that apply to a selection, in order of precedence: the\n * value of each variant's option, then the value of each matching compound\n * variant. A value that is undefined adds nothing.\n */\nfunction reduceValues<Value, Accumulator>(\n compiled: CompiledVariants<Value | undefined>,\n indexes: Int32Array,\n reducer: Reducer<Value, Accumulator>,\n): Accumulator {\n const { reduce } = reducer;\n let accumulator = reducer.initial();\n for (let variant = 0; variant < compiled.valuesByIndex.length; variant += 1) {\n const value =\n compiled.valuesByIndex[variant]?.[indexes[variant] ?? noOption];\n if (value !== undefined) {\n accumulator = reduce(accumulator, value);\n }\n }\n for (const compound of compiled.compounds) {\n if (compound.value !== undefined && matches(compound, indexes)) {\n accumulator = reduce(accumulator, compound.value);\n }\n }\n return accumulator;\n}\n\nexport { matches, reduceValues };\nexport type { Reducer };\n","import type {\n CompoundCondition,\n DefaultVariants,\n RecipeFunction,\n VariantKey,\n VariantSelection,\n} from \"./types.js\";\nimport type {\n LooseVariants,\n SelectedVariants,\n WithVariantKeys,\n} from \"./variants.js\";\nimport { compileVariants, withVariantKeys } from \"./variants.js\";\nimport { createSelector } from \"./selector.js\";\nimport { reduceValues } from \"./reduce-values.js\";\n\n/**\n * How a kind of recipe turns the values of a selection into its result,\n * such as by joining class names or merging style objects.\n *\n * @typeParam Value - The value of an option.\n * @typeParam Accumulator - What the values of a selection are reduced to.\n * @typeParam Result - What a recipe of this kind returns.\n */\ninterface RecipeKind<Value, Accumulator, Result> {\n /**\n * Returns the accumulator that the values of a selection are reduced\n * into, starting from the recipe's `base`, which is `undefined` when the\n * recipe has none. It is called for each result a recipe builds, so it\n * can return a new object each time.\n */\n readonly initial: (base: Value | undefined) => Accumulator;\n /**\n * Adds a value to the accumulator and returns the accumulator. It is\n * called with the values that apply to a selection, in order of\n * precedence: the value of each variant's selected option, in the order\n * of `variants`, then the value of each matching compound variant, in the\n * order of `compoundVariants`. An option or compound variant whose value\n * is `undefined` adds nothing.\n */\n readonly reduce: (accumulator: Accumulator, value: Value) => Accumulator;\n /**\n * Turns the accumulator into the result, for example by freezing it.\n * Without it, the result is the accumulator.\n */\n readonly finish?: ((accumulator: Accumulator) => Result) | undefined;\n /**\n * Whether a recipe builds the result of each declared selection once and\n * returns it again for the same selection. Defaults to `true`.\n */\n readonly cache?: boolean | undefined;\n}\n\n/**\n * Any selection, for variants whose names are not known at compile time,\n * such as `Record<string, Record<string, string>>`.\n */\ntype AnySelection = Readonly<Record<string, unknown>>;\n\n/** The selection of a recipe, or any selection for unknown variant names. */\ntype KindSelection<\n Variants,\n DefaultedName extends keyof Variants,\n> = string extends keyof Variants\n ? AnySelection\n : VariantSelection<Variants, DefaultedName>;\n\n/** The condition of a compound variant, or any for unknown variant names. */\ntype KindCompoundCondition<Variants> = string extends keyof Variants\n ? AnySelection\n : CompoundCondition<Variants>;\n\n/** The default variants of a recipe, or any for unknown variant names. */\ntype KindDefaultVariants<\n Variants,\n DefaultedName extends keyof Variants,\n> = string extends keyof Variants\n ? AnySelection\n : DefaultVariants<Variants, DefaultedName>;\n\n/**\n * The variants of a {@link KindRecipeConfig}: for each variant name, the\n * value of each of its options.\n *\n * @typeParam Value - The value of an option.\n */\ntype KindVariants<Value> = LooseVariants<Value>;\n\n/**\n * A value added when several variants have particular options at the same\n * time.\n *\n * @typeParam Variants - The variant definitions of the recipe.\n * @typeParam Value - The value of an option.\n */\ninterface KindCompoundVariant<Variants, Value> {\n /**\n * The options that must all be selected for\n * {@link KindCompoundVariant.value} to apply.\n */\n readonly variants: KindCompoundCondition<Variants>;\n /** The value added when the condition matches. */\n readonly value: Value;\n}\n\n/**\n * The configuration of a recipe made from a {@link RecipeKind}.\n *\n * @typeParam Value - The value of an option.\n * @typeParam Variants - The variant definitions, keyed by variant name.\n * @typeParam DefaultedName - The names of the variants that have a default.\n */\ninterface KindRecipeConfig<\n Value,\n Variants extends KindVariants<Value>,\n DefaultedName extends keyof Variants,\n> {\n /** The value that the values of every selection are added to. */\n readonly base?: Value | undefined;\n /** For each variant name, the value of each of its options. */\n readonly variants: Variants;\n /**\n * Values added when several variants have particular options at the same\n * time, applied in order after the values of the variants' options.\n */\n readonly compoundVariants?:\n readonly KindCompoundVariant<NoInfer<Variants>, Value>[] | undefined;\n /** The option each variant uses when the recipe is called without it. */\n readonly defaultVariants?:\n KindDefaultVariants<Variants, DefaultedName> | undefined;\n}\n\n/**\n * A recipe made from a {@link RecipeKind}: a function that returns the\n * result of a selection of variants, with the names of those variants in\n * `variantKeys`.\n *\n * @typeParam Selection - The variants the recipe accepts.\n * @typeParam Result - What the recipe returns.\n */\ntype KindRecipe<Selection, Result> = RecipeFunction<Selection, Result> & {\n /**\n * The names of the recipe's variants, in the order of\n * `Object.keys(config.variants)`.\n */\n readonly variantKeys: readonly VariantKey<Selection>[];\n};\n\n/**\n * Creates recipes of one kind, the function that {@link createRecipeKind}\n * returns.\n *\n * @typeParam Value - The value of an option.\n * @typeParam Result - What a recipe of this kind returns.\n * @param config - The base value, variants, compound variants, and default\n * variants of the recipe.\n * @returns The recipe.\n */\ntype CreateKindRecipe<Value, Result> = <\n const Variants extends KindVariants<Value>,\n const DefaultedName extends keyof Variants = never,\n>(\n config: KindRecipeConfig<Value, Variants, DefaultedName>,\n) => KindRecipe<KindSelection<Variants, DefaultedName>, Result>;\n\ninterface LooseRecipeKind {\n readonly initial: (base: unknown) => unknown;\n readonly reduce: (accumulator: unknown, value: unknown) => unknown;\n readonly finish?: ((accumulator: unknown) => unknown) | undefined;\n readonly cache?: boolean | undefined;\n}\n\ninterface LooseKindRecipeConfig {\n readonly base?: unknown;\n readonly variants: KindVariants<unknown>;\n readonly compoundVariants?:\n | readonly {\n readonly variants: SelectedVariants;\n readonly value: unknown;\n }[]\n | undefined;\n readonly defaultVariants?: SelectedVariants | undefined;\n}\n\ntype LooseKindRecipe = WithVariantKeys<\n (selection?: SelectedVariants | null) => unknown\n>;\n\n/**\n * Creates a kind of recipe from how it turns the values of a selection into\n * its result, and returns the function that creates recipes of that kind.\n * A recipe maps a selection of variants to a result, as `cva` of\n * `@lynstack/class-recipe` does for class names, for values of any type,\n * such as style objects.\n *\n * @remarks\n * For each selection, a recipe reduces the value of each variant's selected\n * option and the value of each matching compound variant, with\n * `kind.reduce`, into the accumulator that `kind.initial` returns for the\n * recipe's base, then passes it to `kind.finish`. A variant whose only\n * options are `\"true\"` and `\"false\"` defaults to `false`, an option that the\n * config does not declare adds no value, and properties of the selection\n * that are not variants are ignored. A recipe's `variantKeys` property lists\n * the names of its variants.\n *\n * With the cache, a recipe builds the result of each declared selection\n * once, and calling it again with the same variants returns the same\n * result. Freeze an object result in `kind.finish` so that callers cannot\n * change a result that later calls share. A selection with an undeclared\n * option is built on every call.\n *\n * When the variant names are not known at compile time, as in a library\n * that passes on a config it received, a recipe accepts any selection.\n *\n * @typeParam Value - The value of an option, inferred from the `value`\n * parameter of `kind.reduce` or the `base` parameter of `kind.initial`.\n * @typeParam Accumulator - What the values are reduced to, inferred from\n * `kind.initial`.\n * @typeParam Result - What a recipe returns, inferred from `kind.finish`,\n * or the accumulator without it.\n * @param kind - How a recipe turns the values of a selection into its\n * result, and whether it caches the result.\n * @returns The function that creates recipes of this kind.\n *\n * @example\n * ```ts\n * type Style = Readonly<Record<string, string | number>>;\n *\n * const styleRecipe = createRecipeKind({\n * initial: (base?: Style): Record<string, string | number> => ({ ...base }),\n * reduce: (style, value: Style) => Object.assign(style, value),\n * finish: (style): Style => Object.freeze(style),\n * });\n *\n * const text = styleRecipe({\n * base: { color: \"black\" },\n * variants: {\n * size: { sm: { fontSize: 12 }, lg: { fontSize: 24 } },\n * muted: { true: { opacity: 0.6 } },\n * },\n * compoundVariants: [\n * { variants: { size: \"lg\", muted: true }, value: { fontWeight: 300 } },\n * ],\n * defaultVariants: { size: \"sm\" },\n * });\n *\n * text(); // => { color: \"black\", fontSize: 12 }\n *\n * text({ size: \"lg\", muted: true });\n * // => { color: \"black\", fontSize: 24, opacity: 0.6, fontWeight: 300 }\n *\n * text.variantKeys; // => [\"size\", \"muted\"]\n * ```\n */\nfunction createRecipeKind<Value, Accumulator, Result = Accumulator>(\n kind: RecipeKind<Value, Accumulator, Result>,\n): CreateKindRecipe<Value, Result>;\n\nfunction createRecipeKind(kind: LooseRecipeKind): unknown {\n const { initial, reduce, finish } = kind;\n const options = { cache: kind.cache ?? true };\n\n return (config: LooseKindRecipeConfig): LooseKindRecipe => {\n const { base } = config;\n const compiled = compileVariants<unknown>({\n compoundVariants: config.compoundVariants ?? [],\n defaultVariants: config.defaultVariants ?? {},\n noValue: undefined,\n variants: config.variants,\n });\n const reducer = { initial: (): unknown => initial(base), reduce };\n const build =\n finish === undefined\n ? (indexes: Int32Array): unknown =>\n reduceValues(compiled, indexes, reducer)\n : (indexes: Int32Array): unknown =>\n finish(reduceValues(compiled, indexes, reducer));\n return withVariantKeys(createSelector(compiled, build, options), compiled);\n };\n}\n\nexport { createRecipeKind };\nexport type {\n CreateKindRecipe,\n KindCompoundCondition,\n KindCompoundVariant,\n KindDefaultVariants,\n KindRecipe,\n KindRecipeConfig,\n KindSelection,\n KindVariants,\n RecipeKind,\n};\n","import type { CompiledVariants, Compound } from \"./variants.js\";\nimport { matches } from \"./reduce-values.js\";\nimport { noOption } from \"./variants.js\";\n\n/** Values for some slots, keyed by slot name, as the runtime takes them. */\ntype LooseSlotValues = Readonly<Record<string, unknown>>;\n\n/** How the values of each slot are reduced, as a recipe kind gives it. */\ninterface SlotsKind {\n readonly initial: (base: unknown) => unknown;\n readonly reduce: (accumulator: unknown, value: unknown) => unknown;\n readonly finish?: ((accumulator: unknown) => unknown) | undefined;\n}\n\n/** What a slot recipe's config gives for its slots. */\ninterface SlotsConfig {\n readonly slots: readonly string[];\n readonly base?: LooseSlotValues | undefined;\n}\n\n/**\n * The values that one option or compound variant adds to its slots: the\n * index of each slot it gives a value, and that value at the same position.\n * Slots without a value are left out, so reducing skips them.\n */\ninterface SlotEntries {\n readonly slots: readonly number[];\n readonly values: readonly unknown[];\n}\n\n/**\n * The accumulator of each slot while a selection is reduced, which\n * {@link addEntries} updates in place.\n */\ninterface SlotAccumulators {\n readonly length: number;\n [slot: number]: unknown;\n}\n\nfunction valueOfSlot(\n values: LooseSlotValues | undefined,\n slot: string,\n): unknown {\n return values !== undefined && Object.hasOwn(values, slot)\n ? values[slot]\n : undefined;\n}\n\n/** Returns the entries of `values`, or undefined when no slot has one. */\nfunction entriesOf(\n slots: readonly string[],\n values: LooseSlotValues | undefined,\n): SlotEntries | undefined {\n const slotIndexes: number[] = [];\n const slotValues: unknown[] = [];\n for (const [index, slot] of slots.entries()) {\n const value = valueOfSlot(values, slot);\n if (value !== undefined) {\n slotIndexes.push(index);\n slotValues.push(value);\n }\n }\n return slotIndexes.length === 0\n ? undefined\n : { slots: slotIndexes, values: slotValues };\n}\n\n/** Turns the slot values of each option and compound variant into entries. */\nfunction compileEntries(\n compiled: CompiledVariants<LooseSlotValues | undefined>,\n slots: readonly string[],\n): CompiledVariants<SlotEntries | undefined> {\n return {\n ...compiled,\n compounds: compiled.compounds\n .map((compound): Compound<SlotEntries | undefined> => ({\n conditions: compound.conditions,\n value: entriesOf(slots, compound.value),\n }))\n .filter((compound) => compound.value !== undefined),\n valuesByIndex: compiled.valuesByIndex.map((valuesOfVariant) =>\n valuesOfVariant.map((values) => entriesOf(slots, values)),\n ),\n };\n}\n\n/** Reduces the values of `added` into the accumulators of their slots. */\nfunction addEntries(\n accumulators: SlotAccumulators,\n added: SlotEntries,\n reduce: SlotsKind[\"reduce\"],\n): void {\n const { slots, values } = added;\n for (let entry = 0; entry < slots.length; entry += 1) {\n const slot = slots[entry] ?? 0;\n accumulators[slot] = reduce(accumulators[slot], values[entry]);\n }\n}\n\n/** Returns the frozen result of each slot, keyed by slot name. */\nfunction resultsBySlot(\n names: readonly string[],\n accumulators: SlotAccumulators,\n finish: (accumulator: unknown) => unknown,\n): Readonly<Record<string, unknown>> {\n const results: Record<string, unknown> = {};\n for (let slot = 0; slot < names.length; slot += 1) {\n results[names[slot] ?? \"\"] = finish(accumulators[slot]);\n }\n return Object.freeze(results);\n}\n\nconst asResult = (accumulator: unknown): unknown => accumulator;\n\n/**\n * Returns the function that builds the result of a selection for each slot\n * of a slot recipe: each slot reduces its own values with `kind`, and the\n * results are frozen in an object keyed by slot name.\n *\n * It reduces the values in a loop of its own instead of through\n * `reduceValues`, which recipes share, so that its calls to `kind.reduce`\n * see only the reducers of slot recipes and can be inlined.\n */\nfunction createSlotsBuilder(\n kind: SlotsKind,\n compiled: CompiledVariants<LooseSlotValues | undefined>,\n config: SlotsConfig,\n): (indexes: Int32Array) => Readonly<Record<string, unknown>> {\n const { initial, reduce, finish = asResult } = kind;\n const names = [...config.slots];\n const bases = names.map((slot) => valueOfSlot(config.base, slot));\n const { valuesByIndex, compounds } = compileEntries(compiled, names);\n\n return (indexes) => {\n const accumulators = bases.map((base) => initial(base));\n for (let variant = 0; variant < valuesByIndex.length; variant += 1) {\n const added = valuesByIndex[variant]?.[indexes[variant] ?? noOption];\n if (added !== undefined) {\n addEntries(accumulators, added, reduce);\n }\n }\n for (const compound of compounds) {\n if (compound.value !== undefined && matches(compound, indexes)) {\n addEntries(accumulators, compound.value, reduce);\n }\n }\n return resultsBySlot(names, accumulators, finish);\n };\n}\n\nexport { createSlotsBuilder };\nexport type { LooseSlotValues, SlotsKind };\n","import type {\n KindCompoundCondition,\n KindDefaultVariants,\n KindRecipe,\n KindSelection,\n KindVariants,\n RecipeKind,\n} from \"./recipe-kind.js\";\nimport type { LooseSlotValues, SlotsKind } from \"./slots.js\";\nimport type { SelectedVariants, WithVariantKeys } from \"./variants.js\";\nimport { compileVariants, withVariantKeys } from \"./variants.js\";\nimport { createSelector } from \"./selector.js\";\nimport { createSlotsBuilder } from \"./slots.js\";\n\n/**\n * Values for some of a slot recipe's slots, keyed by slot name.\n *\n * @typeParam Slot - The names of the slots.\n * @typeParam Value - The value of a slot.\n */\ntype SlotValues<Slot extends string, Value> = Readonly<\n Partial<Record<Slot, Value | undefined>>\n>;\n\n/**\n * The variants of a {@link KindSlotRecipeConfig}: for each variant name,\n * the values of each slot for each of its options.\n *\n * @typeParam Value - The value of a slot.\n */\ntype KindSlotVariants<Value> = KindVariants<SlotValues<string, Value>>;\n\n/**\n * Rejects the slots of each option's values that `Slot` does not name,\n * unless the option's slot names are not known at compile time.\n */\ntype NoUnknownSlots<Variants, Slot extends string> = {\n readonly [Name in keyof Variants]: {\n readonly [\n Option in keyof Variants[Name]\n ]: string extends keyof Variants[Name][Option]\n ? unknown\n : Readonly<\n Partial<Record<Exclude<keyof Variants[Name][Option], Slot>, never>>\n >;\n };\n};\n\n/**\n * Values added to some slots when several variants have particular options\n * at the same time.\n *\n * @typeParam Variants - The variant definitions of the slot recipe.\n * @typeParam Slot - The names of the slots.\n * @typeParam Value - The value of a slot.\n */\ninterface KindSlotCompoundVariant<Variants, Slot extends string, Value> {\n /**\n * The options that must all be selected for\n * {@link KindSlotCompoundVariant.value} to apply.\n */\n readonly variants: KindCompoundCondition<Variants>;\n /** The value added to each slot when the condition matches. */\n readonly value: SlotValues<Slot, Value>;\n}\n\n/**\n * The configuration of a slot recipe made from a {@link RecipeKind}.\n *\n * @typeParam Slot - The names of the slots.\n * @typeParam Value - The value of a slot.\n * @typeParam Variants - The variant definitions, keyed by variant name.\n * @typeParam DefaultedName - The names of the variants that have a default.\n */\ninterface KindSlotRecipeConfig<\n Slot extends string,\n Value,\n Variants extends KindSlotVariants<Value>,\n DefaultedName extends keyof Variants,\n> {\n /** The names of the slots, in the order of the recipe's result. */\n readonly slots: readonly Slot[];\n /** The value of each slot that the values of every selection are added to. */\n readonly base?: SlotValues<NoInfer<Slot>, Value> | undefined;\n /** For each variant name, the values of each slot for each of its options. */\n readonly variants: Variants & NoUnknownSlots<Variants, NoInfer<Slot>>;\n /**\n * Values added to some slots when several variants have particular\n * options at the same time, applied in order after the values of the\n * variants' options.\n */\n readonly compoundVariants?:\n | readonly KindSlotCompoundVariant<\n NoInfer<Variants>,\n NoInfer<Slot>,\n Value\n >[]\n | undefined;\n /** The option each variant uses when the recipe is called without it. */\n readonly defaultVariants?:\n KindDefaultVariants<Variants, DefaultedName> | undefined;\n}\n\n/**\n * Creates slot recipes of one kind, the function that\n * {@link createSlotRecipeKind} returns.\n *\n * @typeParam Value - The value of a slot.\n * @typeParam Result - What a recipe of this kind returns for each slot.\n * @param config - The slots, base values, variants, compound variants, and\n * default variants of the slot recipe.\n * @returns The slot recipe.\n */\ntype CreateKindSlotRecipe<Value, Result> = <\n const Slot extends string,\n const Variants extends KindSlotVariants<Value>,\n const DefaultedName extends keyof Variants = never,\n>(\n config: KindSlotRecipeConfig<Slot, Value, Variants, DefaultedName>,\n) => KindRecipe<\n KindSelection<Variants, DefaultedName>,\n Readonly<Record<Slot, Result>>\n>;\n\ninterface LooseRecipeKind extends SlotsKind {\n readonly cache?: boolean | undefined;\n}\n\ninterface LooseSlotRecipeConfig {\n readonly slots: readonly string[];\n readonly base?: LooseSlotValues | undefined;\n readonly variants: KindSlotVariants<unknown>;\n readonly compoundVariants?:\n | readonly {\n readonly variants: SelectedVariants;\n readonly value: LooseSlotValues;\n }[]\n | undefined;\n readonly defaultVariants?: SelectedVariants | undefined;\n}\n\ntype LooseSlotRecipe = WithVariantKeys<\n (selection?: SelectedVariants | null) => Readonly<Record<string, unknown>>\n>;\n\n/**\n * Creates a kind of slot recipe from how it turns the values of each slot\n * into that slot's result, and returns the function that creates slot\n * recipes of that kind. A slot recipe maps a selection of variants to the\n * result of each of several slots, such as the class names or the styles of\n * the elements of a component, as `sva` of `@lynstack/class-recipe` does.\n *\n * @remarks\n * A slot recipe reduces the values of each slot as a recipe of the same\n * kind from {@link createRecipeKind} would: `kind.initial` returns the\n * accumulator for the slot's base value, `kind.reduce` adds the slot's\n * value of each variant's selected option, in the order of `variants`, and\n * of each matching compound variant, in the order of `compoundVariants`,\n * and `kind.finish` turns the accumulator into the slot's result. A slot\n * without values gets the result of its accumulator for an `undefined`\n * base, so every slot is in the result.\n *\n * The result is a frozen object of each slot's result, keyed by slot name\n * in the order of `slots`. With the cache, a slot recipe builds it once for\n * each declared selection and returns the same object for the same\n * variants. Variants, default variants, boolean variants, and undeclared\n * options behave as in {@link createRecipeKind}, and the recipe's\n * `variantKeys` property lists the names of its variants.\n *\n * @typeParam Value - The value of a slot, inferred from the `value`\n * parameter of `kind.reduce` or the `base` parameter of `kind.initial`.\n * @typeParam Accumulator - What the values of a slot are reduced to,\n * inferred from `kind.initial`.\n * @typeParam Result - What a slot recipe returns for each slot, inferred\n * from `kind.finish`, or the accumulator without it.\n * @param kind - How a slot recipe turns the values of each slot into its\n * result, and whether it caches its results. The same kind can create\n * recipes with {@link createRecipeKind}.\n * @returns The function that creates slot recipes of this kind.\n *\n * @example\n * ```ts\n * type Style = Readonly<Record<string, string | number>>;\n *\n * const styleKind = {\n * initial: (base?: Style): Record<string, string | number> => ({ ...base }),\n * reduce: (style: Record<string, string | number>, value: Style) =>\n * Object.assign(style, value),\n * finish: (style: Record<string, string | number>): Style =>\n * Object.freeze(style),\n * };\n *\n * const slotStyleRecipe = createSlotRecipeKind(styleKind);\n *\n * const card = slotStyleRecipe({\n * slots: [\"root\", \"title\"],\n * base: { root: { padding: 16 }, title: { fontSize: 18 } },\n * variants: {\n * tone: {\n * light: { root: { backgroundColor: \"white\" } },\n * dark: { root: { backgroundColor: \"black\" }, title: { color: \"white\" } },\n * },\n * },\n * compoundVariants: [\n * { variants: { tone: \"dark\" }, value: { title: { fontWeight: 600 } } },\n * ],\n * defaultVariants: { tone: \"light\" },\n * });\n *\n * card();\n * // => { root: { padding: 16, backgroundColor: \"white\" }, title: { fontSize: 18 } }\n *\n * card({ tone: \"dark\" });\n * // => {\n * // root: { padding: 16, backgroundColor: \"black\" },\n * // title: { fontSize: 18, color: \"white\", fontWeight: 600 },\n * // }\n *\n * card.variantKeys; // => [\"tone\"]\n * ```\n */\nfunction createSlotRecipeKind<Value, Accumulator, Result = Accumulator>(\n kind: RecipeKind<Value, Accumulator, Result>,\n): CreateKindSlotRecipe<Value, Result>;\n\nfunction createSlotRecipeKind(kind: LooseRecipeKind): unknown {\n const options = { cache: kind.cache ?? true };\n\n return (config: LooseSlotRecipeConfig): LooseSlotRecipe => {\n const compiled = compileVariants<LooseSlotValues | undefined>({\n compoundVariants: config.compoundVariants ?? [],\n defaultVariants: config.defaultVariants ?? {},\n noValue: undefined,\n variants: config.variants,\n });\n const build = createSlotsBuilder(kind, compiled, config);\n return withVariantKeys(createSelector(compiled, build, options), compiled);\n };\n}\n\nexport { createSlotRecipeKind };\nexport type {\n CreateKindSlotRecipe,\n KindSlotCompoundVariant,\n KindSlotRecipeConfig,\n KindSlotVariants,\n SlotValues,\n};\n"],"mappings":"AA8DA,MAAM,UAA4B,OAAO,OAAO,CAAC,CAAC;;;;;;;;AASlD,SAAS,gBACP,QACyB;CACzB,MAAM,QAAQ,OAAO,KAAK,OAAO,QAAQ;CACzC,MAAM,eAAe,MAAM,KAAK,SAC9B,mBAAmB,OAAO,SAAS,SAAS,CAAC,GAAG,OAAO,OAAO,CAChE;CACA,MAAM,gBACJ,aAAa,KACV,mBACC,IAAI,IACF,OAAO,KAAK,cAAc,CAAC,CAAC,KAAK,QAAQ,UAAU,CACjD,QACA,QAAQ,CACV,CAAC,CACH,CACJ;CACF,MAAM,UAAU,cAAc,KAAK,YAAY,QAAQ,OAAO,CAAC;CAE/D,OAAO;EACL,WAAW,iBAAiB,OAAO,kBAAkB,OAAO,aAAa;EACzE,gBAAgB,MAAM,KACnB,MAAM,UACL,aAAa,OAAO,gBAAgB,KAAK,MACxC,iBAAiB,cAAc,MAAM,IAAI,UAAU,KAAA,EACxD;EACA;EACA,aAAa,QAAQ,OAAO,KAAK,OAAO;EACxC;EACA,SAAS,QAAQ,KAAK,QAAQ,UAAU,QAAQ,QAAQ,MAAM,GAAG,KAAK,CAAC,CAAC;EACxE,eAAe,gBAAgB,cAAc,OAAO,OAAO;CAC7D;AACF;;;;;;AAOA,SAAS,OACP,UACA,UACA,SACQ;CACR,IAAI,MAAM;CACV,KAAK,IAAI,UAAU,GAAG,UAAU,SAAS,MAAM,QAAQ,WAAW,GAAG;EACnE,MAAM,QAAQ,YAAY,UAAU,UAAU,OAAO;EACrD,QAAQ,WAAW,SAAA;EACnB,MACE,UAAU,KAAA,KAAa,QAAA,KAAA,KAEnB,MAAM,SAAS,SAAS,QAAQ,YAAY;CACpD;CACA,OAAO;AACT;AAEA,SAAS,YACP,UACA,UACA,SACoB;CACpB,MAAM,SACJ,aAAa,SAAS,SAAS,MAAM,YAAY,GAAG,KACpD,SAAS,eAAe;CAC1B,OAAO,WAAW,KAAA,IAAA,IAEd,SAAS,cAAc,QAAQ,EAAE,IAAI,MAAM;AACjD;AAEA,SAAS,gBACP,cACA,SACW;CACX,MAAM,gBAA2B,CAAC;CAClC,KAAK,MAAM,kBAAkB,cAC3B,cAAc,KAAK,CAAC,SAAS,GAAG,OAAO,OAAO,cAAc,CAAC,CAAC;CAEhE,OAAO;AACT;AAEA,SAAS,QAAQ,SAAoC;CACnD,OAAO,QAAQ,QAAQ,QAAQ,WAAW,SAAS,QAAQ,CAAC;AAC9D;AAEA,SAAS,mBACP,gBACA,SACiC;CAEjC,IAAI,CADgB,OAAO,KAAK,cACjB,CAAC,CAAC,MAAM,WAAW,cAAc,MAAM,CAAC,GACrD,OAAO;CAET,OAAO;EAAE,OAAO;EAAS,MAAM;EAAS,GAAG;CAAe;AAC5D;AAEA,SAAS,iBACP,kBACA,OACA,eACmB;CACnB,OAAO,iBAAiB,SAAS,EAAE,UAAU,YAAY;EACvD,MAAM,aAAa,OAAO,KAAK,QAAQ,CAAC,CACrC,QAAQ,SAAS,SAAS,UAAU,KAAA,CAAS,CAAC,CAC9C,KAAK,SACJ,iBAAiB,eAAe,MAAM,QAAQ,IAAI,GAAG,SAAS,KAAK,CACrE;EACF,OAAO,WAAW,OAAO,GAAG,aAAa,QAAQ,SAAS,CAAC,IACvD,CAAC;GAAE;GAAY;EAAM,CAAC,IACtB,CAAC;CACP,CAAC;AACH;AAEA,SAAS,iBACP,eACA,SACA,OACmB;CACnB,MAAM,UAAU,cAAc;CAC9B,OAAO,CACL,SACA,cAAc,KAAK,CAAC,CACjB,KAAK,WAAW,SAAS,IAAI,MAAM,CAAC,CAAC,CACrC,QAAQ,UAAU,UAAU,KAAA,CAAS,CAC1C;AACF;AAEA,SAAS,iBACP,eACS;CACT,OACE,kBAAkB,KAAA,KAClB,cAAc,OAAO,KACrB,CAAC,GAAG,cAAc,KAAK,CAAC,CAAC,CAAC,OAAO,WAAW,cAAc,MAAM,CAAC;AAErE;AAEA,SAAS,aAAa,OAAoC;CACxD,IAAI,OAAO,UAAU,UACnB,OAAO;CAET,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,YACjD,OAAO,KAAK,IACZ,KAAA;AACN;AAEA,SAAS,cAAc,OAAmC;CAExD,QADmC,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,EAAA,CAErE,KAAK,WAAW,aAAa,MAAM,CAAC,CAAC,CACrC,QAAQ,WAAW,WAAW,KAAA,CAAS;AAC5C;;AAQA,SAAS,gBACP,QACA,UACyB;CACzB,OAAO,OAAO,OAAO,QAAQ,EAC3B,aAAa,OAAO,OAAO,CAAC,GAAG,SAAS,KAAK,CAAC,EAChD,CAAC;AACH;AAEA,SAAS,cAAc,QAAyB;CAC9C,OAAO,WAAW,UAAU,WAAW;AACzC;;;;;;;;;ACjOA,SAAS,eACP,UACA,OACA,SACgD;CAChD,MAAM,UAAU,IAAI,WAAW,SAAS,MAAM,MAAM;CACpD,MAAM,UACJ,QAAQ,SAAS,SAAS,8BACtB,IAAI,IAAoB,IACxB,KAAA;CAEN,QAAQ,aAAa;EACnB,MAAM,MAAM,OAAO,UAAU,YAAY,SAAS,OAAO;EACzD,MAAM,eAAe,QAAA,KAAqB,KAAA,IAAY,SAAS,IAAI,GAAG;EACtE,IAAI,iBAAiB,KAAA,GACnB,OAAO;EAET,MAAM,SAAS,MAAM,OAAO;EAC5B,IAAI,QAAA,IACF,SAAS,IAAI,KAAK,MAAM;EAE1B,OAAO;CACT;AACF;;;;AClCA,SAAS,QAAQ,UAA6B,SAA8B;CAC1E,KAAK,MAAM,CAAC,SAAS,oBAAoB,SAAS,YAChD,IAAI,CAAC,gBAAgB,SAAS,QAAQ,YAAA,CAAoB,GACxD,OAAO;CAGX,OAAO;AACT;;;;;;AAeA,SAAS,aACP,UACA,SACA,SACa;CACb,MAAM,EAAE,WAAW;CACnB,IAAI,cAAc,QAAQ,QAAQ;CAClC,KAAK,IAAI,UAAU,GAAG,UAAU,SAAS,cAAc,QAAQ,WAAW,GAAG;EAC3E,MAAM,QACJ,SAAS,cAAc,QAAQ,GAAG,QAAQ,YAAA;EAC5C,IAAI,UAAU,KAAA,GACZ,cAAc,OAAO,aAAa,KAAK;CAE3C;CACA,KAAK,MAAM,YAAY,SAAS,WAC9B,IAAI,SAAS,UAAU,KAAA,KAAa,QAAQ,UAAU,OAAO,GAC3D,cAAc,OAAO,aAAa,SAAS,KAAK;CAGpD,OAAO;AACT;;;ACoNA,SAAS,iBAAiB,MAAgC;CACxD,MAAM,EAAE,SAAS,QAAQ,WAAW;CACpC,MAAM,UAAU,EAAE,OAAO,KAAK,SAAS,KAAK;CAE5C,QAAQ,WAAmD;EACzD,MAAM,EAAE,SAAS;EACjB,MAAM,WAAW,gBAAyB;GACxC,kBAAkB,OAAO,oBAAoB,CAAC;GAC9C,iBAAiB,OAAO,mBAAmB,CAAC;GAC5C,SAAS,KAAA;GACT,UAAU,OAAO;EACnB,CAAC;EACD,MAAM,UAAU;GAAE,eAAwB,QAAQ,IAAI;GAAG;EAAO;EAOhE,OAAO,gBAAgB,eAAe,UALpC,WAAW,KAAA,KACN,YACC,aAAa,UAAU,SAAS,OAAO,KACxC,YACC,OAAO,aAAa,UAAU,SAAS,OAAO,CAAC,GACA,OAAO,GAAG,QAAQ;CAC3E;AACF;;;AChPA,SAAS,YACP,QACA,MACS;CACT,OAAO,WAAW,KAAA,KAAa,OAAO,OAAO,QAAQ,IAAI,IACrD,OAAO,QACP,KAAA;AACN;;AAGA,SAAS,UACP,OACA,QACyB;CACzB,MAAM,cAAwB,CAAC;CAC/B,MAAM,aAAwB,CAAC;CAC/B,KAAK,MAAM,CAAC,OAAO,SAAS,MAAM,QAAQ,GAAG;EAC3C,MAAM,QAAQ,YAAY,QAAQ,IAAI;EACtC,IAAI,UAAU,KAAA,GAAW;GACvB,YAAY,KAAK,KAAK;GACtB,WAAW,KAAK,KAAK;EACvB;CACF;CACA,OAAO,YAAY,WAAW,IAC1B,KAAA,IACA;EAAE,OAAO;EAAa,QAAQ;CAAW;AAC/C;;AAGA,SAAS,eACP,UACA,OAC2C;CAC3C,OAAO;EACL,GAAG;EACH,WAAW,SAAS,UACjB,KAAK,cAAiD;GACrD,YAAY,SAAS;GACrB,OAAO,UAAU,OAAO,SAAS,KAAK;EACxC,EAAE,CAAC,CACF,QAAQ,aAAa,SAAS,UAAU,KAAA,CAAS;EACpD,eAAe,SAAS,cAAc,KAAK,oBACzC,gBAAgB,KAAK,WAAW,UAAU,OAAO,MAAM,CAAC,CAC1D;CACF;AACF;;AAGA,SAAS,WACP,cACA,OACA,QACM;CACN,MAAM,EAAE,OAAO,WAAW;CAC1B,KAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;EACpD,MAAM,OAAO,MAAM,UAAU;EAC7B,aAAa,QAAQ,OAAO,aAAa,OAAO,OAAO,MAAM;CAC/D;AACF;;AAGA,SAAS,cACP,OACA,cACA,QACmC;CACnC,MAAM,UAAmC,CAAC;CAC1C,KAAK,IAAI,OAAO,GAAG,OAAO,MAAM,QAAQ,QAAQ,GAC9C,QAAQ,MAAM,SAAS,MAAM,OAAO,aAAa,KAAK;CAExD,OAAO,OAAO,OAAO,OAAO;AAC9B;AAEA,MAAM,YAAY,gBAAkC;;;;;;;;;;AAWpD,SAAS,mBACP,MACA,UACA,QAC4D;CAC5D,MAAM,EAAE,SAAS,QAAQ,SAAS,aAAa;CAC/C,MAAM,QAAQ,CAAC,GAAG,OAAO,KAAK;CAC9B,MAAM,QAAQ,MAAM,KAAK,SAAS,YAAY,OAAO,MAAM,IAAI,CAAC;CAChE,MAAM,EAAE,eAAe,cAAc,eAAe,UAAU,KAAK;CAEnE,QAAQ,YAAY;EAClB,MAAM,eAAe,MAAM,KAAK,SAAS,QAAQ,IAAI,CAAC;EACtD,KAAK,IAAI,UAAU,GAAG,UAAU,cAAc,QAAQ,WAAW,GAAG;GAClE,MAAM,QAAQ,cAAc,QAAQ,GAAG,QAAQ,YAAA;GAC/C,IAAI,UAAU,KAAA,GACZ,WAAW,cAAc,OAAO,MAAM;EAE1C;EACA,KAAK,MAAM,YAAY,WACrB,IAAI,SAAS,UAAU,KAAA,KAAa,QAAQ,UAAU,OAAO,GAC3D,WAAW,cAAc,SAAS,OAAO,MAAM;EAGnD,OAAO,cAAc,OAAO,cAAc,MAAM;CAClD;AACF;;;AC6EA,SAAS,qBAAqB,MAAgC;CAC5D,MAAM,UAAU,EAAE,OAAO,KAAK,SAAS,KAAK;CAE5C,QAAQ,WAAmD;EACzD,MAAM,WAAW,gBAA6C;GAC5D,kBAAkB,OAAO,oBAAoB,CAAC;GAC9C,iBAAiB,OAAO,mBAAmB,CAAC;GAC5C,SAAS,KAAA;GACT,UAAU,OAAO;EACnB,CAAC;EAED,OAAO,gBAAgB,eAAe,UADxB,mBAAmB,MAAM,UAAU,MACD,GAAO,OAAO,GAAG,QAAQ;CAC3E;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynstack/recipe",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Fast, type-safe recipes for values of any type: define how a kind of recipe combines values, such as class names or styles, then create recipes of that kind.",
5
5
  "keywords": [
6
6
  "recipe",
@@ -28,6 +28,9 @@
28
28
  "files": [
29
29
  "dist"
30
30
  ],
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
31
34
  "scripts": {
32
35
  "build": "tsdown",
33
36
  "typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.build.json --noEmit",
@@ -38,10 +41,6 @@
38
41
  "test:coverage": "vitest run --coverage",
39
42
  "test:consumer": "tsc -p fixtures/consumer/tsconfig.json",
40
43
  "bench": "pnpm build && vitest bench --run",
41
- "check": "pnpm build && pnpm typecheck && pnpm test:consumer && pnpm lint && pnpm format:check && pnpm test",
42
- "prepack": "pnpm build"
43
- },
44
- "publishConfig": {
45
- "access": "public"
44
+ "check": "pnpm build && pnpm typecheck && pnpm test:consumer && pnpm lint && pnpm format:check && pnpm test"
46
45
  }
47
- }
46
+ }