feat(dees-table): add configurable cell flash comparison and border highlight mode
This commit is contained in:
@@ -695,6 +695,7 @@ export class DeesTable<T> extends DeesElement {
|
||||
this.__editingCell?.rowId === rowId &&
|
||||
this.__editingCell?.colKey === editKey;
|
||||
const isFlashing = !!flashSet?.has(editKey);
|
||||
const useFlashBorder = isFlashing && !!col.flashBorder;
|
||||
const cellClasses = [
|
||||
isEditable ? 'editable' : '',
|
||||
isFocused && !isEditing ? 'focused' : '',
|
||||
@@ -702,8 +703,13 @@ export class DeesTable<T> extends DeesElement {
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
const flashClass = isFlashing
|
||||
? useFlashBorder
|
||||
? 'innerCellContainer flashing-border'
|
||||
: 'innerCellContainer flashing'
|
||||
: 'innerCellContainer';
|
||||
const innerHtml = html`<div
|
||||
class=${isFlashing ? 'innerCellContainer flashing' : 'innerCellContainer'}
|
||||
class=${flashClass}
|
||||
>
|
||||
${isEditing ? this.renderCellEditor(itemArg, col) : content}
|
||||
</div>`;
|
||||
@@ -1362,6 +1368,7 @@ export class DeesTable<T> extends DeesElement {
|
||||
|
||||
const effectiveColumns = this.__getEffectiveColumns();
|
||||
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 newlyFlashing = new Map<string, Set<string>>();
|
||||
|
||||
@@ -1376,7 +1383,26 @@ export class DeesTable<T> extends DeesElement {
|
||||
const prevCells = this.__prevSnapshot?.get(rowId);
|
||||
if (!prevCells) continue; // new row — not an "update"
|
||||
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.
|
||||
if (
|
||||
this.__editingCell &&
|
||||
|
||||
@@ -404,11 +404,44 @@ export const tableStyles: CSSResult[] = [
|
||||
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) {
|
||||
.innerCellContainer.flashing {
|
||||
animation: none;
|
||||
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
|
||||
|
||||
@@ -65,6 +65,25 @@ export interface Column<T = any> {
|
||||
parse?: (editorValue: any, row: T) => any;
|
||||
/** Validate the parsed value before commit. Return string for error, true/void for ok. */
|
||||
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