@prismicio/types-internal
Version:
Prismic types for Custom Types and Prismic Data
185 lines (168 loc) • 4.54 kB
text/typescript
import * as t from "io-ts"
import type { OnFieldFn } from "../../../_internal/utils"
import { WidgetKey } from "../../../common"
import { StringOrNull } from "../../../validators"
import { Group, NestedGroup, traverseNestedGroup } from "../Group"
import type { NestableWidget } from "../nestable"
import { CompositeSlice, traverseCompositeSlice } from "./CompositeSlice"
import { LegacySlice } from "./LegacySlice"
import { SharedSlice, traverseSharedSlice } from "./SharedSlice"
import { SharedSliceRef } from "./SharedSliceRef"
export const LegacySlicesFieldType = "Choice"
export const SlicesFieldType = "Slices"
export const SlicesLabels = t.union([
t.record(
t.string,
t.readonlyArray(
t.exact(
t.intersection([
t.type({
name: t.string,
}),
t.partial({
display: t.string,
}),
]),
),
),
),
t.null,
])
export type SlicesLabels = t.TypeOf<typeof SlicesLabels>
export function slicesConfigReader<T extends SharedSlice | SharedSliceRef>(
codec: t.Type<T, unknown>,
) {
return t.exact(
t.partial({
label: StringOrNull,
labels: SlicesLabels,
choices: t.record(
WidgetKey,
t.union([LegacySlice, CompositeSlice, codec]),
),
}),
)
}
export const StaticSlicesConfig = slicesConfigReader(SharedSlice)
export type StaticSlicesConfig = t.TypeOf<typeof StaticSlicesConfig>
export const DynamicSlicesConfig = slicesConfigReader(SharedSliceRef)
export type DynamicSlicesConfig = t.TypeOf<typeof DynamicSlicesConfig>
const SlicesConfig = {
toStatic(
config: DynamicSlicesConfig,
sharedSlices: Map<string, SharedSlice>,
): StaticSlicesConfig {
const choices: {
[key: string]: LegacySlice | CompositeSlice | SharedSlice
} = Object.entries(config.choices || {}).reduce((acc, [ref, slice]) => {
if (slice.type === "SharedSlice") {
const sharedSlice = sharedSlices.get(ref)
if (sharedSlice) return { ...acc, [ref]: sharedSlice }
else return acc
} else {
return { ...acc, [ref]: slice }
}
}, {})
return { ...config, choices } as StaticSlicesConfig
},
}
export function slicesReader<
T extends StaticSlicesConfig | DynamicSlicesConfig,
>(codec: t.Type<T, unknown>) {
return t.exact(
t.intersection([
t.type({
type: t.keyof({
[SlicesFieldType]: null,
[LegacySlicesFieldType]: null,
}),
}),
t.partial({
fieldset: StringOrNull,
config: codec,
}),
]),
)
}
export const StaticSlices = slicesReader(StaticSlicesConfig)
export type StaticSlices = t.TypeOf<typeof StaticSlices>
export const DynamicSlices = slicesReader(DynamicSlicesConfig)
export type DynamicSlices = t.TypeOf<typeof DynamicSlices>
export const Slices = {
toStatic(
slices: DynamicSlices,
sharedSlices: Map<string, SharedSlice>,
): StaticSlices {
if (!slices.config) return slices as StaticSlices
else {
return {
...slices,
config: SlicesConfig.toStatic(slices.config, sharedSlices),
}
}
},
}
export function traverseSlices<T extends DynamicSlices | StaticSlices>(args: {
path: ReadonlyArray<string>
slices: T
onField: OnFieldFn<NestableWidget | Group | NestedGroup>
}): T {
const { path: prevPath, slices, onField } = args
if (!slices.config?.choices) return slices
let choices: Record<string, typeof slices.config.choices[string]> | undefined
for (const [key, prevModel] of Object.entries(slices.config.choices)) {
const path = [...prevPath, key]
let model
switch (prevModel.type) {
case "Slice":
model = traverseCompositeSlice({
path,
slice: prevModel,
onField: onField as OnFieldFn<NestableWidget>,
})
break
case "SharedSlice":
if ("variations" in prevModel)
model = traverseSharedSlice({
path,
slice: prevModel as SharedSlice,
onField,
})
else model = prevModel
break
// Group and other fields are technically possible because of legacy slices.
case "Group":
model = onField({
path,
key,
field: traverseNestedGroup({
path,
group: prevModel,
onField: onField as OnFieldFn<NestableWidget>,
}),
})
break
default:
model = onField({
path,
key,
field: prevModel,
})
break
}
if (model !== prevModel) {
if (!choices) choices = { ...slices.config.choices }
choices[key] = model as typeof slices.config.choices[string]
}
}
// returns the traversed model instead of a new one if it didn't change
return choices
? {
...slices,
config: {
...slices.config,
choices,
},
}
: slices
}