@prismicio/vue 3.0.0-beta.4 → 3.0.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,9 +19,10 @@ import {
19
19
  isFilled,
20
20
  } from "@prismicio/helpers";
21
21
 
22
+ import { simplyResolveComponent } from "../lib/simplyResolveComponent";
23
+ import { __PRODUCTION__ } from "../lib/__PRODUCTION__";
22
24
  import { usePrismic } from "../usePrismic";
23
25
  import { VueUseOptions } from "../types";
24
- import { simplyResolveComponent } from "../lib/simplyResolveComponent";
25
26
 
26
27
  /**
27
28
  * The default component rendered for images.
@@ -59,8 +60,8 @@ export type PrismicImageProps = {
59
60
  * Adds an additional `srcset` attribute to the image following given widths.
60
61
  *
61
62
  * @remarks
62
- * A special value of `"auto"` is accepted to automatically use image widths
63
- * coming from the API.
63
+ * A special value of `"thumbnails"` is accepted to automatically use image
64
+ * widths coming from the API.
64
65
  * @remarks
65
66
  * A special value of `"defaults"` is accepted to automatically use image
66
67
  * widths coming from the plugin configuration.
@@ -70,7 +71,7 @@ export type PrismicImageProps = {
70
71
  */
71
72
  widths?:
72
73
  | NonNullable<Parameters<typeof asImageWidthSrcSet>[1]>["widths"]
73
- | "auto"
74
+ | "thumbnails"
74
75
  | "defaults";
75
76
 
76
77
  /**
@@ -149,30 +150,20 @@ export const usePrismicImage = (
149
150
  const pixelDensities = unref(props.pixelDensities);
150
151
 
151
152
  if (widths) {
152
- if (pixelDensities) {
153
+ if (!__PRODUCTION__ && pixelDensities) {
153
154
  console.warn(
154
- "[PrismicImage] `widths` and `pixelDensities` props should not be use alongside each others, only `widths` will be applied",
155
+ "[PrismicImage] Only one of `widths` or `pixelDensities` props can be provided. You can resolve this warning by removing either the `widths` or `pixelDensities` prop. `widths` will be used in this case.",
155
156
  props,
156
157
  );
157
158
  }
158
159
 
159
- if (widths === "auto") {
160
- return asImageWidthSrcSet(field, imgixParams);
161
- } else {
162
- // Remove potential thumbnails when using manual widths
163
- const { url, dimensions, alt, copyright } = field;
164
-
165
- return asImageWidthSrcSet(
166
- { url, dimensions, alt, copyright },
167
- {
168
- ...imgixParams,
169
- widths:
170
- widths === "defaults"
171
- ? options.components?.imageWidthSrcSetDefaults
172
- : widths,
173
- },
174
- );
175
- }
160
+ return asImageWidthSrcSet(field, {
161
+ ...imgixParams,
162
+ widths:
163
+ widths === "defaults"
164
+ ? options.components?.imageWidthSrcSetDefaults
165
+ : widths,
166
+ });
176
167
  } else if (pixelDensities) {
177
168
  return asImagePixelDensitySrcSet(field, {
178
169
  ...imgixParams,
@@ -235,7 +226,7 @@ export const PrismicImageImpl = /*#__PURE__*/ defineComponent({
235
226
  widths: {
236
227
  type: [String, Object] as PropType<
237
228
  | NonNullable<Parameters<typeof asImageWidthSrcSet>[1]>["widths"]
238
- | "auto"
229
+ | "thumbnails"
239
230
  | "defaults"
240
231
  >,
241
232
  default: undefined,
@@ -22,6 +22,7 @@ import {
22
22
  asHTML,
23
23
  HTMLFunctionSerializer,
24
24
  HTMLMapSerializer,
25
+ isFilled,
25
26
  LinkResolverFunction,
26
27
  } from "@prismicio/helpers";
27
28
  import { RichTextField } from "@prismicio/types";
@@ -43,7 +44,7 @@ export type PrismicRichTextProps = {
43
44
  /**
44
45
  * The Prismic rich text or title field to render.
45
46
  */
46
- field: RichTextField;
47
+ field: RichTextField | null | undefined;
47
48
 
48
49
  /**
49
50
  * A link resolver function used to resolve link when not using the route
@@ -70,6 +71,12 @@ export type PrismicRichTextProps = {
70
71
  * @defaultValue `"div"`
71
72
  */
72
73
  wrapper?: string | ConcreteComponent;
74
+
75
+ /**
76
+ * The HTML value to be rendered when the field is empty. If a fallback is not
77
+ * given, `""` (nothing) will be rendered.
78
+ */
79
+ fallback?: string;
73
80
  };
74
81
 
75
82
  /**
@@ -102,12 +109,17 @@ export const usePrismicRichText = (
102
109
  const { options } = usePrismic();
103
110
 
104
111
  const html = computed(() => {
112
+ const field = unref(props.field);
113
+
114
+ if (!isFilled.richText(field)) {
115
+ return unref(props.fallback) ?? "";
116
+ }
117
+
105
118
  const linkResolver = unref(props.linkResolver) ?? options.linkResolver;
106
119
  const htmlSerializer =
107
120
  unref(props.htmlSerializer) ?? options.htmlSerializer;
108
121
 
109
- // TODO: Update after https://github.com/prismicio/prismic-helpers/pull/43
110
- return asHTML(unref(props.field), linkResolver, htmlSerializer) || "";
122
+ return asHTML(unref(field), linkResolver, htmlSerializer);
111
123
  });
112
124
 
113
125
  return {
@@ -124,7 +136,7 @@ export const PrismicRichTextImpl = /*#__PURE__*/ defineComponent({
124
136
  name: "PrismicRichText",
125
137
  props: {
126
138
  field: {
127
- type: Array as unknown as PropType<RichTextField>,
139
+ type: Array as unknown as PropType<RichTextField | null | undefined>,
128
140
  required: true,
129
141
  },
130
142
  linkResolver: {
@@ -144,19 +156,19 @@ export const PrismicRichTextImpl = /*#__PURE__*/ defineComponent({
144
156
  default: undefined,
145
157
  required: false,
146
158
  },
159
+ fallback: {
160
+ type: String as PropType<string>,
161
+ default: undefined,
162
+ required: false,
163
+ },
147
164
  },
148
165
  setup(props) {
149
- // Prevent fatal if user didn't check for field, throws `Invalid prop` warn
150
- if (!props.field) {
151
- return () => null;
152
- }
153
-
154
166
  const { html } = usePrismicRichText(props);
155
167
 
156
168
  const root = ref<HTMLElement | Comment | Component | null>(null);
157
169
 
158
170
  const maybeRouter = inject(routerKey, null);
159
- if (maybeRouter) {
171
+ if (maybeRouter && html.value) {
160
172
  type InternalLink = {
161
173
  element: HTMLAnchorElement;
162
174
  listener: EventListener;
@@ -12,7 +12,7 @@ import {
12
12
  VNodeProps,
13
13
  } from "vue";
14
14
 
15
- import { asText } from "@prismicio/helpers";
15
+ import { asText, isFilled } from "@prismicio/helpers";
16
16
  import { RichTextField } from "@prismicio/types";
17
17
 
18
18
  import { VueUseOptions } from "../types";
@@ -29,7 +29,7 @@ export type PrismicTextProps = {
29
29
  /**
30
30
  * The Prismic rich text or title field to render.
31
31
  */
32
- field: RichTextField;
32
+ field: RichTextField | null | undefined;
33
33
 
34
34
  /**
35
35
  * Separator used to join each element.
@@ -44,6 +44,12 @@ export type PrismicTextProps = {
44
44
  * @defaultValue `"div"`
45
45
  */
46
46
  wrapper?: string | ConcreteComponent;
47
+
48
+ /**
49
+ * The string value to be rendered when the field is empty. If a fallback is
50
+ * not given, `""` (nothing) will be rendered.
51
+ */
52
+ fallback?: string;
47
53
  };
48
54
 
49
55
  /**
@@ -74,8 +80,13 @@ export const usePrismicText = (
74
80
  props: UsePrismicTextOptions,
75
81
  ): UsePrismicTextReturnType => {
76
82
  const text = computed(() => {
77
- // TODO: Update after https://github.com/prismicio/prismic-helpers/pull/43
78
- return asText(unref(props.field), unref(props.separator)) || "";
83
+ const field = unref(props.field);
84
+
85
+ if (!isFilled.richText(field)) {
86
+ return unref(props.fallback) ?? "";
87
+ }
88
+
89
+ return asText(unref(field), unref(props.separator));
79
90
  });
80
91
 
81
92
  return {
@@ -92,7 +103,7 @@ export const PrismicTextImpl = /*#__PURE__*/ defineComponent({
92
103
  name: "PrismicText",
93
104
  props: {
94
105
  field: {
95
- type: Array as unknown as PropType<RichTextField>,
106
+ type: Array as unknown as PropType<RichTextField | null | undefined>,
96
107
  required: true,
97
108
  },
98
109
  separator: {
@@ -105,13 +116,13 @@ export const PrismicTextImpl = /*#__PURE__*/ defineComponent({
105
116
  default: undefined,
106
117
  required: false,
107
118
  },
119
+ fallback: {
120
+ type: String as PropType<string>,
121
+ default: undefined,
122
+ required: false,
123
+ },
108
124
  },
109
125
  setup(props) {
110
- // Prevent fatal if user didn't check for field, throws `Invalid prop` warn
111
- if (!props.field) {
112
- return () => null;
113
- }
114
-
115
126
  const { text } = usePrismicText(props);
116
127
 
117
128
  return () => {
@@ -19,6 +19,40 @@ import { simplyResolveComponent } from "../lib/simplyResolveComponent";
19
19
  import { __PRODUCTION__ } from "../lib/__PRODUCTION__";
20
20
  import { usePrismic } from "../usePrismic";
21
21
 
22
+ /**
23
+ * Returns the type of a `SliceLike` type.
24
+ *
25
+ * @typeParam TSlice - The Slice from which the type will be extracted.
26
+ */
27
+ type ExtractSliceType<TSlice extends SliceLike> = TSlice extends SliceLikeRestV2
28
+ ? TSlice["slice_type"]
29
+ : TSlice extends SliceLikeGraphQL
30
+ ? TSlice["type"]
31
+ : never;
32
+
33
+ /**
34
+ * The minimum required properties to represent a Prismic Slice from the Prismic
35
+ * Rest API V2 for the `<SliceZone>` component.
36
+ *
37
+ * If using Prismic's Rest API V2, use the `Slice` export from
38
+ * `@prismicio/types` for a full interface.
39
+ *
40
+ * @typeParam TSliceType - Type name of the Slice.
41
+ */
42
+ export type SliceLikeRestV2<TSliceType extends string = string> = {
43
+ slice_type: Slice<TSliceType>["slice_type"];
44
+ };
45
+
46
+ /**
47
+ * The minimum required properties to represent a Prismic Slice from the Prismic
48
+ * GraphQL API for the `<SliceZone>` component.
49
+ *
50
+ * @typeParam TSliceType - Type name of the Slice.
51
+ */
52
+ export type SliceLikeGraphQL<TSliceType extends string = string> = {
53
+ type: Slice<TSliceType>["slice_type"];
54
+ };
55
+
22
56
  /**
23
57
  * The minimum required properties to represent a Prismic Slice for the
24
58
  * `<SliceZone />` component.
@@ -28,10 +62,9 @@ import { usePrismic } from "../usePrismic";
28
62
  *
29
63
  * @typeParam TSliceType - Type name of the Slice
30
64
  */
31
- export type SliceLike<TSliceType extends string = string> = Pick<
32
- Slice<TSliceType>,
33
- "slice_type"
34
- >;
65
+ export type SliceLike<TSliceType extends string = string> =
66
+ | SliceLikeRestV2<TSliceType>
67
+ | SliceLikeGraphQL<TSliceType>;
35
68
 
36
69
  /**
37
70
  * A looser version of the `SliceZone` type from `@prismicio/types` using `SliceLike`.
@@ -41,7 +74,8 @@ export type SliceLike<TSliceType extends string = string> = Pick<
41
74
  *
42
75
  * @typeParam TSlice - The type(s) of slices in the Slice Zone
43
76
  */
44
- export type SliceZoneLike<TSlice extends SliceLike> = readonly TSlice[];
77
+ export type SliceZoneLike<TSlice extends SliceLike = SliceLike> =
78
+ readonly TSlice[];
45
79
 
46
80
  /**
47
81
  * Vue props for a component rendering content from a Prismic Slice using the
@@ -52,7 +86,8 @@ export type SliceZoneLike<TSlice extends SliceLike> = readonly TSlice[];
52
86
  * available to all Slice components
53
87
  */
54
88
  export type SliceComponentProps<
55
- TSlice extends SliceLike = SliceLike,
89
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
90
+ TSlice extends SliceLike = any,
56
91
  TContext = unknown,
57
92
  > = {
58
93
  /**
@@ -178,7 +213,8 @@ export const getSliceComponentProps = <
178
213
  * @typeParam TContext - Arbitrary data made available to all Slice components
179
214
  */
180
215
  export type SliceComponentType<
181
- TSlice extends SliceLike = SliceLike,
216
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
+ TSlice extends SliceLike = any,
182
218
  TContext = unknown,
183
219
  > =
184
220
  | DefineComponent<SliceComponentProps<TSlice, TContext>>
@@ -196,9 +232,15 @@ export const TODOSliceComponent = __PRODUCTION__
196
232
  name: "TODOSliceComponent",
197
233
  props: getSliceComponentProps(),
198
234
  setup(props) {
235
+ const type = computed(() =>
236
+ "slice_type" in props.slice
237
+ ? props.slice.slice_type
238
+ : props.slice.type,
239
+ );
240
+
199
241
  watchEffect(() => {
200
242
  console.warn(
201
- `[SliceZone] Could not find a component for Slice type "${props.slice.slice_type}"`,
243
+ `[SliceZone] Could not find a component for Slice type "${type.value}"`,
202
244
  props.slice,
203
245
  );
204
246
  });
@@ -208,11 +250,9 @@ export const TODOSliceComponent = __PRODUCTION__
208
250
  "section",
209
251
  {
210
252
  "data-slice-zone-todo-component": "",
211
- "data-slice-type": props.slice.slice_type,
253
+ "data-slice-type": type.value,
212
254
  },
213
- [
214
- `Could not find a component for Slice type "${props.slice.slice_type}"`,
215
- ],
255
+ [`Could not find a component for Slice type "${type.value}"`],
216
256
  );
217
257
  };
218
258
  },
@@ -239,7 +279,7 @@ export type SliceZoneComponents<
239
279
  // signals to future developers that it is a placeholder and should be
240
280
  // implemented.
241
281
  {
242
- [SliceType in keyof Record<TSlice["slice_type"], never>]:
282
+ [SliceType in ExtractSliceType<TSlice>]:
243
283
  | SliceComponentType<Extract<TSlice, SliceLike<SliceType>>, TContext>
244
284
  | string;
245
285
  };
@@ -313,7 +353,7 @@ export type SliceZoneResolverArgs<TSlice extends SliceLike = SliceLike> = {
313
353
  /**
314
354
  * The name of the Slice.
315
355
  */
316
- sliceName: TSlice["slice_type"];
356
+ sliceName: ExtractSliceType<TSlice>;
317
357
 
318
358
  /**
319
359
  * The index of the Slice in the Slice Zone.
@@ -333,26 +373,24 @@ export type SliceZoneResolverArgs<TSlice extends SliceLike = SliceLike> = {
333
373
  * @returns The Vue component to render for a Slice.
334
374
  */
335
375
  export type SliceZoneResolver<
336
- TSlice extends SliceLike = SliceLike,
376
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
377
+ TSlice extends SliceLike = any,
337
378
  TContext = unknown,
338
379
  > = (
339
380
  args: SliceZoneResolverArgs<TSlice>,
340
- ) => SliceComponentType<TSlice, TContext> | string | undefined | null;
381
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
382
+ ) => SliceComponentType<any, TContext> | string | undefined | null;
341
383
 
342
384
  /**
343
385
  * Props for `<SliceZone />`.
344
386
  *
345
- * @typeParam TSlice - The type(s) of slices in the Slice Zone
346
387
  * @typeParam TContext - Arbitrary data made available to all Slice components
347
388
  */
348
- export type SliceZoneProps<
349
- TSlice extends SliceLike = SliceLike,
350
- TContext = unknown,
351
- > = {
389
+ export type SliceZoneProps<TContext = unknown> = {
352
390
  /**
353
391
  * List of Slice data from the Slice Zone.
354
392
  */
355
- slices: SliceZoneLike<TSlice>;
393
+ slices: SliceZoneLike;
356
394
 
357
395
  /**
358
396
  * A record mapping Slice types to Vue components.
@@ -369,7 +407,8 @@ export type SliceZoneProps<
369
407
  *
370
408
  * @returns The Vue component to render for a Slice.
371
409
  */
372
- resolver?: SliceZoneResolver<TSlice, TContext>;
410
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
411
+ resolver?: SliceZoneResolver<any, TContext>;
373
412
 
374
413
  /**
375
414
  * Arbitrary data made available to all Slice components.
@@ -385,7 +424,8 @@ export type SliceZoneProps<
385
424
  *
386
425
  * @defaultValue The Slice Zone default component provided to `@prismicio/vue` plugin if configured, otherwise `null` when `process.env.NODE_ENV === "production"` else {@link TODOSliceComponent}.
387
426
  */
388
- defaultComponent?: SliceComponentType<TSlice, TContext>;
427
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
428
+ defaultComponent?: SliceComponentType<any, TContext>;
389
429
 
390
430
  /**
391
431
  * An HTML tag name, a component, or a functional component used to wrap the
@@ -403,7 +443,7 @@ export const SliceZoneImpl = /*#__PURE__*/ defineComponent({
403
443
  name: "SliceZone",
404
444
  props: {
405
445
  slices: {
406
- type: Array as PropType<SliceZoneLike<SliceLike>>,
446
+ type: Array as PropType<SliceZoneLike>,
407
447
  required: true,
408
448
  },
409
449
  components: {
@@ -442,9 +482,11 @@ export const SliceZoneImpl = /*#__PURE__*/ defineComponent({
442
482
 
443
483
  const renderedSlices = computed(() => {
444
484
  return props.slices.map((slice, index) => {
485
+ const type = "slice_type" in slice ? slice.slice_type : slice.type;
486
+
445
487
  let component =
446
- props.components && slice.slice_type in props.components
447
- ? props.components[slice.slice_type]
488
+ props.components && type in props.components
489
+ ? props.components[type]
448
490
  : props.defaultComponent ||
449
491
  options.components?.sliceZoneDefaultComponent ||
450
492
  TODOSliceComponent;
@@ -453,7 +495,7 @@ export const SliceZoneImpl = /*#__PURE__*/ defineComponent({
453
495
  if (props.resolver) {
454
496
  const resolvedComponent = props.resolver({
455
497
  slice,
456
- sliceName: slice.slice_type,
498
+ sliceName: type,
457
499
  i: index,
458
500
  });
459
501
 
@@ -463,7 +505,7 @@ export const SliceZoneImpl = /*#__PURE__*/ defineComponent({
463
505
  }
464
506
 
465
507
  const p = {
466
- key: `${slice.slice_type}-${index}`,
508
+ key: `${index}-${JSON.stringify(slice)}`,
467
509
  slice,
468
510
  index,
469
511
  context: props.context,
@@ -35,6 +35,8 @@ export type {
35
35
  DefineComponentSliceComponentProps,
36
36
  SliceComponentProps,
37
37
  SliceComponentType,
38
+ SliceLikeRestV2,
39
+ SliceLikeGraphQL,
38
40
  SliceLike,
39
41
  SliceZoneComponents,
40
42
  SliceZoneResolverArgs,
package/src/index.ts CHANGED
@@ -35,6 +35,8 @@ export type {
35
35
  DefineComponentSliceComponentProps,
36
36
  SliceComponentProps,
37
37
  SliceComponentType,
38
+ SliceLikeRestV2,
39
+ SliceLikeGraphQL,
38
40
  SliceLike,
39
41
  SliceZoneComponents,
40
42
  SliceZoneResolver,
@@ -74,6 +74,10 @@
74
74
  "type": "string | object | function",
75
75
  "description": "An HTML tag name, a component, or a functional component used to wrap the output. Defaults to `\"div\"`."
76
76
  },
77
+ "prismic-rich-text/fallback": {
78
+ "type": "string",
79
+ "description": "The HTML value to be rendered when the field is empty. If a fallback is not given, `\"\"` (nothing) will be rendered."
80
+ },
77
81
 
78
82
  "prismic-text/field": {
79
83
  "type": "array",
@@ -87,6 +91,10 @@
87
91
  "type": "string | object | function",
88
92
  "description": "An HTML tag name, a component, or a functional component used to wrap the output. Defaults to `\"div\"`."
89
93
  },
94
+ "prismic-text/fallback": {
95
+ "type": "string",
96
+ "description": "The string value to be rendered when the field is empty. If a fallback is not given, `\"\"` (nothing) will be rendered."
97
+ },
90
98
 
91
99
  "slice-zone/slices": {
92
100
  "type": "array",
package/vetur/tags.json CHANGED
@@ -26,11 +26,11 @@
26
26
  "description": "Component to render a Prismic link field."
27
27
  },
28
28
  "prismic-rich-text": {
29
- "attributes": ["field", "link-resolver", "html-serializer", "wrapper"],
29
+ "attributes": ["field", "link-resolver", "html-serializer", "wrapper", "fallback"],
30
30
  "description": "Component to render a Prismic rich text field as HTML."
31
31
  },
32
32
  "prismic-text": {
33
- "attributes": ["field", "separator", "wrapper"],
33
+ "attributes": ["field", "separator", "wrapper", "fallback"],
34
34
  "description": "Component to render a Prismic rich text field as plain text."
35
35
  },
36
36
  "slice-zone": {