Files
start-os/sdk/base/lib/actions/input/builder/list.ts
Matt Hill d4e019c87b add comments to everything potentially consumer facing (#3127)
* add comments to everything potentially consumer facing

* rework smtp

---------

Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
2026-02-24 14:29:09 -07:00

222 lines
5.9 KiB
TypeScript

import { InputSpec, LazyBuild } from './inputSpec'
import {
ListValueSpecText,
Pattern,
RandomString,
UniqueBy,
ValueSpecList,
ValueSpecListOf,
} from '../inputSpecTypes'
import { z } from 'zod'
/**
* Builder class for defining list-type form fields.
*
* A list presents an interface to add, remove, and reorder items. Items can be
* either text strings ({@link List.text}) or structured objects ({@link List.obj}).
*
* Used with {@link Value.list} to include a list field in an {@link InputSpec}.
*/
export class List<
Type extends StaticValidatedAs,
StaticValidatedAs = Type,
OuterType = unknown,
> {
private constructor(
public build: LazyBuild<
{
spec: ValueSpecList
validator: z.ZodType<Type>
},
OuterType
>,
public readonly validator: z.ZodType<StaticValidatedAs>,
) {}
readonly _TYPE: Type = null as any
/**
* Creates a list of text input items.
*
* @param a - List-level options (name, description, min/max length, defaults)
* @param aSpec - Item-level options (patterns, input mode, masking, generation)
*/
static text(
a: {
name: string
description?: string | null
warning?: string | null
default?: string[]
minLength?: number | null
maxLength?: number | null
},
aSpec: {
/**
* @description Mask (aka camouflage) text input with dots: ● ● ●
* @default false
*/
masked?: boolean
placeholder?: string | null
minLength?: number | null
maxLength?: number | null
/**
* @description A list of regular expressions to which the text must conform to pass validation. A human readable description is provided in case the validation fails.
* @default []
* @example
* ```
[
{
regex: "[a-z]",
description: "May only contain lower case letters from the English alphabet."
}
]
* ```
*/
patterns?: Pattern[]
/**
* @description Informs the browser how to behave and which keyboard to display on mobile
* @default "text"
*/
inputmode?: ListValueSpecText['inputmode']
/**
* @description Displays a button that will generate a random string according to the provided charset and len attributes.
*/
generate?: null | RandomString
},
) {
const validator = z.array(z.string())
return new List<string[]>(() => {
const spec = {
type: 'text' as const,
placeholder: null,
minLength: null,
maxLength: null,
masked: false,
inputmode: 'text' as const,
generate: null,
patterns: aSpec.patterns || [],
...aSpec,
}
const built: ValueSpecListOf<'text'> = {
description: null,
warning: null,
default: [],
type: 'list' as const,
minLength: null,
maxLength: null,
disabled: false,
...a,
spec,
}
return { spec: built, validator }
}, validator)
}
/** Like {@link List.text} but options are resolved lazily at runtime via a builder function. */
static dynamicText<OuterType = unknown>(
getA: LazyBuild<
{
name: string
description?: string | null
warning?: string | null
default?: string[]
minLength?: number | null
maxLength?: number | null
disabled?: false | string
generate?: null | RandomString
spec: {
masked?: boolean
placeholder?: string | null
minLength?: number | null
maxLength?: number | null
patterns?: Pattern[]
inputmode?: ListValueSpecText['inputmode']
}
},
OuterType
>,
) {
const validator = z.array(z.string())
return new List<string[], string[], OuterType>(async (options) => {
const { spec: aSpec, ...a } = await getA(options)
const spec = {
type: 'text' as const,
placeholder: null,
minLength: null,
maxLength: null,
masked: false,
inputmode: 'text' as const,
generate: null,
patterns: aSpec.patterns || [],
...aSpec,
}
const built: ValueSpecListOf<'text'> = {
description: null,
warning: null,
default: [],
type: 'list' as const,
minLength: null,
maxLength: null,
disabled: false,
...a,
spec,
}
return { spec: built, validator }
}, validator)
}
/**
* Creates a list of structured object items, each defined by a nested {@link InputSpec}.
*
* @param a - List-level options (name, description, min/max length)
* @param aSpec - Item-level options (the nested spec, display expression, uniqueness constraint)
*/
static obj<
Type extends StaticValidatedAs,
StaticValidatedAs extends Record<string, any>,
>(
a: {
name: string
description?: string | null
warning?: string | null
default?: []
minLength?: number | null
maxLength?: number | null
},
aSpec: {
spec: InputSpec<Type, StaticValidatedAs>
displayAs?: null | string
uniqueBy?: null | UniqueBy
},
) {
return new List<Type[], StaticValidatedAs[]>(async (options) => {
const { spec: previousSpecSpec, ...restSpec } = aSpec
const built = await previousSpecSpec.build(options)
const spec = {
type: 'object' as const,
displayAs: null,
uniqueBy: null,
...restSpec,
spec: built.spec,
}
const value = {
spec,
default: [],
...a,
}
return {
spec: {
description: null,
warning: null,
minLength: null,
maxLength: null,
type: 'list' as const,
disabled: false,
...value,
},
validator: z.array(built.validator),
}
}, z.array(aSpec.spec.validator))
}
}