@@ -19,7 +19,7 @@ import type { BlockProps } from '../Block';
1919import { Blocks } from '../Blocks' ;
2020import { FileIcon } from '../FileIcon' ;
2121import type { TableRecordKV } from './Table' ;
22- import { type VerticalAlignment , getColumnAlignment } from './utils' ;
22+ import { type VerticalAlignment , getColumnAlignment , isContentRef , isStringArray } from './utils' ;
2323
2424const alignmentMap : Record < 'text-left' | 'text-center' | 'text-right' , string > = {
2525 'text-left' : '**:text-left text-left' ,
@@ -57,18 +57,28 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
5757 return null ;
5858 }
5959
60+ // Because definition and value depends on column, we have to check typing in each case at runtime.
61+ // Validation should have been done at the API level, but we can't know typing based on `definition.type`.
62+ // OpenAPI types cannot really handle discriminated unions based on a dynamic key.
6063 switch ( definition . type ) {
61- case 'checkbox' :
64+ case 'checkbox' : {
65+ if ( value === null || typeof value !== 'boolean' ) {
66+ return null ;
67+ }
6268 return (
6369 < Checkbox
6470 className = { tcls ( 'w-5' , 'h-5' ) }
65- checked = { value as boolean }
71+ checked = { value }
6672 disabled = { true }
6773 aria-labelledby = { ariaLabelledBy }
6874 />
6975 ) ;
76+ }
7077 case 'rating' : {
71- const rating = value as number ;
78+ if ( typeof value !== 'number' ) {
79+ return null ;
80+ }
81+ const rating = value ;
7282 const max = definition . max ;
7383
7484 return (
@@ -107,15 +117,21 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
107117 </ Tag >
108118 ) ;
109119 }
110- case 'number' :
120+ case 'number' : {
121+ if ( typeof value !== 'number' ) {
122+ return null ;
123+ }
111124 return (
112125 < Tag
113126 className = { tcls ( 'text-base' , 'tabular-nums' , 'tracking-tighter' ) }
114127 aria-labelledby = { ariaLabelledBy }
115128 > { `${ value } ` } </ Tag >
116129 ) ;
130+ }
117131 case 'text' : {
118- // @ts -ignore
132+ if ( typeof value !== 'string' ) {
133+ return null ;
134+ }
119135 const fragment = getNodeFragmentByName ( block , value ) ;
120136 if ( ! fragment ) {
121137 return < Tag className = { tcls ( [ 'w-full' , verticalAlignment ] ) } > { '' } </ Tag > ;
@@ -148,8 +164,11 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
148164 ) ;
149165 }
150166 case 'files' : {
167+ if ( ! isStringArray ( value ) ) {
168+ return null ;
169+ }
151170 const files = await Promise . all (
152- ( value as string [ ] ) . map ( ( fileId ) =>
171+ value . map ( ( fileId ) =>
153172 context . contentContext
154173 ? resolveContentRef (
155174 {
@@ -220,10 +239,12 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
220239 ) ;
221240 }
222241 case 'content-ref' : {
223- const contentRef = value ? ( value as ContentRef ) : null ;
242+ if ( value === null || ! isContentRef ( value ) ) {
243+ return null ;
244+ }
224245 const resolved =
225- contentRef && context . contentContext
226- ? await resolveContentRef ( contentRef , context . contentContext , {
246+ value && context . contentContext
247+ ? await resolveContentRef ( value , context . contentContext , {
227248 resolveAnchorText : true ,
228249 iconStyle : [ 'mr-2' , 'text-tint-subtle' ] ,
229250 } )
@@ -238,11 +259,11 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
238259 < StyledLink
239260 href = { resolved . href }
240261 insights = {
241- contentRef
262+ value
242263 ? {
243264 type : 'link_click' ,
244265 link : {
245- target : contentRef ,
266+ target : value ,
246267 position : SiteInsightsLinkPosition . Content ,
247268 } ,
248269 }
@@ -256,8 +277,11 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
256277 ) ;
257278 }
258279 case 'users' : {
280+ if ( ! isStringArray ( value ) ) {
281+ return null ;
282+ }
259283 const resolved = await Promise . all (
260- ( value as string [ ] ) . map ( async ( userId ) => {
284+ value . map ( async ( userId ) => {
261285 const contentRef : ContentRefUser = {
262286 kind : 'user' ,
263287 user : userId ,
@@ -294,10 +318,13 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
294318 ) ;
295319 }
296320 case 'select' : {
321+ if ( ! isStringArray ( value ) ) {
322+ return null ;
323+ }
297324 return (
298325 < Tag aria-labelledby = { ariaLabelledBy } >
299326 < span className = { tcls ( 'inline-flex' , 'gap-2' , 'flex-wrap' ) } >
300- { ( value as string [ ] ) . map ( ( selectId ) => {
327+ { value . map ( ( selectId ) => {
301328 const option = definition . options . find (
302329 ( option ) => option . value === selectId
303330 ) ;
@@ -328,13 +355,12 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
328355 ) ;
329356 }
330357 case 'image' : {
331- const contentRef = value ? ( value as ContentRef ) : null ;
332- if ( ! contentRef ) {
358+ if ( ! isContentRef ( value ) ) {
333359 return null ;
334360 }
335361
336362 const image = context . contentContext
337- ? await resolveContentRef ( contentRef , context . contentContext )
363+ ? await resolveContentRef ( value , context . contentContext )
338364 : null ;
339365
340366 if ( ! image ) {
0 commit comments