JSFiddle - React, Tailwind, and code Playground

by skirtle

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>

<template id="vt-gridview">
  <table v-if="rows" ref="table" :class="['v-gridView', {rowLines: rowLines}]">
    <tr v-if="rows.length === 0">
      <td>
        <div style="padding:4px;" class="noRec">{{empty}}</div>
      </td>
    </tr>
    <template v-else>
      <tr v-if="showHeader">
        <th v-if="colShow(col)" v-for="col in cols" :style="headStyle(col)" :title="col.hTitle" @click="doSort(col)">
          <slot v-if="col.hSlot" :name="col.hSlot" :col="col"></slot>
          <template v-else>
            {{col.name}}<span v-if="sort" v-show="sort.exp==col.sort" style="margin-left:3px;">{{sortGliph}}</span>
          </template>
        </th>
      </tr>
      <v-gridview-tr v-for="(row, idx) in rows" :key="row[rowKeyProp]" :row="row" :idx="idx" :cols="cols" :col-key-prop="colKeyProp" :cols-hidden="colsHidden" :alt-rows="altRows" :sel-row="selRow" @row-selected="rowSelected">
      </v-gridview-tr>
    </template>
  </table>
</template>

<template id="vt-gridview-tr">
  <tr :class="rowClass" :style="rowStyle" @click="rowClick">
    <td :is="cellType(col)" v-if="colShow(col)" v-for="col in cols" :key="col[colKeyProp]" :row="row" :col="col"></td>
  </tr>
</template>

<template id="vt-gridview-td">
  <td v-bind="tdAttrs">
    {{ formattedValue }}
  </td>
</template>

<template id="vt-gridview-td-trans">
  <td v-bind="tdAttrs">
    {{ row.TransID }} <span v-show="row.dirty" style="color:var(--error-color);">✽</span>
  </td>
</template>

<template id="vt-gridview-td-actclear">
  <td v-bind="tdAttrs">
    <input type='text' class='cellInput' :value="row.ClearedDate" @focusout="col.commitInput($event, row, 'ClearedDate')" />
  </td>
</template>

<div id="vmPage" class="flexVert" style="height:100%">
  <a href="#" @click="addRow">Add Row</a>
  <a href="#" @click="removeRow">Remove Row</a>
  <v-gridview v-bind="trans" :sort.sync="trans.sort" @update:sel-row="updateSelRow"...

CSS

:root {
    --surf-color-hs: 200, 15%; 
    --on-color-hsl: 0, 0%, 0%; 
    --error-color-hs: 360, 100%; 
    --sel-color-h: 40; 

    --scrbar-bk-color: hsl(var(--surf-color-hs), 94%);
    --scrbar-sl-color: hsl(var(--surf-color-hs), 70%); 

    --surf0-color: hsl(var(--surf-color-hs), 92%); 
    --surf1-color: hsl(var(--surf-color-hs), 96%); 
    --surf2-color: hsl(var(--surf-color-hs), 98%);  
    --surf3-color: hsl(var(--surf-color-hs), 100%);  

    --grid-rowhf-color: hsl(var(--surf-color-hs), 92%); 
    --grid-row0-color: hsl(var(--surf-color-hs), 100%); 
    --grid-row1-color: hsl(var(--surf-color-hs), 96%); 
    --grid-line-color: hsl(var(--surf-color-hs), 83%); 

    --cont-bk-color: hsl(var(--surf-color-hs), 100%); 
    --cont-bd-color: hsl(var(--surf-color-hs), 78%); 

    --input-bk-color: hsl(var(--surf-color-hs), 100%); 
    --input-bd0-color: hsl(var(--surf-color-hs), 85%); 
    --input-bd1-color: hsl(var(--surf-color-hs), 70%); 

    --icon-color-filter: opacity(60%);                 

    --sel-thick-color: hsl(var(--sel-color-h), 70%, 80%);
    --sel-thin-color: hsl(var(--sel-color-h), 70%, 80%); 
    --sel-webTWAIN-color: 0x0A74A9;                      
    
    --error-color: hsl(var(--error-color-hs), 40%);      
        
    --link-color0: #004078;
    --link-color1: #006ecf;  
    
    --font-fam-main: Verdana, Geneva, sans-serif;
    --font-fam-label: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
    
    --em-high: .87; 
    --em-med: .6;   
    --em-dis: .38;  
}

.v-gridView {
    line-height: 1.1;
    display: table;
    border-collapse: collapse;
    border-bottom: solid 1px hsla(var(--on-color-hsl), .08);
}

.v-gridView th {
    display: table-cell;
    border-collapse: collapse;
    padding: 4px 3px 3px 3px;
    background-color: var(--grid-rowhf-color);
    border-bottom: 1px solid var(--grid-line-color);
    font-family: var(--font-fam-label);
    font-weight: normal;
    font-size: 12px;
    color:...

JavaScript

Vue.component('v-gridview-td', {
  template: '#vt-gridview-td',
  props: ['col', 'row'],
  
  computed: {
    tdAttrs() {
      return {
        class: this.cellClass,
        style: this.cellStyle,
        title: this.row[this.col.bTitle]
      }
    },
 
    cellClass() {
      // console.log('%ccellClass', 'color: yellow');
    },
    cellStyle() {
      return {
        ...this.col.bStyle // override/add col styles
      };
    },
    formattedValue() {
      const col = this.col;
      
      // console.log('%cformatField', 'color: lightblue');
      const val = this.row[col.field];
      if (val === undefined) {
        console.error(`[GridView error]: "${col.field}" field not found in data row.`);
        return;
      }
      return val;
    }
  }
})

Vue.component('v-gridview-td-trans', {
  template: '#vt-gridview-td-trans',
  extends: Vue.component('v-gridview-td')
})

Vue.component('v-gridview-td-actclear', {
  template: '#vt-gridview-td-actclear',
  extends: Vue.component('v-gridview-td')
})

Vue.component('v-gridview', {
  template: '#vt-gridview',
  props: {
    rowKeyProp: {
      type: String,
      default: 'ID'
    },
    colKeyProp: {
      type: String,
      default: 'field'
    },
    showHeader: {
      default: true
    },
    cols: {
      type: Array,
      required: true
    },
    colsHidden: Array,
    empty: String,
    altRows: {
      default: true
    },
    rowLines: {
      default: false
    },
    rows: Array,
    rowClassFn: Function,
    cellStyleFn: Function,
    sort: Object,
    selRow: Object
  },
  created() {

  },
  updated () {
    console.log('updated: parent')
  },
  computed: {
    sortGliph() {
      return this.sort.ord == 'ASC' ? '▲' : '▼';
    }
  },
  methods: {
    colShow(col) {
      return this.colsHidden ? !this.colsHidden.includes(col.field) : true;
    },
    colSortable(col) {
      return this.sort && col.sort
    },
    doSort(col) {
      if (!this.colSortable(col)) return;

      const sort = {
  ...