@@ -24,6 +24,7 @@ import type {
2424import type { DataSource , FieldMetadata , ObjectSchemaMetadata } from '@object-ui/types' ;
2525import type { ObjectAgGridImplProps } from './object-aggrid.types' ;
2626import { FIELD_TYPE_TO_FILTER_TYPE } from './object-aggrid.types' ;
27+ import { createFieldCellRenderer , createFieldCellEditor } from './field-renderers' ;
2728
2829/**
2930 * ObjectAgGridImpl - Metadata-driven AG Grid implementation
@@ -438,166 +439,62 @@ function getFilterType(field: FieldMetadata): string | boolean {
438439
439440/**
440441 * Apply field type-specific formatting to column definition
442+ * Uses field widgets from @object-ui/fields for consistent rendering
441443 */
442444function applyFieldTypeFormatting ( colDef : ColDef , field : FieldMetadata ) : void {
443- switch ( field . type ) {
444- case 'boolean' :
445- colDef . cellRenderer = ( params : any ) => {
446- if ( params . value === true ) return '✓ Yes' ;
447- if ( params . value === false ) return '✗ No' ;
448- return '' ;
449- } ;
450- break ;
451-
452- case 'currency' :
453- colDef . valueFormatter = ( params : any ) => {
454- if ( params . value == null ) return '' ;
455- const currency = ( field as any ) . currency || 'USD' ;
456- const precision = ( field as any ) . precision || 2 ;
457- return new Intl . NumberFormat ( 'en-US' , {
458- style : 'currency' ,
459- currency,
460- minimumFractionDigits : precision ,
461- maximumFractionDigits : precision ,
462- } ) . format ( params . value ) ;
463- } ;
464- break ;
465-
466- case 'percent' :
467- colDef . valueFormatter = ( params : any ) => {
468- if ( params . value == null ) return '' ;
469- const precision = ( field as any ) . precision || 2 ;
470- return `${ ( params . value * 100 ) . toFixed ( precision ) } %` ;
471- } ;
472- break ;
473-
474- case 'date' :
475- colDef . valueFormatter = ( params : any ) => {
476- if ( ! params . value ) return '' ;
477- try {
478- const date = new Date ( params . value ) ;
479- if ( isNaN ( date . getTime ( ) ) ) return '' ;
480- return date . toLocaleDateString ( ) ;
481- } catch {
482- return '' ;
483- }
484- } ;
485- break ;
486-
487- case 'datetime' :
488- colDef . valueFormatter = ( params : any ) => {
489- if ( ! params . value ) return '' ;
490- try {
491- const date = new Date ( params . value ) ;
492- if ( isNaN ( date . getTime ( ) ) ) return '' ;
493- return date . toLocaleString ( ) ;
494- } catch {
495- return '' ;
496- }
497- } ;
498- break ;
499-
500- case 'time' :
501- colDef . valueFormatter = ( params : any ) => {
502- if ( ! params . value ) return '' ;
503- return params . value ;
504- } ;
505- break ;
506-
507- case 'email' :
508- colDef . cellRenderer = ( params : any ) => {
509- if ( ! params . value ) return '' ;
510- const escaped = escapeHtml ( params . value ) ;
511- return `<a href="mailto:${ escaped } " class="text-blue-600 hover:underline">${ escaped } </a>` ;
512- } ;
513- break ;
514-
515- case 'url' :
516- colDef . cellRenderer = ( params : any ) => {
517- if ( ! params . value ) return '' ;
518- const escaped = escapeHtml ( params . value ) ;
519- return `<a href="${ escaped } " target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:underline">${ escaped } </a>` ;
520- } ;
521- break ;
522-
523- case 'phone' :
524- colDef . cellRenderer = ( params : any ) => {
525- if ( ! params . value ) return '' ;
526- const escaped = escapeHtml ( params . value ) ;
527- return `<a href="tel:${ escaped } " class="text-blue-600 hover:underline">${ escaped } </a>` ;
528- } ;
529- break ;
530-
531- case 'select' :
532- colDef . valueFormatter = ( params : any ) => {
533- if ( ! params . value ) return '' ;
534- const options = ( field as any ) . options || [ ] ;
535- const option = options . find ( ( opt : any ) => opt . value === params . value ) ;
536- return option ?. label || params . value ;
537- } ;
538- break ;
539-
540- case 'lookup' :
541- case 'master_detail' :
542- colDef . valueFormatter = ( params : any ) => {
543- if ( ! params . value ) return '' ;
544- // Handle lookup values - could be an object or just an ID
545- if ( typeof params . value === 'object' ) {
546- return params . value . name || params . value . label || params . value . id || '' ;
547- }
548- return String ( params . value ) ;
549- } ;
550- break ;
445+ // Define field types that should use field widgets for rendering
446+ const fieldWidgetTypes = [
447+ 'text' , 'textarea' , 'number' , 'currency' , 'percent' ,
448+ 'boolean' , 'select' , 'date' , 'datetime' , 'time' ,
449+ 'email' , 'phone' , 'url' , 'password' , 'color' ,
450+ 'rating' , 'image' , 'avatar' , 'lookup' , 'slider' , 'code'
451+ ] ;
452+
453+ // Use field widget renderer if the type is supported
454+ if ( fieldWidgetTypes . includes ( field . type ) ) {
455+ colDef . cellRenderer = createFieldCellRenderer ( field ) ;
456+
457+ // Add cell editor for editable fields
458+ if ( colDef . editable ) {
459+ colDef . cellEditor = createFieldCellEditor ( field ) ;
551460
552- case 'number' : {
553- const precision = ( field as any ) . precision ;
554- if ( precision !== undefined ) {
461+ // Configure editor based on field type
462+ if ( [ 'date' , 'datetime' , 'select' , 'lookup' , 'color' ] . includes ( field . type ) ) {
463+ colDef . cellEditorPopup = true ;
464+ }
465+ }
466+ } else {
467+ // Fallback to simple rendering for unsupported types
468+ switch ( field . type ) {
469+ case 'lookup' :
470+ case 'master_detail' :
555471 colDef . valueFormatter = ( params : any ) => {
556- if ( params . value == null ) return '' ;
557- return Number ( params . value ) . toFixed ( precision ) ;
472+ if ( ! params . value ) return '' ;
473+ // Handle lookup values - could be an object or just an ID
474+ if ( typeof params . value === 'object' ) {
475+ return params . value . name || params . value . label || params . value . id || '' ;
476+ }
477+ return String ( params . value ) ;
478+ } ;
479+ break ;
480+
481+ case 'object' :
482+ colDef . cellRenderer = ( ) => '<span class="text-gray-500 italic">[Object]</span>' ;
483+ break ;
484+
485+ case 'vector' :
486+ colDef . cellRenderer = ( ) => '<span class="text-gray-500 italic">[Vector]</span>' ;
487+ break ;
488+
489+ case 'grid' :
490+ colDef . cellRenderer = ( ) => '<span class="text-gray-500 italic">[Grid]</span>' ;
491+ break ;
492+
493+ default :
494+ // Default text rendering
495+ colDef . valueFormatter = ( params : any ) => {
496+ return params . value != null ? String ( params . value ) : '' ;
558497 } ;
559- }
560- break ;
561498 }
562-
563- case 'color' :
564- colDef . cellRenderer = ( params : any ) => {
565- if ( ! params . value ) return '' ;
566- const escaped = escapeHtml ( params . value ) ;
567- return `<div class="flex items-center gap-2">
568- <div style="width: 16px; height: 16px; background-color: ${ escaped } ; border: 1px solid #ccc; border-radius: 2px;"></div>
569- <span>${ escaped } </span>
570- </div>` ;
571- } ;
572- break ;
573-
574- case 'rating' :
575- colDef . cellRenderer = ( params : any ) => {
576- if ( params . value == null ) return '' ;
577- const max = ( field as any ) . max || 5 ;
578- const stars = '⭐' . repeat ( Math . min ( params . value , max ) ) ;
579- return stars ;
580- } ;
581- break ;
582-
583- case 'image' :
584- colDef . cellRenderer = ( params : any ) => {
585- if ( ! params . value ) return '' ;
586- const url = typeof params . value === 'string' ? params . value : params . value . url ;
587- if ( ! url ) return '' ;
588- const escapedUrl = escapeHtml ( url ) ;
589- return `<img src="${ escapedUrl } " alt="" style="width: 40px; height: 40px; object-fit: cover; border-radius: 4px;" />` ;
590- } ;
591- break ;
592-
593- case 'avatar' :
594- colDef . cellRenderer = ( params : any ) => {
595- if ( ! params . value ) return '' ;
596- const url = typeof params . value === 'string' ? params . value : params . value . url ;
597- if ( ! url ) return '' ;
598- const escapedUrl = escapeHtml ( url ) ;
599- return `<img src="${ escapedUrl } " alt="" style="width: 32px; height: 32px; object-fit: cover; border-radius: 50%;" />` ;
600- } ;
601- break ;
602499 }
603500}
0 commit comments