From 6bf7a932f355672b4fb6ddacb119ba97a05e57ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 30 Apr 2019 13:23:22 -0400 Subject: [PATCH 01/46] - edges generation - edges generation tests --- src/core/type/index.ts | 8 + src/dash-table/derived/edges/data.ts | 76 +++++ src/dash-table/derived/edges/dataHeaders.ts | 58 ++++ .../derived/edges/dataOperations.ts | 70 ++++ src/dash-table/derived/edges/filter.ts | 64 ++++ src/dash-table/derived/edges/header.ts | 68 ++++ src/dash-table/derived/edges/type.ts | 111 ++++++ tests/cypress/tests/unit/edges_test.ts | 323 ++++++++++++++++++ 8 files changed, 778 insertions(+) create mode 100644 src/dash-table/derived/edges/data.ts create mode 100644 src/dash-table/derived/edges/dataHeaders.ts create mode 100644 src/dash-table/derived/edges/dataOperations.ts create mode 100644 src/dash-table/derived/edges/filter.ts create mode 100644 src/dash-table/derived/edges/header.ts create mode 100644 src/dash-table/derived/edges/type.ts create mode 100644 tests/cypress/tests/unit/edges_test.ts diff --git a/src/core/type/index.ts b/src/core/type/index.ts index f509ef023..b9e54c774 100644 --- a/src/core/type/index.ts +++ b/src/core/type/index.ts @@ -1,2 +1,10 @@ +export type OptionalMap = { + [tr in TR]?: M +}; export type RequiredPluck = { [r in R]: T[r] }; export type OptionalPluck = { [r in R]?: T[r] }; + +export type RequiredProp = T[R]; +export type OptionalProp = T[R] | undefined; + +export type PropOf = R; \ No newline at end of file diff --git a/src/dash-table/derived/edges/data.ts b/src/dash-table/derived/edges/data.ts new file mode 100644 index 000000000..40d07acd8 --- /dev/null +++ b/src/dash-table/derived/edges/data.ts @@ -0,0 +1,76 @@ +import * as R from 'ramda'; + +import { memoizeOneFactory } from 'core/memoizer'; + +import { + IViewportOffset, + IVisibleColumn, + VisibleColumns, + Data +} from 'dash-table/components/Table/props'; + +import { IConvertedStyle } from '../style'; +import { BorderStyle, BORDER_PROPERTIES, Edges, EdgesMatrices, IDefaultBorderStyle } from './type'; + +const getWeightedStyle = ( + borderStyles: IConvertedStyle[], + column: IVisibleColumn, + index: number, + offset: IViewportOffset, + datum: any +): BorderStyle => { + const res: BorderStyle = {}; + + R.addIndex(R.forEach)((rs, i) => { + if (!rs.matchesColumn(column) || + !rs.matchesRow(index + offset.rows) || + !rs.matchesFilter(datum) + ) { + return; + } + + R.forEach(p => { + const s = rs.style[p] || rs.style.border; + + if (!R.isNil(s)) { + res[p] = [s, i]; + } + }, BORDER_PROPERTIES); + }, borderStyles); + + return res; +}; + +export default memoizeOneFactory(( + columns: VisibleColumns, + data: Data, + borderStyles: IConvertedStyle[], + offset: IViewportOffset, + defaultBorderStyle?: IDefaultBorderStyle +): Edges => { + if (data.length === 0 || columns.length === 0) { + return; + } + + const edges = new EdgesMatrices(data.length, columns.length, defaultBorderStyle); + + R.addIndex(R.forEach)((datum, i) => + R.addIndex(R.forEach)( + (column, j) => { + const cellStyle = getWeightedStyle( + borderStyles, + column, + i, + offset, + datum + ); + + edges.setEdges(i, j, cellStyle); + }, + columns + ), + data + ); + + return edges.getMatrices(); +}); \ No newline at end of file diff --git a/src/dash-table/derived/edges/dataHeaders.ts b/src/dash-table/derived/edges/dataHeaders.ts new file mode 100644 index 000000000..31866325d --- /dev/null +++ b/src/dash-table/derived/edges/dataHeaders.ts @@ -0,0 +1,58 @@ +import * as R from 'ramda'; + +import { memoizeOneFactory } from 'core/memoizer'; + +import { IConvertedStyle } from '../style'; +import { BorderStyle, BORDER_PROPERTIES, Edges, EdgesMatrices, IDefaultBorderStyle } from './type'; + +const getWeightedStyle = ( + borderStyles: IConvertedStyle[], + index: number +): BorderStyle => { + const res: BorderStyle = {}; + + R.addIndex(R.forEach)((rs, i) => { + if (!rs.matchesRow(index)) { + return; + } + + R.forEach(p => { + const s = rs.style[p] || rs.style.border; + + if (!R.isNil(s)) { + res[p] = [s, i]; + } + }, BORDER_PROPERTIES); + }, borderStyles); + + return res; +}; + +export default memoizeOneFactory(( + columns: number, + headerRows: number, + borderStyles: IConvertedStyle[], + defaultBorderStyle?: IDefaultBorderStyle +): Edges => { + if (headerRows === 0 || columns === 0) { + return; + } + + const edges = new EdgesMatrices(headerRows, columns, defaultBorderStyle); + + R.forEach(i => + R.forEach(j => { + const cellStyle = getWeightedStyle( + borderStyles, + i + ); + + edges.setEdges(i, j, cellStyle); + }, + R.range(0, columns) + ), + R.range(0, headerRows) + ); + + return edges.getMatrices(); +}); \ No newline at end of file diff --git a/src/dash-table/derived/edges/dataOperations.ts b/src/dash-table/derived/edges/dataOperations.ts new file mode 100644 index 000000000..0d59850ee --- /dev/null +++ b/src/dash-table/derived/edges/dataOperations.ts @@ -0,0 +1,70 @@ +import * as R from 'ramda'; + +import { memoizeOneFactory } from 'core/memoizer'; + +import { + Data, + IViewportOffset +} from 'dash-table/components/Table/props'; + +import { IConvertedStyle } from '../style'; +import { BorderStyle, BORDER_PROPERTIES, Edges, EdgesMatrices, IDefaultBorderStyle } from './type'; + +const getWeightedStyle = ( + borderStyles: IConvertedStyle[], + index: number, + offset: IViewportOffset, + datum: any +): BorderStyle => { + const res: BorderStyle = {}; + + R.addIndex(R.forEach)((rs, i) => { + if (!rs.matchesRow(index + offset.rows) || + !rs.matchesFilter(datum) + ) { + return; + } + + R.forEach(p => { + const s = rs.style[p] || rs.style.border; + + if (!R.isNil(s)) { + res[p] = [s, i]; + } + }, BORDER_PROPERTIES); + }, borderStyles); + + return res; +}; + +export default memoizeOneFactory(( + columns: number, + data: Data, + borderStyles: IConvertedStyle[], + offset: IViewportOffset, + defaultBorderStyle?: IDefaultBorderStyle +): Edges => { + if (data.length === 0 || columns === 0) { + return; + } + + const edges = new EdgesMatrices(data.length, columns, defaultBorderStyle); + + R.addIndex(R.forEach)((datum, i) => + R.forEach(j => { + const cellStyle = getWeightedStyle( + borderStyles, + i, + offset, + datum + ); + + edges.setEdges(i, j, cellStyle); + }, + R.range(0, columns) + ), + data + ); + + return edges.getMatrices(); +}); \ No newline at end of file diff --git a/src/dash-table/derived/edges/filter.ts b/src/dash-table/derived/edges/filter.ts new file mode 100644 index 000000000..0b2b78543 --- /dev/null +++ b/src/dash-table/derived/edges/filter.ts @@ -0,0 +1,64 @@ +import * as R from 'ramda'; + +import { memoizeOneFactory } from 'core/memoizer'; + +import { + IVisibleColumn, + VisibleColumns +} from 'dash-table/components/Table/props'; + +import { IConvertedStyle } from '../style'; +import { BorderStyle, BORDER_PROPERTIES, Edges, EdgesMatrices, IDefaultBorderStyle } from './type'; + +const getWeightedStyle = ( + borderStyles: IConvertedStyle[], + column: IVisibleColumn +): BorderStyle => { + const res: BorderStyle = {}; + + R.addIndex(R.forEach)((rs, i) => { + if (!rs.matchesColumn(column)) { + return; + } + + R.forEach(p => { + const s = rs.style[p] || rs.style.border; + + if (!R.isNil(s)) { + res[p] = [s, i]; + } + }, BORDER_PROPERTIES); + }, borderStyles); + + return res; +}; + +export default memoizeOneFactory(( + columns: VisibleColumns, + showFilters: boolean, + borderStyles: IConvertedStyle[], + defaultBorderStyle?: IDefaultBorderStyle +): Edges => { + if (!showFilters || columns.length === 0) { + return; + } + + const edges = new EdgesMatrices(1, columns.length, defaultBorderStyle); + + R.forEach(i => + R.addIndex(R.forEach)( + (column, j) => { + const cellStyle = getWeightedStyle( + borderStyles, + column + ); + + edges.setEdges(i, j, cellStyle); + }, + columns + ), + R.range(0, 1) + ); + + return edges.getMatrices(); +}); \ No newline at end of file diff --git a/src/dash-table/derived/edges/header.ts b/src/dash-table/derived/edges/header.ts new file mode 100644 index 000000000..01e01d2e5 --- /dev/null +++ b/src/dash-table/derived/edges/header.ts @@ -0,0 +1,68 @@ +import * as R from 'ramda'; + +import { memoizeOneFactory } from 'core/memoizer'; + +import { + IVisibleColumn, + VisibleColumns +} from 'dash-table/components/Table/props'; + +import { IConvertedStyle } from '../style'; +import { BorderStyle, BORDER_PROPERTIES, Edges, EdgesMatrices, IDefaultBorderStyle } from './type'; + +const getWeightedStyle = ( + borderStyles: IConvertedStyle[], + column: IVisibleColumn, + index: number +): BorderStyle => { + const res: BorderStyle = {}; + + R.addIndex(R.forEach)((rs, i) => { + if (!rs.matchesColumn(column) || + !rs.matchesRow(index) + ) { + return; + } + + R.forEach(p => { + const s = rs.style[p] || rs.style.border; + + if (!R.isNil(s)) { + res[p] = [s, i]; + } + }, BORDER_PROPERTIES); + }, borderStyles); + + return res; +}; + +export default memoizeOneFactory(( + columns: VisibleColumns, + headerRows: number, + borderStyles: IConvertedStyle[], + defaultBorderStyle?: IDefaultBorderStyle +): Edges => { + if (headerRows === 0 || columns.length === 0) { + return; + } + + const edges = new EdgesMatrices(headerRows, columns.length, defaultBorderStyle); + + R.forEach(i => + R.addIndex(R.forEach)( + (column, j) => { + const cellStyle = getWeightedStyle( + borderStyles, + column, + i + ); + + edges.setEdges(i, j, cellStyle); + }, + columns + ), + R.range(0, headerRows) + ); + + return edges.getMatrices(); +}); \ No newline at end of file diff --git a/src/dash-table/derived/edges/type.ts b/src/dash-table/derived/edges/type.ts new file mode 100644 index 000000000..663b7191c --- /dev/null +++ b/src/dash-table/derived/edges/type.ts @@ -0,0 +1,111 @@ +import * as R from 'ramda'; +import { CSSProperties } from 'react'; + +import { OptionalMap, OptionalProp, PropOf } from 'core/type'; + +interface IEdges { + horizontal: Edge[][]; + vertical: Edge[][]; +} + +export interface IDefaultBorderStyle { + vertical?: string; + horizontal?: string; +} + +export type BorderProp = + PropOf | + PropOf | + PropOf | + PropOf; + +export type BorderStyle = + OptionalMap, number]> & + OptionalMap, number]> & + OptionalMap, number]> & + OptionalMap, number]> & + OptionalMap, number]>; + +export const BORDER_PROPERTIES: BorderProp[] = [ + 'borderBottom', + 'borderLeft', + 'borderRight', + 'borderTop' +]; + +export type Edge = any; + +export type Edges = IEdges | undefined; + +export class EdgesMatrix { + private weights: number[][]; + private edges: Edge[][]; + + constructor(rows: number, columns: number, defaultBorderStyle?: Edge) { + this.weights = R.map( + () => new Array(columns).fill(-Infinity), + R.range(0, rows) + ); + + this.edges = R.map( + () => new Array(columns).fill(defaultBorderStyle), + R.range(0, rows) + ); + } + + setEdge(i: number, j: number, edge: Edge, weight: number) { + if (R.isNil(edge) || weight <= this.weights[i][j]) { + return; + } + + this.weights[i][j] = weight; + this.edges[i][j] = edge; + } + + getEdges() { + return this.edges; + } +} + +export class EdgesMatrices { + private horizontal: EdgesMatrix; + private vertical: EdgesMatrix; + + constructor(rows: number, columns: number, defaultBorderStyle?: IDefaultBorderStyle) { + this.horizontal = new EdgesMatrix( + rows + 1, + columns, + defaultBorderStyle && defaultBorderStyle.horizontal + ); + this.vertical = new EdgesMatrix( + rows, + columns + 1, + defaultBorderStyle && defaultBorderStyle.vertical + ); + } + + setEdges(i: number, j: number, style: BorderStyle) { + if (style.borderTop) { + this.horizontal.setEdge(i, j, style.borderTop[0], style.borderTop[1]); + } + + if (style.borderBottom) { + this.horizontal.setEdge(i + 1, j, style.borderBottom[0], style.borderBottom[1]); + } + + if (style.borderLeft) { + this.vertical.setEdge(i, j, style.borderLeft[0], style.borderLeft[1]); + } + + if (style.borderRight) { + this.vertical.setEdge(i, j + 1, style.borderRight[0], style.borderRight[1]); + } + } + + getMatrices() { + return { + horizontal: this.horizontal.getEdges(), + vertical: this.vertical.getEdges() + }; + } +} \ No newline at end of file diff --git a/tests/cypress/tests/unit/edges_test.ts b/tests/cypress/tests/unit/edges_test.ts new file mode 100644 index 000000000..a8413f341 --- /dev/null +++ b/tests/cypress/tests/unit/edges_test.ts @@ -0,0 +1,323 @@ +import dataEdges from 'dash-table/derived/edges/data'; + +describe('data edges', () => { + const edgesFn = dataEdges(); + + it('without data has no edges', () => { + const res = edgesFn( + [{ id: 'id', name: 'id' }], + [], + [], + { columns: 0, rows: 0 } + ); + + expect(res !== undefined).to.equal(false); + }); + + it('without one data row', () => { + const res = edgesFn( + [], + [{ id: 1 }], + [], + { columns: 0, rows: 0 } + ); + + expect(res !== undefined).to.equal(false); + }); + + it('uses `undefined` default style', () => { + const res = edgesFn( + [{ id: 'id', name: 'id' }], + [{ id: 1 }], + [], + { columns: 0, rows: 0 }, + { } + ); + + expect(res !== undefined).to.equal(true); + if (res) { + const { horizontal, vertical } = res; + + expect(horizontal.length).to.equal(2); + expect(horizontal[0].length).to.equal(1); + expect(horizontal[1].length).to.equal(1); + expect(horizontal[0][0] === undefined).to.equal(true); + expect(horizontal[1][0] === undefined).to.equal(true); + + expect(vertical.length).to.equal(1); + expect(vertical[0].length).to.equal(2); + expect(vertical[0][0] === undefined).to.equal(true); + expect(vertical[0][1] === undefined).to.equal(true); + } + }); + + it('uses default style', () => { + const res = edgesFn( + [{ id: 'id', name: 'id' }], + [{ id: 1 }], + [], + { columns: 0, rows: 0 }, + { horizontal: '1px solid red', vertical: '1px solid blue' } + ); + + expect(res !== undefined).to.equal(true); + if (res) { + const { horizontal, vertical } = res; + + expect(horizontal.length).to.equal(2); + expect(horizontal[0].length).to.equal(1); + expect(horizontal[1].length).to.equal(1); + expect(horizontal[0][0]).to.equal('1px solid red'); + expect(horizontal[1][0]).to.equal('1px solid red'); + + expect(vertical.length).to.equal(1); + expect(vertical[0].length).to.equal(2); + expect(vertical[0][0]).to.equal('1px solid blue'); + expect(vertical[0][1]).to.equal('1px solid blue'); + } + }); + + it('uses default style on multiple rows & columns', () => { + const res = edgesFn( + [ + { id: 'id', name: 'id' }, + { id: 'name', name: 'name' } + ], + [ + { id: 1, name: 'a' }, + { id: 1, name: 'b' }, + { id: 2, name: 'a' }, + { id: 2, name: 'b' } + ], + [], + { columns: 0, rows: 0 }, + { horizontal: '1px solid red', vertical: '1px solid blue' } + ); + + expect(res !== undefined).to.equal(true); + if (res) { + const { horizontal, vertical } = res; + + expect(horizontal.length).to.equal(5); + horizontal.forEach(edges => { + expect(edges.length).to.equal(2); + + edges.forEach(edge => { + expect(edge).to.equal('1px solid red'); + }); + }); + + expect(vertical.length).to.equal(4); + vertical.forEach(edges => { + expect(edges.length).to.equal(3); + + edges.forEach(edge => { + expect(edge).to.equal('1px solid blue'); + }); + }); + } + }); + + it('applies `border`', () => { + const res = edgesFn( + [ + { id: 'id', name: 'id' }, + { id: 'name', name: 'name' } + ], + [ + { id: 1, name: 'a' }, + { id: 1, name: 'b' }, + { id: 2, name: 'a' }, + { id: 2, name: 'b' } + ], + [{ + style: { border: '1px solid green' }, + matchesColumn: () => true, + matchesFilter: () => true, + matchesRow: () => true + }], + { columns: 0, rows: 0 }, + { horizontal: '1px solid red', vertical: '1px solid blue' } + ); + + expect(res !== undefined).to.equal(true); + if (res) { + const { horizontal, vertical } = res; + + expect(horizontal.length).to.equal(5); + horizontal.forEach(edges => { + expect(edges.length).to.equal(2); + + edges.forEach(edge => { + expect(edge).to.equal('1px solid green'); + }); + }); + + expect(vertical.length).to.equal(4); + vertical.forEach(edges => { + expect(edges.length).to.equal(3); + + edges.forEach(edge => { + expect(edge).to.equal('1px solid green'); + }); + }); + } + }); + + it('applies `borderLeft` and `borderTop`', () => { + const res = edgesFn( + [ + { id: 'id', name: 'id' }, + { id: 'name', name: 'name' } + ], + [ + { id: 1, name: 'a' }, + { id: 1, name: 'b' }, + { id: 2, name: 'a' }, + { id: 2, name: 'b' } + ], + [{ + style: { borderLeft: '1px solid green', borderTop: '1px solid darkgreen' }, + matchesColumn: () => true, + matchesFilter: () => true, + matchesRow: () => true + }], + { columns: 0, rows: 0 }, + { horizontal: '1px solid red', vertical: '1px solid blue' } + ); + + expect(res !== undefined).to.equal(true); + if (res) { + const { horizontal, vertical } = res; + + expect(horizontal.length).to.equal(5); + horizontal.forEach((edges, rowIndex) => { + expect(edges.length).to.equal(2); + + edges.forEach(edge => { + expect(edge).to.equal(rowIndex === horizontal.length - 1 ? + '1px solid red' : + '1px solid darkgreen' + ); + }); + }); + + expect(vertical.length).to.equal(4); + vertical.forEach(edges => { + expect(edges.length).to.equal(3); + + edges.forEach((edge, index) => { + expect(edge).to.equal(index === edges.length - 1 ? + '1px solid blue' : + '1px solid green' + ); + }); + }); + } + }); + + it('applies `borderLeft` overriden by higher precedence `borderRight`', () => { + const res = edgesFn( + [ + { id: 'id', name: 'id' }, + { id: 'name', name: 'name' } + ], + [ + { id: 1, name: 'a' } + ], + [{ + style: { borderLeft: '1px solid green' }, + matchesColumn: () => true, + matchesFilter: () => true, + matchesRow: () => true + }, { + style: { borderRight: '1px solid darkgreen' }, + matchesColumn: () => true, + matchesFilter: () => true, + matchesRow: () => true + }], + { columns: 0, rows: 0 } + ); + + expect(res !== undefined).to.equal(true); + if (res) { + const { vertical } = res; + + expect(vertical.length).to.equal(1); + expect(vertical[0].length).to.equal(3); + expect(vertical[0][0]).to.equal('1px solid green'); + expect(vertical[0][1]).to.equal('1px solid darkgreen'); + expect(vertical[0][2]).to.equal('1px solid darkgreen'); + } + }); + + it('applies `borderLeft` not overriden by lower precedence `borderRight`', () => { + const res = edgesFn( + [ + { id: 'id', name: 'id' }, + { id: 'name', name: 'name' } + ], + [ + { id: 1, name: 'a' } + ], + [{ + style: { borderRight: '1px solid darkgreen' }, + matchesColumn: () => true, + matchesFilter: () => true, + matchesRow: () => true + }, { + style: { borderLeft: '1px solid green' }, + matchesColumn: () => true, + matchesFilter: () => true, + matchesRow: () => true + }], + { columns: 0, rows: 0 } + ); + + expect(res === undefined).to.equal(false); + if (res) { + const { vertical } = res; + + expect(vertical.length).to.equal(1); + expect(vertical[0].length).to.equal(3); + expect(vertical[0][0]).to.equal('1px solid green'); + expect(vertical[0][1]).to.equal('1px solid green'); + expect(vertical[0][2]).to.equal('1px solid darkgreen'); + } + }); + + it('applies `borderLeft` overriden by higher precedence `border`', () => { + const res = edgesFn( + [ + { id: 'id', name: 'id' }, + { id: 'name', name: 'name' } + ], + [ + { id: 1, name: 'a' } + ], + [{ + style: { borderLeft: '1px solid darkgreen' }, + matchesColumn: () => true, + matchesFilter: () => true, + matchesRow: () => true + }, { + style: { border: '1px solid green' }, + matchesColumn: () => true, + matchesFilter: () => true, + matchesRow: () => true + }], + { columns: 0, rows: 0 } + ); + + expect(res !== undefined).to.equal(true); + if (res) { + const { vertical } = res; + + expect(vertical.length).to.equal(1); + expect(vertical[0].length).to.equal(3); + expect(vertical[0][0]).to.equal('1px solid green'); + expect(vertical[0][1]).to.equal('1px solid green'); + expect(vertical[0][2]).to.equal('1px solid green'); + } + }); +}); \ No newline at end of file From 781513e9f0c8ed572762435ded3abd1c355c4af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 30 Apr 2019 15:51:34 -0400 Subject: [PATCH 02/46] - tweak border calculations - use in actual table - default style --- src/core/math/matrixZipMap.ts | 75 +++++-- src/dash-table/components/CellFactory.tsx | 64 ++++-- src/dash-table/components/HeaderFactory.tsx | 49 ++++- src/dash-table/components/Table/Table.less | 198 ------------------ src/dash-table/components/Table/index.tsx | 15 +- src/dash-table/derived/cell/wrapperStyles.ts | 8 +- src/dash-table/derived/edges/data.ts | 13 +- src/dash-table/derived/edges/filter.ts | 11 +- src/dash-table/derived/edges/header.ts | 11 +- .../{dataOperations.ts => operationOfData.ts} | 13 +- .../{dataHeaders.ts => operationOfHeaders.ts} | 11 +- src/dash-table/derived/edges/type.ts | 58 ++--- .../derived/filter/wrapperStyles.ts | 8 +- .../derived/header/wrapperStyles.ts | 8 +- src/dash-table/derived/style/index.ts | 48 ++--- tests/cypress/tests/unit/edges_test.ts | 103 +++++---- 16 files changed, 305 insertions(+), 388 deletions(-) rename src/dash-table/derived/edges/{dataOperations.ts => operationOfData.ts} (82%) rename src/dash-table/derived/edges/{dataHeaders.ts => operationOfHeaders.ts} (78%) diff --git a/src/core/math/matrixZipMap.ts b/src/core/math/matrixZipMap.ts index 874da4af6..876079ad4 100644 --- a/src/core/math/matrixZipMap.ts +++ b/src/core/math/matrixZipMap.ts @@ -2,17 +2,37 @@ import * as R from 'ramda'; type Matrix = T[][]; -export function matrixMap( +export function matrixMap( m1: Matrix, - m2: Matrix, - cb: (d1: T1, d2: T2, i: number, j: number) => TR + cb: (d1: T1, i: number, j: number) => TR ) { const mapMatrix = R.addIndex(R.map); const mapRow = R.addIndex(R.map); return mapMatrix((iRow, i) => mapRow( - (ijValue, j) => cb(ijValue, m2[i][j], i, j), + (ijValue, j) => cb(ijValue, i, j), + iRow + ), m1 + ); +} + +export function matrixMap2( + m1: Matrix, + m2: Matrix | undefined, + cb: (d1: T1, d2: T2 | undefined, i: number, j: number) => TR +) { + const mapMatrix = R.addIndex(R.map); + const mapRow = R.addIndex(R.map); + + return mapMatrix((iRow, i) => + mapRow( + (ijValue, j) => cb( + ijValue, + m2 ? m2[i][j] : undefined, + i, + j + ), iRow ), m1 ); @@ -20,16 +40,22 @@ export function matrixMap( export function matrixMap3( m1: Matrix, - m2: Matrix, - m3: Matrix, - cb: (d1: T1, d2: T2, d3: T3, i: number, j: number) => TR + m2: Matrix | undefined, + m3: Matrix | undefined, + cb: (d1: T1, d2: T2 | undefined, d3: T3 | undefined, i: number, j: number) => TR ) { const mapMatrix = R.addIndex(R.map); const mapRow = R.addIndex(R.map); return mapMatrix((iRow, i) => mapRow( - (ijValue, j) => cb(ijValue, m2[i][j], m3[i][j], i, j), + (ijValue, j) => cb( + ijValue, + m2 ? m2[i][j] : undefined, + m3 ? m3[i][j] : undefined, + i, + j + ), iRow ), m1 ); @@ -37,17 +63,24 @@ export function matrixMap3( export function matrixMap4( m1: Matrix, - m2: Matrix, - m3: Matrix, - m4: Matrix, - cb: (d1: T1, d2: T2, d3: T3, d4: T4, i: number, j: number) => TR + m2: Matrix | undefined, + m3: Matrix | undefined, + m4: Matrix | undefined, + cb: (d1: T1, d2: T2 | undefined, d3: T3 | undefined, d4: T4 | undefined, i: number, j: number) => TR ) { const mapMatrix = R.addIndex(R.map); const mapRow = R.addIndex(R.map); return mapMatrix((iRow, i) => mapRow( - (ijValue, j) => cb(ijValue, m2[i][j], m3[i][j], m4[i][j], i, j), + (ijValue, j) => cb( + ijValue, + m2 ? m2[i][j] : undefined, + m3 ? m3[i][j] : undefined, + m4 ? m4[i][j] : undefined, + i, + j + ), iRow ), m1 ); @@ -55,17 +88,23 @@ export function matrixMap4( export function matrixMapN( cb: (i: number, j: number, ...args: any[]) => TR, - ...matrices: (any[][])[] + m1: Matrix, + ...matrices: (any[][] | undefined)[] ) { - const m1 = matrices.slice(0, 1); - const ms = matrices.slice(1); - const mapMatrix = R.addIndex(R.map); const mapRow = R.addIndex(R.map); return mapMatrix((iRow, i) => mapRow( - (ijValue, j) => cb(i, j, [ijValue, ...ms.map(m => m[i][j])]), + (ijValue, j) => cb( + i, + j, + [ + ijValue, + m1[i][j], + ...matrices.map(m => m ? m[i][j] : undefined) + ] + ), iRow ), m1 ); diff --git a/src/dash-table/components/CellFactory.tsx b/src/dash-table/components/CellFactory.tsx index 1f47e04e0..00c02610d 100644 --- a/src/dash-table/components/CellFactory.tsx +++ b/src/dash-table/components/CellFactory.tsx @@ -1,3 +1,4 @@ +import * as R from 'ramda'; import React from 'react'; import { ICellFactoryProps } from 'dash-table/components/Table/props'; @@ -8,7 +9,10 @@ import derivedCellStyles from 'dash-table/derived/cell/wrapperStyles'; import derivedDropdowns from 'dash-table/derived/cell/dropdowns'; import { derivedRelevantCellStyles } from 'dash-table/derived/style'; -import { matrixMap3 } from 'core/math/matrixZipMap'; +import derivedDataEdges from 'dash-table/derived/edges/data'; +import derivedOperationEdges from 'dash-table/derived/edges/operationOfData'; + +import { matrixMap, matrixMap3 } from 'core/math/matrixZipMap'; import { arrayMap } from 'core/math/arrayZipMap'; export default class CellFactory { @@ -24,7 +28,10 @@ export default class CellFactory { private readonly cellOperations = derivedCellOperations(), private readonly cellStyles = derivedCellStyles(), private readonly cellWrappers = derivedCellWrappers(propsFn), - private readonly relevantStyles = derivedRelevantCellStyles() + private readonly relevantStyles = derivedRelevantCellStyles(), + + private readonly dataEdges = derivedDataEdges(), + private readonly dataOperationEdges = derivedOperationEdges() ) { } public createCells() { @@ -49,6 +56,13 @@ export default class CellFactory { virtualized } = this.props; + const relevantStyles = this.relevantStyles( + style_cell, + style_data, + style_cell_conditional, + style_data_conditional + ); + const operations = this.cellOperations( data, virtualized.data, @@ -59,14 +73,21 @@ export default class CellFactory { setProps ); - const relevantStyles = this.relevantStyles( - style_cell, - style_data, - style_cell_conditional, - style_data_conditional + const cellBorders = this.dataEdges( + columns, + relevantStyles, + virtualized.data, + virtualized.offset ); - const wrapperStyles = this.cellStyles( + const operationBorders = this.dataOperationEdges( + (row_selectable !== false ? 1 : 0) + (row_deletable ? 1 : 0), + relevantStyles, + virtualized.data, + virtualized.offset + ); + + const cellStyles = this.cellStyles( columns, relevantStyles, virtualized.data, @@ -82,7 +103,7 @@ export default class CellFactory { dropdown_properties ); - const wrappers = this.cellWrappers( + const cellWrappers = this.cellWrappers( active_cell, columns, virtualized.data, @@ -90,7 +111,7 @@ export default class CellFactory { selected_cells ); - const contents = this.cellContents( + const cellContents = this.cellContents( active_cell, columns, virtualized.data, @@ -100,15 +121,28 @@ export default class CellFactory { dropdowns ); + const ops = matrixMap( + operations, + (o, i, j) => React.cloneElement(o, { + style: operationBorders && operationBorders.getStyle(i, j) + }) + ); + const cells = matrixMap3( - wrappers, - wrapperStyles, - contents, - (w, s, c) => React.cloneElement(w, { children: [c], style: s }) + cellWrappers, + cellStyles, + cellContents, + (w, s, c, i, j) => React.cloneElement(w, { + children: [c], + style: R.mergeAll([ + s, + cellBorders && cellBorders.getStyle(i, j) + ]) + }) ); return arrayMap( - operations, + ops, cells, (o, c) => Array.prototype.concat(o, c) ); diff --git a/src/dash-table/components/HeaderFactory.tsx b/src/dash-table/components/HeaderFactory.tsx index 185b0ab1c..12e8231b9 100644 --- a/src/dash-table/components/HeaderFactory.tsx +++ b/src/dash-table/components/HeaderFactory.tsx @@ -2,7 +2,7 @@ import * as R from 'ramda'; import React from 'react'; import { arrayMap } from 'core/math/arrayZipMap'; -import { matrixMap3 } from 'core/math/matrixZipMap'; +import { matrixMap, matrixMap3 } from 'core/math/matrixZipMap'; import { ControlledTableProps } from 'dash-table/components/Table/props'; import derivedHeaderContent from 'dash-table/derived/header/content'; @@ -14,6 +14,9 @@ import derivedHeaderWrappers from 'dash-table/derived/header/wrappers'; import { derivedRelevantHeaderStyles } from 'dash-table/derived/style'; import derivedHeaderStyles from 'dash-table/derived/header/wrapperStyles'; +import derivedHeaderEdges from 'dash-table/derived/edges/header'; +import derivedOperationEdges from 'dash-table/derived/edges/operationOfHeaders'; + export default class HeaderFactory { private readonly headerContent = derivedHeaderContent(); private readonly headerOperations = derivedHeaderOperations(); @@ -21,6 +24,9 @@ export default class HeaderFactory { private readonly headerWrappers = derivedHeaderWrappers(); private readonly relevantStyles = derivedRelevantHeaderStyles(); + private readonly headerEdges = derivedHeaderEdges(); + private readonly headerOperationEdges = derivedOperationEdges(); + private get props() { return this.propsFn(); } @@ -55,17 +61,29 @@ export default class HeaderFactory { const labelsAndIndices = R.zip(labels, indices); + const relevantStyles = this.relevantStyles( + style_cell, + style_header, + style_cell_conditional, + style_header_conditional + ); + const operations = this.headerOperations( headerRows, row_selectable, row_deletable ); - const relevantStyles = this.relevantStyles( - style_cell, - style_header, - style_cell_conditional, - style_header_conditional + const headerBorders = this.headerEdges( + columns, + headerRows, + relevantStyles + ); + + const operationBorders = this.headerOperationEdges( + (row_selectable !== false ? 1 : 0) + (row_deletable ? 1 : 0), + headerRows, + relevantStyles ); const wrapperStyles = this.headerStyles( @@ -91,12 +109,25 @@ export default class HeaderFactory { props ); + const ops = matrixMap( + operations, + (o, i, j) => React.cloneElement(o, { + style: operationBorders && operationBorders.getStyle(i, j) + }) + ); + const headers = matrixMap3( wrappers, wrapperStyles, content, - (w, s, c) => React.cloneElement(w, { children: [c], style: s })); - - return arrayMap(operations, headers, (o, h) => Array.prototype.concat(o, h)); + (w, s, c, i, j) => React.cloneElement(w, { + children: [c], + style: R.mergeAll([ + s, + headerBorders && headerBorders.getStyle(i, j) + ]) + })); + + return arrayMap(ops, headers, (o, h) => Array.prototype.concat(o, h)); } } diff --git a/src/dash-table/components/Table/Table.less b/src/dash-table/components/Table/Table.less index 3c733d52b..eff16e3f5 100644 --- a/src/dash-table/components/Table/Table.less +++ b/src/dash-table/components/Table/Table.less @@ -1,183 +1,5 @@ @import (reference) '~dash-table/style/reset.less'; -.inset-shadow(@color, @left, @top, @right, @bottom) { - box-shadow: inset @left 0px 0px 0px @color, - inset 0px @top 0px 0px @color, - inset @right 0px 0px @color, - inset 0px @bottom 0px 0px @color; -} - -.outline-shadow(@color, @left, @top, @right, @bottom) { - box-shadow: @left 0px 0px 0px @color, - 0px @top 0px 0px @color, - @right 0px 0px @color, - 0px @bottom 0px 0px @color; -} - -.top-left-cells(@isListView) { - .left-cells(@isListView); - .top-cells(@isListView); - - tr:first-of-type { - td:first-of-type, - th:first-of-type { - & when (@isListView = True) { - .inset-shadow(var(--border), 0px, 1px, 0px, -1px); - - &.focused { - .inset-shadow(var(--accent), 0px, 0px, 0px, -1px); - } - } - - & when (@isListView = False) { - .inset-shadow(var(--border), 1px, 1px, -1px, -1px); - - &.focused { - box-shadow: inset 0 0 0 1px var(--accent) - } - } - } - } -} - -.top-cells(@isListView) { - tr:first-of-type { - td, th { - & when (@isListView = True) { - .inset-shadow(var(--border), 0px, 1px, 0px, -1px); - - &.focused { - .inset-shadow(var(--accent), 0px, 0px, 0px, -1px); - } - } - - & when (@isListView = False) { - .inset-shadow(var(--border), 0px, 1px, -1px, -1px); - - &.focused { - box-shadow: inset 0 0 0 1px var(--accent) - } - } - } - } -} - -.left-cells(@isListView) { - tr { - td:first-of-type, - th:first-of-type { - & when (@isListView = True) { - .inset-shadow(var(--border), 0px, 0px, 0px, -1px); - - &.focused { - .inset-shadow(var(--accent), 0px, 0px, 0px, -1px); - } - } - - & when (@isListView = False) { - .inset-shadow(var(--border), 1px, 0px, -1px, -1px); - - &.focused { - box-shadow: inset 0 0 0 1px var(--accent) - } - } - - } - } -} - -.dash-borders(@isListView) { - &:not(.dash-freeze-top):not(.dash-freeze-left) { - .cell-1-1 { - .top-left-cells(@isListView); - } - } - - &:not(.dash-freeze-top).dash-freeze-left { - .cell-1-0 { - .top-left-cells(@isListView); - } - - .cell-1-1 { - .top-cells(@isListView); - } - } - - &.dash-freeze-top:not(.dash-freeze-left) { - .cell-0-1 { - .top-left-cells(@isListView); - } - - .cell-1-1 { - .left-cells(@isListView); - } - } - - &.dash-freeze-top.dash-freeze-left { - .cell-0-0 { - .top-left-cells(@isListView); - } - - .cell-0-1 { - .top-cells(@isListView); - } - - .cell-1-0 { - .left-cells(@isListView); - } - } - - td, th { - & when (@isListView = True) { - .inset-shadow(var(--border), 0px, 0px, 0px, -1px); - } - - & when (@isListView = False) { - .inset-shadow(var(--border), 0px, 0px, -1px, -1px); - } - - &:focus { - outline: none; - } - } - - td.focused { - & when (@isListView = True) { - .inset-shadow(var(--accent), 0px, 0px, 0px, -1px); - } - - & when (@isListView = False) { - box-shadow: inset 0 0 0 1px var(--accent) - } - } - - .dash-filter { - input::placeholder { - color: inherit; - font-size: 0.8em; - padding-right: 5px; - } - - & + .dash-filter { - &:not(:hover):not(:focus-within) { - input::placeholder { - color: transparent; - } - } - } - - &.invalid { - & when (@isListView = True) { - .inset-shadow(red, 0px, 0px, 0px, -1px); - } - - & when (@isListView = False) { - .inset-shadow(red, 1px, 1px, -1px, -1px); - } - } - } -} - .fit-content-polyfill() { width: auto; // MS Edge, IE width: fit-content; // Chrome @@ -380,14 +202,6 @@ } } - &:not(.dash-list-view) { - .dash-borders(False); - } - - &.dash-list-view { - .dash-borders(True); - } - .dash-filter.invalid { background-color: pink; } @@ -550,11 +364,6 @@ overflow-x: visible; } - .dash-spreadsheet-inner th { - box-shadow: inset 1px 0px 0px 0px var(--border), - inset 0px 1px 0px 0px var(--border); - } - .dash-spreadsheet-inner :not(.cell--selected) tr:hover, tr:hover input :not(.cell--selected) { background-color: var(--hover); @@ -578,7 +387,6 @@ } .expanded-row--empty-cell { - box-shadow: none; background-color: transparent; } @@ -633,12 +441,6 @@ color: var(--accent); } - .expanded-row { - box-shadow: inset 2px 0px 0px 0px var(--accent), - inset -1px 0px 0px 0px var(--border); - /* inset 0px 1px 0px 0px var(--border); */ - } - .dash-spreadsheet-inner .dash-delete-cell, .dash-spreadsheet-inner .dash-delete-header { .not-selectable(); diff --git a/src/dash-table/components/Table/index.tsx b/src/dash-table/components/Table/index.tsx index 84c7dbcfb..4c5a0d625 100644 --- a/src/dash-table/components/Table/index.tsx +++ b/src/dash-table/components/Table/index.tsx @@ -1,6 +1,10 @@ import React, { Component } from 'react'; import * as R from 'ramda'; +/*#if DEV*/ +import Logger from 'core/Logger'; +/*#endif*/ + import { memoizeOne, memoizeOneWithFlag } from 'core/memoizer'; import ControlledTable from 'dash-table/components/ControlledTable'; @@ -27,11 +31,10 @@ import 'react-select/dist/react-select.css'; import './Table.less'; import './Dropdown.css'; import { isEqual } from 'core/comparer'; -/*#if DEV*/ -import Logger from 'core/Logger'; -/*#endif*/ +import { Style } from 'dash-table/derived/style/props'; const DERIVED_REGEX = /^derived_/; +const DEFAULT_STYLE: Style = { border: '1px solid #d3d3d3' }; export default class Table extends Component { constructor(props: PropsWithDefaultsAndDerived) { @@ -78,6 +81,7 @@ export default class Table extends Component { + return R.merge(DEFAULT_STYLE, style); + }); private readonly filterCache = memoizeOneWithFlag(filter => filter); private readonly paginationCache = memoizeOneWithFlag(pagination => pagination); diff --git a/src/dash-table/derived/cell/wrapperStyles.ts b/src/dash-table/derived/cell/wrapperStyles.ts index f53d78d98..8fc114b5b 100644 --- a/src/dash-table/derived/cell/wrapperStyles.ts +++ b/src/dash-table/derived/cell/wrapperStyles.ts @@ -4,6 +4,7 @@ import { CSSProperties } from 'react'; import { memoizeOneFactory } from 'core/memoizer'; import { Data, VisibleColumns, IViewportOffset } from 'dash-table/components/Table/props'; import { IConvertedStyle } from '../style'; +import { BORDER_PROPERTIES_AND_FRAGMENTS } from '../edges/type'; type Style = CSSProperties | undefined; @@ -25,7 +26,12 @@ function getter( ) ); - return relevantStyles.length ? R.mergeAll(relevantStyles) : undefined; + return relevantStyles.length ? + R.omit( + BORDER_PROPERTIES_AND_FRAGMENTS, + R.mergeAll(relevantStyles) + ) : + undefined; }, columns), data); } diff --git a/src/dash-table/derived/edges/data.ts b/src/dash-table/derived/edges/data.ts index 40d07acd8..d0ddac532 100644 --- a/src/dash-table/derived/edges/data.ts +++ b/src/dash-table/derived/edges/data.ts @@ -10,7 +10,7 @@ import { } from 'dash-table/components/Table/props'; import { IConvertedStyle } from '../style'; -import { BorderStyle, BORDER_PROPERTIES, Edges, EdgesMatrices, IDefaultBorderStyle } from './type'; +import { BorderStyle, BORDER_PROPERTIES, EdgesMatrices } from './type'; const getWeightedStyle = ( borderStyles: IConvertedStyle[], @@ -43,16 +43,15 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: VisibleColumns, - data: Data, borderStyles: IConvertedStyle[], - offset: IViewportOffset, - defaultBorderStyle?: IDefaultBorderStyle -): Edges => { + data: Data, + offset: IViewportOffset +) => { if (data.length === 0 || columns.length === 0) { return; } - const edges = new EdgesMatrices(data.length, columns.length, defaultBorderStyle); + const edges = new EdgesMatrices(data.length, columns.length); R.addIndex(R.forEach)((datum, i) => R.addIndex(R.forEach)( @@ -72,5 +71,5 @@ export default memoizeOneFactory(( data ); - return edges.getMatrices(); + return edges; }); \ No newline at end of file diff --git a/src/dash-table/derived/edges/filter.ts b/src/dash-table/derived/edges/filter.ts index 0b2b78543..ec4bf77a8 100644 --- a/src/dash-table/derived/edges/filter.ts +++ b/src/dash-table/derived/edges/filter.ts @@ -8,7 +8,7 @@ import { } from 'dash-table/components/Table/props'; import { IConvertedStyle } from '../style'; -import { BorderStyle, BORDER_PROPERTIES, Edges, EdgesMatrices, IDefaultBorderStyle } from './type'; +import { BorderStyle, BORDER_PROPERTIES, EdgesMatrices } from './type'; const getWeightedStyle = ( borderStyles: IConvertedStyle[], @@ -36,14 +36,13 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: VisibleColumns, showFilters: boolean, - borderStyles: IConvertedStyle[], - defaultBorderStyle?: IDefaultBorderStyle -): Edges => { + borderStyles: IConvertedStyle[] +) => { if (!showFilters || columns.length === 0) { return; } - const edges = new EdgesMatrices(1, columns.length, defaultBorderStyle); + const edges = new EdgesMatrices(1, columns.length); R.forEach(i => R.addIndex(R.forEach)( @@ -60,5 +59,5 @@ export default memoizeOneFactory(( R.range(0, 1) ); - return edges.getMatrices(); + return edges; }); \ No newline at end of file diff --git a/src/dash-table/derived/edges/header.ts b/src/dash-table/derived/edges/header.ts index 01e01d2e5..24a2e011f 100644 --- a/src/dash-table/derived/edges/header.ts +++ b/src/dash-table/derived/edges/header.ts @@ -8,7 +8,7 @@ import { } from 'dash-table/components/Table/props'; import { IConvertedStyle } from '../style'; -import { BorderStyle, BORDER_PROPERTIES, Edges, EdgesMatrices, IDefaultBorderStyle } from './type'; +import { BorderStyle, BORDER_PROPERTIES, EdgesMatrices } from './type'; const getWeightedStyle = ( borderStyles: IConvertedStyle[], @@ -39,14 +39,13 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: VisibleColumns, headerRows: number, - borderStyles: IConvertedStyle[], - defaultBorderStyle?: IDefaultBorderStyle -): Edges => { + borderStyles: IConvertedStyle[] +) => { if (headerRows === 0 || columns.length === 0) { return; } - const edges = new EdgesMatrices(headerRows, columns.length, defaultBorderStyle); + const edges = new EdgesMatrices(headerRows, columns.length); R.forEach(i => R.addIndex(R.forEach)( @@ -64,5 +63,5 @@ export default memoizeOneFactory(( R.range(0, headerRows) ); - return edges.getMatrices(); + return edges; }); \ No newline at end of file diff --git a/src/dash-table/derived/edges/dataOperations.ts b/src/dash-table/derived/edges/operationOfData.ts similarity index 82% rename from src/dash-table/derived/edges/dataOperations.ts rename to src/dash-table/derived/edges/operationOfData.ts index 0d59850ee..02392bec3 100644 --- a/src/dash-table/derived/edges/dataOperations.ts +++ b/src/dash-table/derived/edges/operationOfData.ts @@ -8,7 +8,7 @@ import { } from 'dash-table/components/Table/props'; import { IConvertedStyle } from '../style'; -import { BorderStyle, BORDER_PROPERTIES, Edges, EdgesMatrices, IDefaultBorderStyle } from './type'; +import { BorderStyle, BORDER_PROPERTIES, EdgesMatrices } from './type'; const getWeightedStyle = ( borderStyles: IConvertedStyle[], @@ -39,16 +39,15 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: number, - data: Data, borderStyles: IConvertedStyle[], - offset: IViewportOffset, - defaultBorderStyle?: IDefaultBorderStyle -): Edges => { + data: Data, + offset: IViewportOffset +) => { if (data.length === 0 || columns === 0) { return; } - const edges = new EdgesMatrices(data.length, columns, defaultBorderStyle); + const edges = new EdgesMatrices(data.length, columns); R.addIndex(R.forEach)((datum, i) => R.forEach(j => { @@ -66,5 +65,5 @@ export default memoizeOneFactory(( data ); - return edges.getMatrices(); + return edges; }); \ No newline at end of file diff --git a/src/dash-table/derived/edges/dataHeaders.ts b/src/dash-table/derived/edges/operationOfHeaders.ts similarity index 78% rename from src/dash-table/derived/edges/dataHeaders.ts rename to src/dash-table/derived/edges/operationOfHeaders.ts index 31866325d..8471de25a 100644 --- a/src/dash-table/derived/edges/dataHeaders.ts +++ b/src/dash-table/derived/edges/operationOfHeaders.ts @@ -3,7 +3,7 @@ import * as R from 'ramda'; import { memoizeOneFactory } from 'core/memoizer'; import { IConvertedStyle } from '../style'; -import { BorderStyle, BORDER_PROPERTIES, Edges, EdgesMatrices, IDefaultBorderStyle } from './type'; +import { BorderStyle, BORDER_PROPERTIES, EdgesMatrices } from './type'; const getWeightedStyle = ( borderStyles: IConvertedStyle[], @@ -31,14 +31,13 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: number, headerRows: number, - borderStyles: IConvertedStyle[], - defaultBorderStyle?: IDefaultBorderStyle -): Edges => { + borderStyles: IConvertedStyle[] +) => { if (headerRows === 0 || columns === 0) { return; } - const edges = new EdgesMatrices(headerRows, columns, defaultBorderStyle); + const edges = new EdgesMatrices(headerRows, columns); R.forEach(i => R.forEach(j => { @@ -54,5 +53,5 @@ export default memoizeOneFactory(( R.range(0, headerRows) ); - return edges.getMatrices(); + return edges; }); \ No newline at end of file diff --git a/src/dash-table/derived/edges/type.ts b/src/dash-table/derived/edges/type.ts index 663b7191c..391ce6b95 100644 --- a/src/dash-table/derived/edges/type.ts +++ b/src/dash-table/derived/edges/type.ts @@ -2,18 +2,12 @@ import * as R from 'ramda'; import { CSSProperties } from 'react'; import { OptionalMap, OptionalProp, PropOf } from 'core/type'; +import py2jsCssProperties from '../style/py2jsCssProperties'; -interface IEdges { - horizontal: Edge[][]; - vertical: Edge[][]; -} - -export interface IDefaultBorderStyle { - vertical?: string; - horizontal?: string; -} +type Edge = any; -export type BorderProp = +type BorderProp = + PropOf | PropOf | PropOf | PropOf | @@ -33,22 +27,25 @@ export const BORDER_PROPERTIES: BorderProp[] = [ 'borderTop' ]; -export type Edge = any; - -export type Edges = IEdges | undefined; +export const BORDER_PROPERTIES_AND_FRAGMENTS: string[] = R.uniq( + R.filter( + p => p.indexOf('border') === 0, + Array.from(py2jsCssProperties.values()) + ) +); export class EdgesMatrix { private weights: number[][]; private edges: Edge[][]; - constructor(rows: number, columns: number, defaultBorderStyle?: Edge) { + constructor(rows: number, columns: number) { this.weights = R.map( () => new Array(columns).fill(-Infinity), R.range(0, rows) ); this.edges = R.map( - () => new Array(columns).fill(defaultBorderStyle), + () => new Array(columns).fill(undefined), R.range(0, rows) ); } @@ -62,26 +59,24 @@ export class EdgesMatrix { this.edges[i][j] = edge; } + getEdge(i: number, j: number) { + return this.edges[i][j]; + } + + /*#if TEST*/ getEdges() { return this.edges; } + /*#endif*/ } export class EdgesMatrices { private horizontal: EdgesMatrix; private vertical: EdgesMatrix; - constructor(rows: number, columns: number, defaultBorderStyle?: IDefaultBorderStyle) { - this.horizontal = new EdgesMatrix( - rows + 1, - columns, - defaultBorderStyle && defaultBorderStyle.horizontal - ); - this.vertical = new EdgesMatrix( - rows, - columns + 1, - defaultBorderStyle && defaultBorderStyle.vertical - ); + constructor(rows: number, columns: number) { + this.horizontal = new EdgesMatrix(rows + 1, columns); + this.vertical = new EdgesMatrix(rows, columns + 1 ); } setEdges(i: number, j: number, style: BorderStyle) { @@ -102,10 +97,21 @@ export class EdgesMatrices { } } + getStyle(i: number, j: number): CSSProperties { + return { + borderBottom: this.horizontal.getEdge(i + 1, j) || null, + borderTop: this.horizontal.getEdge(i, j) || null, + borderLeft: this.vertical.getEdge(i, j) || null, + borderRight: this.vertical.getEdge(i, j + 1) || null + }; + } + + /*#if TEST*/ getMatrices() { return { horizontal: this.horizontal.getEdges(), vertical: this.vertical.getEdges() }; } + /*#endif*/ } \ No newline at end of file diff --git a/src/dash-table/derived/filter/wrapperStyles.ts b/src/dash-table/derived/filter/wrapperStyles.ts index 81fa97ce2..2d8094339 100644 --- a/src/dash-table/derived/filter/wrapperStyles.ts +++ b/src/dash-table/derived/filter/wrapperStyles.ts @@ -6,6 +6,7 @@ import { memoizeOneFactory } from 'core/memoizer'; import { VisibleColumns } from 'dash-table/components/Table/props'; import { IConvertedStyle } from '../style'; +import { BORDER_PROPERTIES_AND_FRAGMENTS } from '../edges/type'; type Style = CSSProperties | undefined; @@ -22,7 +23,12 @@ function getter( ) ); - return relevantStyles.length ? R.mergeAll(relevantStyles) : undefined; + return relevantStyles.length ? + R.omit( + BORDER_PROPERTIES_AND_FRAGMENTS, + R.mergeAll(relevantStyles) + ) : + undefined; }, columns); } diff --git a/src/dash-table/derived/header/wrapperStyles.ts b/src/dash-table/derived/header/wrapperStyles.ts index d887da1f6..d1ed3d169 100644 --- a/src/dash-table/derived/header/wrapperStyles.ts +++ b/src/dash-table/derived/header/wrapperStyles.ts @@ -6,6 +6,7 @@ import { memoizeOneFactory } from 'core/memoizer'; import { VisibleColumns } from 'dash-table/components/Table/props'; import { IConvertedStyle } from '../style'; +import { BORDER_PROPERTIES_AND_FRAGMENTS } from '../edges/type'; type Style = CSSProperties | undefined; @@ -25,7 +26,12 @@ function getter( ) ); - return relevantStyles.length ? R.mergeAll(relevantStyles) : undefined; + return relevantStyles.length ? + R.omit( + BORDER_PROPERTIES_AND_FRAGMENTS, + R.mergeAll(relevantStyles) + ) : + undefined; }, columns), R.range(0, headerRows)); } diff --git a/src/dash-table/derived/style/index.ts b/src/dash-table/derived/style/index.ts index 641671bea..f80b59d53 100644 --- a/src/dash-table/derived/style/index.ts +++ b/src/dash-table/derived/style/index.ts @@ -74,48 +74,36 @@ export const derivedRelevantCellStyles = memoizeOneFactory(( dataCell: Style, cells: Cells, dataCells: DataCells -) => R.concat( - R.concat( - cell ? [convertElement(cell)] : [], - R.map(convertElement, cells || []) - ), - R.concat( - dataCell ? [convertElement(dataCell)] : [], - R.map(convertElement, dataCells || []) - ) -)); +) => R.unnest([ + cell ? [convertElement(cell)] : [], + R.map(convertElement, cells || []), + dataCell ? [convertElement(dataCell)] : [], + R.map(convertElement, dataCells || []) +])); export const derivedRelevantFilterStyles = memoizeOneFactory(( cell: Style, filter: Style, cells: Cells, filters: BasicFilters -) => R.concat( - R.concat( - cell ? [convertElement(cell)] : [], - R.map(convertElement, cells || []) - ), - R.concat( - filter ? [convertElement(filter)] : [], - R.map(convertElement, filters || []) - ) -)); +) => R.unnest([ + cell ? [convertElement(cell)] : [], + R.map(convertElement, cells || []), + filter ? [convertElement(filter)] : [], + R.map(convertElement, filters || []) +])); export const derivedRelevantHeaderStyles = memoizeOneFactory(( cell: Style, header: Style, cells: Cells, headers: Headers -) => R.concat( - R.concat( - cell ? [convertElement(cell)] : [], - R.map(convertElement, cells || []) - ), - R.concat( - header ? [convertElement(header)] : [], - R.map(convertElement, headers || []) - ) -)); +) => R.unnest([ + cell ? [convertElement(cell)] : [], + R.map(convertElement, cells || []), + header ? [convertElement(header)] : [], + R.map(convertElement, headers || []) +])); export const derivedTableStyle = memoizeOneFactory( (defaultTable: Table, table: Table) => [ diff --git a/tests/cypress/tests/unit/edges_test.ts b/tests/cypress/tests/unit/edges_test.ts index a8413f341..9e64b65e1 100644 --- a/tests/cypress/tests/unit/edges_test.ts +++ b/tests/cypress/tests/unit/edges_test.ts @@ -17,8 +17,8 @@ describe('data edges', () => { it('without one data row', () => { const res = edgesFn( [], - [{ id: 1 }], [], + [{ id: 1 }], { columns: 0, rows: 0 } ); @@ -28,15 +28,14 @@ describe('data edges', () => { it('uses `undefined` default style', () => { const res = edgesFn( [{ id: 'id', name: 'id' }], - [{ id: 1 }], [], - { columns: 0, rows: 0 }, - { } + [{ id: 1 }], + { columns: 0, rows: 0 } ); expect(res !== undefined).to.equal(true); if (res) { - const { horizontal, vertical } = res; + const { horizontal, vertical } = res.getMatrices(); expect(horizontal.length).to.equal(2); expect(horizontal[0].length).to.equal(1); @@ -54,26 +53,25 @@ describe('data edges', () => { it('uses default style', () => { const res = edgesFn( [{ id: 'id', name: 'id' }], - [{ id: 1 }], [], - { columns: 0, rows: 0 }, - { horizontal: '1px solid red', vertical: '1px solid blue' } + [{ id: 1 }], + { columns: 0, rows: 0 } ); expect(res !== undefined).to.equal(true); if (res) { - const { horizontal, vertical } = res; + const { horizontal, vertical } = res.getMatrices(); expect(horizontal.length).to.equal(2); expect(horizontal[0].length).to.equal(1); expect(horizontal[1].length).to.equal(1); - expect(horizontal[0][0]).to.equal('1px solid red'); - expect(horizontal[1][0]).to.equal('1px solid red'); + expect(horizontal[0][0]).to.equal(undefined); + expect(horizontal[1][0]).to.equal(undefined); expect(vertical.length).to.equal(1); expect(vertical[0].length).to.equal(2); - expect(vertical[0][0]).to.equal('1px solid blue'); - expect(vertical[0][1]).to.equal('1px solid blue'); + expect(vertical[0][0]).to.equal(undefined); + expect(vertical[0][1]).to.equal(undefined); } }); @@ -83,27 +81,26 @@ describe('data edges', () => { { id: 'id', name: 'id' }, { id: 'name', name: 'name' } ], + [], [ { id: 1, name: 'a' }, { id: 1, name: 'b' }, { id: 2, name: 'a' }, { id: 2, name: 'b' } ], - [], - { columns: 0, rows: 0 }, - { horizontal: '1px solid red', vertical: '1px solid blue' } + { columns: 0, rows: 0 } ); expect(res !== undefined).to.equal(true); if (res) { - const { horizontal, vertical } = res; + const { horizontal, vertical } = res.getMatrices(); expect(horizontal.length).to.equal(5); horizontal.forEach(edges => { expect(edges.length).to.equal(2); edges.forEach(edge => { - expect(edge).to.equal('1px solid red'); + expect(edge).to.equal(undefined); }); }); @@ -112,7 +109,7 @@ describe('data edges', () => { expect(edges.length).to.equal(3); edges.forEach(edge => { - expect(edge).to.equal('1px solid blue'); + expect(edge).to.equal(undefined); }); }); } @@ -124,25 +121,24 @@ describe('data edges', () => { { id: 'id', name: 'id' }, { id: 'name', name: 'name' } ], - [ - { id: 1, name: 'a' }, - { id: 1, name: 'b' }, - { id: 2, name: 'a' }, - { id: 2, name: 'b' } - ], [{ style: { border: '1px solid green' }, matchesColumn: () => true, matchesFilter: () => true, matchesRow: () => true }], - { columns: 0, rows: 0 }, - { horizontal: '1px solid red', vertical: '1px solid blue' } + [ + { id: 1, name: 'a' }, + { id: 1, name: 'b' }, + { id: 2, name: 'a' }, + { id: 2, name: 'b' } + ], + { columns: 0, rows: 0 } ); expect(res !== undefined).to.equal(true); if (res) { - const { horizontal, vertical } = res; + const { horizontal, vertical } = res.getMatrices(); expect(horizontal.length).to.equal(5); horizontal.forEach(edges => { @@ -170,25 +166,24 @@ describe('data edges', () => { { id: 'id', name: 'id' }, { id: 'name', name: 'name' } ], - [ - { id: 1, name: 'a' }, - { id: 1, name: 'b' }, - { id: 2, name: 'a' }, - { id: 2, name: 'b' } - ], [{ style: { borderLeft: '1px solid green', borderTop: '1px solid darkgreen' }, matchesColumn: () => true, matchesFilter: () => true, matchesRow: () => true }], - { columns: 0, rows: 0 }, - { horizontal: '1px solid red', vertical: '1px solid blue' } + [ + { id: 1, name: 'a' }, + { id: 1, name: 'b' }, + { id: 2, name: 'a' }, + { id: 2, name: 'b' } + ], + { columns: 0, rows: 0 } ); expect(res !== undefined).to.equal(true); if (res) { - const { horizontal, vertical } = res; + const { horizontal, vertical } = res.getMatrices(); expect(horizontal.length).to.equal(5); horizontal.forEach((edges, rowIndex) => { @@ -196,7 +191,7 @@ describe('data edges', () => { edges.forEach(edge => { expect(edge).to.equal(rowIndex === horizontal.length - 1 ? - '1px solid red' : + undefined : '1px solid darkgreen' ); }); @@ -208,7 +203,7 @@ describe('data edges', () => { edges.forEach((edge, index) => { expect(edge).to.equal(index === edges.length - 1 ? - '1px solid blue' : + undefined : '1px solid green' ); }); @@ -216,15 +211,12 @@ describe('data edges', () => { } }); - it('applies `borderLeft` overriden by higher precedence `borderRight`', () => { + it('applies `borderLeft` overridden by higher precedence `borderRight`', () => { const res = edgesFn( [ { id: 'id', name: 'id' }, { id: 'name', name: 'name' } ], - [ - { id: 1, name: 'a' } - ], [{ style: { borderLeft: '1px solid green' }, matchesColumn: () => true, @@ -236,12 +228,15 @@ describe('data edges', () => { matchesFilter: () => true, matchesRow: () => true }], + [ + { id: 1, name: 'a' } + ], { columns: 0, rows: 0 } ); expect(res !== undefined).to.equal(true); if (res) { - const { vertical } = res; + const { vertical } = res.getMatrices(); expect(vertical.length).to.equal(1); expect(vertical[0].length).to.equal(3); @@ -251,15 +246,12 @@ describe('data edges', () => { } }); - it('applies `borderLeft` not overriden by lower precedence `borderRight`', () => { + it('applies `borderLeft` not overridden by lower precedence `borderRight`', () => { const res = edgesFn( [ { id: 'id', name: 'id' }, { id: 'name', name: 'name' } ], - [ - { id: 1, name: 'a' } - ], [{ style: { borderRight: '1px solid darkgreen' }, matchesColumn: () => true, @@ -271,12 +263,15 @@ describe('data edges', () => { matchesFilter: () => true, matchesRow: () => true }], + [ + { id: 1, name: 'a' } + ], { columns: 0, rows: 0 } ); expect(res === undefined).to.equal(false); if (res) { - const { vertical } = res; + const { vertical } = res.getMatrices(); expect(vertical.length).to.equal(1); expect(vertical[0].length).to.equal(3); @@ -286,15 +281,12 @@ describe('data edges', () => { } }); - it('applies `borderLeft` overriden by higher precedence `border`', () => { + it('applies `borderLeft` overridden by higher precedence `border`', () => { const res = edgesFn( [ { id: 'id', name: 'id' }, { id: 'name', name: 'name' } ], - [ - { id: 1, name: 'a' } - ], [{ style: { borderLeft: '1px solid darkgreen' }, matchesColumn: () => true, @@ -306,12 +298,15 @@ describe('data edges', () => { matchesFilter: () => true, matchesRow: () => true }], + [ + { id: 1, name: 'a' } + ], { columns: 0, rows: 0 } ); expect(res !== undefined).to.equal(true); if (res) { - const { vertical } = res; + const { vertical } = res.getMatrices(); expect(vertical.length).to.equal(1); expect(vertical[0].length).to.equal(3); From afa1f0e70c32ada3b84d4b7ce8bbcb63cbf88769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 30 Apr 2019 19:54:37 -0400 Subject: [PATCH 03/46] reconcile styles --- src/core/math/arrayZipMap.ts | 11 +++- src/dash-table/components/CellFactory.tsx | 4 +- .../components/ControlledTable/index.tsx | 41 ++++++------- src/dash-table/components/FilterFactory.tsx | 60 ++++++++++++++++--- src/dash-table/components/HeaderFactory.tsx | 12 +++- src/dash-table/components/Table/Table.less | 24 +++++++- src/dash-table/derived/edges/filter.ts | 6 ++ src/dash-table/derived/edges/header.ts | 6 ++ .../derived/edges/operationOfHeaders.ts | 18 ++++-- src/dash-table/derived/edges/type.ts | 2 - src/dash-table/derived/table/fragments.tsx | 21 +++++-- webpack.test.config.js | 2 +- 12 files changed, 156 insertions(+), 51 deletions(-) diff --git a/src/core/math/arrayZipMap.ts b/src/core/math/arrayZipMap.ts index 2c3909399..36276b370 100644 --- a/src/core/math/arrayZipMap.ts +++ b/src/core/math/arrayZipMap.ts @@ -2,7 +2,16 @@ import * as R from 'ramda'; type Array = T[]; -export function arrayMap( +export function arrayMap( + a1: Array, + cb: (d1: T1, i: number) => TR +) { + const mapArray = R.addIndex(R.map); + + return mapArray((iValue, i) => cb(iValue, i), a1); +} + +export function arrayMap2( a1: Array, a2: Array, cb: (d1: T1, d2: T2, i: number) => TR diff --git a/src/dash-table/components/CellFactory.tsx b/src/dash-table/components/CellFactory.tsx index 00c02610d..f12d83c99 100644 --- a/src/dash-table/components/CellFactory.tsx +++ b/src/dash-table/components/CellFactory.tsx @@ -13,7 +13,7 @@ import derivedDataEdges from 'dash-table/derived/edges/data'; import derivedOperationEdges from 'dash-table/derived/edges/operationOfData'; import { matrixMap, matrixMap3 } from 'core/math/matrixZipMap'; -import { arrayMap } from 'core/math/arrayZipMap'; +import { arrayMap2 } from 'core/math/arrayZipMap'; export default class CellFactory { @@ -141,7 +141,7 @@ export default class CellFactory { }) ); - return arrayMap( + return arrayMap2( ops, cells, (o, c) => Array.prototype.concat(o, c) diff --git a/src/dash-table/components/ControlledTable/index.tsx b/src/dash-table/components/ControlledTable/index.tsx index 387000636..9d591d3cb 100644 --- a/src/dash-table/components/ControlledTable/index.tsx +++ b/src/dash-table/components/ControlledTable/index.tsx @@ -683,6 +683,7 @@ export default class ControlledTable extends PureComponent render() { const { id, + columns, column_conditional_tooltips, column_static_tooltip, content_style, @@ -703,26 +704,6 @@ export default class ControlledTable extends PureComponent virtualization } = this.props; - const containerClasses = [ - 'dash-spreadsheet', - 'dash-spreadsheet-container', - ...(virtualization ? ['dash-virtualized'] : []), - ...(n_fixed_rows ? ['dash-freeze-top'] : []), - ...(n_fixed_columns ? ['dash-freeze-left'] : []), - ...(style_as_list_view ? ['dash-list-view'] : []), - [`dash-${content_style}`] - ]; - - const classes = [ - 'dash-spreadsheet', - 'dash-spreadsheet-inner', - ...(virtualization ? ['dash-virtualized'] : []), - ...(n_fixed_rows ? ['dash-freeze-top'] : []), - ...(n_fixed_columns ? ['dash-freeze-left'] : []), - ...(style_as_list_view ? ['dash-list-view'] : []), - [`dash-${content_style}`] - ]; - const fragmentClasses = [ [ n_fixed_rows && n_fixed_columns ? 'dash-fixed-row dash-fixed-column' : '', @@ -735,13 +716,29 @@ export default class ControlledTable extends PureComponent ]; const rawTable = this.tableFn(); - const grid = derivedTableFragments( + const { grid, empty } = derivedTableFragments( n_fixed_columns, n_fixed_rows, rawTable, virtualized.offset.rows ); + const classes = [ + 'dash-spreadsheet', + ...(virtualization ? ['dash-virtualized'] : []), + ...(n_fixed_rows ? ['dash-freeze-top'] : []), + ...(n_fixed_columns ? ['dash-freeze-left'] : []), + ...(style_as_list_view ? ['dash-list-view'] : []), + ...(empty[0][1] ? ['dash-no-01'] : []), + ...(empty[1][1] ? ['dash-no-11'] : []), + ...(columns.length ? [] : ['dash-no-columns']), + ...(virtualized.data.length ? [] : ['dash-no-data']), + [`dash-${content_style}`] + ]; + + const containerClasses = ['dash-spreadsheet-container', ...classes]; + const innerClasses = ['dash-spreadsheet-inner', ...classes]; + const tableStyle = this.calculateTableStyle(style_table); const gridStyle = derivedTableFragmentStyles( virtualization, @@ -780,7 +777,7 @@ export default class ControlledTable extends PureComponent
{grid.map((row, rowIndex) => (
void; export interface IFilterOptions { @@ -30,6 +33,7 @@ export interface IFilterOptions { style_cell_conditional: Cells; style_filter: Style; style_filter_conditional: BasicFilters; + virtualized: IVirtualizedDerivedData; } export default class FilterFactory { @@ -38,6 +42,9 @@ export default class FilterFactory { private readonly relevantStyles = derivedRelevantFilterStyles(); private readonly headerOperations = derivedHeaderOperations(); + private readonly filterEdges = derivedFilterEdges(); + private readonly filterOperationEdges = derivedOperationEdges(); + private ops = new Map(); private get props() { @@ -136,7 +143,8 @@ export default class FilterFactory { style_cell, style_cell_conditional, style_filter, - style_filter_conditional + style_filter_conditional, + virtualized } = this.props; if (!filtering) { @@ -146,16 +154,39 @@ export default class FilterFactory { this.updateOps(filter, columns); if (filtering_type === FilteringType.Basic) { - const filterStyles = this.relevantStyles( + const relevantStyles = this.relevantStyles( style_cell, style_filter, style_cell_conditional, style_filter_conditional ); + const relevantOperationStyles = this.relevantStyles( + style_cell, + style_filter, + [], + [] + ); + + const isLastRow = virtualized.data.length === 0; + + const filterEdges = this.filterEdges( + columns, + true, + isLastRow, + relevantStyles + ); + + const operationBorders = this.filterOperationEdges( + (row_selectable !== false ? 1 : 0) + (row_deletable ? 1 : 0), + 1, + isLastRow, + relevantOperationStyles + ); + const wrapperStyles = this.filterStyles( columns, - filterStyles + relevantStyles ); const filters = R.addIndex(R.map)((column, index) => { @@ -167,10 +198,16 @@ export default class FilterFactory { ); }, columns); - const styledFilters = arrayMap( + const styledFilters = arrayMap2( filters, wrapperStyles, - (f, s) => React.cloneElement(f, { style: s })); + (f, s, j) => React.cloneElement(f, { + style: R.merge( + s, + filterEdges && filterEdges.getStyle(0, j) + ) + }) + ); const operations = this.headerOperations( 1, @@ -178,7 +215,14 @@ export default class FilterFactory { row_deletable )[0]; - return [operations.concat(styledFilters)]; + const ops = arrayMap( + operations, + (o, j) => React.cloneElement(o, { + style: operationBorders && operationBorders.getStyle(0, j) + }) + ); + + return [ops.concat(styledFilters)]; } else { return [[]]; } diff --git a/src/dash-table/components/HeaderFactory.tsx b/src/dash-table/components/HeaderFactory.tsx index 12e8231b9..6479cc425 100644 --- a/src/dash-table/components/HeaderFactory.tsx +++ b/src/dash-table/components/HeaderFactory.tsx @@ -1,7 +1,7 @@ import * as R from 'ramda'; import React from 'react'; -import { arrayMap } from 'core/math/arrayZipMap'; +import { arrayMap2 } from 'core/math/arrayZipMap'; import { matrixMap, matrixMap3 } from 'core/math/matrixZipMap'; import { ControlledTableProps } from 'dash-table/components/Table/props'; @@ -40,6 +40,7 @@ export default class HeaderFactory { const { columns, + filtering, merge_duplicate_headers, pagination_mode, row_deletable, @@ -51,7 +52,8 @@ export default class HeaderFactory { style_cell, style_cell_conditional, style_header, - style_header_conditional + style_header_conditional, + virtualized } = props; const headerRows = getHeaderRows(columns); @@ -74,15 +76,19 @@ export default class HeaderFactory { row_deletable ); + const isLastRow = !filtering || virtualized.data.length === 0; + const headerBorders = this.headerEdges( columns, headerRows, + isLastRow, relevantStyles ); const operationBorders = this.headerOperationEdges( (row_selectable !== false ? 1 : 0) + (row_deletable ? 1 : 0), headerRows, + isLastRow, relevantStyles ); @@ -128,6 +134,6 @@ export default class HeaderFactory { ]) })); - return arrayMap(ops, headers, (o, h) => Array.prototype.concat(o, h)); + return arrayMap2(ops, headers, (o, h) => Array.prototype.concat(o, h)); } } diff --git a/src/dash-table/components/Table/Table.less b/src/dash-table/components/Table/Table.less index eff16e3f5..e0e0039cd 100644 --- a/src/dash-table/components/Table/Table.less +++ b/src/dash-table/components/Table/Table.less @@ -156,6 +156,28 @@ } } + &:not(.dash-no-11) { + .row-0 { + tr:last-of-type { + td, th { + border-bottom: none !important; + } + } + } + } + + &:not(.dash-no-01) { + .cell-0-0, + .cell-1-0 { + tr { + td:last-of-type, + th:last-of-type { + border-right: none !important; + } + } + } + } + &.dash-freeze-left, &.dash-freeze-top, &.dash-virtualized { @@ -170,7 +192,7 @@ .row-1 { display: flex; flex-direction: row; - overflow: scroll; + overflow: auto; } .cell-0-0, diff --git a/src/dash-table/derived/edges/filter.ts b/src/dash-table/derived/edges/filter.ts index ec4bf77a8..b661e329f 100644 --- a/src/dash-table/derived/edges/filter.ts +++ b/src/dash-table/derived/edges/filter.ts @@ -36,6 +36,7 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: VisibleColumns, showFilters: boolean, + isLastRow: boolean, borderStyles: IConvertedStyle[] ) => { if (!showFilters || columns.length === 0) { @@ -43,6 +44,7 @@ export default memoizeOneFactory(( } const edges = new EdgesMatrices(1, columns.length); + const iNull = isLastRow ? -1 : 0; R.forEach(i => R.addIndex(R.forEach)( @@ -52,6 +54,10 @@ export default memoizeOneFactory(( column ); + if (i === iNull) { + cellStyle.borderBottom = undefined; + } + edges.setEdges(i, j, cellStyle); }, columns diff --git a/src/dash-table/derived/edges/header.ts b/src/dash-table/derived/edges/header.ts index 24a2e011f..7f8b66040 100644 --- a/src/dash-table/derived/edges/header.ts +++ b/src/dash-table/derived/edges/header.ts @@ -39,6 +39,7 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: VisibleColumns, headerRows: number, + isLastRow: boolean, borderStyles: IConvertedStyle[] ) => { if (headerRows === 0 || columns.length === 0) { @@ -46,6 +47,7 @@ export default memoizeOneFactory(( } const edges = new EdgesMatrices(headerRows, columns.length); + const iNull = isLastRow ? -1 : headerRows - 1; R.forEach(i => R.addIndex(R.forEach)( @@ -56,6 +58,10 @@ export default memoizeOneFactory(( i ); + if (i === iNull) { + cellStyle.borderBottom = undefined; + } + edges.setEdges(i, j, cellStyle); }, columns diff --git a/src/dash-table/derived/edges/operationOfHeaders.ts b/src/dash-table/derived/edges/operationOfHeaders.ts index 8471de25a..8203f140a 100644 --- a/src/dash-table/derived/edges/operationOfHeaders.ts +++ b/src/dash-table/derived/edges/operationOfHeaders.ts @@ -31,6 +31,7 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: number, headerRows: number, + isLastRow: boolean, borderStyles: IConvertedStyle[] ) => { if (headerRows === 0 || columns === 0) { @@ -38,16 +39,21 @@ export default memoizeOneFactory(( } const edges = new EdgesMatrices(headerRows, columns); + const iNull = isLastRow ? -1 : headerRows - 1; R.forEach(i => R.forEach(j => { - const cellStyle = getWeightedStyle( - borderStyles, - i - ); + const cellStyle = getWeightedStyle( + borderStyles, + i + ); - edges.setEdges(i, j, cellStyle); - }, + if (i === iNull) { + cellStyle.borderBottom = undefined; + } + + edges.setEdges(i, j, cellStyle); + }, R.range(0, columns) ), R.range(0, headerRows) diff --git a/src/dash-table/derived/edges/type.ts b/src/dash-table/derived/edges/type.ts index 391ce6b95..8c181c6b9 100644 --- a/src/dash-table/derived/edges/type.ts +++ b/src/dash-table/derived/edges/type.ts @@ -7,14 +7,12 @@ import py2jsCssProperties from '../style/py2jsCssProperties'; type Edge = any; type BorderProp = - PropOf | PropOf | PropOf | PropOf | PropOf; export type BorderStyle = - OptionalMap, number]> & OptionalMap, number]> & OptionalMap, number]> & OptionalMap, number]> & diff --git a/src/dash-table/derived/table/fragments.tsx b/src/dash-table/derived/table/fragments.tsx index db5ce3204..1274003b1 100644 --- a/src/dash-table/derived/table/fragments.tsx +++ b/src/dash-table/derived/table/fragments.tsx @@ -18,12 +18,17 @@ function renderFragment(cells: any[][] | null, offset: number = 0) { null; } +const isEmpty = (cells: JSX.Element[][] | null) => + !cells || + cells.length === 0 || + cells[0].length === 0; + export default ( fixedColumns: number, fixedRows: number, cells: JSX.Element[][], offset: number -): (JSX.Element | null)[][] => { +): { grid: (JSX.Element | null)[][], empty: boolean[][] } => { // slice out fixed columns const fixedColumnCells = fixedColumns ? R.map(row => @@ -50,8 +55,14 @@ export default ( fixedColumnCells.splice(0, fixedRows) : null; - return [ - [renderFragment(fixedRowAndColumnCells), renderFragment(fixedRowCells)], - [renderFragment(fixedColumnCells), renderFragment(cells, offset)] - ]; + return { + grid: [ + [renderFragment(fixedRowAndColumnCells), renderFragment(fixedRowCells)], + [renderFragment(fixedColumnCells), renderFragment(cells, offset)] + ], + empty: [ + [isEmpty(fixedRowAndColumnCells), isEmpty(fixedRowCells)], + [isEmpty(fixedColumnCells), isEmpty(cells)] + ] + }; }; \ No newline at end of file diff --git a/webpack.test.config.js b/webpack.test.config.js index 1928de87b..a16598f41 100644 --- a/webpack.test.config.js +++ b/webpack.test.config.js @@ -1,6 +1,6 @@ const config = require('./.config/webpack/base.js')( { - definitions: ['TEST_COPY_PASTE'] + definitions: ['TEST', 'TEST_COPY_PASTE'] }, 'development' ); From 2d2bdb41c9b3815858194aaebcdcdd712daa8174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 30 Apr 2019 21:36:56 -0400 Subject: [PATCH 04/46] reconcile edges in transition zones --- .../components/ControlledTable/index.tsx | 6 ++++-- src/dash-table/components/FilterFactory.tsx | 12 ++++-------- src/dash-table/components/HeaderFactory.tsx | 16 +++++++++------- src/dash-table/components/Table/Table.less | 17 +++++++++++++++-- src/dash-table/derived/edges/filter.ts | 6 ------ src/dash-table/derived/edges/header.ts | 6 ------ .../derived/edges/operationOfHeaders.ts | 6 ------ 7 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/dash-table/components/ControlledTable/index.tsx b/src/dash-table/components/ControlledTable/index.tsx index 9d591d3cb..28678158b 100644 --- a/src/dash-table/components/ControlledTable/index.tsx +++ b/src/dash-table/components/ControlledTable/index.tsx @@ -687,6 +687,7 @@ export default class ControlledTable extends PureComponent column_conditional_tooltips, column_static_tooltip, content_style, + filtering, n_fixed_columns, n_fixed_rows, scrollbarWidth, @@ -729,10 +730,11 @@ export default class ControlledTable extends PureComponent ...(n_fixed_rows ? ['dash-freeze-top'] : []), ...(n_fixed_columns ? ['dash-freeze-left'] : []), ...(style_as_list_view ? ['dash-list-view'] : []), - ...(empty[0][1] ? ['dash-no-01'] : []), - ...(empty[1][1] ? ['dash-no-11'] : []), + ...(empty[0][1] ? ['dash-empty-01'] : []), + ...(empty[1][1] ? ['dash-empty-11'] : []), ...(columns.length ? [] : ['dash-no-columns']), ...(virtualized.data.length ? [] : ['dash-no-data']), + ...(filtering ? [] : ['dash-no-filter']), [`dash-${content_style}`] ]; diff --git a/src/dash-table/components/FilterFactory.tsx b/src/dash-table/components/FilterFactory.tsx index ca73292f7..2af9b4efe 100644 --- a/src/dash-table/components/FilterFactory.tsx +++ b/src/dash-table/components/FilterFactory.tsx @@ -7,7 +7,7 @@ import memoizerCache from 'core/cache/memoizer'; import { memoizeOne } from 'core/memoizer'; import ColumnFilter from 'dash-table/components/Filter/Column'; -import { ColumnId, Filtering, FilteringType, IVisibleColumn, VisibleColumns, RowSelection, IVirtualizedDerivedData } from 'dash-table/components/Table/props'; +import { ColumnId, Filtering, FilteringType, IVisibleColumn, VisibleColumns, RowSelection } from 'dash-table/components/Table/props'; import derivedFilterStyles from 'dash-table/derived/filter/wrapperStyles'; import derivedHeaderOperations from 'dash-table/derived/header/operations'; import { derivedRelevantFilterStyles } from 'dash-table/derived/style'; @@ -33,7 +33,6 @@ export interface IFilterOptions { style_cell_conditional: Cells; style_filter: Style; style_filter_conditional: BasicFilters; - virtualized: IVirtualizedDerivedData; } export default class FilterFactory { @@ -143,8 +142,7 @@ export default class FilterFactory { style_cell, style_cell_conditional, style_filter, - style_filter_conditional, - virtualized + style_filter_conditional } = this.props; if (!filtering) { @@ -168,19 +166,15 @@ export default class FilterFactory { [] ); - const isLastRow = virtualized.data.length === 0; - const filterEdges = this.filterEdges( columns, true, - isLastRow, relevantStyles ); const operationBorders = this.filterOperationEdges( (row_selectable !== false ? 1 : 0) + (row_deletable ? 1 : 0), 1, - isLastRow, relevantOperationStyles ); @@ -202,6 +196,7 @@ export default class FilterFactory { filters, wrapperStyles, (f, s, j) => React.cloneElement(f, { + className: f.props.className + ` filter-row`, style: R.merge( s, filterEdges && filterEdges.getStyle(0, j) @@ -218,6 +213,7 @@ export default class FilterFactory { const ops = arrayMap( operations, (o, j) => React.cloneElement(o, { + className: o.props.className + ` filter-row`, style: operationBorders && operationBorders.getStyle(0, j) }) ); diff --git a/src/dash-table/components/HeaderFactory.tsx b/src/dash-table/components/HeaderFactory.tsx index 6479cc425..8bc8167b2 100644 --- a/src/dash-table/components/HeaderFactory.tsx +++ b/src/dash-table/components/HeaderFactory.tsx @@ -40,7 +40,6 @@ export default class HeaderFactory { const { columns, - filtering, merge_duplicate_headers, pagination_mode, row_deletable, @@ -52,8 +51,7 @@ export default class HeaderFactory { style_cell, style_cell_conditional, style_header, - style_header_conditional, - virtualized + style_header_conditional } = props; const headerRows = getHeaderRows(columns); @@ -76,19 +74,15 @@ export default class HeaderFactory { row_deletable ); - const isLastRow = !filtering || virtualized.data.length === 0; - const headerBorders = this.headerEdges( columns, headerRows, - isLastRow, relevantStyles ); const operationBorders = this.headerOperationEdges( (row_selectable !== false ? 1 : 0) + (row_deletable ? 1 : 0), headerRows, - isLastRow, relevantStyles ); @@ -115,9 +109,14 @@ export default class HeaderFactory { props ); + const iLastHeaderRow = headerRows - 1; + const ops = matrixMap( operations, (o, i, j) => React.cloneElement(o, { + className: i === iLastHeaderRow ? + o.props.className + ` last-header-row` : + o.props.className, style: operationBorders && operationBorders.getStyle(i, j) }) ); @@ -128,6 +127,9 @@ export default class HeaderFactory { content, (w, s, c, i, j) => React.cloneElement(w, { children: [c], + className: i === iLastHeaderRow ? + w.props.className + ` last-header-row` : + w.props.className, style: R.mergeAll([ s, headerBorders && headerBorders.getStyle(i, j) diff --git a/src/dash-table/components/Table/Table.less b/src/dash-table/components/Table/Table.less index e0e0039cd..50a49ef30 100644 --- a/src/dash-table/components/Table/Table.less +++ b/src/dash-table/components/Table/Table.less @@ -156,7 +156,20 @@ } } - &:not(.dash-no-11) { + &:not(.dash-no-data), + &:not(.dash-no-filter) { + tr th.last-header-row { + border-bottom: none !important; + } + } + + &:not(.dash-no-data) { + tr th.filter-row { + border-bottom: none !important; + } + } + + &:not(.dash-empty-11) { .row-0 { tr:last-of-type { td, th { @@ -166,7 +179,7 @@ } } - &:not(.dash-no-01) { + &:not(.dash-empty-01) { .cell-0-0, .cell-1-0 { tr { diff --git a/src/dash-table/derived/edges/filter.ts b/src/dash-table/derived/edges/filter.ts index b661e329f..ec4bf77a8 100644 --- a/src/dash-table/derived/edges/filter.ts +++ b/src/dash-table/derived/edges/filter.ts @@ -36,7 +36,6 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: VisibleColumns, showFilters: boolean, - isLastRow: boolean, borderStyles: IConvertedStyle[] ) => { if (!showFilters || columns.length === 0) { @@ -44,7 +43,6 @@ export default memoizeOneFactory(( } const edges = new EdgesMatrices(1, columns.length); - const iNull = isLastRow ? -1 : 0; R.forEach(i => R.addIndex(R.forEach)( @@ -54,10 +52,6 @@ export default memoizeOneFactory(( column ); - if (i === iNull) { - cellStyle.borderBottom = undefined; - } - edges.setEdges(i, j, cellStyle); }, columns diff --git a/src/dash-table/derived/edges/header.ts b/src/dash-table/derived/edges/header.ts index 7f8b66040..24a2e011f 100644 --- a/src/dash-table/derived/edges/header.ts +++ b/src/dash-table/derived/edges/header.ts @@ -39,7 +39,6 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: VisibleColumns, headerRows: number, - isLastRow: boolean, borderStyles: IConvertedStyle[] ) => { if (headerRows === 0 || columns.length === 0) { @@ -47,7 +46,6 @@ export default memoizeOneFactory(( } const edges = new EdgesMatrices(headerRows, columns.length); - const iNull = isLastRow ? -1 : headerRows - 1; R.forEach(i => R.addIndex(R.forEach)( @@ -58,10 +56,6 @@ export default memoizeOneFactory(( i ); - if (i === iNull) { - cellStyle.borderBottom = undefined; - } - edges.setEdges(i, j, cellStyle); }, columns diff --git a/src/dash-table/derived/edges/operationOfHeaders.ts b/src/dash-table/derived/edges/operationOfHeaders.ts index 8203f140a..ef67a27c2 100644 --- a/src/dash-table/derived/edges/operationOfHeaders.ts +++ b/src/dash-table/derived/edges/operationOfHeaders.ts @@ -31,7 +31,6 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: number, headerRows: number, - isLastRow: boolean, borderStyles: IConvertedStyle[] ) => { if (headerRows === 0 || columns === 0) { @@ -39,7 +38,6 @@ export default memoizeOneFactory(( } const edges = new EdgesMatrices(headerRows, columns); - const iNull = isLastRow ? -1 : headerRows - 1; R.forEach(i => R.forEach(j => { @@ -48,10 +46,6 @@ export default memoizeOneFactory(( i ); - if (i === iNull) { - cellStyle.borderBottom = undefined; - } - edges.setEdges(i, j, cellStyle); }, R.range(0, columns) From 971c26575cfb1c670a63fb8f9657a8361f657a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 30 Apr 2019 21:59:10 -0400 Subject: [PATCH 05/46] improve styles applied on operation columns --- src/dash-table/components/CellFactory.tsx | 9 ++- src/dash-table/components/FilterFactory.tsx | 14 ++--- src/dash-table/components/HeaderFactory.tsx | 13 ++++- src/dash-table/components/Table/Table.less | 4 +- .../derived/edges/operationOfFilters.ts | 57 +++++++++++++++++++ 5 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 src/dash-table/derived/edges/operationOfFilters.ts diff --git a/src/dash-table/components/CellFactory.tsx b/src/dash-table/components/CellFactory.tsx index f12d83c99..c74ee9a94 100644 --- a/src/dash-table/components/CellFactory.tsx +++ b/src/dash-table/components/CellFactory.tsx @@ -63,6 +63,13 @@ export default class CellFactory { style_data_conditional ); + const operationRelevantStyles = this.relevantStyles( + style_cell, + style_data, + R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), + R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_data_conditional) + ); + const operations = this.cellOperations( data, virtualized.data, @@ -82,7 +89,7 @@ export default class CellFactory { const operationBorders = this.dataOperationEdges( (row_selectable !== false ? 1 : 0) + (row_deletable ? 1 : 0), - relevantStyles, + operationRelevantStyles, virtualized.data, virtualized.offset ); diff --git a/src/dash-table/components/FilterFactory.tsx b/src/dash-table/components/FilterFactory.tsx index 2af9b4efe..fa966c45e 100644 --- a/src/dash-table/components/FilterFactory.tsx +++ b/src/dash-table/components/FilterFactory.tsx @@ -15,7 +15,7 @@ import { BasicFilters, Cells, Style } from 'dash-table/derived/style/props'; import { MultiColumnsSyntaxTree, SingleColumnSyntaxTree, getMultiColumnQueryString, getSingleColumnMap } from 'dash-table/syntax-tree'; import derivedFilterEdges from 'dash-table/derived/edges/filter'; -import derivedOperationEdges from 'dash-table/derived/edges/operationOfHeaders'; +import derivedOperationEdges from 'dash-table/derived/edges/operationOfFilters'; type SetFilter = (filter: string, rawFilter: string) => void; @@ -159,11 +159,11 @@ export default class FilterFactory { style_filter_conditional ); - const relevantOperationStyles = this.relevantStyles( + const operationRelevantStyles = this.relevantStyles( style_cell, style_filter, - [], - [] + R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), + R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_filter_conditional) ); const filterEdges = this.filterEdges( @@ -175,7 +175,7 @@ export default class FilterFactory { const operationBorders = this.filterOperationEdges( (row_selectable !== false ? 1 : 0) + (row_deletable ? 1 : 0), 1, - relevantOperationStyles + operationRelevantStyles ); const wrapperStyles = this.filterStyles( @@ -196,7 +196,7 @@ export default class FilterFactory { filters, wrapperStyles, (f, s, j) => React.cloneElement(f, { - className: f.props.className + ` filter-row`, + classes: f.props.classes + ` dash-filter-row`, style: R.merge( s, filterEdges && filterEdges.getStyle(0, j) @@ -213,7 +213,7 @@ export default class FilterFactory { const ops = arrayMap( operations, (o, j) => React.cloneElement(o, { - className: o.props.className + ` filter-row`, + className: o.props.className + ` dash-filter-row`, style: operationBorders && operationBorders.getStyle(0, j) }) ); diff --git a/src/dash-table/components/HeaderFactory.tsx b/src/dash-table/components/HeaderFactory.tsx index 8bc8167b2..838f257c7 100644 --- a/src/dash-table/components/HeaderFactory.tsx +++ b/src/dash-table/components/HeaderFactory.tsx @@ -68,6 +68,13 @@ export default class HeaderFactory { style_header_conditional ); + const operationRelevantStyles = this.relevantStyles( + style_cell, + style_header, + R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), + R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type) && R.isNil(s.if.header_index)), style_header_conditional) + ); + const operations = this.headerOperations( headerRows, row_selectable, @@ -77,7 +84,7 @@ export default class HeaderFactory { const headerBorders = this.headerEdges( columns, headerRows, - relevantStyles + operationRelevantStyles ); const operationBorders = this.headerOperationEdges( @@ -115,7 +122,7 @@ export default class HeaderFactory { operations, (o, i, j) => React.cloneElement(o, { className: i === iLastHeaderRow ? - o.props.className + ` last-header-row` : + o.props.className + ` dash-last-header-row` : o.props.className, style: operationBorders && operationBorders.getStyle(i, j) }) @@ -128,7 +135,7 @@ export default class HeaderFactory { (w, s, c, i, j) => React.cloneElement(w, { children: [c], className: i === iLastHeaderRow ? - w.props.className + ` last-header-row` : + w.props.className + ` dash-last-header-row` : w.props.className, style: R.mergeAll([ s, diff --git a/src/dash-table/components/Table/Table.less b/src/dash-table/components/Table/Table.less index 50a49ef30..b6e78ca4d 100644 --- a/src/dash-table/components/Table/Table.less +++ b/src/dash-table/components/Table/Table.less @@ -158,13 +158,13 @@ &:not(.dash-no-data), &:not(.dash-no-filter) { - tr th.last-header-row { + tr th.dash-last-header-row { border-bottom: none !important; } } &:not(.dash-no-data) { - tr th.filter-row { + tr th.dash-filter-row { border-bottom: none !important; } } diff --git a/src/dash-table/derived/edges/operationOfFilters.ts b/src/dash-table/derived/edges/operationOfFilters.ts new file mode 100644 index 000000000..ef67a27c2 --- /dev/null +++ b/src/dash-table/derived/edges/operationOfFilters.ts @@ -0,0 +1,57 @@ +import * as R from 'ramda'; + +import { memoizeOneFactory } from 'core/memoizer'; + +import { IConvertedStyle } from '../style'; +import { BorderStyle, BORDER_PROPERTIES, EdgesMatrices } from './type'; + +const getWeightedStyle = ( + borderStyles: IConvertedStyle[], + index: number +): BorderStyle => { + const res: BorderStyle = {}; + + R.addIndex(R.forEach)((rs, i) => { + if (!rs.matchesRow(index)) { + return; + } + + R.forEach(p => { + const s = rs.style[p] || rs.style.border; + + if (!R.isNil(s)) { + res[p] = [s, i]; + } + }, BORDER_PROPERTIES); + }, borderStyles); + + return res; +}; + +export default memoizeOneFactory(( + columns: number, + headerRows: number, + borderStyles: IConvertedStyle[] +) => { + if (headerRows === 0 || columns === 0) { + return; + } + + const edges = new EdgesMatrices(headerRows, columns); + + R.forEach(i => + R.forEach(j => { + const cellStyle = getWeightedStyle( + borderStyles, + i + ); + + edges.setEdges(i, j, cellStyle); + }, + R.range(0, columns) + ), + R.range(0, headerRows) + ); + + return edges; +}); \ No newline at end of file From 63c5434d9d749b5ce7978c19678d6b25d6407ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 30 Apr 2019 22:28:07 -0400 Subject: [PATCH 06/46] improve default props --- src/dash-table/dash/DataTable.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dash-table/dash/DataTable.js b/src/dash-table/dash/DataTable.js index 5357183a6..ac731aef3 100644 --- a/src/dash-table/dash/DataTable.js +++ b/src/dash-table/dash/DataTable.js @@ -77,8 +77,9 @@ export const defaultProps = { row_selectable: false, style_table: {}, - style_data_conditional: [], style_cell_conditional: [], + style_data_conditional: [], + style_filter_conditional: [], style_header_conditional: [], virtualization: false }; From 8cd350c0321944ee23fbbaa88dcecb50c03c0854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 1 May 2019 08:03:26 -0400 Subject: [PATCH 07/46] - improved caching - fix standalone filter tests --- src/dash-table/components/CellFactory.tsx | 5 ++-- src/dash-table/components/Filter/Column.tsx | 6 ++++- src/dash-table/components/FilterFactory.tsx | 30 ++++++++++++++------- src/dash-table/components/HeaderFactory.tsx | 5 ++-- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/dash-table/components/CellFactory.tsx b/src/dash-table/components/CellFactory.tsx index c74ee9a94..1a26066a4 100644 --- a/src/dash-table/components/CellFactory.tsx +++ b/src/dash-table/components/CellFactory.tsx @@ -29,6 +29,7 @@ export default class CellFactory { private readonly cellStyles = derivedCellStyles(), private readonly cellWrappers = derivedCellWrappers(propsFn), private readonly relevantStyles = derivedRelevantCellStyles(), + private readonly relevantOperationStyles = derivedRelevantCellStyles(), private readonly dataEdges = derivedDataEdges(), private readonly dataOperationEdges = derivedOperationEdges() @@ -63,7 +64,7 @@ export default class CellFactory { style_data_conditional ); - const operationRelevantStyles = this.relevantStyles( + const relevantOperationStyles = this.relevantOperationStyles( style_cell, style_data, R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), @@ -89,7 +90,7 @@ export default class CellFactory { const operationBorders = this.dataOperationEdges( (row_selectable !== false ? 1 : 0) + (row_deletable ? 1 : 0), - operationRelevantStyles, + relevantOperationStyles, virtualized.data, virtualized.offset ); diff --git a/src/dash-table/components/Filter/Column.tsx b/src/dash-table/components/Filter/Column.tsx index 1c32da746..b6e80f061 100644 --- a/src/dash-table/components/Filter/Column.tsx +++ b/src/dash-table/components/Filter/Column.tsx @@ -15,7 +15,11 @@ interface IColumnFilterProps { value?: string; } -export default class ColumnFilter extends PureComponent { +interface IState { + value?: string; +} + +export default class ColumnFilter extends PureComponent { constructor(props: IColumnFilterProps) { super(props); diff --git a/src/dash-table/components/FilterFactory.tsx b/src/dash-table/components/FilterFactory.tsx index fa966c45e..8af766918 100644 --- a/src/dash-table/components/FilterFactory.tsx +++ b/src/dash-table/components/FilterFactory.tsx @@ -16,6 +16,7 @@ import { MultiColumnsSyntaxTree, SingleColumnSyntaxTree, getMultiColumnQueryStri import derivedFilterEdges from 'dash-table/derived/edges/filter'; import derivedOperationEdges from 'dash-table/derived/edges/operationOfFilters'; +import { EdgesMatrices } from 'dash-table/derived/edges/type'; type SetFilter = (filter: string, rawFilter: string) => void; @@ -39,6 +40,7 @@ export default class FilterFactory { private readonly handlers = new Map(); private readonly filterStyles = derivedFilterStyles(); private readonly relevantStyles = derivedRelevantFilterStyles(); + private readonly relevantOperationStyles = derivedRelevantFilterStyles(); private readonly headerOperations = derivedHeaderOperations(); private readonly filterEdges = derivedFilterEdges(); @@ -130,6 +132,17 @@ export default class FilterFactory { />); }); + private wrapperStyles = memoizeOne(( + styles: any[], + edges: EdgesMatrices | undefined + ) => arrayMap( + styles, + (s, j) => R.merge( + s, + edges && edges.getStyle(0, j) + ) + )); + public createFilters() { const { columns, @@ -159,7 +172,7 @@ export default class FilterFactory { style_filter_conditional ); - const operationRelevantStyles = this.relevantStyles( + const relevantOperationStyles = this.relevantOperationStyles( style_cell, style_filter, R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), @@ -175,12 +188,12 @@ export default class FilterFactory { const operationBorders = this.filterOperationEdges( (row_selectable !== false ? 1 : 0) + (row_deletable ? 1 : 0), 1, - operationRelevantStyles + relevantOperationStyles ); - const wrapperStyles = this.filterStyles( - columns, - relevantStyles + const wrapperStyles = this.wrapperStyles( + this.filterStyles(columns, relevantStyles), + filterEdges ); const filters = R.addIndex(R.map)((column, index) => { @@ -195,12 +208,9 @@ export default class FilterFactory { const styledFilters = arrayMap2( filters, wrapperStyles, - (f, s, j) => React.cloneElement(f, { + (f, s) => React.cloneElement(f, { classes: f.props.classes + ` dash-filter-row`, - style: R.merge( - s, - filterEdges && filterEdges.getStyle(0, j) - ) + style: s }) ); diff --git a/src/dash-table/components/HeaderFactory.tsx b/src/dash-table/components/HeaderFactory.tsx index 838f257c7..d8b8db9ce 100644 --- a/src/dash-table/components/HeaderFactory.tsx +++ b/src/dash-table/components/HeaderFactory.tsx @@ -23,6 +23,7 @@ export default class HeaderFactory { private readonly headerStyles = derivedHeaderStyles(); private readonly headerWrappers = derivedHeaderWrappers(); private readonly relevantStyles = derivedRelevantHeaderStyles(); + private readonly relevantOperationStyles = derivedRelevantHeaderStyles(); private readonly headerEdges = derivedHeaderEdges(); private readonly headerOperationEdges = derivedOperationEdges(); @@ -68,7 +69,7 @@ export default class HeaderFactory { style_header_conditional ); - const operationRelevantStyles = this.relevantStyles( + const relevantOperationStyles = this.relevantOperationStyles( style_cell, style_header, R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), @@ -84,7 +85,7 @@ export default class HeaderFactory { const headerBorders = this.headerEdges( columns, headerRows, - operationRelevantStyles + relevantOperationStyles ); const operationBorders = this.headerOperationEdges( From e016bb5342dd77f6d55128ea263aeb4cd6b3f4e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 1 May 2019 08:37:40 -0400 Subject: [PATCH 08/46] - add basic border styling visual tests --- ...er.percy.tsx => Border.defaults.percy.tsx} | 12 +- .../percy-storybook/Border.style.percy.tsx | 322 ++++++++++++++++++ 2 files changed, 328 insertions(+), 6 deletions(-) rename tests/visual/percy-storybook/{Border.percy.tsx => Border.defaults.percy.tsx} (93%) create mode 100644 tests/visual/percy-storybook/Border.style.percy.tsx diff --git a/tests/visual/percy-storybook/Border.percy.tsx b/tests/visual/percy-storybook/Border.defaults.percy.tsx similarity index 93% rename from tests/visual/percy-storybook/Border.percy.tsx rename to tests/visual/percy-storybook/Border.defaults.percy.tsx index 0f60e9732..d33ca6c2f 100644 --- a/tests/visual/percy-storybook/Border.percy.tsx +++ b/tests/visual/percy-storybook/Border.defaults.percy.tsx @@ -43,7 +43,7 @@ const style_data_conditional = [ { width: 100 } ]; -let props = { +export const BORDER_PROPS_DEFAULTS = { setProps, id: 'table', data: data, @@ -63,18 +63,18 @@ let props2 = { storiesOf('DashTable/Border (available space not filled)', module) .add('with no frozen rows and no frozen columns', () => ()) .add('with frozen rows and no frozen columns', () => ()) .add('with no frozen rows and frozen columns', () => ()) .add('with frozen rows and frozen columns', () => ()); @@ -97,7 +97,7 @@ storiesOf('DashTable/Border (available space filled)', module) n_fixed_rows={1} />)); -let props3 = Object.assign({}, props, { +let props3 = Object.assign({}, BORDER_PROPS_DEFAULTS, { style_as_list_view: true }); diff --git a/tests/visual/percy-storybook/Border.style.percy.tsx b/tests/visual/percy-storybook/Border.style.percy.tsx new file mode 100644 index 000000000..622218ad6 --- /dev/null +++ b/tests/visual/percy-storybook/Border.style.percy.tsx @@ -0,0 +1,322 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import DataTable from 'dash-table/dash/DataTable'; +import { BORDER_PROPS_DEFAULTS } from './Border.defaults.percy'; + +const props_style_data = { + ...BORDER_PROPS_DEFAULTS, + style_data: { + border: '1px solid hotpink' + } +}; + +const props_specific_borders = { + ...BORDER_PROPS_DEFAULTS, + style_data: {}, + style_data_conditional: [ + { + if: { + row_index: 1, + column_id: 'b' + }, + borderTop: '2px solid red' + }, + { + if: { + row_index: 2, + column_id: 'b' + }, + borderRight: '2px solid blue' + }, + { + if: { + row_index: 3, + column_id: 'b' + }, + borderBottom: '2px solid magenta' + }, + { + if: { + row_index: 4, + column_id: 'b' + }, + borderLeft: '2px solid green' + } + ] +}; + +const props_sharing_horizontal = { + ...BORDER_PROPS_DEFAULTS, + style_data: {}, + style_data_conditional: [ + { + if: { column_id: 'b' }, + max_width: 200, + min_width: 200, + width: 200, + border: '1px solid cyan' + }, + { + if: { column_id: 'c' }, + max_width: 200, + min_width: 200, + width: 200, + border: '1px solid magenta' + } + ] +}; +const props_sharing_vertical = { + ...BORDER_PROPS_DEFAULTS, + style_data: {}, + style_data_conditional: [ + { + if: { + row_index: 1, + column_id: 'a' + }, + border: '2px solid red' + }, + { + if: { + row_index: 2, + column_id: 'a' + }, + border: '2px solid blue' + }, + { + if: { + row_index: 3, + column_id: 'a' + }, + border: '2px solid magenta' + }, + { + if: { + row_index: 4, + column_id: 'a' + }, + border: '2px solid green' + }, + { + if: { + row_index: 4, + column_id: 'c' + }, + border: '2px solid green' + }, + { + if: { + row_index: 3, + column_id: 'c' + }, + border: '2px solid magenta' + }, + { + if: { + row_index: 2, + column_id: 'c' + }, + border: '2px solid blue' + }, + { + if: { + row_index: 1, + column_id: 'c' + }, + border: '2px solid red' + } + ] +}; +const props_top_bottom = { + ...BORDER_PROPS_DEFAULTS, + style_data: {}, + style_data_conditional: [ + { + if: { + row_index: 2, + column_id: 'b' + }, + borderTop: '2px solid red' + }, + { + if: { + row_index: 1, + column_id: 'b' + }, + borderBottom: '2px solid blue' + }, + { + if: { + row_index: 1, + column_id: 'c' + }, + borderBottom: '2px solid blue' + }, + { + if: { + row_index: 2, + column_id: 'c' + }, + borderTop: '2px solid red' + } + ] +}; +const props_left_right = { + ...BORDER_PROPS_DEFAULTS, + style_data: {}, + style_data_conditional: [ + { + if: { + row_index: 2, + column_id: 'b' + }, + borderRight: '2px solid red' + }, + { + if: { + row_index: 2, + column_id: 'c' + }, + borderLeft: '2px solid blue' + }, + { + if: { + row_index: 3, + column_id: 'c' + }, + borderLeft: '2px solid blue' + }, + { + if: { + row_index: 3, + column_id: 'b' + }, + borderRight: '2px solid red' + } + ] +}; +const props_viewport = { + ...BORDER_PROPS_DEFAULTS, + style_data: {}, + style_data_conditional: [ + { + if: { + row_index: 2, + column_id: 'b' + }, + borderTop: '2px solid red', + borderLeft: '2px solid red' + }, + { + if: { + row_index: 2, + column_id: 'c' + }, + borderTop: '2px solid red', + borderRight: '2px solid red' + }, + { + if: { + row_index: 3, + column_id: 'b' + }, + borderLeft: '2px solid red' + }, + { + if: { + row_index: 3, + column_id: 'c' + }, + borderRight: '2px solid red' + }, + { + if: { + row_index: 4, + column_id: 'b' + }, + borderLeft: '2px solid red', + borderBottom: '2px solid red' + }, + { + if: { + row_index: 4, + column_id: 'c' + }, + borderBottom: '2px solid red', + borderRight: '2px solid red' + } + ] +}; + +storiesOf('DashTable/Border, custom styles', module) + .add('with defaults', () => ()) + .add('with cell style', () => ()) + .add('with data style', () => ()) + .add('with header style', () => ()) + .add('with filter style', () => ()) + .add('with header / data style - data wins on header', () => ()) + .add('with header / filter / data style - data wins on filter, filter wins on header', () => ()) + .add('with style_data', () => ) + .add('with style_data conditional, border top, right, bottom, left', () => ( + + )) + .add( + 'with style_data conditional, sharing borders horizontal borders', + () => + ) + .add('with style_data conditional, sharing vertical borders', () => ( + + )) + .add( + 'with style_data conditional, border-top and border-bottom fighting', + () => + ) + .add( + 'with style_data conditional, border-left and border-right fighting', + () => + ) + .add('with style_data conditional, borders creating a "viewport"', () => ( + + )); \ No newline at end of file From 06ee1167ea5c159c71397ef1213d2c3034fb01f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 1 May 2019 09:05:49 -0400 Subject: [PATCH 09/46] update border style visual tests --- .../percy-storybook/Border.style.percy.tsx | 271 +----------------- 1 file changed, 3 insertions(+), 268 deletions(-) diff --git a/tests/visual/percy-storybook/Border.style.percy.tsx b/tests/visual/percy-storybook/Border.style.percy.tsx index 622218ad6..a9c1f02a6 100644 --- a/tests/visual/percy-storybook/Border.style.percy.tsx +++ b/tests/visual/percy-storybook/Border.style.percy.tsx @@ -3,251 +3,6 @@ import { storiesOf } from '@storybook/react'; import DataTable from 'dash-table/dash/DataTable'; import { BORDER_PROPS_DEFAULTS } from './Border.defaults.percy'; -const props_style_data = { - ...BORDER_PROPS_DEFAULTS, - style_data: { - border: '1px solid hotpink' - } -}; - -const props_specific_borders = { - ...BORDER_PROPS_DEFAULTS, - style_data: {}, - style_data_conditional: [ - { - if: { - row_index: 1, - column_id: 'b' - }, - borderTop: '2px solid red' - }, - { - if: { - row_index: 2, - column_id: 'b' - }, - borderRight: '2px solid blue' - }, - { - if: { - row_index: 3, - column_id: 'b' - }, - borderBottom: '2px solid magenta' - }, - { - if: { - row_index: 4, - column_id: 'b' - }, - borderLeft: '2px solid green' - } - ] -}; - -const props_sharing_horizontal = { - ...BORDER_PROPS_DEFAULTS, - style_data: {}, - style_data_conditional: [ - { - if: { column_id: 'b' }, - max_width: 200, - min_width: 200, - width: 200, - border: '1px solid cyan' - }, - { - if: { column_id: 'c' }, - max_width: 200, - min_width: 200, - width: 200, - border: '1px solid magenta' - } - ] -}; -const props_sharing_vertical = { - ...BORDER_PROPS_DEFAULTS, - style_data: {}, - style_data_conditional: [ - { - if: { - row_index: 1, - column_id: 'a' - }, - border: '2px solid red' - }, - { - if: { - row_index: 2, - column_id: 'a' - }, - border: '2px solid blue' - }, - { - if: { - row_index: 3, - column_id: 'a' - }, - border: '2px solid magenta' - }, - { - if: { - row_index: 4, - column_id: 'a' - }, - border: '2px solid green' - }, - { - if: { - row_index: 4, - column_id: 'c' - }, - border: '2px solid green' - }, - { - if: { - row_index: 3, - column_id: 'c' - }, - border: '2px solid magenta' - }, - { - if: { - row_index: 2, - column_id: 'c' - }, - border: '2px solid blue' - }, - { - if: { - row_index: 1, - column_id: 'c' - }, - border: '2px solid red' - } - ] -}; -const props_top_bottom = { - ...BORDER_PROPS_DEFAULTS, - style_data: {}, - style_data_conditional: [ - { - if: { - row_index: 2, - column_id: 'b' - }, - borderTop: '2px solid red' - }, - { - if: { - row_index: 1, - column_id: 'b' - }, - borderBottom: '2px solid blue' - }, - { - if: { - row_index: 1, - column_id: 'c' - }, - borderBottom: '2px solid blue' - }, - { - if: { - row_index: 2, - column_id: 'c' - }, - borderTop: '2px solid red' - } - ] -}; -const props_left_right = { - ...BORDER_PROPS_DEFAULTS, - style_data: {}, - style_data_conditional: [ - { - if: { - row_index: 2, - column_id: 'b' - }, - borderRight: '2px solid red' - }, - { - if: { - row_index: 2, - column_id: 'c' - }, - borderLeft: '2px solid blue' - }, - { - if: { - row_index: 3, - column_id: 'c' - }, - borderLeft: '2px solid blue' - }, - { - if: { - row_index: 3, - column_id: 'b' - }, - borderRight: '2px solid red' - } - ] -}; -const props_viewport = { - ...BORDER_PROPS_DEFAULTS, - style_data: {}, - style_data_conditional: [ - { - if: { - row_index: 2, - column_id: 'b' - }, - borderTop: '2px solid red', - borderLeft: '2px solid red' - }, - { - if: { - row_index: 2, - column_id: 'c' - }, - borderTop: '2px solid red', - borderRight: '2px solid red' - }, - { - if: { - row_index: 3, - column_id: 'b' - }, - borderLeft: '2px solid red' - }, - { - if: { - row_index: 3, - column_id: 'c' - }, - borderRight: '2px solid red' - }, - { - if: { - row_index: 4, - column_id: 'b' - }, - borderLeft: '2px solid red', - borderBottom: '2px solid red' - }, - { - if: { - row_index: 4, - column_id: 'c' - }, - borderBottom: '2px solid red', - borderRight: '2px solid red' - } - ] -}; - storiesOf('DashTable/Border, custom styles', module) .add('with defaults', () => ( ()) .add('with header / filter / data style - data wins on filter, filter wins on header', () => ()) - .add('with style_data', () => ) - .add('with style_data conditional, border top, right, bottom, left', () => ( - - )) - .add( - 'with style_data conditional, sharing borders horizontal borders', - () => - ) - .add('with style_data conditional, sharing vertical borders', () => ( - - )) - .add( - 'with style_data conditional, border-top and border-bottom fighting', - () => - ) - .add( - 'with style_data conditional, border-left and border-right fighting', - () => - ) - .add('with style_data conditional, borders creating a "viewport"', () => ( - - )); \ No newline at end of file + />)); \ No newline at end of file From a6d2fb554f436dd0c5fce85226b7b44c10b8e3b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 1 May 2019 13:55:38 -0400 Subject: [PATCH 10/46] new approach to reconcile borders --- src/core/environment/index.ts | 10 +- src/dash-table/components/CellFactory.tsx | 43 +--- src/dash-table/components/EdgeFactory.tsx | 213 ++++++++++++++++++ src/dash-table/components/FilterFactory.tsx | 29 +-- src/dash-table/components/HeaderFactory.tsx | 32 +-- src/dash-table/components/Table/Table.less | 13 -- src/dash-table/components/Table/index.tsx | 8 - src/dash-table/derived/edges/data.ts | 3 +- src/dash-table/derived/edges/filter.ts | 3 +- src/dash-table/derived/edges/header.ts | 3 +- .../derived/edges/operationOfData.ts | 3 +- .../derived/edges/operationOfFilters.ts | 9 +- .../derived/edges/operationOfHeaders.ts | 3 +- src/dash-table/derived/edges/type.ts | 67 +++--- src/dash-table/derived/table/index.tsx | 15 +- .../percy-storybook/Border.style.percy.tsx | 58 +++++ 16 files changed, 353 insertions(+), 159 deletions(-) create mode 100644 src/dash-table/components/EdgeFactory.tsx diff --git a/src/core/environment/index.ts b/src/core/environment/index.ts index dfd399ad6..630b18051 100644 --- a/src/core/environment/index.ts +++ b/src/core/environment/index.ts @@ -1,9 +1,13 @@ -import { DebugLevel, LogLevel } from 'core/Logger'; import CookieStorage from 'core/storage/Cookie'; +import { DebugLevel, LogLevel } from 'core/Logger'; + +import { Edge } from 'dash-table/derived/edges/type'; const DASH_DEBUG = 'dash_debug'; const DASH_LOG = 'dash_log'; +const DEFAULT_EDGE: Edge = '1px solid #d3d3d3'; + interface ISearchParams { get: (key: string) => string | null; } @@ -33,4 +37,8 @@ export default class Environment { (LogLevel as any)[log] || LogLevel.ERROR : LogLevel.ERROR; } + + public static get defaultEdge(): Edge { + return DEFAULT_EDGE; + } } \ No newline at end of file diff --git a/src/dash-table/components/CellFactory.tsx b/src/dash-table/components/CellFactory.tsx index 1a26066a4..b0c9e5886 100644 --- a/src/dash-table/components/CellFactory.tsx +++ b/src/dash-table/components/CellFactory.tsx @@ -1,6 +1,9 @@ import * as R from 'ramda'; import React from 'react'; +import { matrixMap, matrixMap3 } from 'core/math/matrixZipMap'; +import { arrayMap2 } from 'core/math/arrayZipMap'; + import { ICellFactoryProps } from 'dash-table/components/Table/props'; import derivedCellWrappers from 'dash-table/derived/cell/wrappers'; import derivedCellContents from 'dash-table/derived/cell/contents'; @@ -8,12 +11,7 @@ import derivedCellOperations from 'dash-table/derived/cell/operations'; import derivedCellStyles from 'dash-table/derived/cell/wrapperStyles'; import derivedDropdowns from 'dash-table/derived/cell/dropdowns'; import { derivedRelevantCellStyles } from 'dash-table/derived/style'; - -import derivedDataEdges from 'dash-table/derived/edges/data'; -import derivedOperationEdges from 'dash-table/derived/edges/operationOfData'; - -import { matrixMap, matrixMap3 } from 'core/math/matrixZipMap'; -import { arrayMap2 } from 'core/math/arrayZipMap'; +import { EdgesMatrices } from 'dash-table/derived/edges/type'; export default class CellFactory { @@ -28,14 +26,10 @@ export default class CellFactory { private readonly cellOperations = derivedCellOperations(), private readonly cellStyles = derivedCellStyles(), private readonly cellWrappers = derivedCellWrappers(propsFn), - private readonly relevantStyles = derivedRelevantCellStyles(), - private readonly relevantOperationStyles = derivedRelevantCellStyles(), - - private readonly dataEdges = derivedDataEdges(), - private readonly dataOperationEdges = derivedOperationEdges() + private readonly relevantStyles = derivedRelevantCellStyles() ) { } - public createCells() { + public createCells(dataEdges: EdgesMatrices | undefined, dataOpEdges: EdgesMatrices | undefined) { const { active_cell, columns, @@ -64,13 +58,6 @@ export default class CellFactory { style_data_conditional ); - const relevantOperationStyles = this.relevantOperationStyles( - style_cell, - style_data, - R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), - R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_data_conditional) - ); - const operations = this.cellOperations( data, virtualized.data, @@ -81,20 +68,6 @@ export default class CellFactory { setProps ); - const cellBorders = this.dataEdges( - columns, - relevantStyles, - virtualized.data, - virtualized.offset - ); - - const operationBorders = this.dataOperationEdges( - (row_selectable !== false ? 1 : 0) + (row_deletable ? 1 : 0), - relevantOperationStyles, - virtualized.data, - virtualized.offset - ); - const cellStyles = this.cellStyles( columns, relevantStyles, @@ -132,7 +105,7 @@ export default class CellFactory { const ops = matrixMap( operations, (o, i, j) => React.cloneElement(o, { - style: operationBorders && operationBorders.getStyle(i, j) + style: dataOpEdges && dataOpEdges.getStyle(i, j) }) ); @@ -144,7 +117,7 @@ export default class CellFactory { children: [c], style: R.mergeAll([ s, - cellBorders && cellBorders.getStyle(i, j) + dataEdges && dataEdges.getStyle(i, j) ]) }) ); diff --git a/src/dash-table/components/EdgeFactory.tsx b/src/dash-table/components/EdgeFactory.tsx new file mode 100644 index 000000000..99980c279 --- /dev/null +++ b/src/dash-table/components/EdgeFactory.tsx @@ -0,0 +1,213 @@ +import * as R from 'ramda'; + +import { memoizeOne } from 'core/memoizer'; + +import derivedDataEdges from 'dash-table/derived/edges/data'; +import derivedDataOpEdges from 'dash-table/derived/edges/operationOfData'; +import derivedFilterEdges from 'dash-table/derived/edges/filter'; +import derivedFilterOpEdges from 'dash-table/derived/edges/operationOfFilters'; +import derivedHeaderEdges from 'dash-table/derived/edges/header'; +import derivedHeaderOpEdges from 'dash-table/derived/edges/operationOfHeaders'; +import { EdgesMatrices } from 'dash-table/derived/edges/type'; + +import getHeaderRows from 'dash-table/derived/header/headerRows'; + +import { derivedRelevantCellStyles, derivedRelevantFilterStyles, derivedRelevantHeaderStyles } from 'dash-table/derived/style'; +import { Style, Cells, DataCells, BasicFilters, Headers } from 'dash-table/derived/style/props'; + +import { ControlledTableProps, VisibleColumns, IViewportOffset, Data } from './Table/props'; + +export default class EdgeFactory { + private readonly dataStyles = derivedRelevantCellStyles(); + private readonly dataOpStyles = derivedRelevantCellStyles(); + private readonly filterStyles = derivedRelevantFilterStyles(); + private readonly filterOpStyles = derivedRelevantFilterStyles(); + private readonly headerStyles = derivedRelevantHeaderStyles(); + private readonly headerOpStyles = derivedRelevantHeaderStyles(); + + private readonly getDataEdges = derivedDataEdges(); + private readonly getDataOpEdges = derivedDataOpEdges(); + private readonly getFilterEdges = derivedFilterEdges(); + private readonly getFilterOpEdges = derivedFilterOpEdges(); + private readonly getHeaderEdges = derivedHeaderEdges(); + private readonly getHeaderOpEdges = derivedHeaderOpEdges(); + + private hReconcile(target: EdgesMatrices | undefined, next: EdgesMatrices | undefined, cutoffWeight: number): void { + if (!target || !next) { + return; + } + + const hNext = next.getMatrices().horizontal; + const hTarget = target.getMatrices().horizontal; + + const iNext = 0; + const iTarget = hTarget.rows - 1; + + R.forEach(j => + ( + hNext.getWeight(iNext, j) > cutoffWeight || + (hNext.getWeight(iNext, j) > hTarget.getWeight(iTarget, j)) + ) && hTarget.setEdge(iTarget, j, undefined, -Infinity, true), + R.range(0, hTarget.columns) + ); + } + + private vReconcile(target: EdgesMatrices | undefined, next: EdgesMatrices | undefined, cutoffWeight: number): void { + if (!target || !next) { + return; + } + + const vNext = target.getMatrices().vertical; + const vTarget = target.getMatrices().vertical; + + const jNext = 0; + const jTarget = vTarget.columns - 1; + + R.forEach(i => + ( + vNext.getWeight(i, jNext) > cutoffWeight || + (vNext.getWeight(i, jNext) > vTarget.getWeight(i, jTarget)) + ) && vTarget.setEdge(i, jTarget, undefined, -Infinity, true), + R.range(0, vTarget.rows) + ); + } + + private get props() { + return this.propsFn(); + } + + constructor(private readonly propsFn: () => ControlledTableProps) { + + } + + public createEdges() { + const { + columns, + filtering, + row_deletable, + row_selectable, + style_cell, + style_cell_conditional, + style_data, + style_data_conditional, + style_filter, + style_filter_conditional, + style_header, + style_header_conditional, + virtualized + } = this.props; + + return this.memoizedCreateEdges( + columns, + (row_deletable ? 1 : 0) + (row_selectable ? 1 : 0), + !!filtering, + style_cell, + style_cell_conditional, + style_data, + style_data_conditional, + style_filter, + style_filter_conditional, + style_header, + style_header_conditional, + virtualized.data, + virtualized.offset + ); + } + + public memoizedCreateEdges = memoizeOne(( + columns: VisibleColumns, + operations: number, + filtering: boolean, + style_cell: Style, + style_cell_conditional: Cells, + style_data: Style, + style_data_conditional: DataCells, + style_filter: Style, + style_filter_conditional: BasicFilters, + style_header: Style, + style_header_conditional: Headers, + data: Data, + offset: IViewportOffset + ) => { + const dataEdges = this.getDataEdges( + columns, + this.dataStyles( + style_cell, + style_data, + style_cell_conditional, + style_data_conditional + ), + data, + offset + ); + + const dataOpEdges = this.getDataOpEdges( + operations, + this.dataOpStyles( + style_cell, + style_data, + R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), + R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_data_conditional) + ), + data, + offset + ); + + const filterEdges = this.getFilterEdges( + columns, + filtering, + this.filterStyles( + style_cell, + style_filter, + style_cell_conditional, + style_filter_conditional + ) + ); + + const filterOpEdges = this.getFilterOpEdges( + operations, + filtering, + this.filterOpStyles( + style_cell, + style_filter, + R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), + R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_filter_conditional) + ) + ); + + const headerEdges = this.getHeaderEdges( + columns, + getHeaderRows(columns), + this.headerStyles( + style_cell, + style_header, + style_cell_conditional, + style_header_conditional + ) + ); + + const headerOpEdges = this.getHeaderOpEdges( + operations, + getHeaderRows(columns), + this.headerOpStyles( + style_cell, + style_header, + R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), + R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type) && R.isNil(s.if.header_index)), style_header_conditional) + ) + ); + + const cutoffWeight = (style_cell ? 1 : 0) + style_cell_conditional.length - 1; + + this.hReconcile(headerEdges, filterEdges || dataEdges, cutoffWeight); + this.hReconcile(headerOpEdges, filterOpEdges || dataOpEdges, cutoffWeight); + this.hReconcile(filterEdges, dataEdges, cutoffWeight); + this.hReconcile(filterOpEdges, dataOpEdges, cutoffWeight); + + this.vReconcile(headerOpEdges, headerEdges, cutoffWeight); + this.vReconcile(filterOpEdges, filterEdges, cutoffWeight); + this.vReconcile(dataOpEdges, dataEdges, cutoffWeight); + + return { dataEdges, dataOpEdges, filterEdges, filterOpEdges, headerEdges, headerOpEdges }; + }); +} \ No newline at end of file diff --git a/src/dash-table/components/FilterFactory.tsx b/src/dash-table/components/FilterFactory.tsx index 8af766918..2a4a7bda5 100644 --- a/src/dash-table/components/FilterFactory.tsx +++ b/src/dash-table/components/FilterFactory.tsx @@ -14,8 +14,6 @@ import { derivedRelevantFilterStyles } from 'dash-table/derived/style'; import { BasicFilters, Cells, Style } from 'dash-table/derived/style/props'; import { MultiColumnsSyntaxTree, SingleColumnSyntaxTree, getMultiColumnQueryString, getSingleColumnMap } from 'dash-table/syntax-tree'; -import derivedFilterEdges from 'dash-table/derived/edges/filter'; -import derivedOperationEdges from 'dash-table/derived/edges/operationOfFilters'; import { EdgesMatrices } from 'dash-table/derived/edges/type'; type SetFilter = (filter: string, rawFilter: string) => void; @@ -40,12 +38,8 @@ export default class FilterFactory { private readonly handlers = new Map(); private readonly filterStyles = derivedFilterStyles(); private readonly relevantStyles = derivedRelevantFilterStyles(); - private readonly relevantOperationStyles = derivedRelevantFilterStyles(); private readonly headerOperations = derivedHeaderOperations(); - private readonly filterEdges = derivedFilterEdges(); - private readonly filterOperationEdges = derivedOperationEdges(); - private ops = new Map(); private get props() { @@ -143,7 +137,7 @@ export default class FilterFactory { ) )); - public createFilters() { + public createFilters(filterEdges: EdgesMatrices | undefined, filterOpEdges: EdgesMatrices | undefined) { const { columns, filter, @@ -172,25 +166,6 @@ export default class FilterFactory { style_filter_conditional ); - const relevantOperationStyles = this.relevantOperationStyles( - style_cell, - style_filter, - R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), - R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_filter_conditional) - ); - - const filterEdges = this.filterEdges( - columns, - true, - relevantStyles - ); - - const operationBorders = this.filterOperationEdges( - (row_selectable !== false ? 1 : 0) + (row_deletable ? 1 : 0), - 1, - relevantOperationStyles - ); - const wrapperStyles = this.wrapperStyles( this.filterStyles(columns, relevantStyles), filterEdges @@ -224,7 +199,7 @@ export default class FilterFactory { operations, (o, j) => React.cloneElement(o, { className: o.props.className + ` dash-filter-row`, - style: operationBorders && operationBorders.getStyle(0, j) + style: filterOpEdges && filterOpEdges.getStyle(0, j) }) ); diff --git a/src/dash-table/components/HeaderFactory.tsx b/src/dash-table/components/HeaderFactory.tsx index d8b8db9ce..3a5249f1b 100644 --- a/src/dash-table/components/HeaderFactory.tsx +++ b/src/dash-table/components/HeaderFactory.tsx @@ -14,8 +14,7 @@ import derivedHeaderWrappers from 'dash-table/derived/header/wrappers'; import { derivedRelevantHeaderStyles } from 'dash-table/derived/style'; import derivedHeaderStyles from 'dash-table/derived/header/wrapperStyles'; -import derivedHeaderEdges from 'dash-table/derived/edges/header'; -import derivedOperationEdges from 'dash-table/derived/edges/operationOfHeaders'; +import { EdgesMatrices } from 'dash-table/derived/edges/type'; export default class HeaderFactory { private readonly headerContent = derivedHeaderContent(); @@ -23,10 +22,6 @@ export default class HeaderFactory { private readonly headerStyles = derivedHeaderStyles(); private readonly headerWrappers = derivedHeaderWrappers(); private readonly relevantStyles = derivedRelevantHeaderStyles(); - private readonly relevantOperationStyles = derivedRelevantHeaderStyles(); - - private readonly headerEdges = derivedHeaderEdges(); - private readonly headerOperationEdges = derivedOperationEdges(); private get props() { return this.propsFn(); @@ -36,7 +31,7 @@ export default class HeaderFactory { } - public createHeaders() { + public createHeaders(headerEdges: EdgesMatrices | undefined, headerOpEdges: EdgesMatrices | undefined) { const props = this.props; const { @@ -69,31 +64,12 @@ export default class HeaderFactory { style_header_conditional ); - const relevantOperationStyles = this.relevantOperationStyles( - style_cell, - style_header, - R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), - R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type) && R.isNil(s.if.header_index)), style_header_conditional) - ); - const operations = this.headerOperations( headerRows, row_selectable, row_deletable ); - const headerBorders = this.headerEdges( - columns, - headerRows, - relevantOperationStyles - ); - - const operationBorders = this.headerOperationEdges( - (row_selectable !== false ? 1 : 0) + (row_deletable ? 1 : 0), - headerRows, - relevantStyles - ); - const wrapperStyles = this.headerStyles( columns, headerRows, @@ -125,7 +101,7 @@ export default class HeaderFactory { className: i === iLastHeaderRow ? o.props.className + ` dash-last-header-row` : o.props.className, - style: operationBorders && operationBorders.getStyle(i, j) + style: headerOpEdges && headerOpEdges.getStyle(i, j) }) ); @@ -140,7 +116,7 @@ export default class HeaderFactory { w.props.className, style: R.mergeAll([ s, - headerBorders && headerBorders.getStyle(i, j) + headerEdges && headerEdges.getStyle(i, j) ]) })); diff --git a/src/dash-table/components/Table/Table.less b/src/dash-table/components/Table/Table.less index b6e78ca4d..4427862a7 100644 --- a/src/dash-table/components/Table/Table.less +++ b/src/dash-table/components/Table/Table.less @@ -156,19 +156,6 @@ } } - &:not(.dash-no-data), - &:not(.dash-no-filter) { - tr th.dash-last-header-row { - border-bottom: none !important; - } - } - - &:not(.dash-no-data) { - tr th.dash-filter-row { - border-bottom: none !important; - } - } - &:not(.dash-empty-11) { .row-0 { tr:last-of-type { diff --git a/src/dash-table/components/Table/index.tsx b/src/dash-table/components/Table/index.tsx index 4c5a0d625..4220f4692 100644 --- a/src/dash-table/components/Table/index.tsx +++ b/src/dash-table/components/Table/index.tsx @@ -31,10 +31,8 @@ import 'react-select/dist/react-select.css'; import './Table.less'; import './Dropdown.css'; import { isEqual } from 'core/comparer'; -import { Style } from 'dash-table/derived/style/props'; const DERIVED_REGEX = /^derived_/; -const DEFAULT_STYLE: Style = { border: '1px solid #d3d3d3' }; export default class Table extends Component { constructor(props: PropsWithDefaultsAndDerived) { @@ -81,7 +79,6 @@ export default class Table extends Component { - return R.merge(DEFAULT_STYLE, style); - }); private readonly filterCache = memoizeOneWithFlag(filter => filter); private readonly paginationCache = memoizeOneWithFlag(pagination => pagination); diff --git a/src/dash-table/derived/edges/data.ts b/src/dash-table/derived/edges/data.ts index d0ddac532..79f12f2e0 100644 --- a/src/dash-table/derived/edges/data.ts +++ b/src/dash-table/derived/edges/data.ts @@ -1,5 +1,6 @@ import * as R from 'ramda'; +import Environment from 'core/environment'; import { memoizeOneFactory } from 'core/memoizer'; import { @@ -51,7 +52,7 @@ export default memoizeOneFactory(( return; } - const edges = new EdgesMatrices(data.length, columns.length); + const edges = new EdgesMatrices(data.length, columns.length, Environment.defaultEdge); R.addIndex(R.forEach)((datum, i) => R.addIndex(R.forEach)( diff --git a/src/dash-table/derived/edges/filter.ts b/src/dash-table/derived/edges/filter.ts index ec4bf77a8..f4c2c2b16 100644 --- a/src/dash-table/derived/edges/filter.ts +++ b/src/dash-table/derived/edges/filter.ts @@ -1,5 +1,6 @@ import * as R from 'ramda'; +import Environment from 'core/environment'; import { memoizeOneFactory } from 'core/memoizer'; import { @@ -42,7 +43,7 @@ export default memoizeOneFactory(( return; } - const edges = new EdgesMatrices(1, columns.length); + const edges = new EdgesMatrices(1, columns.length, Environment.defaultEdge); R.forEach(i => R.addIndex(R.forEach)( diff --git a/src/dash-table/derived/edges/header.ts b/src/dash-table/derived/edges/header.ts index 24a2e011f..0eedb8c53 100644 --- a/src/dash-table/derived/edges/header.ts +++ b/src/dash-table/derived/edges/header.ts @@ -1,5 +1,6 @@ import * as R from 'ramda'; +import Environment from 'core/environment'; import { memoizeOneFactory } from 'core/memoizer'; import { @@ -45,7 +46,7 @@ export default memoizeOneFactory(( return; } - const edges = new EdgesMatrices(headerRows, columns.length); + const edges = new EdgesMatrices(headerRows, columns.length, Environment.defaultEdge); R.forEach(i => R.addIndex(R.forEach)( diff --git a/src/dash-table/derived/edges/operationOfData.ts b/src/dash-table/derived/edges/operationOfData.ts index 02392bec3..6884f0da5 100644 --- a/src/dash-table/derived/edges/operationOfData.ts +++ b/src/dash-table/derived/edges/operationOfData.ts @@ -1,5 +1,6 @@ import * as R from 'ramda'; +import Environment from 'core/environment'; import { memoizeOneFactory } from 'core/memoizer'; import { @@ -47,7 +48,7 @@ export default memoizeOneFactory(( return; } - const edges = new EdgesMatrices(data.length, columns); + const edges = new EdgesMatrices(data.length, columns, Environment.defaultEdge); R.addIndex(R.forEach)((datum, i) => R.forEach(j => { diff --git a/src/dash-table/derived/edges/operationOfFilters.ts b/src/dash-table/derived/edges/operationOfFilters.ts index ef67a27c2..957094b9f 100644 --- a/src/dash-table/derived/edges/operationOfFilters.ts +++ b/src/dash-table/derived/edges/operationOfFilters.ts @@ -1,5 +1,6 @@ import * as R from 'ramda'; +import Environment from 'core/environment'; import { memoizeOneFactory } from 'core/memoizer'; import { IConvertedStyle } from '../style'; @@ -30,14 +31,14 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: number, - headerRows: number, + filtering: boolean, borderStyles: IConvertedStyle[] ) => { - if (headerRows === 0 || columns === 0) { + if (!filtering || columns === 0) { return; } - const edges = new EdgesMatrices(headerRows, columns); + const edges = new EdgesMatrices(1, columns, Environment.defaultEdge); R.forEach(i => R.forEach(j => { @@ -50,7 +51,7 @@ export default memoizeOneFactory(( }, R.range(0, columns) ), - R.range(0, headerRows) + R.range(0, 1) ); return edges; diff --git a/src/dash-table/derived/edges/operationOfHeaders.ts b/src/dash-table/derived/edges/operationOfHeaders.ts index ef67a27c2..296b52dca 100644 --- a/src/dash-table/derived/edges/operationOfHeaders.ts +++ b/src/dash-table/derived/edges/operationOfHeaders.ts @@ -1,5 +1,6 @@ import * as R from 'ramda'; +import Environment from 'core/environment'; import { memoizeOneFactory } from 'core/memoizer'; import { IConvertedStyle } from '../style'; @@ -37,7 +38,7 @@ export default memoizeOneFactory(( return; } - const edges = new EdgesMatrices(headerRows, columns); + const edges = new EdgesMatrices(headerRows, columns, Environment.defaultEdge); R.forEach(i => R.forEach(j => { diff --git a/src/dash-table/derived/edges/type.ts b/src/dash-table/derived/edges/type.ts index 8c181c6b9..7d7542ce9 100644 --- a/src/dash-table/derived/edges/type.ts +++ b/src/dash-table/derived/edges/type.ts @@ -4,7 +4,7 @@ import { CSSProperties } from 'react'; import { OptionalMap, OptionalProp, PropOf } from 'core/type'; import py2jsCssProperties from '../style/py2jsCssProperties'; -type Edge = any; +export type Edge = any; type BorderProp = PropOf | @@ -36,20 +36,24 @@ export class EdgesMatrix { private weights: number[][]; private edges: Edge[][]; - constructor(rows: number, columns: number) { + constructor( + public readonly rows: number, + public readonly columns: number, + defaultEdge?: Edge + ) { this.weights = R.map( () => new Array(columns).fill(-Infinity), R.range(0, rows) ); this.edges = R.map( - () => new Array(columns).fill(undefined), + () => new Array(columns).fill(defaultEdge), R.range(0, rows) ); } - setEdge(i: number, j: number, edge: Edge, weight: number) { - if (R.isNil(edge) || weight <= this.weights[i][j]) { + setEdge(i: number, j: number, edge: Edge, weight: number, force: boolean = false) { + if (!force && (R.isNil(edge) || weight <= this.weights[i][j])) { return; } @@ -57,24 +61,22 @@ export class EdgesMatrix { this.edges[i][j] = edge; } - getEdge(i: number, j: number) { - return this.edges[i][j]; - } + getEdge = (i: number, j: number) => this.edges[i][j]; - /*#if TEST*/ - getEdges() { - return this.edges; - } - /*#endif*/ + getEdges = () => this.edges; + + getWeight = (i: number, j: number) => this.weights[i][j]; + + isDefault = (i: number, j: number) => !isFinite(this.weights[i][j]); } export class EdgesMatrices { private horizontal: EdgesMatrix; private vertical: EdgesMatrix; - constructor(rows: number, columns: number) { - this.horizontal = new EdgesMatrix(rows + 1, columns); - this.vertical = new EdgesMatrix(rows, columns + 1 ); + constructor(rows: number, columns: number, defaultEdge?: Edge) { + this.horizontal = new EdgesMatrix(rows + 1, columns, defaultEdge); + this.vertical = new EdgesMatrix(rows, columns + 1, defaultEdge); } setEdges(i: number, j: number, style: BorderStyle) { @@ -95,21 +97,20 @@ export class EdgesMatrices { } } - getStyle(i: number, j: number): CSSProperties { - return { - borderBottom: this.horizontal.getEdge(i + 1, j) || null, - borderTop: this.horizontal.getEdge(i, j) || null, - borderLeft: this.vertical.getEdge(i, j) || null, - borderRight: this.vertical.getEdge(i, j + 1) || null - }; - } - - /*#if TEST*/ - getMatrices() { - return { - horizontal: this.horizontal.getEdges(), - vertical: this.vertical.getEdges() - }; - } - /*#endif*/ + getEdges = () => ({ + horizontal: this.horizontal.getEdges(), + vertical: this.vertical.getEdges() + }) + + getMatrices = () => ({ + horizontal: this.horizontal, + vertical: this.vertical + }) + + getStyle = (i: number, j: number): CSSProperties => ({ + borderBottom: this.horizontal.getEdge(i + 1, j) || null, + borderTop: this.horizontal.getEdge(i, j) || null, + borderLeft: this.vertical.getEdge(i, j) || null, + borderRight: this.vertical.getEdge(i, j + 1) || null + }) } \ No newline at end of file diff --git a/src/dash-table/derived/table/index.tsx b/src/dash-table/derived/table/index.tsx index b4a59ad92..3e7f20a7a 100644 --- a/src/dash-table/derived/table/index.tsx +++ b/src/dash-table/derived/table/index.tsx @@ -3,6 +3,7 @@ import * as R from 'ramda'; import { memoizeOne } from 'core/memoizer'; import CellFactory from 'dash-table/components/CellFactory'; +import EdgeFactory from 'dash-table/components/EdgeFactory'; import FilterFactory from 'dash-table/components/FilterFactory'; import HeaderFactory from 'dash-table/components/HeaderFactory'; import { clearSelection } from 'dash-table/utils/actions'; @@ -22,13 +23,16 @@ function filterPropsFn(propsFn: () => ControlledTableProps, setFilter: any) { function getter( cellFactory: CellFactory, filterFactory: FilterFactory, - headerFactory: HeaderFactory + headerFactory: HeaderFactory, + edgeFactory: EdgeFactory ): JSX.Element[][] { const cells: JSX.Element[][] = []; - const dataCells = cellFactory.createCells(); - const filters = filterFactory.createFilters(); - const headers = headerFactory.createHeaders(); + const edges = edgeFactory.createEdges(); + + const dataCells = cellFactory.createCells(edges.dataEdges, edges.dataOpEdges); + const filters = filterFactory.createFilters(edges.filterEdges, edges.filterOpEdges); + const headers = headerFactory.createHeaders(edges.headerEdges, edges.headerOpEdges); cells.push(...headers); cells.push(...filters); @@ -50,6 +54,7 @@ export default (propsFn: () => ControlledTableProps) => { return filterPropsFn(propsFn, setFilter(props.setProps, props.setState)); }); const headerFactory = new HeaderFactory(propsFn); + const edgeFactory = new EdgeFactory(propsFn); - return getter.bind(undefined, cellFactory, filterFactory, headerFactory); + return getter.bind(undefined, cellFactory, filterFactory, headerFactory, edgeFactory); }; diff --git a/tests/visual/percy-storybook/Border.style.percy.tsx b/tests/visual/percy-storybook/Border.style.percy.tsx index a9c1f02a6..7ecda8fb6 100644 --- a/tests/visual/percy-storybook/Border.style.percy.tsx +++ b/tests/visual/percy-storybook/Border.style.percy.tsx @@ -33,6 +33,15 @@ storiesOf('DashTable/Border, custom styles', module) border: '1px solid hotpink' }} />)) + .add('with header / cell (data) style - header wins on cell (data)', () => ()) .add('with header / data style - data wins on header', () => ()) + .add('with header / filter / cell (data) style - filter wins on header, filter wins on cell (data)', () => ()) + .add('with header / data / cell (filter) style - header wins on cell (filter), data wins on cell (filter)', () => ()) + .add('with cell (header) / filter / data style - filter wins on cell (header), data wins on filter', () => ()) + .add('with data / cell (header, filter) style - data wins on filter', () => ()) .add('with header / filter / data style - data wins on filter, filter wins on header', () => ( Date: Wed, 1 May 2019 14:03:16 -0400 Subject: [PATCH 11/46] fix edge unit tests --- tests/cypress/tests/unit/edges_test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/cypress/tests/unit/edges_test.ts b/tests/cypress/tests/unit/edges_test.ts index 9e64b65e1..eb78857ad 100644 --- a/tests/cypress/tests/unit/edges_test.ts +++ b/tests/cypress/tests/unit/edges_test.ts @@ -35,7 +35,7 @@ describe('data edges', () => { expect(res !== undefined).to.equal(true); if (res) { - const { horizontal, vertical } = res.getMatrices(); + const { horizontal, vertical } = res.getEdges(); expect(horizontal.length).to.equal(2); expect(horizontal[0].length).to.equal(1); @@ -60,7 +60,7 @@ describe('data edges', () => { expect(res !== undefined).to.equal(true); if (res) { - const { horizontal, vertical } = res.getMatrices(); + const { horizontal, vertical } = res.getEdges(); expect(horizontal.length).to.equal(2); expect(horizontal[0].length).to.equal(1); @@ -93,7 +93,7 @@ describe('data edges', () => { expect(res !== undefined).to.equal(true); if (res) { - const { horizontal, vertical } = res.getMatrices(); + const { horizontal, vertical } = res.getEdges(); expect(horizontal.length).to.equal(5); horizontal.forEach(edges => { @@ -138,7 +138,7 @@ describe('data edges', () => { expect(res !== undefined).to.equal(true); if (res) { - const { horizontal, vertical } = res.getMatrices(); + const { horizontal, vertical } = res.getEdges(); expect(horizontal.length).to.equal(5); horizontal.forEach(edges => { @@ -183,7 +183,7 @@ describe('data edges', () => { expect(res !== undefined).to.equal(true); if (res) { - const { horizontal, vertical } = res.getMatrices(); + const { horizontal, vertical } = res.getEdges(); expect(horizontal.length).to.equal(5); horizontal.forEach((edges, rowIndex) => { @@ -236,7 +236,7 @@ describe('data edges', () => { expect(res !== undefined).to.equal(true); if (res) { - const { vertical } = res.getMatrices(); + const { vertical } = res.getEdges(); expect(vertical.length).to.equal(1); expect(vertical[0].length).to.equal(3); @@ -271,7 +271,7 @@ describe('data edges', () => { expect(res === undefined).to.equal(false); if (res) { - const { vertical } = res.getMatrices(); + const { vertical } = res.getEdges(); expect(vertical.length).to.equal(1); expect(vertical[0].length).to.equal(3); @@ -306,7 +306,7 @@ describe('data edges', () => { expect(res !== undefined).to.equal(true); if (res) { - const { vertical } = res.getMatrices(); + const { vertical } = res.getEdges(); expect(vertical.length).to.equal(1); expect(vertical[0].length).to.equal(3); From 6d67d6880b9f79531a0fda8dbfac0ff21ec925bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 1 May 2019 14:12:14 -0400 Subject: [PATCH 12/46] update edge tests --- tests/cypress/tests/unit/edges_test.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/cypress/tests/unit/edges_test.ts b/tests/cypress/tests/unit/edges_test.ts index eb78857ad..19be012b1 100644 --- a/tests/cypress/tests/unit/edges_test.ts +++ b/tests/cypress/tests/unit/edges_test.ts @@ -1,4 +1,5 @@ import dataEdges from 'dash-table/derived/edges/data'; +import Environment from 'core/environment'; describe('data edges', () => { const edgesFn = dataEdges(); @@ -40,13 +41,13 @@ describe('data edges', () => { expect(horizontal.length).to.equal(2); expect(horizontal[0].length).to.equal(1); expect(horizontal[1].length).to.equal(1); - expect(horizontal[0][0] === undefined).to.equal(true); - expect(horizontal[1][0] === undefined).to.equal(true); + expect(horizontal[0][0]).to.equal(Environment.defaultEdge); + expect(horizontal[1][0]).to.equal(Environment.defaultEdge); expect(vertical.length).to.equal(1); expect(vertical[0].length).to.equal(2); - expect(vertical[0][0] === undefined).to.equal(true); - expect(vertical[0][1] === undefined).to.equal(true); + expect(vertical[0][0]).to.equal(Environment.defaultEdge); + expect(vertical[0][1]).to.equal(Environment.defaultEdge); } }); @@ -65,13 +66,13 @@ describe('data edges', () => { expect(horizontal.length).to.equal(2); expect(horizontal[0].length).to.equal(1); expect(horizontal[1].length).to.equal(1); - expect(horizontal[0][0]).to.equal(undefined); - expect(horizontal[1][0]).to.equal(undefined); + expect(horizontal[0][0]).to.equal(Environment.defaultEdge); + expect(horizontal[1][0]).to.equal(Environment.defaultEdge); expect(vertical.length).to.equal(1); expect(vertical[0].length).to.equal(2); - expect(vertical[0][0]).to.equal(undefined); - expect(vertical[0][1]).to.equal(undefined); + expect(vertical[0][0]).to.equal(Environment.defaultEdge); + expect(vertical[0][1]).to.equal(Environment.defaultEdge); } }); @@ -100,7 +101,7 @@ describe('data edges', () => { expect(edges.length).to.equal(2); edges.forEach(edge => { - expect(edge).to.equal(undefined); + expect(edge).to.equal(Environment.defaultEdge); }); }); @@ -109,7 +110,7 @@ describe('data edges', () => { expect(edges.length).to.equal(3); edges.forEach(edge => { - expect(edge).to.equal(undefined); + expect(edge).to.equal(Environment.defaultEdge); }); }); } @@ -191,7 +192,7 @@ describe('data edges', () => { edges.forEach(edge => { expect(edge).to.equal(rowIndex === horizontal.length - 1 ? - undefined : + Environment.defaultEdge : '1px solid darkgreen' ); }); @@ -203,7 +204,7 @@ describe('data edges', () => { edges.forEach((edge, index) => { expect(edge).to.equal(index === edges.length - 1 ? - undefined : + Environment.defaultEdge : '1px solid green' ); }); From 128da307e0063611e1860a4db3d9d755dc635bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 1 May 2019 14:18:48 -0400 Subject: [PATCH 13/46] clean up --- src/dash-table/components/EdgeFactory.tsx | 2 +- src/dash-table/components/FilterFactory.tsx | 2 -- src/dash-table/components/HeaderFactory.tsx | 8 -------- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/dash-table/components/EdgeFactory.tsx b/src/dash-table/components/EdgeFactory.tsx index 99980c279..0f638ca3c 100644 --- a/src/dash-table/components/EdgeFactory.tsx +++ b/src/dash-table/components/EdgeFactory.tsx @@ -114,7 +114,7 @@ export default class EdgeFactory { ); } - public memoizedCreateEdges = memoizeOne(( + private memoizedCreateEdges = memoizeOne(( columns: VisibleColumns, operations: number, filtering: boolean, diff --git a/src/dash-table/components/FilterFactory.tsx b/src/dash-table/components/FilterFactory.tsx index 2a4a7bda5..f72812da9 100644 --- a/src/dash-table/components/FilterFactory.tsx +++ b/src/dash-table/components/FilterFactory.tsx @@ -184,7 +184,6 @@ export default class FilterFactory { filters, wrapperStyles, (f, s) => React.cloneElement(f, { - classes: f.props.classes + ` dash-filter-row`, style: s }) ); @@ -198,7 +197,6 @@ export default class FilterFactory { const ops = arrayMap( operations, (o, j) => React.cloneElement(o, { - className: o.props.className + ` dash-filter-row`, style: filterOpEdges && filterOpEdges.getStyle(0, j) }) ); diff --git a/src/dash-table/components/HeaderFactory.tsx b/src/dash-table/components/HeaderFactory.tsx index 3a5249f1b..a8a605f2f 100644 --- a/src/dash-table/components/HeaderFactory.tsx +++ b/src/dash-table/components/HeaderFactory.tsx @@ -93,14 +93,9 @@ export default class HeaderFactory { props ); - const iLastHeaderRow = headerRows - 1; - const ops = matrixMap( operations, (o, i, j) => React.cloneElement(o, { - className: i === iLastHeaderRow ? - o.props.className + ` dash-last-header-row` : - o.props.className, style: headerOpEdges && headerOpEdges.getStyle(i, j) }) ); @@ -111,9 +106,6 @@ export default class HeaderFactory { content, (w, s, c, i, j) => React.cloneElement(w, { children: [c], - className: i === iLastHeaderRow ? - w.props.className + ` dash-last-header-row` : - w.props.className, style: R.mergeAll([ s, headerEdges && headerEdges.getStyle(i, j) From 46cab577a5f42f226f1b86fe92605b945816badc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 1 May 2019 15:07:41 -0400 Subject: [PATCH 14/46] simulacrum of immutability through types --- src/dash-table/components/CellFactory.tsx | 4 +- src/dash-table/components/EdgeFactory.tsx | 57 ++++++---- src/dash-table/components/FilterFactory.tsx | 6 +- src/dash-table/components/HeaderFactory.tsx | 4 +- src/dash-table/derived/edges/type.ts | 113 +++++++++++++++++--- 5 files changed, 140 insertions(+), 44 deletions(-) diff --git a/src/dash-table/components/CellFactory.tsx b/src/dash-table/components/CellFactory.tsx index b0c9e5886..a25fd496b 100644 --- a/src/dash-table/components/CellFactory.tsx +++ b/src/dash-table/components/CellFactory.tsx @@ -11,7 +11,7 @@ import derivedCellOperations from 'dash-table/derived/cell/operations'; import derivedCellStyles from 'dash-table/derived/cell/wrapperStyles'; import derivedDropdowns from 'dash-table/derived/cell/dropdowns'; import { derivedRelevantCellStyles } from 'dash-table/derived/style'; -import { EdgesMatrices } from 'dash-table/derived/edges/type'; +import { IEdgesMatrices } from 'dash-table/derived/edges/type'; export default class CellFactory { @@ -29,7 +29,7 @@ export default class CellFactory { private readonly relevantStyles = derivedRelevantCellStyles() ) { } - public createCells(dataEdges: EdgesMatrices | undefined, dataOpEdges: EdgesMatrices | undefined) { + public createCells(dataEdges: IEdgesMatrices | undefined, dataOpEdges: IEdgesMatrices | undefined) { const { active_cell, columns, diff --git a/src/dash-table/components/EdgeFactory.tsx b/src/dash-table/components/EdgeFactory.tsx index 0f638ca3c..a89fa055c 100644 --- a/src/dash-table/components/EdgeFactory.tsx +++ b/src/dash-table/components/EdgeFactory.tsx @@ -8,7 +8,7 @@ import derivedFilterEdges from 'dash-table/derived/edges/filter'; import derivedFilterOpEdges from 'dash-table/derived/edges/operationOfFilters'; import derivedHeaderEdges from 'dash-table/derived/edges/header'; import derivedHeaderOpEdges from 'dash-table/derived/edges/operationOfHeaders'; -import { EdgesMatrices } from 'dash-table/derived/edges/type'; +import { EdgesMatrices, IEdgesMatrices } from 'dash-table/derived/edges/type'; import getHeaderRows from 'dash-table/derived/header/headerRows'; @@ -32,11 +32,13 @@ export default class EdgeFactory { private readonly getHeaderEdges = derivedHeaderEdges(); private readonly getHeaderOpEdges = derivedHeaderOpEdges(); - private hReconcile(target: EdgesMatrices | undefined, next: EdgesMatrices | undefined, cutoffWeight: number): void { + private hReconcile(target: EdgesMatrices | undefined, next: EdgesMatrices | undefined, cutoffWeight: number): EdgesMatrices | undefined { if (!target || !next) { - return; + return target; } + target = target.clone(); + const hNext = next.getMatrices().horizontal; const hTarget = target.getMatrices().horizontal; @@ -50,13 +52,17 @@ export default class EdgeFactory { ) && hTarget.setEdge(iTarget, j, undefined, -Infinity, true), R.range(0, hTarget.columns) ); + + return target; } - private vReconcile(target: EdgesMatrices | undefined, next: EdgesMatrices | undefined, cutoffWeight: number): void { + private vReconcile(target: EdgesMatrices | undefined, next: EdgesMatrices | undefined, cutoffWeight: number): EdgesMatrices | undefined { if (!target || !next) { - return; + return target; } + target = target.clone(); + const vNext = target.getMatrices().vertical; const vTarget = target.getMatrices().vertical; @@ -70,6 +76,8 @@ export default class EdgeFactory { ) && vTarget.setEdge(i, jTarget, undefined, -Infinity, true), R.range(0, vTarget.rows) ); + + return target; } private get props() { @@ -129,7 +137,7 @@ export default class EdgeFactory { data: Data, offset: IViewportOffset ) => { - const dataEdges = this.getDataEdges( + let dataEdges = this.getDataEdges( columns, this.dataStyles( style_cell, @@ -141,7 +149,7 @@ export default class EdgeFactory { offset ); - const dataOpEdges = this.getDataOpEdges( + let dataOpEdges = this.getDataOpEdges( operations, this.dataOpStyles( style_cell, @@ -153,7 +161,7 @@ export default class EdgeFactory { offset ); - const filterEdges = this.getFilterEdges( + let filterEdges = this.getFilterEdges( columns, filtering, this.filterStyles( @@ -164,7 +172,7 @@ export default class EdgeFactory { ) ); - const filterOpEdges = this.getFilterOpEdges( + let filterOpEdges = this.getFilterOpEdges( operations, filtering, this.filterOpStyles( @@ -175,7 +183,7 @@ export default class EdgeFactory { ) ); - const headerEdges = this.getHeaderEdges( + let headerEdges = this.getHeaderEdges( columns, getHeaderRows(columns), this.headerStyles( @@ -186,7 +194,7 @@ export default class EdgeFactory { ) ); - const headerOpEdges = this.getHeaderOpEdges( + let headerOpEdges = this.getHeaderOpEdges( operations, getHeaderRows(columns), this.headerOpStyles( @@ -199,15 +207,22 @@ export default class EdgeFactory { const cutoffWeight = (style_cell ? 1 : 0) + style_cell_conditional.length - 1; - this.hReconcile(headerEdges, filterEdges || dataEdges, cutoffWeight); - this.hReconcile(headerOpEdges, filterOpEdges || dataOpEdges, cutoffWeight); - this.hReconcile(filterEdges, dataEdges, cutoffWeight); - this.hReconcile(filterOpEdges, dataOpEdges, cutoffWeight); - - this.vReconcile(headerOpEdges, headerEdges, cutoffWeight); - this.vReconcile(filterOpEdges, filterEdges, cutoffWeight); - this.vReconcile(dataOpEdges, dataEdges, cutoffWeight); - - return { dataEdges, dataOpEdges, filterEdges, filterOpEdges, headerEdges, headerOpEdges }; + headerEdges = this.hReconcile(headerEdges, filterEdges || dataEdges, cutoffWeight); + headerOpEdges = this.hReconcile(headerOpEdges, filterOpEdges || dataOpEdges, cutoffWeight); + filterEdges = this.hReconcile(filterEdges, dataEdges, cutoffWeight); + filterOpEdges = this.hReconcile(filterOpEdges, dataOpEdges, cutoffWeight); + + headerOpEdges = this.vReconcile(headerOpEdges, headerEdges, cutoffWeight); + filterOpEdges = this.vReconcile(filterOpEdges, filterEdges, cutoffWeight); + dataOpEdges = this.vReconcile(dataOpEdges, dataEdges, cutoffWeight); + + return { + dataEdges: dataEdges as (IEdgesMatrices | undefined), + dataOpEdges: dataOpEdges as (IEdgesMatrices | undefined), + filterEdges: filterEdges as (IEdgesMatrices | undefined), + filterOpEdges: filterOpEdges as (IEdgesMatrices | undefined), + headerEdges: headerEdges as (IEdgesMatrices | undefined), + headerOpEdges: headerOpEdges as (IEdgesMatrices | undefined) + }; }); } \ No newline at end of file diff --git a/src/dash-table/components/FilterFactory.tsx b/src/dash-table/components/FilterFactory.tsx index f72812da9..9d65d586c 100644 --- a/src/dash-table/components/FilterFactory.tsx +++ b/src/dash-table/components/FilterFactory.tsx @@ -14,7 +14,7 @@ import { derivedRelevantFilterStyles } from 'dash-table/derived/style'; import { BasicFilters, Cells, Style } from 'dash-table/derived/style/props'; import { MultiColumnsSyntaxTree, SingleColumnSyntaxTree, getMultiColumnQueryString, getSingleColumnMap } from 'dash-table/syntax-tree'; -import { EdgesMatrices } from 'dash-table/derived/edges/type'; +import { IEdgesMatrices } from 'dash-table/derived/edges/type'; type SetFilter = (filter: string, rawFilter: string) => void; @@ -128,7 +128,7 @@ export default class FilterFactory { private wrapperStyles = memoizeOne(( styles: any[], - edges: EdgesMatrices | undefined + edges: IEdgesMatrices | undefined ) => arrayMap( styles, (s, j) => R.merge( @@ -137,7 +137,7 @@ export default class FilterFactory { ) )); - public createFilters(filterEdges: EdgesMatrices | undefined, filterOpEdges: EdgesMatrices | undefined) { + public createFilters(filterEdges: IEdgesMatrices | undefined, filterOpEdges: IEdgesMatrices | undefined) { const { columns, filter, diff --git a/src/dash-table/components/HeaderFactory.tsx b/src/dash-table/components/HeaderFactory.tsx index a8a605f2f..22b2538c3 100644 --- a/src/dash-table/components/HeaderFactory.tsx +++ b/src/dash-table/components/HeaderFactory.tsx @@ -14,7 +14,7 @@ import derivedHeaderWrappers from 'dash-table/derived/header/wrappers'; import { derivedRelevantHeaderStyles } from 'dash-table/derived/style'; import derivedHeaderStyles from 'dash-table/derived/header/wrapperStyles'; -import { EdgesMatrices } from 'dash-table/derived/edges/type'; +import { IEdgesMatrices } from 'dash-table/derived/edges/type'; export default class HeaderFactory { private readonly headerContent = derivedHeaderContent(); @@ -31,7 +31,7 @@ export default class HeaderFactory { } - public createHeaders(headerEdges: EdgesMatrices | undefined, headerOpEdges: EdgesMatrices | undefined) { + public createHeaders(headerEdges: IEdgesMatrices | undefined, headerOpEdges: IEdgesMatrices | undefined) { const props = this.props; const { diff --git a/src/dash-table/derived/edges/type.ts b/src/dash-table/derived/edges/type.ts index 7d7542ce9..60f3636ad 100644 --- a/src/dash-table/derived/edges/type.ts +++ b/src/dash-table/derived/edges/type.ts @@ -32,24 +32,70 @@ export const BORDER_PROPERTIES_AND_FRAGMENTS: string[] = R.uniq( ) ); -export class EdgesMatrix { +export interface IEdgesMatrix { + getEdge(i: number, j: number): Edge; + getEdges(): Edge[][]; + getWeight(i: number, j: number): number; + isDefault(i: number, j: number): boolean; +} + +export interface IEdgesMatrices { + getEdges(): { + horizontal: Edge[][], + vertical: Edge[][] + }; + getMatrices(): { + horizontal: EdgesMatrix, + vertical: EdgesMatrix + }; + getStyle(i: number, j: number): CSSProperties; +} + +export class EdgesMatrix implements IEdgesMatrix { private weights: number[][]; private edges: Edge[][]; + public readonly rows: number; + public readonly columns: number; + public readonly defaultEdge: Edge | undefined; + + constructor(m: EdgesMatrix); constructor( - public readonly rows: number, - public readonly columns: number, + rows: number, + columns: number, + defaultEdge?: Edge + ); + constructor( + rowsOrMatrix: number | EdgesMatrix, + columns?: number, defaultEdge?: Edge ) { - this.weights = R.map( - () => new Array(columns).fill(-Infinity), - R.range(0, rows) - ); - - this.edges = R.map( - () => new Array(columns).fill(defaultEdge), - R.range(0, rows) - ); + if (typeof rowsOrMatrix === 'number' && typeof columns !== 'undefined') { + const rows = rowsOrMatrix; + + this.rows = rows; + this.columns = columns; + this.defaultEdge = defaultEdge; + + this.weights = R.map( + () => new Array(columns).fill(-Infinity), + R.range(0, rows) + ); + + this.edges = R.map( + () => new Array(columns).fill(defaultEdge), + R.range(0, rows) + ); + } else { + const source = rowsOrMatrix as EdgesMatrix; + + this.rows = source.rows; + this.columns = source.columns; + this.defaultEdge = source.defaultEdge; + + this.weights = R.clone(source.weights); + this.edges = R.clone(source.edges); + } } setEdge(i: number, j: number, edge: Edge, weight: number, force: boolean = false) { @@ -70,13 +116,46 @@ export class EdgesMatrix { isDefault = (i: number, j: number) => !isFinite(this.weights[i][j]); } -export class EdgesMatrices { +export class EdgesMatrices implements IEdgesMatrices { private horizontal: EdgesMatrix; private vertical: EdgesMatrix; - constructor(rows: number, columns: number, defaultEdge?: Edge) { - this.horizontal = new EdgesMatrix(rows + 1, columns, defaultEdge); - this.vertical = new EdgesMatrix(rows, columns + 1, defaultEdge); + private readonly rows: number; + private readonly columns: number; + private readonly defaultEdge: Edge | undefined; + + constructor(m: EdgesMatrices); + constructor( + rows: number, + columns: number, + defaultEdge?: Edge + ); + constructor( + rowsOrMatrix: number | EdgesMatrices, + columns?: number, + defaultEdge?: Edge + + ) { + if (typeof rowsOrMatrix === 'number' && typeof columns !== 'undefined') { + const rows = rowsOrMatrix; + + this.rows = rows; + this.columns = columns; + this.defaultEdge = defaultEdge; + + this.horizontal = new EdgesMatrix(rows + 1, columns, defaultEdge); + this.vertical = new EdgesMatrix(rows, columns + 1, defaultEdge); + } else { + const source = rowsOrMatrix as EdgesMatrices; + + this.rows = source.rows; + this.columns = source.columns; + this.defaultEdge = source.defaultEdge; + + this.horizontal = new EdgesMatrix(source.horizontal); + this.vertical = new EdgesMatrix(source.vertical); + } + } setEdges(i: number, j: number, style: BorderStyle) { @@ -113,4 +192,6 @@ export class EdgesMatrices { borderLeft: this.vertical.getEdge(i, j) || null, borderRight: this.vertical.getEdge(i, j + 1) || null }) + + clone = () => new EdgesMatrices(this); } \ No newline at end of file From 634ddcbc96792d5361f82f0239a58b6378d6a64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 1 May 2019 15:31:10 -0400 Subject: [PATCH 15/46] clean up ops vs. other edges usage --- src/dash-table/components/EdgeFactory.tsx | 66 ++++++++----------- .../derived/edges/operationOfData.ts | 1 + .../derived/edges/operationOfFilters.ts | 10 +-- .../derived/edges/operationOfHeaders.ts | 4 +- src/dash-table/derived/style/index.ts | 11 ++-- 5 files changed, 40 insertions(+), 52 deletions(-) diff --git a/src/dash-table/components/EdgeFactory.tsx b/src/dash-table/components/EdgeFactory.tsx index a89fa055c..d8ee5e5a0 100644 --- a/src/dash-table/components/EdgeFactory.tsx +++ b/src/dash-table/components/EdgeFactory.tsx @@ -19,11 +19,8 @@ import { ControlledTableProps, VisibleColumns, IViewportOffset, Data } from './T export default class EdgeFactory { private readonly dataStyles = derivedRelevantCellStyles(); - private readonly dataOpStyles = derivedRelevantCellStyles(); private readonly filterStyles = derivedRelevantFilterStyles(); - private readonly filterOpStyles = derivedRelevantFilterStyles(); private readonly headerStyles = derivedRelevantHeaderStyles(); - private readonly headerOpStyles = derivedRelevantHeaderStyles(); private readonly getDataEdges = derivedDataEdges(); private readonly getDataOpEdges = derivedDataOpEdges(); @@ -137,26 +134,37 @@ export default class EdgeFactory { data: Data, offset: IViewportOffset ) => { + const dataStyles = this.dataStyles( + style_cell, + style_data, + style_cell_conditional, + style_data_conditional + ); + + const filterStyles = this.filterStyles( + style_cell, + style_filter, + style_cell_conditional, + style_filter_conditional + ); + + const headerStyles = this.headerStyles( + style_cell, + style_header, + style_cell_conditional, + style_header_conditional + ); + let dataEdges = this.getDataEdges( columns, - this.dataStyles( - style_cell, - style_data, - style_cell_conditional, - style_data_conditional - ), + dataStyles, data, offset ); let dataOpEdges = this.getDataOpEdges( operations, - this.dataOpStyles( - style_cell, - style_data, - R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), - R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_data_conditional) - ), + dataStyles, data, offset ); @@ -164,45 +172,25 @@ export default class EdgeFactory { let filterEdges = this.getFilterEdges( columns, filtering, - this.filterStyles( - style_cell, - style_filter, - style_cell_conditional, - style_filter_conditional - ) + filterStyles ); let filterOpEdges = this.getFilterOpEdges( operations, filtering, - this.filterOpStyles( - style_cell, - style_filter, - R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), - R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_filter_conditional) - ) + filterStyles ); let headerEdges = this.getHeaderEdges( columns, getHeaderRows(columns), - this.headerStyles( - style_cell, - style_header, - style_cell_conditional, - style_header_conditional - ) + headerStyles ); let headerOpEdges = this.getHeaderOpEdges( operations, getHeaderRows(columns), - this.headerOpStyles( - style_cell, - style_header, - R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type)), style_cell_conditional), - R.filter(s => R.isNil(s.if) || (R.isNil(s.if.column_id) && R.isNil(s.if.column_type) && R.isNil(s.if.header_index)), style_header_conditional) - ) + headerStyles ); const cutoffWeight = (style_cell ? 1 : 0) + style_cell_conditional.length - 1; diff --git a/src/dash-table/derived/edges/operationOfData.ts b/src/dash-table/derived/edges/operationOfData.ts index 6884f0da5..737c761ce 100644 --- a/src/dash-table/derived/edges/operationOfData.ts +++ b/src/dash-table/derived/edges/operationOfData.ts @@ -21,6 +21,7 @@ const getWeightedStyle = ( R.addIndex(R.forEach)((rs, i) => { if (!rs.matchesRow(index + offset.rows) || + !rs.matchesColumn(undefined) || !rs.matchesFilter(datum) ) { return; diff --git a/src/dash-table/derived/edges/operationOfFilters.ts b/src/dash-table/derived/edges/operationOfFilters.ts index 957094b9f..97d134ae7 100644 --- a/src/dash-table/derived/edges/operationOfFilters.ts +++ b/src/dash-table/derived/edges/operationOfFilters.ts @@ -7,13 +7,12 @@ import { IConvertedStyle } from '../style'; import { BorderStyle, BORDER_PROPERTIES, EdgesMatrices } from './type'; const getWeightedStyle = ( - borderStyles: IConvertedStyle[], - index: number + borderStyles: IConvertedStyle[] ): BorderStyle => { const res: BorderStyle = {}; R.addIndex(R.forEach)((rs, i) => { - if (!rs.matchesRow(index)) { + if (!rs.matchesColumn(undefined)) { return; } @@ -42,10 +41,7 @@ export default memoizeOneFactory(( R.forEach(i => R.forEach(j => { - const cellStyle = getWeightedStyle( - borderStyles, - i - ); + const cellStyle = getWeightedStyle(borderStyles); edges.setEdges(i, j, cellStyle); }, diff --git a/src/dash-table/derived/edges/operationOfHeaders.ts b/src/dash-table/derived/edges/operationOfHeaders.ts index 296b52dca..ce0ce4a22 100644 --- a/src/dash-table/derived/edges/operationOfHeaders.ts +++ b/src/dash-table/derived/edges/operationOfHeaders.ts @@ -13,7 +13,9 @@ const getWeightedStyle = ( const res: BorderStyle = {}; R.addIndex(R.forEach)((rs, i) => { - if (!rs.matchesRow(index)) { + if (!rs.matchesColumn(undefined) || + !rs.matchesRow(index) + ) { return; } diff --git a/src/dash-table/derived/style/index.ts b/src/dash-table/derived/style/index.ts index f80b59d53..eb338bac6 100644 --- a/src/dash-table/derived/style/index.ts +++ b/src/dash-table/derived/style/index.ts @@ -28,8 +28,8 @@ import { QuerySyntaxTree } from 'dash-table/syntax-tree'; export interface IConvertedStyle { style: CSSProperties; - matchesColumn: (column: IVisibleColumn) => boolean; - matchesRow: (index: number) => boolean; + matchesColumn: (column: IVisibleColumn | undefined) => boolean; + matchesRow: (index: number | undefined) => boolean; matchesFilter: (datum: Datum) => boolean; } @@ -41,17 +41,18 @@ function convertElement(style: GenericStyle) { let ast: QuerySyntaxTree; return { - matchesColumn: (column: IVisibleColumn) => + matchesColumn: (column: IVisibleColumn | undefined) => !style.if || ( + !R.isNil(column) && ifColumnId(style.if, column.id) && ifColumnType(style.if, column.type) ), - matchesRow: (index: number) => + matchesRow: (index: number | undefined) => indexFilter === undefined ? true : typeof indexFilter === 'number' ? index === indexFilter : - indexFilter === 'odd' ? index % 2 === 1 : index % 2 === 0, + !R.isNil(index) && (indexFilter === 'odd' ? index % 2 === 1 : index % 2 === 0), matchesFilter: (datum: Datum) => !style.if || style.if.filter === undefined || From ea151caeb1d388dd481cb94fabebeb529b18a0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 1 May 2019 15:48:10 -0400 Subject: [PATCH 16/46] - rework - style as list view --- src/dash-table/components/Table/Table.less | 7 +++++++ src/dash-table/derived/edges/type.ts | 6 ++++-- .../visual/percy-storybook/Border.style.percy.tsx | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/dash-table/components/Table/Table.less b/src/dash-table/components/Table/Table.less index 4427862a7..8a93a5bbd 100644 --- a/src/dash-table/components/Table/Table.less +++ b/src/dash-table/components/Table/Table.less @@ -224,6 +224,13 @@ } } + &.dash-list-view { + td, th { + border-left: none !important; + border-right: none !important; + } + } + .dash-filter.invalid { background-color: pink; } diff --git a/src/dash-table/derived/edges/type.ts b/src/dash-table/derived/edges/type.ts index 60f3636ad..0c01c1509 100644 --- a/src/dash-table/derived/edges/type.ts +++ b/src/dash-table/derived/edges/type.ts @@ -114,6 +114,8 @@ export class EdgesMatrix implements IEdgesMatrix { getWeight = (i: number, j: number) => this.weights[i][j]; isDefault = (i: number, j: number) => !isFinite(this.weights[i][j]); + + clone = () => new EdgesMatrix(this); } export class EdgesMatrices implements IEdgesMatrices { @@ -152,8 +154,8 @@ export class EdgesMatrices implements IEdgesMatrices { this.columns = source.columns; this.defaultEdge = source.defaultEdge; - this.horizontal = new EdgesMatrix(source.horizontal); - this.vertical = new EdgesMatrix(source.vertical); + this.horizontal = source.horizontal.clone(); + this.vertical = source.vertical.clone(); } } diff --git a/tests/visual/percy-storybook/Border.style.percy.tsx b/tests/visual/percy-storybook/Border.style.percy.tsx index 7ecda8fb6..79679f945 100644 --- a/tests/visual/percy-storybook/Border.style.percy.tsx +++ b/tests/visual/percy-storybook/Border.style.percy.tsx @@ -112,4 +112,18 @@ storiesOf('DashTable/Border, custom styles', module) style_header={{ border: '1px solid hotpink' }} + />)) + .add('style as list view', () => ()); \ No newline at end of file From 204c2407e02014964b672817868155867d987828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 1 May 2019 18:10:11 -0400 Subject: [PATCH 17/46] revert filter styling regression --- src/dash-table/components/Table/Table.less | 39 ++++++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/dash-table/components/Table/Table.less b/src/dash-table/components/Table/Table.less index 8a93a5bbd..5ed3f88da 100644 --- a/src/dash-table/components/Table/Table.less +++ b/src/dash-table/components/Table/Table.less @@ -156,6 +156,27 @@ } } + .dash-filter { + input::placeholder { + color: inherit; + font-size: 0.8em; + padding-right: 5px; + } + + & + .dash-filter { + &:not(:hover):not(:focus-within) { + input::placeholder { + color: transparent; + } + } + } + + &.invalid { + background-color: pink; + border: 1px solid red !important; + } + } + &:not(.dash-empty-11) { .row-0 { tr:last-of-type { @@ -178,6 +199,13 @@ } } + &.dash-list-view { + td, th { + border-left: none !important; + border-right: none !important; + } + } + &.dash-freeze-left, &.dash-freeze-top, &.dash-virtualized { @@ -224,17 +252,6 @@ } } - &.dash-list-view { - td, th { - border-left: none !important; - border-right: none !important; - } - } - - .dash-filter.invalid { - background-color: pink; - } - .selected-row { td, th { background-color: var(--selected-row); From f57165f073727adab39709845dade0bc17b845da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 1 May 2019 18:19:07 -0400 Subject: [PATCH 18/46] revert border styling --- src/dash-table/components/Table/Table.less | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/dash-table/components/Table/Table.less b/src/dash-table/components/Table/Table.less index 5ed3f88da..7ea1b943d 100644 --- a/src/dash-table/components/Table/Table.less +++ b/src/dash-table/components/Table/Table.less @@ -170,11 +170,6 @@ } } } - - &.invalid { - background-color: pink; - border: 1px solid red !important; - } } &:not(.dash-empty-11) { From e37107896133106a83dc236df4225538007906e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 2 May 2019 09:37:19 -0400 Subject: [PATCH 19/46] rework style_as_list_view --- src/dash-table/components/EdgeFactory.tsx | 21 +++++--- src/dash-table/components/Table/Table.less | 7 --- src/dash-table/derived/edges/data.ts | 5 +- src/dash-table/derived/edges/filter.ts | 5 +- src/dash-table/derived/edges/header.ts | 5 +- .../derived/edges/operationOfData.ts | 5 +- .../derived/edges/operationOfFilters.ts | 5 +- .../derived/edges/operationOfHeaders.ts | 5 +- src/dash-table/derived/edges/type.ts | 53 ++++++++++++------- 9 files changed, 67 insertions(+), 44 deletions(-) diff --git a/src/dash-table/components/EdgeFactory.tsx b/src/dash-table/components/EdgeFactory.tsx index d8ee5e5a0..7ec066c21 100644 --- a/src/dash-table/components/EdgeFactory.tsx +++ b/src/dash-table/components/EdgeFactory.tsx @@ -91,6 +91,7 @@ export default class EdgeFactory { filtering, row_deletable, row_selectable, + style_as_list_view, style_cell, style_cell_conditional, style_data, @@ -106,6 +107,7 @@ export default class EdgeFactory { columns, (row_deletable ? 1 : 0) + (row_selectable ? 1 : 0), !!filtering, + style_as_list_view, style_cell, style_cell_conditional, style_data, @@ -123,6 +125,7 @@ export default class EdgeFactory { columns: VisibleColumns, operations: number, filtering: boolean, + style_as_list_view: boolean, style_cell: Style, style_cell_conditional: Cells, style_data: Style, @@ -159,38 +162,44 @@ export default class EdgeFactory { columns, dataStyles, data, - offset + offset, + style_as_list_view ); let dataOpEdges = this.getDataOpEdges( operations, dataStyles, data, - offset + offset, + style_as_list_view ); let filterEdges = this.getFilterEdges( columns, filtering, - filterStyles + filterStyles, + style_as_list_view ); let filterOpEdges = this.getFilterOpEdges( operations, filtering, - filterStyles + filterStyles, + style_as_list_view ); let headerEdges = this.getHeaderEdges( columns, getHeaderRows(columns), - headerStyles + headerStyles, + style_as_list_view ); let headerOpEdges = this.getHeaderOpEdges( operations, getHeaderRows(columns), - headerStyles + headerStyles, + style_as_list_view ); const cutoffWeight = (style_cell ? 1 : 0) + style_cell_conditional.length - 1; diff --git a/src/dash-table/components/Table/Table.less b/src/dash-table/components/Table/Table.less index 7ea1b943d..0ea868ff8 100644 --- a/src/dash-table/components/Table/Table.less +++ b/src/dash-table/components/Table/Table.less @@ -194,13 +194,6 @@ } } - &.dash-list-view { - td, th { - border-left: none !important; - border-right: none !important; - } - } - &.dash-freeze-left, &.dash-freeze-top, &.dash-virtualized { diff --git a/src/dash-table/derived/edges/data.ts b/src/dash-table/derived/edges/data.ts index 79f12f2e0..b3002a301 100644 --- a/src/dash-table/derived/edges/data.ts +++ b/src/dash-table/derived/edges/data.ts @@ -46,13 +46,14 @@ export default memoizeOneFactory(( columns: VisibleColumns, borderStyles: IConvertedStyle[], data: Data, - offset: IViewportOffset + offset: IViewportOffset, + listViewStyle: boolean ) => { if (data.length === 0 || columns.length === 0) { return; } - const edges = new EdgesMatrices(data.length, columns.length, Environment.defaultEdge); + const edges = new EdgesMatrices(data.length, columns.length, Environment.defaultEdge, true, !listViewStyle); R.addIndex(R.forEach)((datum, i) => R.addIndex(R.forEach)( diff --git a/src/dash-table/derived/edges/filter.ts b/src/dash-table/derived/edges/filter.ts index f4c2c2b16..5e0bc6b45 100644 --- a/src/dash-table/derived/edges/filter.ts +++ b/src/dash-table/derived/edges/filter.ts @@ -37,13 +37,14 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: VisibleColumns, showFilters: boolean, - borderStyles: IConvertedStyle[] + borderStyles: IConvertedStyle[], + listViewStyle: boolean ) => { if (!showFilters || columns.length === 0) { return; } - const edges = new EdgesMatrices(1, columns.length, Environment.defaultEdge); + const edges = new EdgesMatrices(1, columns.length, Environment.defaultEdge, true, !listViewStyle); R.forEach(i => R.addIndex(R.forEach)( diff --git a/src/dash-table/derived/edges/header.ts b/src/dash-table/derived/edges/header.ts index 0eedb8c53..9631f1b9c 100644 --- a/src/dash-table/derived/edges/header.ts +++ b/src/dash-table/derived/edges/header.ts @@ -40,13 +40,14 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: VisibleColumns, headerRows: number, - borderStyles: IConvertedStyle[] + borderStyles: IConvertedStyle[], + listViewStyle: boolean ) => { if (headerRows === 0 || columns.length === 0) { return; } - const edges = new EdgesMatrices(headerRows, columns.length, Environment.defaultEdge); + const edges = new EdgesMatrices(headerRows, columns.length, Environment.defaultEdge, true, !listViewStyle); R.forEach(i => R.addIndex(R.forEach)( diff --git a/src/dash-table/derived/edges/operationOfData.ts b/src/dash-table/derived/edges/operationOfData.ts index 737c761ce..0ae13401a 100644 --- a/src/dash-table/derived/edges/operationOfData.ts +++ b/src/dash-table/derived/edges/operationOfData.ts @@ -43,13 +43,14 @@ export default memoizeOneFactory(( columns: number, borderStyles: IConvertedStyle[], data: Data, - offset: IViewportOffset + offset: IViewportOffset, + listViewStyle: boolean ) => { if (data.length === 0 || columns === 0) { return; } - const edges = new EdgesMatrices(data.length, columns, Environment.defaultEdge); + const edges = new EdgesMatrices(data.length, columns, Environment.defaultEdge, true, !listViewStyle); R.addIndex(R.forEach)((datum, i) => R.forEach(j => { diff --git a/src/dash-table/derived/edges/operationOfFilters.ts b/src/dash-table/derived/edges/operationOfFilters.ts index 97d134ae7..a6c258a44 100644 --- a/src/dash-table/derived/edges/operationOfFilters.ts +++ b/src/dash-table/derived/edges/operationOfFilters.ts @@ -31,13 +31,14 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: number, filtering: boolean, - borderStyles: IConvertedStyle[] + borderStyles: IConvertedStyle[], + listViewStyle: boolean ) => { if (!filtering || columns === 0) { return; } - const edges = new EdgesMatrices(1, columns, Environment.defaultEdge); + const edges = new EdgesMatrices(1, columns, Environment.defaultEdge, true, !listViewStyle); R.forEach(i => R.forEach(j => { diff --git a/src/dash-table/derived/edges/operationOfHeaders.ts b/src/dash-table/derived/edges/operationOfHeaders.ts index ce0ce4a22..fc81fec9a 100644 --- a/src/dash-table/derived/edges/operationOfHeaders.ts +++ b/src/dash-table/derived/edges/operationOfHeaders.ts @@ -34,13 +34,14 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: number, headerRows: number, - borderStyles: IConvertedStyle[] + borderStyles: IConvertedStyle[], + listViewStyle: boolean ) => { if (headerRows === 0 || columns === 0) { return; } - const edges = new EdgesMatrices(headerRows, columns, Environment.defaultEdge); + const edges = new EdgesMatrices(headerRows, columns, Environment.defaultEdge, true, !listViewStyle); R.forEach(i => R.forEach(j => { diff --git a/src/dash-table/derived/edges/type.ts b/src/dash-table/derived/edges/type.ts index 0c01c1509..25eeec97b 100644 --- a/src/dash-table/derived/edges/type.ts +++ b/src/dash-table/derived/edges/type.ts @@ -119,8 +119,11 @@ export class EdgesMatrix implements IEdgesMatrix { } export class EdgesMatrices implements IEdgesMatrices { - private horizontal: EdgesMatrix; - private vertical: EdgesMatrix; + private readonly horizontal: EdgesMatrix; + private readonly vertical: EdgesMatrix; + + private readonly horizontalEdges: boolean; + private readonly verticalEdges: boolean; private readonly rows: number; private readonly columns: number; @@ -130,13 +133,16 @@ export class EdgesMatrices implements IEdgesMatrices { constructor( rows: number, columns: number, - defaultEdge?: Edge + defaultEdge: Edge | undefined, + horizontalEdges?: boolean, + verticalEdges?: boolean ); constructor( rowsOrMatrix: number | EdgesMatrices, columns?: number, - defaultEdge?: Edge - + defaultEdge?: Edge, + horizontalEdges?: boolean, + verticalEdges?: boolean ) { if (typeof rowsOrMatrix === 'number' && typeof columns !== 'undefined') { const rows = rowsOrMatrix; @@ -145,8 +151,11 @@ export class EdgesMatrices implements IEdgesMatrices { this.columns = columns; this.defaultEdge = defaultEdge; - this.horizontal = new EdgesMatrix(rows + 1, columns, defaultEdge); - this.vertical = new EdgesMatrix(rows, columns + 1, defaultEdge); + this.horizontalEdges = R.isNil(horizontalEdges) || horizontalEdges; + this.verticalEdges = R.isNil(verticalEdges) || verticalEdges; + + this.horizontal = new EdgesMatrix(rows + 1, columns, this.horizontalEdges ? defaultEdge : undefined); + this.vertical = new EdgesMatrix(rows, columns + 1, this.verticalEdges ? defaultEdge : undefined); } else { const source = rowsOrMatrix as EdgesMatrices; @@ -156,25 +165,31 @@ export class EdgesMatrices implements IEdgesMatrices { this.horizontal = source.horizontal.clone(); this.vertical = source.vertical.clone(); - } + this.horizontalEdges = source.horizontalEdges; + this.verticalEdges = source.verticalEdges; + } } setEdges(i: number, j: number, style: BorderStyle) { - if (style.borderTop) { - this.horizontal.setEdge(i, j, style.borderTop[0], style.borderTop[1]); + if (this.horizontalEdges) { + if (style.borderTop) { + this.horizontal.setEdge(i, j, style.borderTop[0], style.borderTop[1]); + } + + if (style.borderBottom) { + this.horizontal.setEdge(i + 1, j, style.borderBottom[0], style.borderBottom[1]); + } } - if (style.borderBottom) { - this.horizontal.setEdge(i + 1, j, style.borderBottom[0], style.borderBottom[1]); - } - - if (style.borderLeft) { - this.vertical.setEdge(i, j, style.borderLeft[0], style.borderLeft[1]); - } + if (this.verticalEdges) { + if (style.borderLeft) { + this.vertical.setEdge(i, j, style.borderLeft[0], style.borderLeft[1]); + } - if (style.borderRight) { - this.vertical.setEdge(i, j + 1, style.borderRight[0], style.borderRight[1]); + if (style.borderRight) { + this.vertical.setEdge(i, j + 1, style.borderRight[0], style.borderRight[1]); + } } } From 1ab49827f5d4012eb3ad4aed7c6c96bc803c72ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 2 May 2019 09:54:31 -0400 Subject: [PATCH 20/46] support active cell styling again --- src/dash-table/components/EdgeFactory.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/dash-table/components/EdgeFactory.tsx b/src/dash-table/components/EdgeFactory.tsx index 7ec066c21..4a6004564 100644 --- a/src/dash-table/components/EdgeFactory.tsx +++ b/src/dash-table/components/EdgeFactory.tsx @@ -15,7 +15,7 @@ import getHeaderRows from 'dash-table/derived/header/headerRows'; import { derivedRelevantCellStyles, derivedRelevantFilterStyles, derivedRelevantHeaderStyles } from 'dash-table/derived/style'; import { Style, Cells, DataCells, BasicFilters, Headers } from 'dash-table/derived/style/props'; -import { ControlledTableProps, VisibleColumns, IViewportOffset, Data } from './Table/props'; +import { ControlledTableProps, VisibleColumns, IViewportOffset, Data, ICellCoordinates } from './Table/props'; export default class EdgeFactory { private readonly dataStyles = derivedRelevantCellStyles(); @@ -87,6 +87,7 @@ export default class EdgeFactory { public createEdges() { const { + active_cell, columns, filtering, row_deletable, @@ -104,6 +105,7 @@ export default class EdgeFactory { } = this.props; return this.memoizedCreateEdges( + active_cell, columns, (row_deletable ? 1 : 0) + (row_selectable ? 1 : 0), !!filtering, @@ -122,6 +124,7 @@ export default class EdgeFactory { } private memoizedCreateEdges = memoizeOne(( + active_cell: ICellCoordinates, columns: VisibleColumns, operations: number, filtering: boolean, @@ -213,6 +216,15 @@ export default class EdgeFactory { filterOpEdges = this.vReconcile(filterOpEdges, filterEdges, cutoffWeight); dataOpEdges = this.vReconcile(dataOpEdges, dataEdges, cutoffWeight); + if (dataEdges && active_cell && !R.isNil(active_cell.column) && !R.isNil(active_cell.row)) { + dataEdges.setEdges(active_cell.row, active_cell.column, { + borderBottom: ['1px solid red', Infinity], + borderLeft: ['1px solid red', Infinity], + borderRight: ['1px solid red', Infinity], + borderTop: ['1px solid red', Infinity] + }); + } + return { dataEdges: dataEdges as (IEdgesMatrices | undefined), dataOpEdges: dataOpEdges as (IEdgesMatrices | undefined), From 307b6507b122afb658b8d82cf15786516367cb18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 2 May 2019 10:15:27 -0400 Subject: [PATCH 21/46] fix active_cell (0,0) case --- src/dash-table/components/EdgeFactory.tsx | 19 ++++++++++--------- src/dash-table/derived/edges/type.ts | 2 +- .../percy-storybook/Border.style.percy.tsx | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/dash-table/components/EdgeFactory.tsx b/src/dash-table/components/EdgeFactory.tsx index 4a6004564..b2ba4b676 100644 --- a/src/dash-table/components/EdgeFactory.tsx +++ b/src/dash-table/components/EdgeFactory.tsx @@ -169,6 +169,16 @@ export default class EdgeFactory { style_as_list_view ); + if (dataEdges && active_cell && !R.isNil(active_cell.column) && !R.isNil(active_cell.row)) { + dataEdges = dataEdges.clone(); + dataEdges.setEdges(active_cell.row, active_cell.column, { + borderBottom: ['1px solid red', Infinity], + borderLeft: ['1px solid red', Infinity], + borderRight: ['1px solid red', Infinity], + borderTop: ['1px solid red', Infinity] + }); + } + let dataOpEdges = this.getDataOpEdges( operations, dataStyles, @@ -216,15 +226,6 @@ export default class EdgeFactory { filterOpEdges = this.vReconcile(filterOpEdges, filterEdges, cutoffWeight); dataOpEdges = this.vReconcile(dataOpEdges, dataEdges, cutoffWeight); - if (dataEdges && active_cell && !R.isNil(active_cell.column) && !R.isNil(active_cell.row)) { - dataEdges.setEdges(active_cell.row, active_cell.column, { - borderBottom: ['1px solid red', Infinity], - borderLeft: ['1px solid red', Infinity], - borderRight: ['1px solid red', Infinity], - borderTop: ['1px solid red', Infinity] - }); - } - return { dataEdges: dataEdges as (IEdgesMatrices | undefined), dataOpEdges: dataOpEdges as (IEdgesMatrices | undefined), diff --git a/src/dash-table/derived/edges/type.ts b/src/dash-table/derived/edges/type.ts index 25eeec97b..34b5d6c2c 100644 --- a/src/dash-table/derived/edges/type.ts +++ b/src/dash-table/derived/edges/type.ts @@ -113,7 +113,7 @@ export class EdgesMatrix implements IEdgesMatrix { getWeight = (i: number, j: number) => this.weights[i][j]; - isDefault = (i: number, j: number) => !isFinite(this.weights[i][j]); + isDefault = (i: number, j: number) => this.weights[i][j] === -Infinity; clone = () => new EdgesMatrix(this); } diff --git a/tests/visual/percy-storybook/Border.style.percy.tsx b/tests/visual/percy-storybook/Border.style.percy.tsx index 79679f945..7959de239 100644 --- a/tests/visual/percy-storybook/Border.style.percy.tsx +++ b/tests/visual/percy-storybook/Border.style.percy.tsx @@ -7,6 +7,24 @@ storiesOf('DashTable/Border, custom styles', module) .add('with defaults', () => ()) + .add('with defaults & active cell (1,1)', () => ()) + .add('with defaults & active cell (0, 0)', () => ()) .add('with cell style', () => ( Date: Thu, 2 May 2019 10:25:36 -0400 Subject: [PATCH 22/46] rework active_cell rework... --- src/dash-table/components/EdgeFactory.tsx | 11 +---------- src/dash-table/derived/edges/data.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/dash-table/components/EdgeFactory.tsx b/src/dash-table/components/EdgeFactory.tsx index b2ba4b676..3b2eca9ed 100644 --- a/src/dash-table/components/EdgeFactory.tsx +++ b/src/dash-table/components/EdgeFactory.tsx @@ -166,19 +166,10 @@ export default class EdgeFactory { dataStyles, data, offset, + active_cell, style_as_list_view ); - if (dataEdges && active_cell && !R.isNil(active_cell.column) && !R.isNil(active_cell.row)) { - dataEdges = dataEdges.clone(); - dataEdges.setEdges(active_cell.row, active_cell.column, { - borderBottom: ['1px solid red', Infinity], - borderLeft: ['1px solid red', Infinity], - borderRight: ['1px solid red', Infinity], - borderTop: ['1px solid red', Infinity] - }); - } - let dataOpEdges = this.getDataOpEdges( operations, dataStyles, diff --git a/src/dash-table/derived/edges/data.ts b/src/dash-table/derived/edges/data.ts index b3002a301..54355f180 100644 --- a/src/dash-table/derived/edges/data.ts +++ b/src/dash-table/derived/edges/data.ts @@ -7,7 +7,8 @@ import { IViewportOffset, IVisibleColumn, VisibleColumns, - Data + Data, + ICellCoordinates } from 'dash-table/components/Table/props'; import { IConvertedStyle } from '../style'; @@ -47,6 +48,7 @@ export default memoizeOneFactory(( borderStyles: IConvertedStyle[], data: Data, offset: IViewportOffset, + active_cell: ICellCoordinates, listViewStyle: boolean ) => { if (data.length === 0 || columns.length === 0) { @@ -73,5 +75,12 @@ export default memoizeOneFactory(( data ); + edges.setEdges(active_cell.row, active_cell.column, { + borderBottom: ['1px solid red', Infinity], + borderLeft: ['1px solid red', Infinity], + borderRight: ['1px solid red', Infinity], + borderTop: ['1px solid red', Infinity] + }); + return edges; }); \ No newline at end of file From 630d3f4074cccbb8178a088ee4e2bfa02cdade7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 2 May 2019 10:54:55 -0400 Subject: [PATCH 23/46] fix edge unit tests --- tests/cypress/tests/unit/edges_test.ts | 40 +++++++++++++++++++------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/tests/cypress/tests/unit/edges_test.ts b/tests/cypress/tests/unit/edges_test.ts index 19be012b1..c39f9a01e 100644 --- a/tests/cypress/tests/unit/edges_test.ts +++ b/tests/cypress/tests/unit/edges_test.ts @@ -9,7 +9,9 @@ describe('data edges', () => { [{ id: 'id', name: 'id' }], [], [], - { columns: 0, rows: 0 } + { columns: 0, rows: 0 }, + { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + false ); expect(res !== undefined).to.equal(false); @@ -20,7 +22,9 @@ describe('data edges', () => { [], [], [{ id: 1 }], - { columns: 0, rows: 0 } + { columns: 0, rows: 0 }, + { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + false ); expect(res !== undefined).to.equal(false); @@ -31,7 +35,9 @@ describe('data edges', () => { [{ id: 'id', name: 'id' }], [], [{ id: 1 }], - { columns: 0, rows: 0 } + { columns: 0, rows: 0 }, + { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + false ); expect(res !== undefined).to.equal(true); @@ -56,7 +62,9 @@ describe('data edges', () => { [{ id: 'id', name: 'id' }], [], [{ id: 1 }], - { columns: 0, rows: 0 } + { columns: 0, rows: 0 }, + { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + false ); expect(res !== undefined).to.equal(true); @@ -89,7 +97,9 @@ describe('data edges', () => { { id: 2, name: 'a' }, { id: 2, name: 'b' } ], - { columns: 0, rows: 0 } + { columns: 0, rows: 0 }, + { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + false ); expect(res !== undefined).to.equal(true); @@ -134,7 +144,9 @@ describe('data edges', () => { { id: 2, name: 'a' }, { id: 2, name: 'b' } ], - { columns: 0, rows: 0 } + { columns: 0, rows: 0 }, + { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + false ); expect(res !== undefined).to.equal(true); @@ -179,7 +191,9 @@ describe('data edges', () => { { id: 2, name: 'a' }, { id: 2, name: 'b' } ], - { columns: 0, rows: 0 } + { columns: 0, rows: 0 }, + { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + false ); expect(res !== undefined).to.equal(true); @@ -232,7 +246,9 @@ describe('data edges', () => { [ { id: 1, name: 'a' } ], - { columns: 0, rows: 0 } + { columns: 0, rows: 0 }, + { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + false ); expect(res !== undefined).to.equal(true); @@ -267,7 +283,9 @@ describe('data edges', () => { [ { id: 1, name: 'a' } ], - { columns: 0, rows: 0 } + { columns: 0, rows: 0 }, + { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + false ); expect(res === undefined).to.equal(false); @@ -302,7 +320,9 @@ describe('data edges', () => { [ { id: 1, name: 'a' } ], - { columns: 0, rows: 0 } + { columns: 0, rows: 0 }, + { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + false ); expect(res !== undefined).to.equal(true); From 6e592b4f5b86bd12f6a3443c5d3158012321d443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 2 May 2019 12:32:41 -0400 Subject: [PATCH 24/46] fix edge tests --- src/dash-table/derived/edges/data.ts | 16 +++++++++------- tests/cypress/tests/unit/edges_test.ts | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/dash-table/derived/edges/data.ts b/src/dash-table/derived/edges/data.ts index 54355f180..a103f6f92 100644 --- a/src/dash-table/derived/edges/data.ts +++ b/src/dash-table/derived/edges/data.ts @@ -48,7 +48,7 @@ export default memoizeOneFactory(( borderStyles: IConvertedStyle[], data: Data, offset: IViewportOffset, - active_cell: ICellCoordinates, + active_cell: ICellCoordinates | undefined, listViewStyle: boolean ) => { if (data.length === 0 || columns.length === 0) { @@ -75,12 +75,14 @@ export default memoizeOneFactory(( data ); - edges.setEdges(active_cell.row, active_cell.column, { - borderBottom: ['1px solid red', Infinity], - borderLeft: ['1px solid red', Infinity], - borderRight: ['1px solid red', Infinity], - borderTop: ['1px solid red', Infinity] - }); + if (active_cell) { + edges.setEdges(active_cell.row, active_cell.column, { + borderBottom: ['1px solid red', Infinity], + borderLeft: ['1px solid red', Infinity], + borderRight: ['1px solid red', Infinity], + borderTop: ['1px solid red', Infinity] + }); + } return edges; }); \ No newline at end of file diff --git a/tests/cypress/tests/unit/edges_test.ts b/tests/cypress/tests/unit/edges_test.ts index c39f9a01e..6de45c85f 100644 --- a/tests/cypress/tests/unit/edges_test.ts +++ b/tests/cypress/tests/unit/edges_test.ts @@ -10,7 +10,7 @@ describe('data edges', () => { [], [], { columns: 0, rows: 0 }, - { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + undefined, false ); @@ -23,7 +23,7 @@ describe('data edges', () => { [], [{ id: 1 }], { columns: 0, rows: 0 }, - { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + undefined, false ); @@ -36,7 +36,7 @@ describe('data edges', () => { [], [{ id: 1 }], { columns: 0, rows: 0 }, - { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + undefined, false ); @@ -63,7 +63,7 @@ describe('data edges', () => { [], [{ id: 1 }], { columns: 0, rows: 0 }, - { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + undefined, false ); @@ -98,7 +98,7 @@ describe('data edges', () => { { id: 2, name: 'b' } ], { columns: 0, rows: 0 }, - { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + undefined, false ); @@ -145,7 +145,7 @@ describe('data edges', () => { { id: 2, name: 'b' } ], { columns: 0, rows: 0 }, - { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + undefined, false ); @@ -192,7 +192,7 @@ describe('data edges', () => { { id: 2, name: 'b' } ], { columns: 0, rows: 0 }, - { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + undefined, false ); @@ -247,7 +247,7 @@ describe('data edges', () => { { id: 1, name: 'a' } ], { columns: 0, rows: 0 }, - { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + undefined, false ); @@ -284,7 +284,7 @@ describe('data edges', () => { { id: 1, name: 'a' } ], { columns: 0, rows: 0 }, - { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + undefined, false ); @@ -321,7 +321,7 @@ describe('data edges', () => { { id: 1, name: 'a' } ], { columns: 0, rows: 0 }, - { column: -1, column_id: '-1', row: -1, row_id: '-1' }, + undefined, false ); From dcc9257221541ec916ae2437eb0cedd44068ff73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 2 May 2019 14:47:50 -0400 Subject: [PATCH 25/46] var(--accent) active border color (revert) --- src/core/environment/index.ts | 5 +++++ src/dash-table/derived/edges/data.ts | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/core/environment/index.ts b/src/core/environment/index.ts index 630b18051..8343129f9 100644 --- a/src/core/environment/index.ts +++ b/src/core/environment/index.ts @@ -7,6 +7,7 @@ const DASH_DEBUG = 'dash_debug'; const DASH_LOG = 'dash_log'; const DEFAULT_EDGE: Edge = '1px solid #d3d3d3'; +const ACTIVE_EDGE: Edge = '1px solid var(--accent)'; interface ISearchParams { get: (key: string) => string | null; @@ -41,4 +42,8 @@ export default class Environment { public static get defaultEdge(): Edge { return DEFAULT_EDGE; } + + public static get activeEdge(): Edge { + return ACTIVE_EDGE; + } } \ No newline at end of file diff --git a/src/dash-table/derived/edges/data.ts b/src/dash-table/derived/edges/data.ts index a103f6f92..94d4a9ad6 100644 --- a/src/dash-table/derived/edges/data.ts +++ b/src/dash-table/derived/edges/data.ts @@ -77,10 +77,10 @@ export default memoizeOneFactory(( if (active_cell) { edges.setEdges(active_cell.row, active_cell.column, { - borderBottom: ['1px solid red', Infinity], - borderLeft: ['1px solid red', Infinity], - borderRight: ['1px solid red', Infinity], - borderTop: ['1px solid red', Infinity] + borderBottom: [Environment.activeEdge, Infinity], + borderLeft: [Environment.activeEdge, Infinity], + borderRight: [Environment.activeEdge, Infinity], + borderTop: [Environment.activeEdge, Infinity] }); } From 41c46e055139236ec0ee058215c89dfdae425ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Mon, 6 May 2019 14:35:09 -0400 Subject: [PATCH 26/46] update fixture height to show border as before --- tests/visual/percy-storybook/fixtures.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/visual/percy-storybook/fixtures.ts b/tests/visual/percy-storybook/fixtures.ts index 8ec610969..19dc6eac5 100644 --- a/tests/visual/percy-storybook/fixtures.ts +++ b/tests/visual/percy-storybook/fixtures.ts @@ -33,7 +33,7 @@ export default [ editable: true, css: [{ selector: '.dash-spreadsheet.dash-freeze-top', - rule: 'height: 100px;' + rule: 'height: 110px;' }] } }, From e6798271386db51abf6f76c900489670ad4601e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Mon, 6 May 2019 15:55:39 -0400 Subject: [PATCH 27/46] simplify undefined test --- tests/cypress/tests/unit/edges_test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/cypress/tests/unit/edges_test.ts b/tests/cypress/tests/unit/edges_test.ts index 6de45c85f..dc69d5ebd 100644 --- a/tests/cypress/tests/unit/edges_test.ts +++ b/tests/cypress/tests/unit/edges_test.ts @@ -14,7 +14,7 @@ describe('data edges', () => { false ); - expect(res !== undefined).to.equal(false); + expect(res).to.equal(undefined); }); it('without one data row', () => { @@ -27,7 +27,7 @@ describe('data edges', () => { false ); - expect(res !== undefined).to.equal(false); + expect(res).to.equal(undefined); }); it('uses `undefined` default style', () => { @@ -40,7 +40,7 @@ describe('data edges', () => { false ); - expect(res !== undefined).to.equal(true); + expect(res).to.equal(undefined); if (res) { const { horizontal, vertical } = res.getEdges(); @@ -67,7 +67,7 @@ describe('data edges', () => { false ); - expect(res !== undefined).to.equal(true); + expect(res).to.equal(undefined); if (res) { const { horizontal, vertical } = res.getEdges(); @@ -102,7 +102,7 @@ describe('data edges', () => { false ); - expect(res !== undefined).to.equal(true); + expect(res).to.equal(undefined); if (res) { const { horizontal, vertical } = res.getEdges(); @@ -149,7 +149,7 @@ describe('data edges', () => { false ); - expect(res !== undefined).to.equal(true); + expect(res).to.equal(undefined); if (res) { const { horizontal, vertical } = res.getEdges(); @@ -196,7 +196,7 @@ describe('data edges', () => { false ); - expect(res !== undefined).to.equal(true); + expect(res).to.equal(undefined); if (res) { const { horizontal, vertical } = res.getEdges(); @@ -251,7 +251,7 @@ describe('data edges', () => { false ); - expect(res !== undefined).to.equal(true); + expect(res).to.equal(undefined); if (res) { const { vertical } = res.getEdges(); @@ -325,7 +325,7 @@ describe('data edges', () => { false ); - expect(res !== undefined).to.equal(true); + expect(res).to.equal(undefined); if (res) { const { vertical } = res.getEdges(); From 2c1e4d6c36320feea42b330adb6f7bb11aebf370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Mon, 6 May 2019 16:15:23 -0400 Subject: [PATCH 28/46] edge tests --- tests/cypress/tests/unit/edges_test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/cypress/tests/unit/edges_test.ts b/tests/cypress/tests/unit/edges_test.ts index dc69d5ebd..677ccd9e0 100644 --- a/tests/cypress/tests/unit/edges_test.ts +++ b/tests/cypress/tests/unit/edges_test.ts @@ -40,7 +40,7 @@ describe('data edges', () => { false ); - expect(res).to.equal(undefined); + expect(res).to.not.equal(undefined); if (res) { const { horizontal, vertical } = res.getEdges(); @@ -67,7 +67,7 @@ describe('data edges', () => { false ); - expect(res).to.equal(undefined); + expect(res).to.not.equal(undefined); if (res) { const { horizontal, vertical } = res.getEdges(); @@ -102,7 +102,7 @@ describe('data edges', () => { false ); - expect(res).to.equal(undefined); + expect(res).to.not.equal(undefined); if (res) { const { horizontal, vertical } = res.getEdges(); @@ -149,7 +149,7 @@ describe('data edges', () => { false ); - expect(res).to.equal(undefined); + expect(res).to.not.equal(undefined); if (res) { const { horizontal, vertical } = res.getEdges(); @@ -196,7 +196,7 @@ describe('data edges', () => { false ); - expect(res).to.equal(undefined); + expect(res).to.not.equal(undefined); if (res) { const { horizontal, vertical } = res.getEdges(); @@ -251,7 +251,7 @@ describe('data edges', () => { false ); - expect(res).to.equal(undefined); + expect(res).to.not.equal(undefined); if (res) { const { vertical } = res.getEdges(); @@ -325,7 +325,7 @@ describe('data edges', () => { false ); - expect(res).to.equal(undefined); + expect(res).to.not.equal(undefined); if (res) { const { vertical } = res.getEdges(); From 58472edd39e924b0ead51c3567b2ce99a3f5da25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Mon, 6 May 2019 16:55:13 -0400 Subject: [PATCH 29/46] additional case --- tests/cypress/tests/unit/edges_test.ts | 73 +++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/tests/cypress/tests/unit/edges_test.ts b/tests/cypress/tests/unit/edges_test.ts index 677ccd9e0..67a043969 100644 --- a/tests/cypress/tests/unit/edges_test.ts +++ b/tests/cypress/tests/unit/edges_test.ts @@ -325,15 +325,76 @@ describe('data edges', () => { false ); - expect(res).to.not.equal(undefined); + expect(res !== undefined).to.equal(true); if (res) { - const { vertical } = res.getEdges(); + const { horizontal, vertical } = res.getEdges(); + + expect(horizontal.length).to.equal(2); + horizontal.forEach(edges => { + expect(edges.length).to.equal(2); + + edges.forEach(edge => { + expect(edge).to.equal('1px solid green'); + }); + }); expect(vertical.length).to.equal(1); - expect(vertical[0].length).to.equal(3); - expect(vertical[0][0]).to.equal('1px solid green'); - expect(vertical[0][1]).to.equal('1px solid green'); - expect(vertical[0][2]).to.equal('1px solid green'); + vertical.forEach(edges => { + expect(edges.length).to.equal(3); + + edges.forEach(edge => { + expect(edge).to.equal('1px solid green'); + }); + }); + } + }); + + it('applies `border` overridden by higher precedence `borderLeft`', () => { + const res = edgesFn( + [ + { id: 'id', name: 'id' }, + { id: 'name', name: 'name' } + ], + [{ + style: { border: '1px solid green' }, + matchesColumn: () => true, + matchesFilter: () => true, + matchesRow: () => true + }, { + style: { borderLeft: '1px solid darkgreen' }, + matchesColumn: () => true, + matchesFilter: () => true, + matchesRow: () => true + }], + [ + { id: 1, name: 'a' } + ], + { columns: 0, rows: 0 }, + undefined, + false + ); + + expect(res !== undefined).to.equal(true); + if (res) { + const { horizontal, vertical } = res.getEdges(); + + expect(horizontal.length).to.equal(2); + horizontal.forEach(edges => { + expect(edges.length).to.equal(2); + + edges.forEach(edge => { + expect(edge).to.equal('1px solid green'); + }); + }); + + expect(vertical.length).to.equal(1); + vertical.forEach(edges => { + expect(edges.length).to.equal(3); + + edges.forEach((edge, j) => { + expect(edge).to.equal(j + 1 === edges.length ? '1px solid green' : '1px solid darkgreen'); + }); + }); } }); }); \ No newline at end of file From 525c36d2c23dc45ab95bf86b1efe818e5d86f74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 7 May 2019 15:27:36 -0400 Subject: [PATCH 30/46] refactor out filter mapping logic from the filter factory --- src/dash-table/components/EdgeFactory.tsx | 9 ++- src/dash-table/components/FilterFactory.tsx | 81 ++++++++------------- src/dash-table/components/Table/Table.less | 4 + src/dash-table/components/Table/index.tsx | 2 + src/dash-table/components/Table/props.ts | 2 + src/dash-table/derived/edges/filter.ts | 12 +++ src/dash-table/derived/filter/asts.ts | 67 +++++++++++++++++ src/dash-table/derived/table/index.tsx | 12 ++- 8 files changed, 134 insertions(+), 55 deletions(-) create mode 100644 src/dash-table/derived/filter/asts.ts diff --git a/src/dash-table/components/EdgeFactory.tsx b/src/dash-table/components/EdgeFactory.tsx index 3b2eca9ed..ce8ee5689 100644 --- a/src/dash-table/components/EdgeFactory.tsx +++ b/src/dash-table/components/EdgeFactory.tsx @@ -16,6 +16,7 @@ import { derivedRelevantCellStyles, derivedRelevantFilterStyles, derivedRelevant import { Style, Cells, DataCells, BasicFilters, Headers } from 'dash-table/derived/style/props'; import { ControlledTableProps, VisibleColumns, IViewportOffset, Data, ICellCoordinates } from './Table/props'; +import { SingleColumnSyntaxTree } from 'dash-table/syntax-tree'; export default class EdgeFactory { private readonly dataStyles = derivedRelevantCellStyles(); @@ -44,7 +45,7 @@ export default class EdgeFactory { R.forEach(j => ( - hNext.getWeight(iNext, j) > cutoffWeight || + (hNext.getWeight(iNext, j) > cutoffWeight && hTarget.getWeight(iTarget, j) !== Infinity) || (hNext.getWeight(iNext, j) > hTarget.getWeight(iTarget, j)) ) && hTarget.setEdge(iTarget, j, undefined, -Infinity, true), R.range(0, hTarget.columns) @@ -68,7 +69,7 @@ export default class EdgeFactory { R.forEach(i => ( - vNext.getWeight(i, jNext) > cutoffWeight || + (vNext.getWeight(i, jNext) > cutoffWeight && vTarget.getWeight(i, jTarget) !== Infinity) || (vNext.getWeight(i, jNext) > vTarget.getWeight(i, jTarget)) ) && vTarget.setEdge(i, jTarget, undefined, -Infinity, true), R.range(0, vTarget.rows) @@ -90,6 +91,7 @@ export default class EdgeFactory { active_cell, columns, filtering, + map, row_deletable, row_selectable, style_as_list_view, @@ -109,6 +111,7 @@ export default class EdgeFactory { columns, (row_deletable ? 1 : 0) + (row_selectable ? 1 : 0), !!filtering, + map, style_as_list_view, style_cell, style_cell_conditional, @@ -128,6 +131,7 @@ export default class EdgeFactory { columns: VisibleColumns, operations: number, filtering: boolean, + filterMap: Map, style_as_list_view: boolean, style_cell: Style, style_cell_conditional: Cells, @@ -181,6 +185,7 @@ export default class EdgeFactory { let filterEdges = this.getFilterEdges( columns, filtering, + filterMap, filterStyles, style_as_list_view ); diff --git a/src/dash-table/components/FilterFactory.tsx b/src/dash-table/components/FilterFactory.tsx index 9d65d586c..efa8910c0 100644 --- a/src/dash-table/components/FilterFactory.tsx +++ b/src/dash-table/components/FilterFactory.tsx @@ -12,11 +12,16 @@ import derivedFilterStyles from 'dash-table/derived/filter/wrapperStyles'; import derivedHeaderOperations from 'dash-table/derived/header/operations'; import { derivedRelevantFilterStyles } from 'dash-table/derived/style'; import { BasicFilters, Cells, Style } from 'dash-table/derived/style/props'; -import { MultiColumnsSyntaxTree, SingleColumnSyntaxTree, getMultiColumnQueryString, getSingleColumnMap } from 'dash-table/syntax-tree'; +import { SingleColumnSyntaxTree, getMultiColumnQueryString } from 'dash-table/syntax-tree'; import { IEdgesMatrices } from 'dash-table/derived/edges/type'; +import { updateMap } from 'dash-table/derived/filter/asts'; -type SetFilter = (filter: string, rawFilter: string) => void; +type SetFilter = ( + filter: string, + rawFilter: string, + map: Map +) => void; export interface IFilterOptions { columns: VisibleColumns; @@ -24,6 +29,7 @@ export interface IFilterOptions { filtering: Filtering; filtering_type: FilteringType; id: string; + map: Map; rawFilterQuery: string; row_deletable: boolean; row_selectable: RowSelection; @@ -40,8 +46,6 @@ export default class FilterFactory { private readonly relevantStyles = derivedRelevantFilterStyles(); private readonly headerOperations = derivedHeaderOperations(); - private ops = new Map(); - private get props() { return this.propsFn(); } @@ -50,19 +54,14 @@ export default class FilterFactory { } - private onChange = (column: IVisibleColumn, setFilter: SetFilter, ev: any) => { + private onChange = (column: IVisibleColumn, map: Map, setFilter: SetFilter, ev: any) => { Logger.debug('Filter -- onChange', column.id, ev.target.value && ev.target.value.trim()); const value = ev.target.value.trim(); - const safeColumnId = column.id.toString(); - if (value && value.length) { - this.ops.set(safeColumnId, new SingleColumnSyntaxTree(value, column)); - } else { - this.ops.delete(safeColumnId); - } + map = updateMap(map, column, value); - const asts = Array.from(this.ops.values()); + const asts = Array.from(map.values()); const globalFilter = getMultiColumnQueryString(asts); const rawGlobalFilter = R.map( @@ -70,58 +69,39 @@ export default class FilterFactory { R.filter(ast => Boolean(ast), asts) ).join(' && '); - setFilter(globalFilter, rawGlobalFilter); + setFilter(globalFilter, rawGlobalFilter, map); } - private getEventHandler = (fn: Function, column: IVisibleColumn, setFilter: SetFilter): any => { + private getEventHandler = ( + fn: Function, + column: IVisibleColumn, + map: Map, + setFilter: SetFilter + ): any => { const fnHandler = (this.handlers.get(fn) || this.handlers.set(fn, new Map()).get(fn)); const columnIdHandler = (fnHandler.get(column.id) || fnHandler.set(column.id, new Map()).get(column.id)); return ( columnIdHandler.get(setFilter) || - (columnIdHandler.set(setFilter, fn.bind(this, column, setFilter)).get(setFilter)) + (columnIdHandler.set(setFilter, fn.bind(this, column, map, setFilter)).get(setFilter)) ); } - private updateOps = memoizeOne((query: string, columns: IVisibleColumn[]) => { - const multiQuery = new MultiColumnsSyntaxTree(query); - - const newOps = getSingleColumnMap(multiQuery, columns); - if (!newOps) { - return; - } - - /* Mapping multi-column to single column queries will expand - * compressed forms. If the new ast query is equal to the - * old one, keep the old one instead. - * - * If the value was changed by the user, the current ast will - * have been modified already and the UI experience will also - * be consistent in that case. - */ - R.forEach(([key, ast]) => { - const newAst = newOps.get(key); - - if (newAst && newAst.toQueryString() === ast.toQueryString()) { - newOps.set(key, ast); - } - }, Array.from(this.ops.entries())); - - this.ops = newOps; - }); - private filter = memoizerCache<[ColumnId, number]>()(( column: IVisibleColumn, index: number, - ast: SingleColumnSyntaxTree | undefined, setFilter: SetFilter ) => { + const { map } = this.props; + + const ast = map.get(column.id.toString()); + return (); }); @@ -137,10 +117,12 @@ export default class FilterFactory { ) )); - public createFilters(filterEdges: IEdgesMatrices | undefined, filterOpEdges: IEdgesMatrices | undefined) { + public createFilters( + filterEdges: IEdgesMatrices | undefined, + filterOpEdges: IEdgesMatrices | undefined + ) { const { columns, - filter, filtering, filtering_type, row_deletable, @@ -156,8 +138,6 @@ export default class FilterFactory { return []; } - this.updateOps(filter, columns); - if (filtering_type === FilteringType.Basic) { const relevantStyles = this.relevantStyles( style_cell, @@ -175,7 +155,6 @@ export default class FilterFactory { return this.filter.get(column.id, index)( column, index, - this.ops.get(column.id.toString()), setFilter ); }, columns); @@ -194,14 +173,14 @@ export default class FilterFactory { row_deletable )[0]; - const ops = arrayMap( + const operators = arrayMap( operations, (o, j) => React.cloneElement(o, { style: filterOpEdges && filterOpEdges.getStyle(0, j) }) ); - return [ops.concat(styledFilters)]; + return [operators.concat(styledFilters)]; } else { return [[]]; } diff --git a/src/dash-table/components/Table/Table.less b/src/dash-table/components/Table/Table.less index 0ea868ff8..4d919b91e 100644 --- a/src/dash-table/components/Table/Table.less +++ b/src/dash-table/components/Table/Table.less @@ -170,6 +170,10 @@ } } } + + &.invalid { + background-color: pink; + } } &:not(.dash-empty-11) { diff --git a/src/dash-table/components/Table/index.tsx b/src/dash-table/components/Table/index.tsx index 4220f4692..7bcde84bd 100644 --- a/src/dash-table/components/Table/index.tsx +++ b/src/dash-table/components/Table/index.tsx @@ -31,6 +31,7 @@ import 'react-select/dist/react-select.css'; import './Table.less'; import './Dropdown.css'; import { isEqual } from 'core/comparer'; +import { SingleColumnSyntaxTree } from 'dash-table/syntax-tree'; const DERIVED_REGEX = /^derived_/; @@ -40,6 +41,7 @@ export default class Table extends Component(), rawFilterQuery: '', scrollbarWidth: 0 }; diff --git a/src/dash-table/components/Table/props.ts b/src/dash-table/components/Table/props.ts index 4625f139e..ced520b08 100644 --- a/src/dash-table/components/Table/props.ts +++ b/src/dash-table/components/Table/props.ts @@ -12,6 +12,7 @@ import { ConditionalTooltip, Tooltip } from 'dash-table/tooltips/props'; +import { SingleColumnSyntaxTree } from 'dash-table/syntax-tree'; export enum ColumnType { Any = 'any', @@ -238,6 +239,7 @@ export interface IUSerInterfaceTooltip { export interface IState { forcedResizeOnly: boolean; + map: Map; rawFilterQuery: string; scrollbarWidth: number; tooltip?: IUSerInterfaceTooltip; diff --git a/src/dash-table/derived/edges/filter.ts b/src/dash-table/derived/edges/filter.ts index 5e0bc6b45..229a486ff 100644 --- a/src/dash-table/derived/edges/filter.ts +++ b/src/dash-table/derived/edges/filter.ts @@ -10,6 +10,7 @@ import { import { IConvertedStyle } from '../style'; import { BorderStyle, BORDER_PROPERTIES, EdgesMatrices } from './type'; +import { SingleColumnSyntaxTree } from 'dash-table/syntax-tree'; const getWeightedStyle = ( borderStyles: IConvertedStyle[], @@ -37,6 +38,7 @@ const getWeightedStyle = ( export default memoizeOneFactory(( columns: VisibleColumns, showFilters: boolean, + map: Map, borderStyles: IConvertedStyle[], listViewStyle: boolean ) => { @@ -55,6 +57,16 @@ export default memoizeOneFactory(( ); edges.setEdges(i, j, cellStyle); + + const ast = map.get(column.id.toString()); + if (ast && !ast.isValid) { + edges.setEdges(i, j, { + borderBottom: [Environment.activeEdge, Infinity], + borderLeft: [Environment.activeEdge, Infinity], + borderRight: [Environment.activeEdge, Infinity], + borderTop: [Environment.activeEdge, Infinity] + }); + } }, columns ), diff --git a/src/dash-table/derived/filter/asts.ts b/src/dash-table/derived/filter/asts.ts new file mode 100644 index 000000000..f75e7aed0 --- /dev/null +++ b/src/dash-table/derived/filter/asts.ts @@ -0,0 +1,67 @@ +import * as R from 'ramda'; + +import { memoizeOneFactory } from 'core/memoizer'; + +import { VisibleColumns, IVisibleColumn } from 'dash-table/components/Table/props'; +import { SingleColumnSyntaxTree, MultiColumnsSyntaxTree, getSingleColumnMap } from 'dash-table/syntax-tree'; + +export default memoizeOneFactory(( + map: Map, + query: string, + columns: VisibleColumns +): Map => { + const multiQuery = new MultiColumnsSyntaxTree(query); + const reversed = getSingleColumnMap(multiQuery, columns); + + /* + * Couldn't process the query, just use the previous value. + */ + if (!reversed) { + return map; + } + + /* Mapping multi-column to single column queries will expand + * compressed forms. If the new ast query is equal to the + * old one, keep the old one instead. + * + * If the value was changed by the user, the current ast will + * have been modified already and the UI experience will also + * be consistent in that case. + */ + let newMap = map; + + R.forEach(([key, ast]) => { + const newAst = reversed.get(key); + + if (newAst && newAst.toQueryString() === ast.toQueryString()) { + /* + * Only return a new map instance if something changes + */ + newMap = (newMap === map) ? + new Map(map) : + newMap; + + newMap.set(key, ast); + } + }, Array.from(map.entries())); + + return newMap; +}); + +export const updateMap = ( + map: Map, + column: IVisibleColumn, + value: any +): Map => { + const safeColumnId = column.id.toString(); + + map = new Map(map); + + if (value && value.length) { + map.set(safeColumnId, new SingleColumnSyntaxTree(value, column)); + } else { + map.delete(safeColumnId); + } + + return map; +}; \ No newline at end of file diff --git a/src/dash-table/derived/table/index.tsx b/src/dash-table/derived/table/index.tsx index 3e7f20a7a..26739a193 100644 --- a/src/dash-table/derived/table/index.tsx +++ b/src/dash-table/derived/table/index.tsx @@ -9,9 +9,17 @@ import HeaderFactory from 'dash-table/components/HeaderFactory'; import { clearSelection } from 'dash-table/utils/actions'; import { ControlledTableProps, SetProps, SetState } from 'dash-table/components/Table/props'; -const handleSetFilter = (setProps: SetProps, setState: SetState, filter: string, rawFilterQuery: string) => { +import { SingleColumnSyntaxTree } from 'dash-table/syntax-tree'; + +const handleSetFilter = ( + setProps: SetProps, + setState: SetState, + filter: string, + rawFilterQuery: string, + map: Map +) => { setProps({ filter, ...clearSelection }); - setState({ rawFilterQuery }); + setState({ map, rawFilterQuery }); }; function filterPropsFn(propsFn: () => ControlledTableProps, setFilter: any) { From ba26e745c2f81e7bcbf369f4fe745de7928a0f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 8 May 2019 10:43:40 -0400 Subject: [PATCH 31/46] fix filters on update and init --- src/dash-table/components/FilterFactory.tsx | 25 +++-------- src/dash-table/components/Table/index.tsx | 22 +++++++++- .../derived/filter/{asts.ts => map.ts} | 44 ++++++++++++------- 3 files changed, 53 insertions(+), 38 deletions(-) rename src/dash-table/derived/filter/{asts.ts => map.ts} (56%) diff --git a/src/dash-table/components/FilterFactory.tsx b/src/dash-table/components/FilterFactory.tsx index efa8910c0..e0d5e7c96 100644 --- a/src/dash-table/components/FilterFactory.tsx +++ b/src/dash-table/components/FilterFactory.tsx @@ -15,7 +15,7 @@ import { BasicFilters, Cells, Style } from 'dash-table/derived/style/props'; import { SingleColumnSyntaxTree, getMultiColumnQueryString } from 'dash-table/syntax-tree'; import { IEdgesMatrices } from 'dash-table/derived/edges/type'; -import { updateMap } from 'dash-table/derived/filter/asts'; +import { updateMap } from 'dash-table/derived/filter/map'; type SetFilter = ( filter: string, @@ -41,7 +41,6 @@ export interface IFilterOptions { } export default class FilterFactory { - private readonly handlers = new Map(); private readonly filterStyles = derivedFilterStyles(); private readonly relevantStyles = derivedRelevantFilterStyles(); private readonly headerOperations = derivedHeaderOperations(); @@ -72,28 +71,12 @@ export default class FilterFactory { setFilter(globalFilter, rawGlobalFilter, map); } - private getEventHandler = ( - fn: Function, - column: IVisibleColumn, - map: Map, - setFilter: SetFilter - ): any => { - const fnHandler = (this.handlers.get(fn) || this.handlers.set(fn, new Map()).get(fn)); - const columnIdHandler = (fnHandler.get(column.id) || fnHandler.set(column.id, new Map()).get(column.id)); - - return ( - columnIdHandler.get(setFilter) || - (columnIdHandler.set(setFilter, fn.bind(this, column, map, setFilter)).get(setFilter)) - ); - } - private filter = memoizerCache<[ColumnId, number]>()(( column: IVisibleColumn, index: number, + map: Map, setFilter: SetFilter ) => { - const { map } = this.props; - const ast = map.get(column.id.toString()); return (); }); @@ -125,6 +108,7 @@ export default class FilterFactory { columns, filtering, filtering_type, + map, row_deletable, row_selectable, setFilter, @@ -155,6 +139,7 @@ export default class FilterFactory { return this.filter.get(column.id, index)( column, index, + map, setFilter ); }, columns); diff --git a/src/dash-table/components/Table/index.tsx b/src/dash-table/components/Table/index.tsx index 7bcde84bd..ce1807367 100644 --- a/src/dash-table/components/Table/index.tsx +++ b/src/dash-table/components/Table/index.tsx @@ -32,6 +32,7 @@ import './Table.less'; import './Dropdown.css'; import { isEqual } from 'core/comparer'; import { SingleColumnSyntaxTree } from 'dash-table/syntax-tree'; +import derivedFilterMap from 'dash-table/derived/filter/map'; const DERIVED_REGEX = /^derived_/; @@ -41,12 +42,30 @@ export default class Table extends Component(), + map: this.filterMap( + new Map(), + props.filter, + props.columns + ), rawFilterQuery: '', scrollbarWidth: 0 }; } + componentWillReceiveProps(nextProps: PropsWithDefaultsAndDerived) { + if (nextProps.filter !== this.props.filter) { + this.setState(state => { + const map = this.filterMap( + state.map, + nextProps.filter, + nextProps.columns + ); + + return map !== state.map ? { map } : null; + }); + } + } + shouldComponentUpdate(nextProps: any, nextState: any) { const props: any = this.props; const state: any = this.state; @@ -270,6 +289,7 @@ export default class Table extends Component (state: Partial) => this.setState(state as IState)); + private readonly filterMap = derivedFilterMap(); private readonly paginator = derivedPaginator(); private readonly viewport = derivedViewportData(); private readonly viewportSelectedRows = derivedSelectedRows(); diff --git a/src/dash-table/derived/filter/asts.ts b/src/dash-table/derived/filter/map.ts similarity index 56% rename from src/dash-table/derived/filter/asts.ts rename to src/dash-table/derived/filter/map.ts index f75e7aed0..6bd4b7481 100644 --- a/src/dash-table/derived/filter/asts.ts +++ b/src/dash-table/derived/filter/map.ts @@ -5,18 +5,23 @@ import { memoizeOneFactory } from 'core/memoizer'; import { VisibleColumns, IVisibleColumn } from 'dash-table/components/Table/props'; import { SingleColumnSyntaxTree, MultiColumnsSyntaxTree, getSingleColumnMap } from 'dash-table/syntax-tree'; +const cloneIf = ( + current: Map, + base: Map +) => current === base ? new Map(base) : current; + export default memoizeOneFactory(( map: Map, query: string, columns: VisibleColumns ): Map => { const multiQuery = new MultiColumnsSyntaxTree(query); - const reversed = getSingleColumnMap(multiQuery, columns); + const reversedMap = getSingleColumnMap(multiQuery, columns); /* * Couldn't process the query, just use the previous value. */ - if (!reversed) { + if (!reversedMap) { return map; } @@ -30,20 +35,25 @@ export default memoizeOneFactory(( */ let newMap = map; - R.forEach(([key, ast]) => { - const newAst = reversed.get(key); + const keys = R.uniq( + R.concat( + Array.from(map.keys()), + Array.from(reversedMap.keys()) + ) + ); - if (newAst && newAst.toQueryString() === ast.toQueryString()) { - /* - * Only return a new map instance if something changes - */ - newMap = (newMap === map) ? - new Map(map) : - newMap; + R.forEach(key => { + const ast = map.get(key); + const reversedAst = reversedMap.get(key); - newMap.set(key, ast); + if (!R.isNil(reversedAst) && ( + R.isNil(ast) || + reversedAst.toQueryString() !== ast.toQueryString() + )) { + newMap = cloneIf(newMap, map); + newMap.set(key, reversedAst); } - }, Array.from(map.entries())); + }, keys); return newMap; }); @@ -55,13 +65,13 @@ export const updateMap = ( ): Map => { const safeColumnId = column.id.toString(); - map = new Map(map); + const newMap = new Map(map); if (value && value.length) { - map.set(safeColumnId, new SingleColumnSyntaxTree(value, column)); + newMap.set(safeColumnId, new SingleColumnSyntaxTree(value, column)); } else { - map.delete(safeColumnId); + newMap.delete(safeColumnId); } - return map; + return newMap; }; \ No newline at end of file From 2c3f41e332104326db4b7c16c8fb083fd54c1e09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 8 May 2019 15:07:29 -0400 Subject: [PATCH 32/46] handle fixed rows/columns for edges --- src/dash-table/components/EdgeFactory.tsx | 105 ++++++- .../percy-storybook/Border.style.percy.tsx | 294 +++++++++--------- 2 files changed, 248 insertions(+), 151 deletions(-) diff --git a/src/dash-table/components/EdgeFactory.tsx b/src/dash-table/components/EdgeFactory.tsx index ce8ee5689..931f21816 100644 --- a/src/dash-table/components/EdgeFactory.tsx +++ b/src/dash-table/components/EdgeFactory.tsx @@ -18,6 +18,8 @@ import { Style, Cells, DataCells, BasicFilters, Headers } from 'dash-table/deriv import { ControlledTableProps, VisibleColumns, IViewportOffset, Data, ICellCoordinates } from './Table/props'; import { SingleColumnSyntaxTree } from 'dash-table/syntax-tree'; +type EdgesMatricesOp = EdgesMatrices | undefined; + export default class EdgeFactory { private readonly dataStyles = derivedRelevantCellStyles(); private readonly filterStyles = derivedRelevantFilterStyles(); @@ -30,6 +32,62 @@ export default class EdgeFactory { private readonly getHeaderEdges = derivedHeaderEdges(); private readonly getHeaderOpEdges = derivedHeaderOpEdges(); + private static hasPrecedence(target: number, other: number, cutoff: number): boolean { + return (other <= cutoff || target === Infinity) && other <= target; + } + + private hOverride(previous: EdgesMatricesOp, target: EdgesMatricesOp, cutoffWeight: number): EdgesMatricesOp { + if (!previous || !target) { + return target; + } + + previous = previous.clone(); + target = target.clone(); + + const hPrevious = previous.getMatrices().horizontal; + const hTarget = target.getMatrices().horizontal; + + const iPrevious = hPrevious.rows - 1; + const iTarget = 0; + + R.forEach(j => + EdgeFactory.hasPrecedence( + hPrevious.getWeight(iPrevious, j), + hTarget.getWeight(iTarget, j), + cutoffWeight + ) && hTarget.setEdge(iTarget, j, hPrevious.getEdge(iPrevious, j), Infinity, true) + , R.range(0, hPrevious.columns) + ); + + return target; + } + + private vOverride(previous: EdgesMatricesOp, target: EdgesMatricesOp, cutoffWeight: number): EdgesMatricesOp { + if (!previous || !target) { + return target; + } + + previous = previous.clone(); + target = target.clone(); + + const hPrevious = previous.getMatrices().vertical; + const hTarget = target.getMatrices().vertical; + + const jPrevious = hPrevious.columns - 1; + const jTarget = 0; + + R.forEach(i => + EdgeFactory.hasPrecedence( + hPrevious.getWeight(i, jPrevious), + hTarget.getWeight(i, jTarget), + cutoffWeight + ) && hTarget.setEdge(i, jTarget, hPrevious.getEdge(i, jPrevious), Infinity, true) + , R.range(0, hPrevious.rows) + ); + + return target; + } + private hReconcile(target: EdgesMatrices | undefined, next: EdgesMatrices | undefined, cutoffWeight: number): EdgesMatrices | undefined { if (!target || !next) { return target; @@ -44,9 +102,10 @@ export default class EdgeFactory { const iTarget = hTarget.rows - 1; R.forEach(j => - ( - (hNext.getWeight(iNext, j) > cutoffWeight && hTarget.getWeight(iTarget, j) !== Infinity) || - (hNext.getWeight(iNext, j) > hTarget.getWeight(iTarget, j)) + !EdgeFactory.hasPrecedence( + hTarget.getWeight(iTarget, j), + hNext.getWeight(iNext, j), + cutoffWeight ) && hTarget.setEdge(iTarget, j, undefined, -Infinity, true), R.range(0, hTarget.columns) ); @@ -61,16 +120,17 @@ export default class EdgeFactory { target = target.clone(); - const vNext = target.getMatrices().vertical; + const vNext = next.getMatrices().vertical; const vTarget = target.getMatrices().vertical; const jNext = 0; const jTarget = vTarget.columns - 1; R.forEach(i => - ( - (vNext.getWeight(i, jNext) > cutoffWeight && vTarget.getWeight(i, jTarget) !== Infinity) || - (vNext.getWeight(i, jNext) > vTarget.getWeight(i, jTarget)) + !EdgeFactory.hasPrecedence( + vTarget.getWeight(i, jTarget), + vNext.getWeight(i, jNext), + cutoffWeight ) && vTarget.setEdge(i, jTarget, undefined, -Infinity, true), R.range(0, vTarget.rows) ); @@ -92,6 +152,8 @@ export default class EdgeFactory { columns, filtering, map, + n_fixed_columns, + n_fixed_rows, row_deletable, row_selectable, style_as_list_view, @@ -112,6 +174,8 @@ export default class EdgeFactory { (row_deletable ? 1 : 0) + (row_selectable ? 1 : 0), !!filtering, map, + n_fixed_columns, + n_fixed_rows, style_as_list_view, style_cell, style_cell_conditional, @@ -132,6 +196,8 @@ export default class EdgeFactory { operations: number, filtering: boolean, filterMap: Map, + _n_fixed_columns: number, + n_fixed_rows: number, style_as_list_view: boolean, style_cell: Style, style_cell_conditional: Cells, @@ -165,6 +231,8 @@ export default class EdgeFactory { style_header_conditional ); + const headerRows = getHeaderRows(columns); + let dataEdges = this.getDataEdges( columns, dataStyles, @@ -199,14 +267,14 @@ export default class EdgeFactory { let headerEdges = this.getHeaderEdges( columns, - getHeaderRows(columns), + headerRows, headerStyles, style_as_list_view ); let headerOpEdges = this.getHeaderOpEdges( operations, - getHeaderRows(columns), + headerRows, headerStyles, style_as_list_view ); @@ -222,6 +290,25 @@ export default class EdgeFactory { filterOpEdges = this.vReconcile(filterOpEdges, filterEdges, cutoffWeight); dataOpEdges = this.vReconcile(dataOpEdges, dataEdges, cutoffWeight); + if (n_fixed_rows === headerRows) { + if (filtering) { + filterEdges = this.hOverride(headerEdges, filterEdges, cutoffWeight); + filterOpEdges = this.hOverride(headerOpEdges, filterOpEdges, cutoffWeight); + } else { + dataEdges = this.hOverride(headerEdges, dataEdges, cutoffWeight); + dataOpEdges = this.hOverride(headerOpEdges, dataOpEdges, cutoffWeight); + } + } else if (filtering && n_fixed_rows === headerRows + 1) { + dataEdges = this.hOverride(filterEdges, dataEdges, cutoffWeight); + dataOpEdges = this.hOverride(filterOpEdges, dataOpEdges, cutoffWeight); + } + + if (_n_fixed_columns === operations) { + headerEdges = this.vOverride(headerOpEdges, headerEdges, cutoffWeight); + filterEdges = this.vOverride(filterOpEdges, filterEdges, cutoffWeight); + dataEdges = this.vOverride(dataOpEdges, dataEdges, cutoffWeight); + } + return { dataEdges: dataEdges as (IEdgesMatrices | undefined), dataOpEdges: dataOpEdges as (IEdgesMatrices | undefined), diff --git a/tests/visual/percy-storybook/Border.style.percy.tsx b/tests/visual/percy-storybook/Border.style.percy.tsx index 7959de239..36873459d 100644 --- a/tests/visual/percy-storybook/Border.style.percy.tsx +++ b/tests/visual/percy-storybook/Border.style.percy.tsx @@ -1,147 +1,157 @@ +import * as R from 'ramda'; import React from 'react'; import { storiesOf } from '@storybook/react'; import DataTable from 'dash-table/dash/DataTable'; import { BORDER_PROPS_DEFAULTS } from './Border.defaults.percy'; -storiesOf('DashTable/Border, custom styles', module) - .add('with defaults', () => ()) - .add('with defaults & active cell (1,1)', () => ()) - .add('with defaults & active cell (0, 0)', () => ()) - .add('with cell style', () => ()) - .add('with data style', () => ()) - .add('with header style', () => ()) - .add('with filter style', () => ()) - .add('with header / cell (data) style - header wins on cell (data)', () => ()) - .add('with header / data style - data wins on header', () => ()) - .add('with header / filter / cell (data) style - filter wins on header, filter wins on cell (data)', () => ()) - .add('with header / data / cell (filter) style - header wins on cell (filter), data wins on cell (filter)', () => ()) - .add('with cell (header) / filter / data style - filter wins on cell (header), data wins on filter', () => ()) - .add('with data / cell (header, filter) style - data wins on filter', () => ()) - .add('with header / filter / data style - data wins on filter, filter wins on header', () => ()) - .add('style as list view', () => ()); \ No newline at end of file +const variants = [ + { name: 'with ops', props: { row_deletable: true, row_selectable: 'single' } }, + { name: 'fixed columns', props: { n_fixed_columns: 2, row_deletable: true, row_selectable: 'single' } }, + { name: 'fixed rows', props: { n_fixed_rows: 1, row_deletable: true, row_selectable: 'single' } }, + { name: 'fixed columns & rows', props: { n_fixed_columns: 2, n_fixed_rows: 1, row_deletable: true, row_selectable: 'single' } } +]; + +variants.forEach(variant => { + storiesOf(`DashTable/Border, custom styles (variant=${variant.name})`, module) + .add('with defaults', () => ()) + .add('with defaults & active cell (1,1)', () => ()) + .add('with defaults & active cell (0, 0)', () => ()) + .add('with cell style', () => ()) + .add('with data style', () => ()) + .add('with header style', () => ()) + .add('with filter style', () => ()) + .add('with header / cell (data) style - header wins on cell (data)', () => ()) + .add('with header / data style - data wins on header', () => ()) + .add('with header / filter / cell (data) style - filter wins on header, filter wins on cell (data)', () => ()) + .add('with header / data / cell (filter) style - header wins on cell (filter), data wins on cell (filter)', () => ()) + .add('with cell (header) / filter / data style - filter wins on cell (header), data wins on filter', () => ()) + .add('with data / cell (header, filter) style - data wins on filter', () => ()) + .add('with header / filter / data style - data wins on filter, filter wins on header', () => ()) + .add('style as list view', () => ()); +}); \ No newline at end of file From 26ebf344a6972454605a99c0ff0a4f675a46b07e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 8 May 2019 15:16:01 -0400 Subject: [PATCH 33/46] performance --- src/dash-table/components/EdgeFactory.tsx | 77 +++++++++++------------ 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/src/dash-table/components/EdgeFactory.tsx b/src/dash-table/components/EdgeFactory.tsx index 931f21816..289cce55c 100644 --- a/src/dash-table/components/EdgeFactory.tsx +++ b/src/dash-table/components/EdgeFactory.tsx @@ -32,18 +32,19 @@ export default class EdgeFactory { private readonly getHeaderEdges = derivedHeaderEdges(); private readonly getHeaderOpEdges = derivedHeaderOpEdges(); + private static clone(target: EdgesMatricesOp) { + return target && target.clone(); + } + private static hasPrecedence(target: number, other: number, cutoff: number): boolean { return (other <= cutoff || target === Infinity) && other <= target; } - private hOverride(previous: EdgesMatricesOp, target: EdgesMatricesOp, cutoffWeight: number): EdgesMatricesOp { + private hOverride(previous: EdgesMatricesOp, target: EdgesMatricesOp, cutoffWeight: number) { if (!previous || !target) { - return target; + return; } - previous = previous.clone(); - target = target.clone(); - const hPrevious = previous.getMatrices().horizontal; const hTarget = target.getMatrices().horizontal; @@ -58,18 +59,13 @@ export default class EdgeFactory { ) && hTarget.setEdge(iTarget, j, hPrevious.getEdge(iPrevious, j), Infinity, true) , R.range(0, hPrevious.columns) ); - - return target; } - private vOverride(previous: EdgesMatricesOp, target: EdgesMatricesOp, cutoffWeight: number): EdgesMatricesOp { + private vOverride(previous: EdgesMatricesOp, target: EdgesMatricesOp, cutoffWeight: number) { if (!previous || !target) { - return target; + return; } - previous = previous.clone(); - target = target.clone(); - const hPrevious = previous.getMatrices().vertical; const hTarget = target.getMatrices().vertical; @@ -84,17 +80,13 @@ export default class EdgeFactory { ) && hTarget.setEdge(i, jTarget, hPrevious.getEdge(i, jPrevious), Infinity, true) , R.range(0, hPrevious.rows) ); - - return target; } - private hReconcile(target: EdgesMatrices | undefined, next: EdgesMatrices | undefined, cutoffWeight: number): EdgesMatrices | undefined { + private hReconcile(target: EdgesMatrices | undefined, next: EdgesMatrices | undefined, cutoffWeight: number) { if (!target || !next) { - return target; + return; } - target = target.clone(); - const hNext = next.getMatrices().horizontal; const hTarget = target.getMatrices().horizontal; @@ -109,17 +101,13 @@ export default class EdgeFactory { ) && hTarget.setEdge(iTarget, j, undefined, -Infinity, true), R.range(0, hTarget.columns) ); - - return target; } - private vReconcile(target: EdgesMatrices | undefined, next: EdgesMatrices | undefined, cutoffWeight: number): EdgesMatrices | undefined { + private vReconcile(target: EdgesMatrices | undefined, next: EdgesMatrices | undefined, cutoffWeight: number) { if (!target || !next) { - return target; + return; } - target = target.clone(); - const vNext = next.getMatrices().vertical; const vTarget = target.getMatrices().vertical; @@ -134,8 +122,6 @@ export default class EdgeFactory { ) && vTarget.setEdge(i, jTarget, undefined, -Infinity, true), R.range(0, vTarget.rows) ); - - return target; } private get props() { @@ -281,32 +267,39 @@ export default class EdgeFactory { const cutoffWeight = (style_cell ? 1 : 0) + style_cell_conditional.length - 1; - headerEdges = this.hReconcile(headerEdges, filterEdges || dataEdges, cutoffWeight); - headerOpEdges = this.hReconcile(headerOpEdges, filterOpEdges || dataOpEdges, cutoffWeight); - filterEdges = this.hReconcile(filterEdges, dataEdges, cutoffWeight); - filterOpEdges = this.hReconcile(filterOpEdges, dataOpEdges, cutoffWeight); + headerEdges = EdgeFactory.clone(headerEdges); + headerOpEdges = EdgeFactory.clone(headerOpEdges); + filterEdges = EdgeFactory.clone(filterEdges); + filterOpEdges = EdgeFactory.clone(filterOpEdges); + dataEdges = EdgeFactory.clone(dataEdges); + dataOpEdges = EdgeFactory.clone(dataOpEdges); + + this.hReconcile(headerEdges, filterEdges || dataEdges, cutoffWeight); + this.hReconcile(headerOpEdges, filterOpEdges || dataOpEdges, cutoffWeight); + this.hReconcile(filterEdges, dataEdges, cutoffWeight); + this.hReconcile(filterOpEdges, dataOpEdges, cutoffWeight); - headerOpEdges = this.vReconcile(headerOpEdges, headerEdges, cutoffWeight); - filterOpEdges = this.vReconcile(filterOpEdges, filterEdges, cutoffWeight); - dataOpEdges = this.vReconcile(dataOpEdges, dataEdges, cutoffWeight); + this.vReconcile(headerOpEdges, headerEdges, cutoffWeight); + this.vReconcile(filterOpEdges, filterEdges, cutoffWeight); + this.vReconcile(dataOpEdges, dataEdges, cutoffWeight); if (n_fixed_rows === headerRows) { if (filtering) { - filterEdges = this.hOverride(headerEdges, filterEdges, cutoffWeight); - filterOpEdges = this.hOverride(headerOpEdges, filterOpEdges, cutoffWeight); + this.hOverride(headerEdges, filterEdges, cutoffWeight); + this.hOverride(headerOpEdges, filterOpEdges, cutoffWeight); } else { - dataEdges = this.hOverride(headerEdges, dataEdges, cutoffWeight); - dataOpEdges = this.hOverride(headerOpEdges, dataOpEdges, cutoffWeight); + this.hOverride(headerEdges, dataEdges, cutoffWeight); + this.hOverride(headerOpEdges, dataOpEdges, cutoffWeight); } } else if (filtering && n_fixed_rows === headerRows + 1) { - dataEdges = this.hOverride(filterEdges, dataEdges, cutoffWeight); - dataOpEdges = this.hOverride(filterOpEdges, dataOpEdges, cutoffWeight); + this.hOverride(filterEdges, dataEdges, cutoffWeight); + this.hOverride(filterOpEdges, dataOpEdges, cutoffWeight); } if (_n_fixed_columns === operations) { - headerEdges = this.vOverride(headerOpEdges, headerEdges, cutoffWeight); - filterEdges = this.vOverride(filterOpEdges, filterEdges, cutoffWeight); - dataEdges = this.vOverride(dataOpEdges, dataEdges, cutoffWeight); + this.vOverride(headerOpEdges, headerEdges, cutoffWeight); + this.vOverride(filterOpEdges, filterEdges, cutoffWeight); + this.vOverride(dataOpEdges, dataEdges, cutoffWeight); } return { From a6ed2e3c996736e168ff5918d98afe1100b4b368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 8 May 2019 15:59:35 -0400 Subject: [PATCH 34/46] remove css overrides & cleanup unused changes --- .../components/ControlledTable/index.tsx | 11 +-------- src/dash-table/components/EdgeFactory.tsx | 24 +++++++++++-------- src/dash-table/components/Table/Table.less | 22 ----------------- src/dash-table/derived/table/fragments.tsx | 21 ++++------------ 4 files changed, 20 insertions(+), 58 deletions(-) diff --git a/src/dash-table/components/ControlledTable/index.tsx b/src/dash-table/components/ControlledTable/index.tsx index 28678158b..611ee1d05 100644 --- a/src/dash-table/components/ControlledTable/index.tsx +++ b/src/dash-table/components/ControlledTable/index.tsx @@ -683,15 +683,12 @@ export default class ControlledTable extends PureComponent render() { const { id, - columns, column_conditional_tooltips, column_static_tooltip, content_style, - filtering, n_fixed_columns, n_fixed_rows, scrollbarWidth, - style_as_list_view, style_table, tooltip, tooltip_delay, @@ -717,7 +714,7 @@ export default class ControlledTable extends PureComponent ]; const rawTable = this.tableFn(); - const { grid, empty } = derivedTableFragments( + const grid = derivedTableFragments( n_fixed_columns, n_fixed_rows, rawTable, @@ -729,12 +726,6 @@ export default class ControlledTable extends PureComponent ...(virtualization ? ['dash-virtualized'] : []), ...(n_fixed_rows ? ['dash-freeze-top'] : []), ...(n_fixed_columns ? ['dash-freeze-left'] : []), - ...(style_as_list_view ? ['dash-list-view'] : []), - ...(empty[0][1] ? ['dash-empty-01'] : []), - ...(empty[1][1] ? ['dash-empty-11'] : []), - ...(columns.length ? [] : ['dash-no-columns']), - ...(virtualized.data.length ? [] : ['dash-no-data']), - ...(filtering ? [] : ['dash-no-filter']), [`dash-${content_style}`] ]; diff --git a/src/dash-table/components/EdgeFactory.tsx b/src/dash-table/components/EdgeFactory.tsx index 289cce55c..031b5395f 100644 --- a/src/dash-table/components/EdgeFactory.tsx +++ b/src/dash-table/components/EdgeFactory.tsx @@ -51,14 +51,16 @@ export default class EdgeFactory { const iPrevious = hPrevious.rows - 1; const iTarget = 0; - R.forEach(j => - EdgeFactory.hasPrecedence( + R.forEach(j => { + if (EdgeFactory.hasPrecedence( hPrevious.getWeight(iPrevious, j), hTarget.getWeight(iTarget, j), cutoffWeight - ) && hTarget.setEdge(iTarget, j, hPrevious.getEdge(iPrevious, j), Infinity, true) - , R.range(0, hPrevious.columns) - ); + )) { + hTarget.setEdge(iTarget, j, hPrevious.getEdge(iPrevious, j), Infinity, true); + } + hPrevious.setEdge(iPrevious, j, undefined, -Infinity, true); + }, R.range(0, hPrevious.columns)); } private vOverride(previous: EdgesMatricesOp, target: EdgesMatricesOp, cutoffWeight: number) { @@ -72,14 +74,16 @@ export default class EdgeFactory { const jPrevious = hPrevious.columns - 1; const jTarget = 0; - R.forEach(i => - EdgeFactory.hasPrecedence( + R.forEach(i => { + if (EdgeFactory.hasPrecedence( hPrevious.getWeight(i, jPrevious), hTarget.getWeight(i, jTarget), cutoffWeight - ) && hTarget.setEdge(i, jTarget, hPrevious.getEdge(i, jPrevious), Infinity, true) - , R.range(0, hPrevious.rows) - ); + )) { + hTarget.setEdge(i, jTarget, hPrevious.getEdge(i, jPrevious), Infinity, true); + } + hPrevious.setEdge(i, jPrevious, undefined, -Infinity, true); + }, R.range(0, hPrevious.rows)); } private hReconcile(target: EdgesMatrices | undefined, next: EdgesMatrices | undefined, cutoffWeight: number) { diff --git a/src/dash-table/components/Table/Table.less b/src/dash-table/components/Table/Table.less index 4d919b91e..764996e68 100644 --- a/src/dash-table/components/Table/Table.less +++ b/src/dash-table/components/Table/Table.less @@ -176,28 +176,6 @@ } } - &:not(.dash-empty-11) { - .row-0 { - tr:last-of-type { - td, th { - border-bottom: none !important; - } - } - } - } - - &:not(.dash-empty-01) { - .cell-0-0, - .cell-1-0 { - tr { - td:last-of-type, - th:last-of-type { - border-right: none !important; - } - } - } - } - &.dash-freeze-left, &.dash-freeze-top, &.dash-virtualized { diff --git a/src/dash-table/derived/table/fragments.tsx b/src/dash-table/derived/table/fragments.tsx index 1274003b1..db5ce3204 100644 --- a/src/dash-table/derived/table/fragments.tsx +++ b/src/dash-table/derived/table/fragments.tsx @@ -18,17 +18,12 @@ function renderFragment(cells: any[][] | null, offset: number = 0) { null; } -const isEmpty = (cells: JSX.Element[][] | null) => - !cells || - cells.length === 0 || - cells[0].length === 0; - export default ( fixedColumns: number, fixedRows: number, cells: JSX.Element[][], offset: number -): { grid: (JSX.Element | null)[][], empty: boolean[][] } => { +): (JSX.Element | null)[][] => { // slice out fixed columns const fixedColumnCells = fixedColumns ? R.map(row => @@ -55,14 +50,8 @@ export default ( fixedColumnCells.splice(0, fixedRows) : null; - return { - grid: [ - [renderFragment(fixedRowAndColumnCells), renderFragment(fixedRowCells)], - [renderFragment(fixedColumnCells), renderFragment(cells, offset)] - ], - empty: [ - [isEmpty(fixedRowAndColumnCells), isEmpty(fixedRowCells)], - [isEmpty(fixedColumnCells), isEmpty(cells)] - ] - }; + return [ + [renderFragment(fixedRowAndColumnCells), renderFragment(fixedRowCells)], + [renderFragment(fixedColumnCells), renderFragment(cells, offset)] + ]; }; \ No newline at end of file From d4af434c5e99e2bf26b2071ab1b49b67f9465bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 8 May 2019 16:38:58 -0400 Subject: [PATCH 35/46] add it back.. does not handle fixed inside a fragment --- .../components/ControlledTable/index.tsx | 11 +++++++++- src/dash-table/components/Table/Table.less | 22 +++++++++++++++++++ src/dash-table/derived/table/fragments.tsx | 21 +++++++++++++----- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/dash-table/components/ControlledTable/index.tsx b/src/dash-table/components/ControlledTable/index.tsx index 611ee1d05..28678158b 100644 --- a/src/dash-table/components/ControlledTable/index.tsx +++ b/src/dash-table/components/ControlledTable/index.tsx @@ -683,12 +683,15 @@ export default class ControlledTable extends PureComponent render() { const { id, + columns, column_conditional_tooltips, column_static_tooltip, content_style, + filtering, n_fixed_columns, n_fixed_rows, scrollbarWidth, + style_as_list_view, style_table, tooltip, tooltip_delay, @@ -714,7 +717,7 @@ export default class ControlledTable extends PureComponent ]; const rawTable = this.tableFn(); - const grid = derivedTableFragments( + const { grid, empty } = derivedTableFragments( n_fixed_columns, n_fixed_rows, rawTable, @@ -726,6 +729,12 @@ export default class ControlledTable extends PureComponent ...(virtualization ? ['dash-virtualized'] : []), ...(n_fixed_rows ? ['dash-freeze-top'] : []), ...(n_fixed_columns ? ['dash-freeze-left'] : []), + ...(style_as_list_view ? ['dash-list-view'] : []), + ...(empty[0][1] ? ['dash-empty-01'] : []), + ...(empty[1][1] ? ['dash-empty-11'] : []), + ...(columns.length ? [] : ['dash-no-columns']), + ...(virtualized.data.length ? [] : ['dash-no-data']), + ...(filtering ? [] : ['dash-no-filter']), [`dash-${content_style}`] ]; diff --git a/src/dash-table/components/Table/Table.less b/src/dash-table/components/Table/Table.less index 764996e68..4d919b91e 100644 --- a/src/dash-table/components/Table/Table.less +++ b/src/dash-table/components/Table/Table.less @@ -176,6 +176,28 @@ } } + &:not(.dash-empty-11) { + .row-0 { + tr:last-of-type { + td, th { + border-bottom: none !important; + } + } + } + } + + &:not(.dash-empty-01) { + .cell-0-0, + .cell-1-0 { + tr { + td:last-of-type, + th:last-of-type { + border-right: none !important; + } + } + } + } + &.dash-freeze-left, &.dash-freeze-top, &.dash-virtualized { diff --git a/src/dash-table/derived/table/fragments.tsx b/src/dash-table/derived/table/fragments.tsx index db5ce3204..1274003b1 100644 --- a/src/dash-table/derived/table/fragments.tsx +++ b/src/dash-table/derived/table/fragments.tsx @@ -18,12 +18,17 @@ function renderFragment(cells: any[][] | null, offset: number = 0) { null; } +const isEmpty = (cells: JSX.Element[][] | null) => + !cells || + cells.length === 0 || + cells[0].length === 0; + export default ( fixedColumns: number, fixedRows: number, cells: JSX.Element[][], offset: number -): (JSX.Element | null)[][] => { +): { grid: (JSX.Element | null)[][], empty: boolean[][] } => { // slice out fixed columns const fixedColumnCells = fixedColumns ? R.map(row => @@ -50,8 +55,14 @@ export default ( fixedColumnCells.splice(0, fixedRows) : null; - return [ - [renderFragment(fixedRowAndColumnCells), renderFragment(fixedRowCells)], - [renderFragment(fixedColumnCells), renderFragment(cells, offset)] - ]; + return { + grid: [ + [renderFragment(fixedRowAndColumnCells), renderFragment(fixedRowCells)], + [renderFragment(fixedColumnCells), renderFragment(cells, offset)] + ], + empty: [ + [isEmpty(fixedRowAndColumnCells), isEmpty(fixedRowCells)], + [isEmpty(fixedColumnCells), isEmpty(cells)] + ] + }; }; \ No newline at end of file From ce238a4a0fdf87689a8c30b27694e54ac3756dff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 8 May 2019 17:09:08 -0400 Subject: [PATCH 36/46] refactor border style tests --- .../percy-storybook/Border.style.percy.tsx | 251 ++++++++++-------- 1 file changed, 139 insertions(+), 112 deletions(-) diff --git a/tests/visual/percy-storybook/Border.style.percy.tsx b/tests/visual/percy-storybook/Border.style.percy.tsx index 36873459d..957c691f4 100644 --- a/tests/visual/percy-storybook/Border.style.percy.tsx +++ b/tests/visual/percy-storybook/Border.style.percy.tsx @@ -5,153 +5,180 @@ import DataTable from 'dash-table/dash/DataTable'; import { BORDER_PROPS_DEFAULTS } from './Border.defaults.percy'; const variants = [ + { name: 'base', props: { } }, { name: 'with ops', props: { row_deletable: true, row_selectable: 'single' } }, { name: 'fixed columns', props: { n_fixed_columns: 2, row_deletable: true, row_selectable: 'single' } }, { name: 'fixed rows', props: { n_fixed_rows: 1, row_deletable: true, row_selectable: 'single' } }, - { name: 'fixed columns & rows', props: { n_fixed_columns: 2, n_fixed_rows: 1, row_deletable: true, row_selectable: 'single' } } + { name: 'fixed columns & rows', props: { n_fixed_columns: 2, n_fixed_rows: 1, row_deletable: true, row_selectable: 'single' } }, + { name: 'fixed columns & rows inside fragments', props: { n_fixed_columns: 3, n_fixed_rows: 2, row_deletable: true, row_selectable: 'single' } } ]; -variants.forEach(variant => { - storiesOf(`DashTable/Border, custom styles (variant=${variant.name})`, module) - .add('with defaults', () => ()) - .add('with defaults & active cell (1,1)', () => ()) - .add('with defaults & active cell (0, 0)', () => ()) - .add('with cell style', () => ()) - .add('with data style', () => ()) - .add('with header style', () => ()) - .add('with filter style', () => ()) - .add('with header / cell (data) style - header wins on cell (data)', () => ()) - .add('with header / data style - data wins on header', () => ()) - .add('with header / filter / cell (data) style - filter wins on header, filter wins on cell (data)', () => ()) - .add('with header / data / cell (filter) style - header wins on cell (filter), data wins on cell (filter)', () => ()) - .add('with cell (header) / filter / data style - filter wins on cell (header), data wins on filter', () => ()) - .add('with data / cell (header, filter) style - data wins on filter', () => ()) - .add('with header / filter / data style - data wins on filter, filter wins on header', () => ()) - .add('style as list view', () => ()); -}); \ No newline at end of file + }, + style_as_list_view: true + } + } +]; + +const tests = R.xprod(scenarios, variants); + +R.reduce( + (chain, [scenario, variant]) => chain.add(`${scenario.name} (${variant.name})`, () => ()), + storiesOf(`DashTable/Border, custom styles`, module), + tests +); \ No newline at end of file From 2e036f7ee5f943d614f8954b40541625223d8150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 8 May 2019 18:52:39 -0400 Subject: [PATCH 37/46] fix filter test --- tests/visual/percy-storybook/Border.style.percy.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/visual/percy-storybook/Border.style.percy.tsx b/tests/visual/percy-storybook/Border.style.percy.tsx index 957c691f4..5f2509217 100644 --- a/tests/visual/percy-storybook/Border.style.percy.tsx +++ b/tests/visual/percy-storybook/Border.style.percy.tsx @@ -60,6 +60,7 @@ const scenarios = [ }, { name: 'with filter style', props: { + filtering: true, style_filter: { border: '1px solid hotpink' } From 1c9749fd36f3371dded27f57a960a87ac0b8c83b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 8 May 2019 19:54:42 -0400 Subject: [PATCH 38/46] fix standalone filter reset test --- src/dash-table/components/Table/index.tsx | 22 ++++++++++++---------- src/dash-table/derived/filter/map.ts | 7 +++++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/dash-table/components/Table/index.tsx b/src/dash-table/components/Table/index.tsx index ce1807367..6d1862834 100644 --- a/src/dash-table/components/Table/index.tsx +++ b/src/dash-table/components/Table/index.tsx @@ -53,17 +53,19 @@ export default class Table extends Component { - const map = this.filterMap( - state.map, - nextProps.filter, - nextProps.columns - ); - - return map !== state.map ? { map } : null; - }); + if (nextProps.filter === this.props.filter) { + return; } + + this.setState(state => { + const map = this.filterMap( + state.map, + nextProps.filter, + nextProps.columns + ); + + return map !== state.map ? { map } : null; + }); } shouldComponentUpdate(nextProps: any, nextState: any) { diff --git a/src/dash-table/derived/filter/map.ts b/src/dash-table/derived/filter/map.ts index 6bd4b7481..88787701b 100644 --- a/src/dash-table/derived/filter/map.ts +++ b/src/dash-table/derived/filter/map.ts @@ -46,10 +46,13 @@ export default memoizeOneFactory(( const ast = map.get(key); const reversedAst = reversedMap.get(key); - if (!R.isNil(reversedAst) && ( + if (R.isNil(reversedAst)) { + newMap = cloneIf(newMap, map); + newMap.delete(key); + } else if ( R.isNil(ast) || reversedAst.toQueryString() !== ast.toQueryString() - )) { + ) { newMap = cloneIf(newMap, map); newMap.set(key, reversedAst); } From bc350857a8cf0e510f47c30a113a42f60963cb48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 9 May 2019 12:21:16 -0400 Subject: [PATCH 39/46] fix ops columns styling --- .../derived/edges/operationOfData.ts | 4 +- .../derived/edges/operationOfFilters.ts | 2 +- .../derived/edges/operationOfHeaders.ts | 2 +- src/dash-table/derived/style/index.ts | 16 ++- .../percy-storybook/Border.style.percy.tsx | 125 +++++++++++++++++- 5 files changed, 138 insertions(+), 11 deletions(-) diff --git a/src/dash-table/derived/edges/operationOfData.ts b/src/dash-table/derived/edges/operationOfData.ts index 0ae13401a..d4bbd906a 100644 --- a/src/dash-table/derived/edges/operationOfData.ts +++ b/src/dash-table/derived/edges/operationOfData.ts @@ -20,8 +20,8 @@ const getWeightedStyle = ( const res: BorderStyle = {}; R.addIndex(R.forEach)((rs, i) => { - if (!rs.matchesRow(index + offset.rows) || - !rs.matchesColumn(undefined) || + if (rs.checksColumn() || + !rs.matchesRow(index + offset.rows) || !rs.matchesFilter(datum) ) { return; diff --git a/src/dash-table/derived/edges/operationOfFilters.ts b/src/dash-table/derived/edges/operationOfFilters.ts index a6c258a44..5098262b1 100644 --- a/src/dash-table/derived/edges/operationOfFilters.ts +++ b/src/dash-table/derived/edges/operationOfFilters.ts @@ -12,7 +12,7 @@ const getWeightedStyle = ( const res: BorderStyle = {}; R.addIndex(R.forEach)((rs, i) => { - if (!rs.matchesColumn(undefined)) { + if (rs.checksColumn()) { return; } diff --git a/src/dash-table/derived/edges/operationOfHeaders.ts b/src/dash-table/derived/edges/operationOfHeaders.ts index fc81fec9a..60c83e336 100644 --- a/src/dash-table/derived/edges/operationOfHeaders.ts +++ b/src/dash-table/derived/edges/operationOfHeaders.ts @@ -13,7 +13,7 @@ const getWeightedStyle = ( const res: BorderStyle = {}; R.addIndex(R.forEach)((rs, i) => { - if (!rs.matchesColumn(undefined) || + if (rs.checksColumn() || !rs.matchesRow(index) ) { return; diff --git a/src/dash-table/derived/style/index.ts b/src/dash-table/derived/style/index.ts index eb338bac6..61d6e6930 100644 --- a/src/dash-table/derived/style/index.ts +++ b/src/dash-table/derived/style/index.ts @@ -28,6 +28,9 @@ import { QuerySyntaxTree } from 'dash-table/syntax-tree'; export interface IConvertedStyle { style: CSSProperties; + checksColumn: () => boolean; + checksRow: () => boolean; + checksFilter: () => boolean; matchesColumn: (column: IVisibleColumn | undefined) => boolean; matchesRow: (index: number | undefined) => boolean; matchesFilter: (datum: Datum) => boolean; @@ -36,16 +39,23 @@ export interface IConvertedStyle { type GenericIf = Partial; type GenericStyle = Style & Partial<{ if: GenericIf }>; -function convertElement(style: GenericStyle) { +function convertElement(style: GenericStyle): IConvertedStyle { const indexFilter = style.if && (style.if.header_index || style.if.row_index); let ast: QuerySyntaxTree; return { + checksColumn: () => !R.isNil(style.if) && ( + !R.isNil(style.if.column_id) || + !R.isNil(style.if.column_type) + ), + checksRow: () => !R.isNil(indexFilter), + checksFilter: () => !R.isNil(style.if) && !R.isNil(style.if.filter), + matchesColumn: (column: IVisibleColumn | undefined) => !style.if || ( !R.isNil(column) && - ifColumnId(style.if, column.id) && - ifColumnType(style.if, column.type) + ifColumnId(style.if, column && column.id) && + ifColumnType(style.if, column && column.type) ), matchesRow: (index: number | undefined) => indexFilter === undefined ? diff --git a/tests/visual/percy-storybook/Border.style.percy.tsx b/tests/visual/percy-storybook/Border.style.percy.tsx index 5f2509217..ecfbff3e8 100644 --- a/tests/visual/percy-storybook/Border.style.percy.tsx +++ b/tests/visual/percy-storybook/Border.style.percy.tsx @@ -4,8 +4,7 @@ import { storiesOf } from '@storybook/react'; import DataTable from 'dash-table/dash/DataTable'; import { BORDER_PROPS_DEFAULTS } from './Border.defaults.percy'; -const variants = [ - { name: 'base', props: { } }, +const OPS_VARIANTS: ITest[] = [ { name: 'with ops', props: { row_deletable: true, row_selectable: 'single' } }, { name: 'fixed columns', props: { n_fixed_columns: 2, row_deletable: true, row_selectable: 'single' } }, { name: 'fixed rows', props: { n_fixed_rows: 1, row_deletable: true, row_selectable: 'single' } }, @@ -13,7 +12,17 @@ const variants = [ { name: 'fixed columns & rows inside fragments', props: { n_fixed_columns: 3, n_fixed_rows: 2, row_deletable: true, row_selectable: 'single' } } ]; -const scenarios = [ +interface ITest { + name: string; + props: any; +} + +const ALL_VARIANTS: ITest[] = [ + { name: 'base', props: {} }, + ...OPS_VARIANTS +]; + +const scenarios: ITest[] = [ { name: 'with defaults', props: {} @@ -170,7 +179,115 @@ const scenarios = [ } ]; -const tests = R.xprod(scenarios, variants); +const ops_scenarios: ITest[] = [ + { + name: 'data ops do not get styled on conditional column_id', + props: { + style_data: { + border: '1px solid black' + }, + style_data_conditional: [{ + if: { column_id: 'a' }, + border: '1px solid red' + }] + } + }, { + name: 'data ops do not get styled on conditional column_type', + props: { + style_data: { + border: '1px solid black' + }, + style_data_conditional: [{ + if: { column_type: 'any' }, + border: '1px solid red' + }] + } + }, { + name: 'data ops get styled on conditional row_index', + props: { + style_data: { + border: '1px solid black' + }, + style_data_conditional: [{ + if: { row_index: 1 }, + border: '1px solid red' + }] + } + }, { + name: 'data ops get styled on conditional filter', + props: { + style_data: { + border: '1px solid black' + }, + style_data_conditional: [{ + if: { filter: '{a} eq 85' }, + border: '1px solid red' + }] + } + }, { + name: 'header ops do not get styled on conditional column_id', + props: { + style_header: { + border: '1px solid black' + }, + style_header_conditional: [{ + if: { column_id: 'a' }, + border: '1px solid red' + }] + } + }, { + name: 'header ops do not get styled on conditional column_type', + props: { + style_header: { + border: '1px solid black' + }, + style_header_conditional: [{ + if: { column_type: 'any' }, + border: '1px solid red' + }] + } + }, { + name: 'header ops get styled on conditional header_index', + props: { + style_header: { + border: '1px solid black' + }, + style_header_conditional: [{ + if: { header_index: 0 }, + border: '1px solid red' + }] + } + }, { + name: 'filter ops do not get styled on conditional column_id', + props: { + filtering: true, + style_filter: { + border: '1px solid black' + }, + style_filter_conditional: [{ + if: { column_id: 'a' }, + border: '1px solid red' + }] + } + }, { + name: 'filter ops do not get styled on conditional column_type', + props: { + filtering: true, + style_filter: { + border: '1px solid black' + }, + style_filter_conditional: [{ + if: { column_type: 'any' }, + border: '1px solid red' + }] + } + } +]; + +const tests = R.concat( + R.xprod(scenarios, ALL_VARIANTS), + R.xprod(ops_scenarios, OPS_VARIANTS) +); R.reduce( (chain, [scenario, variant]) => chain.add(`${scenario.name} (${variant.name})`, () => ( Date: Thu, 9 May 2019 13:37:03 -0400 Subject: [PATCH 40/46] disappearing invalid filter test (will fail) --- tests/cypress/tests/standalone/filtering_test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/cypress/tests/standalone/filtering_test.ts b/tests/cypress/tests/standalone/filtering_test.ts index 7417860da..a6bbb6053 100644 --- a/tests/cypress/tests/standalone/filtering_test.ts +++ b/tests/cypress/tests/standalone/filtering_test.ts @@ -113,6 +113,17 @@ describe('filter', () => { DashTable.getCellById(0, 'ccc').within(() => cy.get('.dash-cell-value').should('have.html', '100')); }); + it('typing invalid followed by valid query fragment does not reset invalid', () => { + DashTable.getFilterById('ccc').click(); + DOM.focused.type(`gt`); + DashTable.getFilterById('ddd').click(); + DOM.focused.type('lt 20000'); + DashTable.getFilterById('eee').click(); + + DashTable.getFilterById('ccc').within(() => cy.get('input').should('have.value', 'gt')); + DashTable.getFilterById('ddd').within(() => cy.get('input').should('have.value', 'lt 20000')); + }); + it('reset updates results and filter fields', () => { let cell_0; let cell_1; From 98801b93535b92825ec7fa7d1c10c6cacc514875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 9 May 2019 13:53:47 -0400 Subject: [PATCH 41/46] fix disappearing invalid filter test --- src/dash-table/components/EdgeFactory.tsx | 4 +-- src/dash-table/components/Table/index.tsx | 33 ++++++++++++++--------- src/dash-table/components/Table/props.ts | 5 +++- src/dash-table/derived/table/index.tsx | 4 +-- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/dash-table/components/EdgeFactory.tsx b/src/dash-table/components/EdgeFactory.tsx index 031b5395f..f333ab382 100644 --- a/src/dash-table/components/EdgeFactory.tsx +++ b/src/dash-table/components/EdgeFactory.tsx @@ -141,7 +141,7 @@ export default class EdgeFactory { active_cell, columns, filtering, - map, + workFilter, n_fixed_columns, n_fixed_rows, row_deletable, @@ -163,7 +163,7 @@ export default class EdgeFactory { columns, (row_deletable ? 1 : 0) + (row_selectable ? 1 : 0), !!filtering, - map, + workFilter.map, n_fixed_columns, n_fixed_rows, style_as_list_view, diff --git a/src/dash-table/components/Table/index.tsx b/src/dash-table/components/Table/index.tsx index 6d1862834..aa105baf3 100644 --- a/src/dash-table/components/Table/index.tsx +++ b/src/dash-table/components/Table/index.tsx @@ -42,11 +42,14 @@ export default class Table extends Component(), - props.filter, - props.columns - ), + workFilter: { + value: props.filter, + map: this.filterMap( + new Map(), + props.filter, + props.columns + ) + }, rawFilterQuery: '', scrollbarWidth: 0 }; @@ -58,13 +61,19 @@ export default class Table extends Component { - const map = this.filterMap( - state.map, - nextProps.filter, - nextProps.columns - ); - - return map !== state.map ? { map } : null; + const { workFilter: { map: currentMap, value } } = state; + + if (value !== nextProps.filter) { + const map = this.filterMap( + currentMap, + nextProps.filter, + nextProps.columns + ); + + return map !== currentMap ? { workFilter: { map, value} } : null; + } else { + return null; + } }); } diff --git a/src/dash-table/components/Table/props.ts b/src/dash-table/components/Table/props.ts index ced520b08..3b3fc3e15 100644 --- a/src/dash-table/components/Table/props.ts +++ b/src/dash-table/components/Table/props.ts @@ -239,7 +239,10 @@ export interface IUSerInterfaceTooltip { export interface IState { forcedResizeOnly: boolean; - map: Map; + workFilter: { + value: string, + map: Map + }; rawFilterQuery: string; scrollbarWidth: number; tooltip?: IUSerInterfaceTooltip; diff --git a/src/dash-table/derived/table/index.tsx b/src/dash-table/derived/table/index.tsx index 26739a193..14417c49e 100644 --- a/src/dash-table/derived/table/index.tsx +++ b/src/dash-table/derived/table/index.tsx @@ -19,13 +19,13 @@ const handleSetFilter = ( map: Map ) => { setProps({ filter, ...clearSelection }); - setState({ map, rawFilterQuery }); + setState({ workFilter: { map, value: filter }, rawFilterQuery }); }; function filterPropsFn(propsFn: () => ControlledTableProps, setFilter: any) { const props = propsFn(); - return R.merge(props, { setFilter }); + return R.merge(props, { map: props.workFilter.map, setFilter }); } function getter( From 2be996f22d0bf72fd29e8a22fada6c24f0699373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 9 May 2019 13:56:25 -0400 Subject: [PATCH 42/46] add new operation columns visual tests --- tests/visual/percy-storybook/Border.style.percy.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/visual/percy-storybook/Border.style.percy.tsx b/tests/visual/percy-storybook/Border.style.percy.tsx index ecfbff3e8..43ad5c224 100644 --- a/tests/visual/percy-storybook/Border.style.percy.tsx +++ b/tests/visual/percy-storybook/Border.style.percy.tsx @@ -188,6 +188,7 @@ const ops_scenarios: ITest[] = [ }, style_data_conditional: [{ if: { column_id: 'a' }, + backgroundColor: 'pink', border: '1px solid red' }] } @@ -199,6 +200,7 @@ const ops_scenarios: ITest[] = [ }, style_data_conditional: [{ if: { column_type: 'any' }, + backgroundColor: 'pink', border: '1px solid red' }] } @@ -210,6 +212,7 @@ const ops_scenarios: ITest[] = [ }, style_data_conditional: [{ if: { row_index: 1 }, + backgroundColor: 'pink', border: '1px solid red' }] } @@ -221,6 +224,7 @@ const ops_scenarios: ITest[] = [ }, style_data_conditional: [{ if: { filter: '{a} eq 85' }, + backgroundColor: 'pink', border: '1px solid red' }] } @@ -232,6 +236,7 @@ const ops_scenarios: ITest[] = [ }, style_header_conditional: [{ if: { column_id: 'a' }, + backgroundColor: 'pink', border: '1px solid red' }] } @@ -243,6 +248,7 @@ const ops_scenarios: ITest[] = [ }, style_header_conditional: [{ if: { column_type: 'any' }, + backgroundColor: 'pink', border: '1px solid red' }] } @@ -254,6 +260,7 @@ const ops_scenarios: ITest[] = [ }, style_header_conditional: [{ if: { header_index: 0 }, + backgroundColor: 'pink', border: '1px solid red' }] } @@ -266,6 +273,7 @@ const ops_scenarios: ITest[] = [ }, style_filter_conditional: [{ if: { column_id: 'a' }, + backgroundColor: 'pink', border: '1px solid red' }] } @@ -278,6 +286,7 @@ const ops_scenarios: ITest[] = [ }, style_filter_conditional: [{ if: { column_type: 'any' }, + backgroundColor: 'pink', border: '1px solid red' }] } From ea31de134a6cdd93f47d563f086a43438f1358cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 9 May 2019 15:46:05 -0400 Subject: [PATCH 43/46] messy ops styling --- src/dash-table/components/CellFactory.tsx | 41 ++++++++++++------- src/dash-table/components/FilterFactory.tsx | 20 +++++++-- src/dash-table/components/HeaderFactory.tsx | 22 +++++++--- src/dash-table/derived/cell/wrapperStyles.ts | 28 +++++++++++++ .../derived/filter/wrapperStyles.ts | 24 +++++++++++ .../derived/header/wrapperStyles.ts | 24 +++++++++++ 6 files changed, 136 insertions(+), 23 deletions(-) diff --git a/src/dash-table/components/CellFactory.tsx b/src/dash-table/components/CellFactory.tsx index a25fd496b..6e3821e23 100644 --- a/src/dash-table/components/CellFactory.tsx +++ b/src/dash-table/components/CellFactory.tsx @@ -1,14 +1,14 @@ import * as R from 'ramda'; import React from 'react'; -import { matrixMap, matrixMap3 } from 'core/math/matrixZipMap'; +import { matrixMap2, matrixMap3 } from 'core/math/matrixZipMap'; import { arrayMap2 } from 'core/math/arrayZipMap'; import { ICellFactoryProps } from 'dash-table/components/Table/props'; import derivedCellWrappers from 'dash-table/derived/cell/wrappers'; import derivedCellContents from 'dash-table/derived/cell/contents'; import derivedCellOperations from 'dash-table/derived/cell/operations'; -import derivedCellStyles from 'dash-table/derived/cell/wrapperStyles'; +import derivedCellStyles, { derivedDataOpStyles } from 'dash-table/derived/cell/wrapperStyles'; import derivedDropdowns from 'dash-table/derived/cell/dropdowns'; import { derivedRelevantCellStyles } from 'dash-table/derived/style'; import { IEdgesMatrices } from 'dash-table/derived/edges/type'; @@ -25,6 +25,7 @@ export default class CellFactory { private readonly cellDropdowns = derivedDropdowns(), private readonly cellOperations = derivedCellOperations(), private readonly cellStyles = derivedCellStyles(), + private readonly dataOpStyles = derivedDataOpStyles(), private readonly cellWrappers = derivedCellWrappers(propsFn), private readonly relevantStyles = derivedRelevantCellStyles() ) { } @@ -58,18 +59,15 @@ export default class CellFactory { style_data_conditional ); - const operations = this.cellOperations( - data, + const cellStyles = this.cellStyles( + columns, + relevantStyles, virtualized.data, - virtualized.indices, - row_selectable, - row_deletable, - selected_rows, - setProps + virtualized.offset ); - const cellStyles = this.cellStyles( - columns, + const dataOpStyles = this.dataOpStyles( + (row_selectable ? 1 : 0) + (row_deletable ? 1 : 0), relevantStyles, virtualized.data, virtualized.offset @@ -84,6 +82,16 @@ export default class CellFactory { dropdown_properties ); + const operations = this.cellOperations( + data, + virtualized.data, + virtualized.indices, + row_selectable, + row_deletable, + selected_rows, + setProps + ); + const cellWrappers = this.cellWrappers( active_cell, columns, @@ -102,10 +110,15 @@ export default class CellFactory { dropdowns ); - const ops = matrixMap( + const ops = matrixMap2( operations, - (o, i, j) => React.cloneElement(o, { - style: dataOpEdges && dataOpEdges.getStyle(i, j) + dataOpStyles, + (o, s, i, j) => React.cloneElement(o, { + style: R.mergeAll([ + dataOpEdges && dataOpEdges.getStyle(i, j), + s, + o.props.style + ]) }) ); diff --git a/src/dash-table/components/FilterFactory.tsx b/src/dash-table/components/FilterFactory.tsx index e0d5e7c96..4c774f4f8 100644 --- a/src/dash-table/components/FilterFactory.tsx +++ b/src/dash-table/components/FilterFactory.tsx @@ -8,7 +8,7 @@ import { memoizeOne } from 'core/memoizer'; import ColumnFilter from 'dash-table/components/Filter/Column'; import { ColumnId, Filtering, FilteringType, IVisibleColumn, VisibleColumns, RowSelection } from 'dash-table/components/Table/props'; -import derivedFilterStyles from 'dash-table/derived/filter/wrapperStyles'; +import derivedFilterStyles, { derivedFilterOpStyles } from 'dash-table/derived/filter/wrapperStyles'; import derivedHeaderOperations from 'dash-table/derived/header/operations'; import { derivedRelevantFilterStyles } from 'dash-table/derived/style'; import { BasicFilters, Cells, Style } from 'dash-table/derived/style/props'; @@ -42,6 +42,7 @@ export interface IFilterOptions { export default class FilterFactory { private readonly filterStyles = derivedFilterStyles(); + private readonly filterOpStyles = derivedFilterOpStyles(); private readonly relevantStyles = derivedRelevantFilterStyles(); private readonly headerOperations = derivedHeaderOperations(); @@ -135,6 +136,12 @@ export default class FilterFactory { filterEdges ); + const opStyles = this.filterOpStyles( + 1, + (row_selectable ? 1 : 0) + (row_deletable ? 1 : 0), + relevantStyles + )[0]; + const filters = R.addIndex(R.map)((column, index) => { return this.filter.get(column.id, index)( column, @@ -158,10 +165,15 @@ export default class FilterFactory { row_deletable )[0]; - const operators = arrayMap( + const operators = arrayMap2( operations, - (o, j) => React.cloneElement(o, { - style: filterOpEdges && filterOpEdges.getStyle(0, j) + opStyles, + (o, s, j) => React.cloneElement(o, { + style: R.mergeAll([ + filterOpEdges && filterOpEdges.getStyle(0, j), + s, + o.props.style + ]) }) ); diff --git a/src/dash-table/components/HeaderFactory.tsx b/src/dash-table/components/HeaderFactory.tsx index 22b2538c3..59db68ddf 100644 --- a/src/dash-table/components/HeaderFactory.tsx +++ b/src/dash-table/components/HeaderFactory.tsx @@ -2,7 +2,7 @@ import * as R from 'ramda'; import React from 'react'; import { arrayMap2 } from 'core/math/arrayZipMap'; -import { matrixMap, matrixMap3 } from 'core/math/matrixZipMap'; +import { matrixMap2, matrixMap3 } from 'core/math/matrixZipMap'; import { ControlledTableProps } from 'dash-table/components/Table/props'; import derivedHeaderContent from 'dash-table/derived/header/content'; @@ -12,7 +12,7 @@ import getLabels from 'dash-table/derived/header/labels'; import derivedHeaderOperations from 'dash-table/derived/header/operations'; import derivedHeaderWrappers from 'dash-table/derived/header/wrappers'; import { derivedRelevantHeaderStyles } from 'dash-table/derived/style'; -import derivedHeaderStyles from 'dash-table/derived/header/wrapperStyles'; +import derivedHeaderStyles, { derivedHeaderOpStyles } from 'dash-table/derived/header/wrapperStyles'; import { IEdgesMatrices } from 'dash-table/derived/edges/type'; @@ -20,6 +20,7 @@ export default class HeaderFactory { private readonly headerContent = derivedHeaderContent(); private readonly headerOperations = derivedHeaderOperations(); private readonly headerStyles = derivedHeaderStyles(); + private readonly headerOpStyles = derivedHeaderOpStyles(); private readonly headerWrappers = derivedHeaderWrappers(); private readonly relevantStyles = derivedRelevantHeaderStyles(); @@ -76,6 +77,12 @@ export default class HeaderFactory { relevantStyles ); + const opStyles = this.headerOpStyles( + headerRows, + (row_selectable ? 1 : 0) + (row_deletable ? 1 : 0), + relevantStyles + ); + const wrappers = this.headerWrappers( columns, labelsAndIndices, @@ -93,10 +100,15 @@ export default class HeaderFactory { props ); - const ops = matrixMap( + const ops = matrixMap2( operations, - (o, i, j) => React.cloneElement(o, { - style: headerOpEdges && headerOpEdges.getStyle(i, j) + opStyles, + (o, s, i, j) => React.cloneElement(o, { + style: R.mergeAll([ + headerOpEdges && headerOpEdges.getStyle(i, j), + s, + o.props.style + ]) }) ); diff --git a/src/dash-table/derived/cell/wrapperStyles.ts b/src/dash-table/derived/cell/wrapperStyles.ts index 8fc114b5b..764acf72f 100644 --- a/src/dash-table/derived/cell/wrapperStyles.ts +++ b/src/dash-table/derived/cell/wrapperStyles.ts @@ -35,4 +35,32 @@ function getter( }, columns), data); } +function opGetter( + columns: number, + columnStyles: IConvertedStyle[], + data: Data, + offset: IViewportOffset +) { + return R.addIndex(R.map)((datum, index) => R.map(_ => { + const relevantStyles = R.map( + s => s.style, + R.filter( + style => + !style.checksColumn() && + style.matchesRow(index + offset.rows) && + style.matchesFilter(datum), + columnStyles + ) + ); + + return relevantStyles.length ? + R.omit( + BORDER_PROPERTIES_AND_FRAGMENTS, + R.mergeAll(relevantStyles) + ) : + undefined; + }, R.range(0, columns)), data); +} + export default memoizeOneFactory(getter); +export const derivedDataOpStyles = memoizeOneFactory(opGetter); \ No newline at end of file diff --git a/src/dash-table/derived/filter/wrapperStyles.ts b/src/dash-table/derived/filter/wrapperStyles.ts index 2d8094339..1e85e0d55 100644 --- a/src/dash-table/derived/filter/wrapperStyles.ts +++ b/src/dash-table/derived/filter/wrapperStyles.ts @@ -32,4 +32,28 @@ function getter( }, columns); } +function opGetter( + rows: number, + columns: number, + columnStyles: IConvertedStyle[] +) { + return R.map(() => R.map(() => { + const relevantStyles = R.map( + s => s.style, + R.filter( + style => !style.checksColumn(), + columnStyles + ) + ); + + return relevantStyles.length ? + R.omit( + BORDER_PROPERTIES_AND_FRAGMENTS, + R.mergeAll(relevantStyles) + ) : + undefined; + }, R.range(0, columns)), R.range(0, rows)); +} + export default memoizeOneFactory(getter); +export const derivedFilterOpStyles = memoizeOneFactory(opGetter); \ No newline at end of file diff --git a/src/dash-table/derived/header/wrapperStyles.ts b/src/dash-table/derived/header/wrapperStyles.ts index d1ed3d169..607567cfc 100644 --- a/src/dash-table/derived/header/wrapperStyles.ts +++ b/src/dash-table/derived/header/wrapperStyles.ts @@ -35,4 +35,28 @@ function getter( }, columns), R.range(0, headerRows)); } +function opGetter( + rows: number, + columns: number, + columnStyles: IConvertedStyle[] +) { + return R.map(() => R.map(() => { + const relevantStyles = R.map( + s => s.style, + R.filter( + style => !style.checksColumn(), + columnStyles + ) + ); + + return relevantStyles.length ? + R.omit( + BORDER_PROPERTIES_AND_FRAGMENTS, + R.mergeAll(relevantStyles) + ) : + undefined; + }, R.range(0, columns)), R.range(0, rows)); +} + export default memoizeOneFactory(getter); +export const derivedHeaderOpStyles = memoizeOneFactory(opGetter); \ No newline at end of file From f2409c32637a875524738253fe7efb668eb55687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 10 May 2019 12:25:03 -0400 Subject: [PATCH 44/46] update border visual tests --- tests/visual/percy-storybook/Border.defaults.percy.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/visual/percy-storybook/Border.defaults.percy.tsx b/tests/visual/percy-storybook/Border.defaults.percy.tsx index d33ca6c2f..55fdf3c4f 100644 --- a/tests/visual/percy-storybook/Border.defaults.percy.tsx +++ b/tests/visual/percy-storybook/Border.defaults.percy.tsx @@ -40,7 +40,12 @@ const style_table = { }; const style_data_conditional = [ - { width: 100 } + { + if: {}, + width: 100, + minWidth: 100, + maxWidth: 100 + } ]; export const BORDER_PROPS_DEFAULTS = { From 0816c53ad2073df69000fca45b3513a393b1b86f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 10 May 2019 12:59:57 -0400 Subject: [PATCH 45/46] n_fixed_rows misalignment --- src/dash-table/components/ControlledTable/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dash-table/components/ControlledTable/index.tsx b/src/dash-table/components/ControlledTable/index.tsx index 28678158b..80d233dc7 100644 --- a/src/dash-table/components/ControlledTable/index.tsx +++ b/src/dash-table/components/ControlledTable/index.tsx @@ -644,7 +644,7 @@ export default class ControlledTable extends PureComponent this.stylesheet.setRule( `.dash-fixed-row:not(.dash-fixed-column) th:nth-of-type(${index + 1})`, - `width: ${width}; min-width: ${width}; max-width: ${width};` + `width: ${width} !important; min-width: ${width} !important; max-width: ${width} !important;` ); }); } @@ -657,7 +657,7 @@ export default class ControlledTable extends PureComponent this.stylesheet.setRule( `.dash-fixed-column.dash-fixed-row th:nth-of-type(${index + 1})`, - `width: ${width}; min-width: ${width}; max-width: ${width};` + `width: ${width} !important; min-width: ${width} !important; max-width: ${width} !important;` ); }); } From ea4893312dc03759651647afe0a1b87304f88f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 10 May 2019 14:21:13 -0400 Subject: [PATCH 46/46] update changelog --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a318398c..8cad4e752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,26 @@ This project adheres to [Semantic Versioning](http://semver.org/). - `derived_viewport_selected_row_ids` mirrors `derived_viewport_selected_rows` - `derived_virtual_selected_row_ids` mirrors `derived_virtual_selected_rows` +[#424](https://github.com/plotly/dash-table/pull/424) +- Customizable cell borders through `style_**` props + - cell borders now no longer use `box-shadow` and use `border` instead + - Supports CSS shorthands: + border, border_bottom, border_left, border_right, border_top + - style_** props will ignore the following CSS rules: + border_bottom_color, border_bottom_left_radius, border_bottom_right_radius, border_bottom_style, border_bottom_width, border_collapse, border_color, border_corner_shape, border_image_source, border_image_width, border_left_color, border_left_style, border_left_width, border_right_color, border_right_style, border_right_width, border_spacing, border_style, border_top_color, border_top_left_radius, border_top_right_radius, border_top_style, border_top_width, border_width + - Styles priority: + 1. Props priority in decreasing order + style_data_conditional + style_data + style_filter_conditional + style_filter + style_header_conditional + style_header + style_cell_conditional + style_cell + 2. Within each props, higher index rules win over lower index rules + 3. Previously applied styles of equal priority win over later ones (applied top to bottom, left to right) + ### Changed [#397](https://github.com/plotly/dash-table/pull/397) - Rename `filtering_settings` to `filter`