Skip to content

Commit

Permalink
refactor!: <SliceZone />
Browse files Browse the repository at this point in the history
  • Loading branch information
lihbr committed Dec 10, 2024
1 parent 8c59d56 commit 3325e86
Show file tree
Hide file tree
Showing 9 changed files with 858 additions and 1,078 deletions.
620 changes: 0 additions & 620 deletions src/SliceZone.ts

This file was deleted.

113 changes: 113 additions & 0 deletions src/SliceZone/SliceZone.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<script lang="ts" setup>
import { computed } from "vue"
import Wrapper from "../lib/Wrapper.vue"
import type { ComponentOrTagName } from "../types"
import type {
SliceComponentType,
SliceLike,
SliceZoneComponents,
SliceZoneLike,
} from "./types"
import { usePrismic } from "../usePrismic"
import { TODOSliceComponent } from "./TODOSliceComponent"
/**
* Props for `<SliceZone />`.
*
* @typeParam TContext - Arbitrary data made available to all Slice components
*/
export type SliceZoneProps<TContext = unknown> = {
/**
* List of Slice data from the Slice Zone.
*/
slices: SliceZoneLike<SliceLike & Record<string, unknown>>
/**
* A record mapping Slice types to Vue components.
*/
components?: SliceZoneComponents
/**
* The Vue component rendered if a component mapping from the `components`
* prop cannot be found.
*
* @remarks
* Components will be rendered using the {@link SliceComponentProps} interface.
*
* @defaultValue The Slice Zone default component provided to `@prismicio/vue` plugin if configured, otherwise `null` when `process.env.NODE_ENV === "production"` else {@link TODOSliceComponent}.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
defaultComponent?: SliceComponentType<any, TContext>
/**
* Arbitrary data made available to all Slice components.
*/
context?: TContext
/**
* An HTML tag name or a component used to wrap the output. `<SliceZone />` is
* not wrapped by default.
*
* @defaultValue `"template"` (no wrapper)
*/
wrapper?: ComponentOrTagName
}
const props = defineProps<SliceZoneProps>()
defineOptions({ name: "SliceZone" })
const { options } = usePrismic()
const renderedSlices = computed(() => {
return props.slices.map((slice, index) => {
const type =
"slice_type" in slice ? (slice.slice_type as string) : slice.type
const key =
"id" in slice && typeof slice.id === "string"
? slice.id
: `${index}-${JSON.stringify(slice)}`
const is =
props.components?.[type] ||
props.defaultComponent ||
options.components?.sliceZoneDefaultComponent
if (!is) {
return { is: TODOSliceComponent, key, props: { slice } }
}
if (slice.__mapped) {
const { __mapped, ...mappedProps } = slice
return { is, key, props: mappedProps }
}
return {
is,
key,
props: {
slice,
index,
context: props.context,
slices: props.slices,
},
}
})
})
</script>

<template>
<Wrapper v-if="slices" :wrapper="wrapper">
<Component
v-for="renderedSlice in renderedSlices"
:is="renderedSlice.is"
:key="renderedSlice.key"
v-bind="renderedSlice.props"
/>
</Wrapper>
</template>
49 changes: 49 additions & 0 deletions src/SliceZone/TODOSliceComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { PropType } from "vue"
import { computed, defineComponent, h, watchEffect } from "vue"

import type { SliceComponentType, SliceLike } from "./types"

/**
* This Slice component can be used as a reminder to provide a proper
* implementation.
*
* This is also the default Vue component rendered when a component mapping
* cannot be found in `<SliceZone />`.
*/
export const TODOSliceComponent =
typeof process !== "undefined" && process.env.NODE_ENV === "development"
? /*#__PURE__*/ (defineComponent({
name: "TODOSliceComponent",
props: {
slice: {
type: Object as PropType<SliceLike>,
required: true,
},
},
setup(props) {
const type = computed(() => {
return "slice_type" in props.slice
? props.slice.slice_type
: props.slice.type
})

watchEffect(() => {
console.warn(
`[SliceZone] Could not find a component for Slice type "${type.value}"`,
props.slice,
)
})

return () => {
return h(
"section",
{
"data-slice-zone-todo-component": "",
"data-slice-type": type.value,
},
[`Could not find a component for Slice type "${type.value}"`],
)
}
},
}) as SliceComponentType)
: ((() => null) as SliceComponentType)
67 changes: 67 additions & 0 deletions src/SliceZone/defineSliceZoneComponents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { markRaw } from "vue"

import type {
SliceComponentType,
SliceLike,
SliceZoneComponents,
} from "./types"

/**
* Gets an optimized record of Slice types mapped to Vue components. Each
* components will be rendered for each instance of their Slice type.
*
* @remarks
* This is essentially an helper function to ensure {@link markRaw} is correctly
* applied on each components, improving performances.
*
* @example
*
* ```javascript
* // Defining a slice components
* import { defineSliceZoneComponents } from "@prismicio/vue";
*
* export default {
* data() {
* components: defineSliceZoneComponents({
* foo: Foo,
* bar: defineAsyncComponent(
* () => new Promise((res) => res(Bar)),
* ),
* baz: "Baz",
* }),
* }
* };
* ```
*
* @typeParam TSlice - The type(s) of slices in the Slice Zone
* @typeParam TContext - Arbitrary data made available to all Slice components
*
* @param components - {@link SliceZoneComponents}
*
* @returns A new optimized record of {@link SliceZoneComponents}
*/
export const defineSliceZoneComponents = <
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TSlice extends SliceLike = any,
TContext = unknown,
>(
components: SliceZoneComponents<TSlice, TContext>,
): SliceZoneComponents<TSlice, TContext> => {
const result = {} as SliceZoneComponents<TSlice, TContext>

let type: keyof typeof components
for (type in components) {
const component = components[type]
result[type] =
typeof component === "string"
? component
: markRaw(
component as SliceComponentType<
Extract<TSlice, SliceLike<typeof type>>,
TContext
>,
)
}

return result
}
100 changes: 100 additions & 0 deletions src/SliceZone/getSliceComponentProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import type { PropType } from "vue"

import type { SliceComponentProps, SliceLike } from "./types"

/**
* Native Vue props for a component rendering content from a Prismic Slice using
* the `<SliceZone />` component.
*
* @typeParam TSlice - The Slice type
* @typeParam TContext - Arbitrary data passed to `<SliceZone />` and made
* available to all Slice components
*/
export type DefineComponentSliceComponentProps<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TSlice extends SliceLike = any,
TContext = unknown,
> = {
slice: {
type: PropType<SliceComponentProps<TSlice, TContext>["slice"]>
required: true
}
index: {
type: PropType<SliceComponentProps<TSlice, TContext>["index"]>
required: true
}
slices: {
type: PropType<SliceComponentProps<TSlice, TContext>["slices"]>
required: true
}
context: {
type: PropType<SliceComponentProps<TSlice, TContext>["context"]>
required: true
}
}

/**
* Gets native Vue props for a component rendering content from a Prismic Slice
* using the `<SliceZone />` component.
*
* Props are: `["slice", "index", "slices", "context"]`
*
* @example
*
* ```javascript
* // Defining a new slice component
* import { getSliceComponentProps } from "@prismicio/vue"
*
* export default {
* props: getSliceComponentProps(),
* }
* ```
*
* @example
*
* ```javascript
* // Defining a new slice component with visual hint
* import { getSliceComponentProps } from "@prismicio/vue"
*
* export default {
* props: getSliceComponentProps(["slice", "index", "slices", "context"]),
* }
* ```
*
* @typeParam TSlice - The Slice type
* @typeParam TContext - Arbitrary data passed to `<SliceZone />` and made
* available to all Slice components
*
* @param propsHint - An optional array of prop names used for the sole purpose
* of having a visual hint of which props are made available to the slice,
* this parameters doesn't have any effect
*
* @returns Props object to use with {@link defineComponent}
*/
export const getSliceComponentProps = <
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TSlice extends SliceLike = any,
TContext = unknown,
>(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
propsHint?: ["slice", "index", "slices", "context"],
): DefineComponentSliceComponentProps<TSlice, TContext> => ({
slice: {
type: Object as PropType<SliceComponentProps<TSlice, TContext>["slice"]>,
required: true,
},
index: {
type: Number as PropType<SliceComponentProps<TSlice, TContext>["index"]>,
required: true,
},
slices: {
type: Array as PropType<SliceComponentProps<TSlice, TContext>["slices"]>,
required: true,
},
context: {
type: null as unknown as PropType<
SliceComponentProps<TSlice, TContext>["context"]
>,
required: true,
},
})
16 changes: 16 additions & 0 deletions src/SliceZone/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export { TODOSliceComponent } from "./TODOSliceComponent"

export type { DefineComponentSliceComponentProps } from "./getSliceComponentProps"
export { getSliceComponentProps } from "./getSliceComponentProps"

export { defineSliceZoneComponents } from "./defineSliceZoneComponents"

export type {
SliceComponentProps,
SliceComponentType,
SliceLike,
SliceLikeGraphQL,
SliceLikeRestV2,
SliceZoneComponents,
SliceZoneLike,
} from "./types"
Loading

0 comments on commit 3325e86

Please sign in to comment.