prismicio / prismic-helpers

Set of helpers to manage Prismic data
https://prismic.io/docs/technical-reference/prismicio-helpers
Apache License 2.0
15 stars 9 forks source link

feat: add `isFilled` helpers #34

Closed lihbr closed 2 years ago

lihbr commented 2 years ago

Types of changes

Description

Adds a collection of helpers to check if a field is filled. The isFilled export contains a function for each field type (Rich Text, Color, Group, Slice Zone, etc.).

import { isFilled } from "@prismicio/helpers";
import { RichTextField } from "@prismicio/types";

declare const field: RichTextField;

if (isFilled.richText(field)) {
    // `field` is RichTextField<"filled">
} else {
    // `field` is RichTextField<"empty">
}

Resolves: #33

Old PR description Are we fine saying `TitleField` and `RichTextField` are empty when `asText(field) === ""`? What about `RichTextField` only containing an image (or embed)?

Checklist:

image

angeloashmore commented 2 years ago

It turns out getting this to work with TypeScript is pretty difficult. Converting input types with generics (e.g. LinkField<"foo", "fr-fr", { foo: TimestampField }>) to a filled type is either not possible or is so complex I can't figure it out. I tried different iterations of a large converter type (Field => Field<"filled">), but using the infer declaration seems to fall apart very quickly.

Rather than have one isFilled function, we could have multiple versions for different field types. By doing it this way, we can set up the generics per-function specific to the field type it expects.

For example, a isLinkFilled function could look like this:

const isLinkFilled = <
    TypeEnum = string,
    LangEnum = string,
    DataInterface extends Record<
        string,
        AnyRegularField | GroupField | SliceZone
    > = never,
>(
    field: LinkField<TypeEnum, LangEnum, DataInterface>,
): field is LinkField<TypeEnum, LangEnum, DataInterface, "filled"> => {
    // logic here
};

And be used like this:

if (isLinkFilled(link)) {
    link;
    // FilledLinkToDocumentField<string, string, never>
    // | FilledLinkToWebField
    // | FilledLinkToMediaField
} else {
    link;
    // EmptyLinkField<"Any">
}

This function also works for types within LinkField, like RelationField.

We would need the following helpers:

Some of these would be aliases (e.g. isColorField contains the same logic as isKeyTextFilled). Splitting logic like this also means unused functions can be tree-shaken.

Possible APIs:

Are we fine saying TitleField and RichTextField are empty when asText(field) === ""? What about RichTextField only containing an image (or embed)?

Good point, I didn't think about non-text values. I think this is sufficient to support empty Rich Text fields (and also supports Group and Slice Zone fields):

    // ...

    } else if (Array.isArray(field)) {
        if (field.length === 1 && "text" in field[0]) {
            // TitleField, RichTextField
            return !!field[0].text;
        } else {
            // GroupField, SliceZone
            return !!field.length;
        }
    } else if (typeof field === "object") {

    // ...

🐕

angeloashmore commented 2 years ago

TypeScript-supported version described above: https://github.com/prismicio/prismic-helpers/pull/35

lihbr commented 2 years ago

Good point, was testing some minor type inferences but didn't went in depth with those tbh (maybe a great time to start writing type test here)~

For the API, I'm fine with isColorFilled() or isFilled.color(), maybe the latter offers better self discovery through intellisense(?)

Is isFilledColor() more consistent with our exising API? (maybe it reads wrong haha "if this is a filled color, then...")

No hard opinion on it~

angeloashmore commented 2 years ago

I think isFilled.color() works well because of the discovery aspect you mentioned. It also doesn't pollute the top-level exports list with a bunch of isXFilled() helpers.