@wordpress/media-fields 0.1.1-next.6deb34194.0

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.
Files changed (117) hide show
  1. package/LICENSE.md +788 -0
  2. package/README.md +52 -0
  3. package/build/alt_text/index.js +51 -0
  4. package/build/alt_text/index.js.map +7 -0
  5. package/build/caption/index.js +52 -0
  6. package/build/caption/index.js.map +7 -0
  7. package/build/description/index.js +52 -0
  8. package/build/description/index.js.map +7 -0
  9. package/build/filename/index.js +50 -0
  10. package/build/filename/index.js.map +7 -0
  11. package/build/filename/view.js +43 -0
  12. package/build/filename/view.js.map +7 -0
  13. package/build/filesize/index.js +90 -0
  14. package/build/filesize/index.js.map +7 -0
  15. package/build/index.js +62 -0
  16. package/build/index.js.map +7 -0
  17. package/build/media_dimensions/index.js +45 -0
  18. package/build/media_dimensions/index.js.map +7 -0
  19. package/build/media_thumbnail/index.js +47 -0
  20. package/build/media_thumbnail/index.js.map +7 -0
  21. package/build/media_thumbnail/view.js +94 -0
  22. package/build/media_thumbnail/view.js.map +7 -0
  23. package/build/mime_type/index.js +38 -0
  24. package/build/mime_type/index.js.map +7 -0
  25. package/build/types.js +19 -0
  26. package/build/types.js.map +7 -0
  27. package/build/utils/get-media-type-from-mime-type.js +60 -0
  28. package/build/utils/get-media-type-from-mime-type.js.map +7 -0
  29. package/build/utils/get-raw-content.js +42 -0
  30. package/build/utils/get-raw-content.js.map +7 -0
  31. package/build/utils/get-rendered-content.js +42 -0
  32. package/build/utils/get-rendered-content.js.map +7 -0
  33. package/build-module/alt_text/index.js +30 -0
  34. package/build-module/alt_text/index.js.map +7 -0
  35. package/build-module/caption/index.js +31 -0
  36. package/build-module/caption/index.js.map +7 -0
  37. package/build-module/description/index.js +31 -0
  38. package/build-module/description/index.js.map +7 -0
  39. package/build-module/filename/index.js +19 -0
  40. package/build-module/filename/index.js.map +7 -0
  41. package/build-module/filename/view.js +25 -0
  42. package/build-module/filename/view.js.map +7 -0
  43. package/build-module/filesize/index.js +69 -0
  44. package/build-module/filesize/index.js.map +7 -0
  45. package/build-module/index.js +20 -0
  46. package/build-module/index.js.map +7 -0
  47. package/build-module/media_dimensions/index.js +24 -0
  48. package/build-module/media_dimensions/index.js.map +7 -0
  49. package/build-module/media_thumbnail/index.js +16 -0
  50. package/build-module/media_thumbnail/index.js.map +7 -0
  51. package/build-module/media_thumbnail/view.js +77 -0
  52. package/build-module/media_thumbnail/view.js.map +7 -0
  53. package/build-module/mime_type/index.js +17 -0
  54. package/build-module/mime_type/index.js.map +7 -0
  55. package/build-module/types.js +1 -0
  56. package/build-module/types.js.map +7 -0
  57. package/build-module/utils/get-media-type-from-mime-type.js +35 -0
  58. package/build-module/utils/get-media-type-from-mime-type.js.map +7 -0
  59. package/build-module/utils/get-raw-content.js +17 -0
  60. package/build-module/utils/get-raw-content.js.map +7 -0
  61. package/build-module/utils/get-rendered-content.js +17 -0
  62. package/build-module/utils/get-rendered-content.js.map +7 -0
  63. package/build-style/style-rtl.css +130 -0
  64. package/build-style/style.css +130 -0
  65. package/build-types/alt_text/index.d.ts +5 -0
  66. package/build-types/alt_text/index.d.ts.map +1 -0
  67. package/build-types/caption/index.d.ts +5 -0
  68. package/build-types/caption/index.d.ts.map +1 -0
  69. package/build-types/description/index.d.ts +5 -0
  70. package/build-types/description/index.d.ts.map +1 -0
  71. package/build-types/filename/index.d.ts +8 -0
  72. package/build-types/filename/index.d.ts.map +1 -0
  73. package/build-types/filename/view.d.ts +7 -0
  74. package/build-types/filename/view.d.ts.map +1 -0
  75. package/build-types/filesize/index.d.ts +8 -0
  76. package/build-types/filesize/index.d.ts.map +1 -0
  77. package/build-types/index.d.ts +10 -0
  78. package/build-types/index.d.ts.map +1 -0
  79. package/build-types/media_dimensions/index.d.ts +5 -0
  80. package/build-types/media_dimensions/index.d.ts.map +1 -0
  81. package/build-types/media_thumbnail/index.d.ts +8 -0
  82. package/build-types/media_thumbnail/index.d.ts.map +1 -0
  83. package/build-types/media_thumbnail/view.d.ts +4 -0
  84. package/build-types/media_thumbnail/view.d.ts.map +1 -0
  85. package/build-types/mime_type/index.d.ts +5 -0
  86. package/build-types/mime_type/index.d.ts.map +1 -0
  87. package/build-types/stories/index.story.d.ts +25 -0
  88. package/build-types/stories/index.story.d.ts.map +1 -0
  89. package/build-types/types.d.ts +18 -0
  90. package/build-types/types.d.ts.map +1 -0
  91. package/build-types/utils/get-media-type-from-mime-type.d.ts +18 -0
  92. package/build-types/utils/get-media-type-from-mime-type.d.ts.map +1 -0
  93. package/build-types/utils/get-raw-content.d.ts +17 -0
  94. package/build-types/utils/get-raw-content.d.ts.map +1 -0
  95. package/build-types/utils/get-rendered-content.d.ts +17 -0
  96. package/build-types/utils/get-rendered-content.d.ts.map +1 -0
  97. package/package.json +61 -0
  98. package/src/alt_text/index.tsx +30 -0
  99. package/src/caption/index.tsx +35 -0
  100. package/src/description/index.tsx +37 -0
  101. package/src/filename/index.ts +26 -0
  102. package/src/filename/view.tsx +39 -0
  103. package/src/filesize/index.tsx +96 -0
  104. package/src/index.ts +15 -0
  105. package/src/media_dimensions/index.ts +29 -0
  106. package/src/media_thumbnail/index.tsx +22 -0
  107. package/src/media_thumbnail/style.scss +49 -0
  108. package/src/media_thumbnail/view.tsx +104 -0
  109. package/src/mime_type/index.ts +19 -0
  110. package/src/stories/index.story.tsx +290 -0
  111. package/src/style.scss +1 -0
  112. package/src/types.ts +24 -0
  113. package/src/utils/get-media-type-from-mime-type.ts +54 -0
  114. package/src/utils/get-raw-content.ts +32 -0
  115. package/src/utils/get-rendered-content.ts +32 -0
  116. package/tsconfig.json +31 -0
  117. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useSelect } from '@wordpress/data';
5
+ import { store as coreStore } from '@wordpress/core-data';
6
+ import {
7
+ __experimentalTruncate as Truncate,
8
+ __experimentalVStack as VStack,
9
+ Icon,
10
+ } from '@wordpress/components';
11
+ import type { Attachment } from '@wordpress/core-data';
12
+ import { getFilename } from '@wordpress/url';
13
+ import type { DataViewRenderFieldProps } from '@wordpress/dataviews';
14
+ /**
15
+ * Internal dependencies
16
+ */
17
+ import { getMediaTypeFromMimeType } from '../utils/get-media-type-from-mime-type';
18
+ import type { MediaItem } from '../types';
19
+
20
+ export default function MediaThumbnailView( {
21
+ item,
22
+ config,
23
+ }: DataViewRenderFieldProps< MediaItem > ) {
24
+ const _featuredMedia = useSelect(
25
+ ( select ) => {
26
+ // Avoid the network request if it's not needed. `featured_media` is
27
+ // 0 for images and media without featured media.
28
+ if ( ! item.featured_media ) {
29
+ return;
30
+ }
31
+ return select( coreStore ).getEntityRecord< Attachment >(
32
+ 'postType',
33
+ 'attachment',
34
+ item.featured_media
35
+ );
36
+ },
37
+ [ item.featured_media ]
38
+ );
39
+ const featuredMedia = item.featured_media ? _featuredMedia : item;
40
+
41
+ // Fetching.
42
+ if ( ! featuredMedia ) {
43
+ return null;
44
+ }
45
+
46
+ const filename = getFilename( featuredMedia.source_url || '' );
47
+
48
+ if (
49
+ // Ensure the featured media is an image.
50
+ getMediaTypeFromMimeType( featuredMedia.mime_type ).type === 'image'
51
+ ) {
52
+ return (
53
+ <div className="dataviews-media-field__media-thumbnail">
54
+ <img
55
+ className="dataviews-media-field__media-thumbnail--image"
56
+ src={ featuredMedia.source_url }
57
+ srcSet={
58
+ featuredMedia?.media_details?.sizes
59
+ ? (
60
+ Object.values(
61
+ featuredMedia.media_details.sizes
62
+ ) as Array< {
63
+ source_url: string;
64
+ width: number;
65
+ } >
66
+ )
67
+ .map(
68
+ ( size ) =>
69
+ `${ size.source_url } ${ size.width }w`
70
+ )
71
+ .join( ', ' )
72
+ : undefined
73
+ }
74
+ sizes={ config?.sizes || '100vw' }
75
+ alt={ featuredMedia.alt_text || featuredMedia.title.raw }
76
+ />
77
+ </div>
78
+ );
79
+ }
80
+
81
+ return (
82
+ <div className="dataviews-media-field__media-thumbnail">
83
+ <VStack
84
+ justify="center"
85
+ alignment="center"
86
+ className="dataviews-media-field__media-thumbnail__stack"
87
+ spacing={ 0 }
88
+ >
89
+ <Icon
90
+ className="dataviews-media-field__media-thumbnail--icon"
91
+ icon={ getMediaTypeFromMimeType( item.mime_type ).icon }
92
+ size={ 24 }
93
+ />
94
+ { !! filename && (
95
+ <div className="dataviews-media-field__media-thumbnail__filename">
96
+ <Truncate className="dataviews-media-field__media-thumbnail__filename__truncate">
97
+ { filename }
98
+ </Truncate>
99
+ </div>
100
+ ) }
101
+ </VStack>
102
+ </div>
103
+ );
104
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __ } from '@wordpress/i18n';
5
+ import type { Attachment, Updatable } from '@wordpress/core-data';
6
+ import type { Field } from '@wordpress/dataviews';
7
+
8
+ const mimeTypeField: Partial< Field< Updatable< Attachment > > > = {
9
+ id: 'mime_type',
10
+ type: 'text',
11
+ label: __( 'File type' ),
12
+ getValue: ( { item } ) => item?.mime_type || '',
13
+ render: ( { item } ) => item?.mime_type || '-',
14
+ enableSorting: true,
15
+ filterBy: false,
16
+ readOnly: true,
17
+ };
18
+
19
+ export default mimeTypeField;
@@ -0,0 +1,290 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useState } from '@wordpress/element';
5
+ import { DataForm, DataViews, type Form } from '@wordpress/dataviews';
6
+ import type { Field, View } from '@wordpress/dataviews';
7
+
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import {
12
+ altTextField,
13
+ captionField,
14
+ descriptionField,
15
+ filenameField,
16
+ filesizeField,
17
+ mediaDimensionsField,
18
+ mediaThumbnailField,
19
+ mimeTypeField,
20
+ type MediaItem,
21
+ } from '../index';
22
+
23
+ export default {
24
+ title: 'Fields/Media Fields',
25
+ component: DataForm,
26
+ };
27
+
28
+ // Sample data for media fields
29
+ const sampleMediaItem: MediaItem = {
30
+ id: 123,
31
+ date: '2024-01-15T10:30:00',
32
+ date_gmt: '2024-01-15T10:30:00',
33
+ guid: {
34
+ raw: 'https://cldup.com/cXyG__fTLN.jpg',
35
+ rendered: 'https://cldup.com/cXyG__fTLN.jpg',
36
+ },
37
+ modified: '2024-01-15T10:30:00',
38
+ modified_gmt: '2024-01-15T10:30:00',
39
+ slug: 'sample-image',
40
+ status: 'publish',
41
+ type: 'attachment',
42
+ link: 'https://example.com/sample-image/',
43
+ title: {
44
+ raw: 'Sample Image',
45
+ rendered: 'Sample Image',
46
+ },
47
+ author: 1,
48
+ featured_media: 0,
49
+ comment_status: 'open',
50
+ ping_status: 'closed',
51
+ template: '',
52
+ meta: {},
53
+ permalink_template: 'https://example.com/?attachment_id=123',
54
+ generated_slug: 'sample-image',
55
+ class_list: [ 'post-123', 'attachment' ],
56
+ alt_text: 'A beautiful sample image',
57
+ caption: {
58
+ raw: 'A caption for the image',
59
+ rendered: '<p>A caption for the image</p>\n',
60
+ },
61
+ description: {
62
+ raw: 'This is a detailed description of the sample image. It contains useful information about what the image depicts and its context.',
63
+ rendered:
64
+ '<p>This is a detailed description of the sample image. It contains useful information about what the image depicts and its context.</p>',
65
+ },
66
+ mime_type: 'image/jpeg',
67
+ media_type: 'image',
68
+ post: null,
69
+ source_url: 'https://cldup.com/cXyG__fTLN.jpg',
70
+ media_details: {
71
+ file: 'sample-image.jpg',
72
+ width: 1920,
73
+ height: 1080,
74
+ filesize: 524288,
75
+ image_meta: {
76
+ aperture: '2.8',
77
+ credit: '',
78
+ camera: 'Sample Camera',
79
+ caption: '',
80
+ created_timestamp: '1705315800',
81
+ copyright: '',
82
+ focal_length: '50',
83
+ iso: '100',
84
+ shutter_speed: '0.004',
85
+ title: '',
86
+ orientation: '1',
87
+ keywords: [],
88
+ },
89
+ sizes: {
90
+ thumbnail: {
91
+ file: 'sample-image-150x150.jpg',
92
+ width: 150,
93
+ height: 150,
94
+ filesize: 8192,
95
+ mime_type: 'image/jpeg',
96
+ source_url: 'https://cldup.com/cXyG__fTLN.jpg',
97
+ },
98
+ medium: {
99
+ file: 'sample-image-300x169.jpg',
100
+ width: 300,
101
+ height: 169,
102
+ filesize: 24576,
103
+ mime_type: 'image/jpeg',
104
+ source_url: 'https://cldup.com/cXyG__fTLN.jpg',
105
+ },
106
+ },
107
+ },
108
+ missing_image_sizes: [],
109
+ };
110
+
111
+ // Sample data for a non-image file (ZIP)
112
+ const sampleMediaItemZip: MediaItem = {
113
+ id: 101,
114
+ date: '2025-11-07T00:28:54',
115
+ date_gmt: '2025-11-07T00:28:54',
116
+ guid: {
117
+ raw: 'http://localhost:8888/wp-content/uploads/2025/11/gutenberg-v22-0-0.zip',
118
+ rendered:
119
+ 'http://localhost:8888/wp-content/uploads/2025/11/gutenberg-v22-0-0.zip',
120
+ },
121
+ modified: '2025-11-07T00:28:54',
122
+ modified_gmt: '2025-11-07T00:28:54',
123
+ slug: 'gutenberg-v22-0-0',
124
+ status: 'publish',
125
+ type: 'attachment',
126
+ link: 'http://localhost:8888/gutenberg-v22-0-0/',
127
+ title: {
128
+ raw: 'gutenberg-v22-0-0',
129
+ rendered: 'gutenberg-v22-0-0',
130
+ },
131
+ author: 1,
132
+ featured_media: 0,
133
+ comment_status: 'open',
134
+ ping_status: 'closed',
135
+ template: '',
136
+ meta: {},
137
+ permalink_template: 'http://localhost:8888/?attachment_id=101',
138
+ generated_slug: 'gutenberg-v22-0-0',
139
+ class_list: [ 'post-101', 'attachment' ],
140
+ alt_text: '',
141
+ caption: {
142
+ raw: '',
143
+ rendered: '<p>gutenberg-v22-0-0</p>\n',
144
+ },
145
+ description: {
146
+ raw: '',
147
+ rendered: '',
148
+ },
149
+ mime_type: 'application/zip',
150
+ media_type: 'file',
151
+ post: 1,
152
+ source_url:
153
+ 'http://localhost:8888/wp-content/uploads/2025/11/gutenberg-v22-0-0.zip',
154
+ media_details: {
155
+ file: 'gutenberg-v22-0-0.zip',
156
+ filesize: 19988723,
157
+ width: 0,
158
+ height: 0,
159
+ image_meta: {
160
+ aperture: '',
161
+ credit: '',
162
+ camera: '',
163
+ caption: '',
164
+ created_timestamp: '',
165
+ copyright: '',
166
+ focal_length: '',
167
+ iso: '',
168
+ shutter_speed: '',
169
+ title: '',
170
+ orientation: '',
171
+ keywords: [],
172
+ },
173
+ sizes: {},
174
+ },
175
+ missing_image_sizes: [],
176
+ };
177
+
178
+ // Create a showcase of all media fields.
179
+ const showcaseFields = [
180
+ mediaThumbnailField,
181
+ filenameField,
182
+ altTextField,
183
+ captionField,
184
+ descriptionField,
185
+ mimeTypeField,
186
+ mediaDimensionsField,
187
+ filesizeField,
188
+ ] as Field< any >[];
189
+
190
+ const DataFormsComponent = ( { type }: { type: 'regular' | 'panel' } ) => {
191
+ const [ data, setData ] = useState< MediaItem >( sampleMediaItem );
192
+
193
+ const handleChange = ( updates: Partial< MediaItem > ) => {
194
+ setData( ( prev: MediaItem ) => ( { ...prev, ...updates } ) );
195
+ };
196
+
197
+ // Form configuration for the media fields showcase.
198
+ const showcaseForm: Form = {
199
+ layout: {
200
+ type,
201
+ },
202
+ fields: [
203
+ 'media_thumbnail',
204
+ 'alt_text',
205
+ 'caption',
206
+ 'description',
207
+ 'filename',
208
+ 'mime_type',
209
+ 'media_dimensions',
210
+ 'filesize',
211
+ ],
212
+ };
213
+
214
+ return (
215
+ <div style={ { padding: '20px' } }>
216
+ <h2>Media Fields</h2>
217
+ <p>
218
+ This story demonstrates all the media fields from the
219
+ @wordpress/media-fields package within a DataForm.
220
+ </p>
221
+
222
+ <DataForm
223
+ data={ data }
224
+ fields={ showcaseFields }
225
+ form={ showcaseForm }
226
+ onChange={ handleChange }
227
+ />
228
+ </div>
229
+ );
230
+ };
231
+
232
+ export const DataFormsPreview = {
233
+ render: DataFormsComponent,
234
+ argTypes: {
235
+ type: {
236
+ control: { type: 'select' },
237
+ description: 'Choose the layout type.',
238
+ options: [ 'regular', 'panel' ],
239
+ },
240
+ },
241
+ args: {
242
+ type: 'regular',
243
+ },
244
+ };
245
+
246
+ export const DataViewsPreview = () => {
247
+ const [ view, setView ] = useState< View >( {
248
+ type: 'table',
249
+ fields: showcaseFields
250
+ .map( ( f ) => f.id )
251
+ .filter( ( id ) => id !== 'media_thumbnail' ),
252
+ descriptionField: undefined,
253
+ mediaField: 'media_thumbnail',
254
+ showTitle: false,
255
+ } );
256
+ const [ data ] = useState< MediaItem[] >( [
257
+ sampleMediaItem,
258
+ sampleMediaItemZip,
259
+ ] );
260
+
261
+ const paginationInfo = {
262
+ totalItems: 2,
263
+ totalPages: 1,
264
+ };
265
+
266
+ const defaultLayouts = {
267
+ table: {},
268
+ list: {},
269
+ grid: {},
270
+ };
271
+
272
+ return (
273
+ <div style={ { padding: '20px' } }>
274
+ <h2>Media Fields DataViews Preview</h2>
275
+ <p>
276
+ This story demonstrates all the media fields from the
277
+ @wordpress/media-fields package, rendered in a DataViews
278
+ component, allowing preview of view state and layout switching.
279
+ </p>
280
+ <DataViews
281
+ data={ data }
282
+ fields={ showcaseFields }
283
+ view={ view }
284
+ onChangeView={ ( nextView: View ) => setView( nextView ) }
285
+ paginationInfo={ paginationInfo }
286
+ defaultLayouts={ defaultLayouts }
287
+ />
288
+ </div>
289
+ );
290
+ };
package/src/style.scss ADDED
@@ -0,0 +1 @@
1
+ @use "./media_thumbnail/style.scss" as *;
package/src/types.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import type { Attachment, Updatable, Post } from '@wordpress/core-data';
5
+
6
+ export type MediaKind = 'image' | 'video' | 'audio' | 'application';
7
+
8
+ export interface MediaType {
9
+ type: MediaKind;
10
+ label: string;
11
+ icon: JSX.Element;
12
+ }
13
+
14
+ // TODO: Update the Attachment type separately.
15
+ export interface MediaItem extends Attachment< 'edit' > {
16
+ // featured_media is not in the Attachment type. See https://github.com/WordPress/gutenberg/blob/trunk/packages/core-data/src/entity-types/attachment.ts#L10
17
+ featured_media: number;
18
+ _embedded?: {
19
+ // TODO: Include wp:attached-to properly, and backport PHP changes from wordpress-develop to support this.
20
+ 'wp:attached-to'?: Post[] | Partial< Post >[];
21
+ };
22
+ }
23
+
24
+ export type MediaItemUpdatable = Updatable< Attachment >;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __ } from '@wordpress/i18n';
5
+ import { audio, video, image, file } from '@wordpress/icons';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import type { MediaType } from '../types';
11
+
12
+ /**
13
+ * Get the media type from a mime type, including an icon.
14
+ * TODO - media types should be formalized somewhere.
15
+ *
16
+ * References:
17
+ * https://developer.wordpress.org/reference/functions/wp_mime_type_icon/
18
+ * https://developer.wordpress.org/reference/hooks/mime_types/
19
+ * https://developer.wordpress.org/reference/functions/wp_get_mime_types/
20
+ *
21
+ * @param mimeType - The mime type to get the media type from.
22
+ * @return The media type.
23
+ */
24
+ export function getMediaTypeFromMimeType( mimeType: string ): MediaType {
25
+ if ( mimeType.startsWith( 'image/' ) ) {
26
+ return {
27
+ type: 'image',
28
+ label: __( 'Image' ),
29
+ icon: image,
30
+ };
31
+ }
32
+
33
+ if ( mimeType.startsWith( 'video/' ) ) {
34
+ return {
35
+ type: 'video',
36
+ label: __( 'Video' ),
37
+ icon: video,
38
+ };
39
+ }
40
+
41
+ if ( mimeType.startsWith( 'audio/' ) ) {
42
+ return {
43
+ type: 'audio',
44
+ label: __( 'Audio' ),
45
+ icon: audio,
46
+ };
47
+ }
48
+
49
+ return {
50
+ type: 'application',
51
+ label: __( 'Application' ),
52
+ icon: file,
53
+ };
54
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Utility function to extract raw content from either a string or an object
3
+ * containing raw and rendered properties.
4
+ *
5
+ * This handles the inconsistency in WordPress REST API responses where
6
+ * some fields like caption and description can be either:
7
+ * - A simple string
8
+ * - An object with { raw: string, rendered: string }
9
+ *
10
+ * @param content - The content to extract raw value from
11
+ * @return The raw content string, or empty string if content is falsy
12
+ */
13
+ export function getRawContent(
14
+ content: string | { raw: string; rendered: string } | undefined | null
15
+ ): string {
16
+ if ( ! content ) {
17
+ return '';
18
+ }
19
+
20
+ // If it's a string, return it directly
21
+ if ( typeof content === 'string' ) {
22
+ return content;
23
+ }
24
+
25
+ // If it's an object with raw property, return the raw value
26
+ if ( typeof content === 'object' && 'raw' in content ) {
27
+ return content.raw || '';
28
+ }
29
+
30
+ // Fallback to empty string
31
+ return '';
32
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Utility function to extract rendered content from either a string or an object
3
+ * containing raw and rendered properties.
4
+ *
5
+ * This handles the inconsistency in WordPress REST API responses where
6
+ * some fields like caption and description can be either:
7
+ * - A simple string
8
+ * - An object with { raw: string, rendered: string }
9
+ *
10
+ * @param content - The content to extract raw value from
11
+ * @return The rendered content string, falling back to raw or empty string if content is falsy
12
+ */
13
+ export function getRenderedContent(
14
+ content: string | { raw: string; rendered: string } | undefined | null
15
+ ): string {
16
+ if ( ! content ) {
17
+ return '';
18
+ }
19
+
20
+ // If it's a string, return it directly
21
+ if ( typeof content === 'string' ) {
22
+ return content;
23
+ }
24
+
25
+ // If it's an object with raw property, return the raw value
26
+ if ( typeof content === 'object' ) {
27
+ return content.rendered || content.raw || '';
28
+ }
29
+
30
+ // Fallback to empty string
31
+ return '';
32
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig.json",
3
+ "extends": "../../tsconfig.base.json",
4
+ "compilerOptions": {
5
+ "types": [
6
+ "gutenberg-env",
7
+ "gutenberg-test-env",
8
+ "jest",
9
+ "@testing-library/jest-dom"
10
+ ]
11
+ },
12
+ "references": [
13
+ { "path": "../components" },
14
+ { "path": "../core-data" },
15
+ { "path": "../data" },
16
+ { "path": "../dataviews" },
17
+ { "path": "../element" },
18
+ { "path": "../i18n" },
19
+ { "path": "../icons" },
20
+ { "path": "../primitives" },
21
+ { "path": "../url" }
22
+ ],
23
+ "exclude": [
24
+ "src/**/*.android.js",
25
+ "src/**/*.ios.js",
26
+ "src/**/*.native.js",
27
+ "src/**/react-native-*",
28
+ "src/**/stories/**/*.js", // only exclude js files, tsx files should be checked
29
+ "src/**/test/**/*.js" // only exclude js files, ts{x} files should be checked
30
+ ]
31
+ }