feat(dees-table): add configurable cell flash comparison and border highlight mode
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-04-12 - 3.77.0 - feat(dees-table)
|
||||||
|
add configurable cell flash comparison and border highlight mode
|
||||||
|
|
||||||
|
- adds column-level flashCompare support so update highlighting can detect meaningful changes for custom cell values
|
||||||
|
- adds flashBorder styling for cells with badges, icons, or custom rendered content where text-color flashing is not visible
|
||||||
|
- avoids false-positive flash animations for non-primitive cell values unless a custom comparator is provided
|
||||||
|
|
||||||
## 2026-04-12 - 3.76.1 - fix(demo-inputs)
|
## 2026-04-12 - 3.76.1 - fix(demo-inputs)
|
||||||
wrap input component demos in dees-form containers for consistent form integration
|
wrap input component demos in dees-form containers for consistent form integration
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-catalog',
|
name: '@design.estate/dees-catalog',
|
||||||
version: '3.76.1',
|
version: '3.77.0',
|
||||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -695,6 +695,7 @@ export class DeesTable<T> extends DeesElement {
|
|||||||
this.__editingCell?.rowId === rowId &&
|
this.__editingCell?.rowId === rowId &&
|
||||||
this.__editingCell?.colKey === editKey;
|
this.__editingCell?.colKey === editKey;
|
||||||
const isFlashing = !!flashSet?.has(editKey);
|
const isFlashing = !!flashSet?.has(editKey);
|
||||||
|
const useFlashBorder = isFlashing && !!col.flashBorder;
|
||||||
const cellClasses = [
|
const cellClasses = [
|
||||||
isEditable ? 'editable' : '',
|
isEditable ? 'editable' : '',
|
||||||
isFocused && !isEditing ? 'focused' : '',
|
isFocused && !isEditing ? 'focused' : '',
|
||||||
@@ -702,8 +703,13 @@ export class DeesTable<T> extends DeesElement {
|
|||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ');
|
.join(' ');
|
||||||
|
const flashClass = isFlashing
|
||||||
|
? useFlashBorder
|
||||||
|
? 'innerCellContainer flashing-border'
|
||||||
|
: 'innerCellContainer flashing'
|
||||||
|
: 'innerCellContainer';
|
||||||
const innerHtml = html`<div
|
const innerHtml = html`<div
|
||||||
class=${isFlashing ? 'innerCellContainer flashing' : 'innerCellContainer'}
|
class=${flashClass}
|
||||||
>
|
>
|
||||||
${isEditing ? this.renderCellEditor(itemArg, col) : content}
|
${isEditing ? this.renderCellEditor(itemArg, col) : content}
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -1362,6 +1368,7 @@ export class DeesTable<T> extends DeesElement {
|
|||||||
|
|
||||||
const effectiveColumns = this.__getEffectiveColumns();
|
const effectiveColumns = this.__getEffectiveColumns();
|
||||||
const visibleCols = effectiveColumns.filter((c) => !c.hidden);
|
const visibleCols = effectiveColumns.filter((c) => !c.hidden);
|
||||||
|
const colByKey = new Map<string, Column<T>>(visibleCols.map((c) => [String(c.key), c]));
|
||||||
const nextSnapshot = new Map<string, Map<string, unknown>>();
|
const nextSnapshot = new Map<string, Map<string, unknown>>();
|
||||||
const newlyFlashing = new Map<string, Set<string>>();
|
const newlyFlashing = new Map<string, Set<string>>();
|
||||||
|
|
||||||
@@ -1376,7 +1383,26 @@ export class DeesTable<T> extends DeesElement {
|
|||||||
const prevCells = this.__prevSnapshot?.get(rowId);
|
const prevCells = this.__prevSnapshot?.get(rowId);
|
||||||
if (!prevCells) continue; // new row — not an "update"
|
if (!prevCells) continue; // new row — not an "update"
|
||||||
for (const [colKey, nextVal] of cellMap) {
|
for (const [colKey, nextVal] of cellMap) {
|
||||||
if (prevCells.get(colKey) !== nextVal) {
|
const prevVal = prevCells.get(colKey);
|
||||||
|
|
||||||
|
// Look up the column definition for flash options.
|
||||||
|
const colDef = colByKey.get(colKey);
|
||||||
|
|
||||||
|
// Determine whether the cell changed.
|
||||||
|
let changed: boolean;
|
||||||
|
if (colDef?.flashCompare) {
|
||||||
|
// Explicit custom comparator — caller decides.
|
||||||
|
changed = colDef.flashCompare(prevVal, nextVal);
|
||||||
|
} else if (nextVal !== null && nextVal !== undefined && typeof nextVal === 'object') {
|
||||||
|
// Non-primitive (TemplateResult, object, array, etc.) — skip by
|
||||||
|
// default. Custom renderings don't benefit from the text-color
|
||||||
|
// flash and reference inequality causes false positives.
|
||||||
|
changed = false;
|
||||||
|
} else {
|
||||||
|
changed = prevVal !== nextVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
// Don't flash the cell the user is actively editing.
|
// Don't flash the cell the user is actively editing.
|
||||||
if (
|
if (
|
||||||
this.__editingCell &&
|
this.__editingCell &&
|
||||||
|
|||||||
@@ -404,11 +404,44 @@ export const tableStyles: CSSResult[] = [
|
|||||||
100% { color: var(--dees-color-text-primary); }
|
100% { color: var(--dees-color-text-primary); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Border/background flash variant for cells with styled content
|
||||||
|
(badges, icons, custom components) where a text-color animation
|
||||||
|
would be invisible. Activated via flashBorder on Column. */
|
||||||
|
.innerCellContainer.flashing-border {
|
||||||
|
animation: dees-table-cell-flash-border
|
||||||
|
var(--dees-table-flash-duration, 900ms)
|
||||||
|
var(--dees-table-flash-easing);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dees-table-cell-flash-border {
|
||||||
|
0%,
|
||||||
|
35% {
|
||||||
|
box-shadow: inset 0 0 0 1.5px var(--dees-table-flash-color);
|
||||||
|
background: ${cssManager.bdTheme(
|
||||||
|
'hsl(45 93% 62% / 0.10)',
|
||||||
|
'hsl(45 93% 62% / 0.08)'
|
||||||
|
)};
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: inset 0 0 0 1.5px transparent;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.innerCellContainer.flashing {
|
.innerCellContainer.flashing {
|
||||||
animation: none;
|
animation: none;
|
||||||
color: var(--dees-table-flash-color);
|
color: var(--dees-table-flash-color);
|
||||||
}
|
}
|
||||||
|
.innerCellContainer.flashing-border {
|
||||||
|
animation: none;
|
||||||
|
box-shadow: inset 0 0 0 1.5px var(--dees-table-flash-color);
|
||||||
|
background: ${cssManager.bdTheme(
|
||||||
|
'hsl(45 93% 62% / 0.10)',
|
||||||
|
'hsl(45 93% 62% / 0.08)'
|
||||||
|
)};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dev-time warning banner shown when highlight-updates="flash" but
|
/* Dev-time warning banner shown when highlight-updates="flash" but
|
||||||
|
|||||||
@@ -65,6 +65,25 @@ export interface Column<T = any> {
|
|||||||
parse?: (editorValue: any, row: T) => any;
|
parse?: (editorValue: any, row: T) => any;
|
||||||
/** Validate the parsed value before commit. Return string for error, true/void for ok. */
|
/** Validate the parsed value before commit. Return string for error, true/void for ok. */
|
||||||
validate?: (value: any, row: T) => true | string | void;
|
validate?: (value: any, row: T) => true | string | void;
|
||||||
|
|
||||||
|
// ─── Flash highlight options ───
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom comparison for flash-on-update diffing.
|
||||||
|
* Return `true` if the cell should flash (i.e. the values differ).
|
||||||
|
* When absent, non-primitive cell values are skipped entirely
|
||||||
|
* (only strings, numbers, booleans, null, and undefined are diffed).
|
||||||
|
*/
|
||||||
|
flashCompare?: (prevVal: any, nextVal: any) => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When `true`, flash this cell with a border/background pulse instead of
|
||||||
|
* the default text-color animation. Useful for cells containing styled
|
||||||
|
* badges, icons, or custom web-component renderings where a text-color
|
||||||
|
* change would be invisible.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
flashBorder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user