UNPKG

@quarksuite-two/core

Version:

A web-focused toolkit for creating, assembling and distributing design tokens for custom design systems.

203 lines (194 loc) 6.28 kB
/// <reference types="./types/workflow.d.ts" /> /** * A workflow helper that converts an `action` into an emitter by presetting its * `y` modifier. * * @template X, Y, Result * * @param {(y: Y, x: X) => Result} action - the action to convert * @param {Y} y - the action modifier to preset * @returns {(x: X) => Result} the resultant emitter * * @remarks * This workflow helper will be invaluable when you find yourself repeating actions * in multiple places on the same data. It's also what enables the powerful data * composition patterns provided by the other helpers. * * `preset` is the fulcrum of advanced QuarkSuite use. * * @example * Generate a rainbow hue from any valid CSS color * * ```js * const rainbow = preset(adjust, { hue: 360, steps: 8 }); * * rainbow("chartreuse"); * ``` */ export function preset(action, y) { return (x) => action(y, x); } // Last typedef is borrowed from: https://dev.to/badhusband/comment/1dk7c /** * @template {any[]} T * * @typedef {T extends [...any[], infer R] ? R : never } Last - Selects the last item of a tuple */ /** * A workflow helper that combines a sequence of `emitters` into a new emitter. * * @template {readonly [(x: any) => any, ...Array<(x: any) => any>, (x: any) => any]} EmitterSequence * @template {EmitterSequence} Sequence * * @param {Sequence} emitters - the sequence of emitters to combine * @returns {Last<Sequence>} a new emitter from execution sequence * * @remarks * This workflow helper is excellent for combining simple emitters into complex * processes. The main use case is wrapping common data processing patterns into a * reusable interface. * * @example * Desaturate and lighten any valid CSS color before converting to hex * * ```js * const desat = preset(adjust, { chroma: -20 }); * const lighten = preset(adjust, { lightness: 25 }); * const hex = preset(convert, "hex"); * * const base = process(desat, lighten, hex); * * base("dodgerblue"); * ``` * * @see {@link preset} */ export function process(...emitters) { // @ts-ignore: The return type is correct but TypeScript thinks it's (x: any) => any return compose(...emitters); } /** * A workflow helper that transforms data of type `x` (any valid data type) * through a sequence of `emitters`. * * @template X * @template {readonly [(x: any) => any, ...Array<(x: any) => any>, (x: any) => any]} EmitterSequence * @template {EmitterSequence} Sequence * * @param {X} x - the data to pass through * @param {Sequence} emitters - the sequence of emitters to execute * @returns {ReturnType<Last<Sequence>>} the transformed data * * @remarks * This workflow helper is useful when you want immediate data processing. * Pipelines can use the output of other pipelines as input which makes chaining * operations painless. * * @example * A color to accented triadic scheme pipeline * * ```js * const intensify = preset(adjust, { chroma: 30 }); * const withMagenta = preset(mix, { target: "magenta", strength: 42 }); * const scheme = preset(harmony, { configuration: "triadic", accented: true }); * * pipeline("crimson", intensify, withMagenta, scheme); * ``` * * @see {@link preset} */ export function pipeline(x, ...emitters) { return compose(...emitters)(x); } /** * A workflow helper that maps the execution result of an `emitter` to a * collection of `xs`. * * @template X, R * @template {X[]} Collection * * @param {(x: X) => R} emitter - the emitter to execute on each value * @param {Collection} xs - the collection of values * @returns {R[]} the collection of results * * @remarks * This workflow helper comes in handy when you have a scale of values and you * want to perform an operation on *each* one. `xs` doesn't have to be a generated * scale. It can also be any grouping of values with the same type. * * @example * Propagating an analogous color harmony over an interpolated blend * * ```js * const xs = mix({ * target: "rebeccapurple", * strength: 75, * steps: 4 * }, "dodgerblue"); * * propagate(preset(harmony, { configuration: "analogous" }), xs); * ``` * * @example * Grouping values for batch operation * * ```js * const red = adjust({ lightness: 25 }, "crimson"); * const green = adjust({ lightness: -25 * 1.25 }, "chartreuse"); * const blue = adjust({ lightness: -25 * 1.5 }, "dodgerblue"); * * propagate(preset(mix, { target: "magenta" }), [red, green, blue]); * ``` * * @see {@link preset} */ export function propagate(emitter, xs) { return xs.map((x) => emitter(x)); } /** * A workflow helper that maps a collection of `xs` to a sequence of `emitters`. * * @template X * @template {X[]} Collection * @template {readonly [(x: any) => any, ...Array<(x: any) => any>, (x: any) => any]} EmitterSequence * @template {EmitterSequence} Sequence * * @param {Collection} xs - the collection to delegate * @param {Sequence} emitters - the sequence of emitters * @returns {{ [P in keyof Sequence]: ReturnType<Sequence[P]> }} the collection of results * * @remarks * This workflow helper will aid you in situations where you have a collection * of values that you want to *individually* process. If you don't want to alter * a value at a given index, set the emitter that matches it to `undefined`. * * The `emitters` sequence maps to the length of the collection, so if you pass * in two emitters only the first two items in the collection will be processed. * * Finally, you can target **specific values** in the collection by invoking your * emitters only at their corresponding positions. * * @example * Delegating color adjustments to a tetradic harmony * * ```js * const scheme = harmony({ configuration: "tetradic" }, "coral"); * * const adjusted = delegate( * scheme, * undefined, // no need to alter the base color * preset(adjust, { lightness: 10 }), * preset(adjust, { lightness: 15 }), * preset(adjust, { lightness: 20 }) * ); * ``` * * @see {@link preset} */ export function delegate(xs, ...emitters) { // @ts-ignore: The return type is correct, but TypeScript thinks it's any[] return xs.map((x, pos) => (emitters[pos] ? emitters[pos](x) : x)); } function compose(...fns) { return (x) => fns.reduce((g, f) => f(g), x); }