@genesislcap/grid-pro 15.16.0 → 15.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/custom-elements.json +1896 -1338
- package/dist/dts/datasource/index.d.ts +3 -0
- package/dist/dts/datasource/index.d.ts.map +1 -1
- package/dist/dts/datasource/row-match.classes.d.ts +47 -0
- package/dist/dts/datasource/row-match.classes.d.ts.map +1 -0
- package/dist/dts/datasource/row-match.tracker.d.ts +67 -0
- package/dist/dts/datasource/row-match.tracker.d.ts.map +1 -0
- package/dist/dts/datasource/row-match.types.d.ts +86 -0
- package/dist/dts/datasource/row-match.types.d.ts.map +1 -0
- package/dist/dts/grid-pro.styles.d.ts +14 -0
- package/dist/dts/grid-pro.styles.d.ts.map +1 -1
- package/dist/dts/index.d.ts +1 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/react.d.ts +13 -13
- package/dist/dts/row-alert/index.d.ts +3 -0
- package/dist/dts/row-alert/index.d.ts.map +1 -0
- package/dist/dts/row-alert/row-alert.d.ts +132 -0
- package/dist/dts/row-alert/row-alert.d.ts.map +1 -0
- package/dist/dts/row-alert/row-alert.types.d.ts +62 -0
- package/dist/dts/row-alert/row-alert.types.d.ts.map +1 -0
- package/dist/dts/state-persistence/kv-state.d.ts +3 -0
- package/dist/dts/state-persistence/kv-state.d.ts.map +1 -1
- package/dist/dts/state-persistence/local-state.d.ts +3 -0
- package/dist/dts/state-persistence/local-state.d.ts.map +1 -1
- package/dist/dts/state-persistence/state-persistence.d.ts +16 -0
- package/dist/dts/state-persistence/state-persistence.d.ts.map +1 -1
- package/dist/esm/datasource/index.js +3 -0
- package/dist/esm/datasource/row-match.classes.js +53 -0
- package/dist/esm/datasource/row-match.tracker.js +117 -0
- package/dist/esm/datasource/row-match.types.js +98 -0
- package/dist/esm/grid-pro.styles.js +48 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/row-alert/index.js +2 -0
- package/dist/esm/row-alert/row-alert.js +197 -0
- package/dist/esm/row-alert/row-alert.types.js +63 -0
- package/dist/esm/state-persistence/kv-state.js +21 -0
- package/dist/esm/state-persistence/local-state.js +19 -0
- package/dist/grid-pro.api.json +11119 -8496
- package/dist/grid-pro.d.ts +441 -0
- package/dist/react.cjs +9 -9
- package/dist/react.mjs +7 -7
- package/package.json +14 -14
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { installAlertFlashClock } from '@genesislcap/foundation-ui';
|
|
2
|
+
import { matchesRowCriteria } from './row-match.types';
|
|
3
|
+
/**
|
|
4
|
+
* Class the grid puts on rows satisfying the criteria. The grid's own styles flash it in step
|
|
5
|
+
* with any alerting tab, so a tab that draws you to a grid also shows you which rows caused it.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export const ROW_MATCH_ALERT_CLASS = 'grid-pro-row-alert';
|
|
10
|
+
/**
|
|
11
|
+
* Class the grid puts on just the `flashField` cell of a matching row, for grids that flash a
|
|
12
|
+
* single column rather than the whole row.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
export const ROW_MATCH_ALERT_CELL_CLASS = 'grid-pro-row-alert-cell';
|
|
17
|
+
/**
|
|
18
|
+
* Row class rules that flag rows meeting {@link RowMatchCriteria}, for spreading into
|
|
19
|
+
* `gridOptions.rowClassRules`.
|
|
20
|
+
*
|
|
21
|
+
* Pairs with {@link DatasourceRowMatchTracker}: the tracker decides whether a tab should flash,
|
|
22
|
+
* these decide which rows made it flash. Both take the same criteria, so the two cannot disagree.
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* gridOptions = { rowClassRules: createRowMatchClassRules(criteria) };
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export function createRowMatchClassRules(criteria) {
|
|
31
|
+
installAlertFlashClock();
|
|
32
|
+
return {
|
|
33
|
+
[ROW_MATCH_ALERT_CLASS]: (params) => matchesRowCriteria(criteria, params.data),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Cell class rules that flag only the `flashField` cell of a matching row, for spreading into
|
|
38
|
+
* `gridOptions.defaultColDef.cellClassRules`.
|
|
39
|
+
*
|
|
40
|
+
* They belong on `defaultColDef` rather than a single column because AG Grid has no grid-level
|
|
41
|
+
* `cellClassRules`, and the rule itself checks which column it is running against.
|
|
42
|
+
*
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
export function createRowMatchCellClassRules(criteria) {
|
|
46
|
+
installAlertFlashClock();
|
|
47
|
+
const { flashField } = criteria;
|
|
48
|
+
return {
|
|
49
|
+
[ROW_MATCH_ALERT_CELL_CLASS]: (params) => !!flashField &&
|
|
50
|
+
params.colDef.field === flashField &&
|
|
51
|
+
matchesRowCriteria(criteria, params.data),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { datasourceEventNames, } from '../grid-pro-genesis-datasource/datasource-events.types';
|
|
2
|
+
import { matchesRowCriteria } from './row-match.types';
|
|
3
|
+
/**
|
|
4
|
+
* Tracks which datasource rows satisfy {@link RowMatchCriteria} as stream updates arrive,
|
|
5
|
+
* and reports whether any currently do.
|
|
6
|
+
*
|
|
7
|
+
* Rows are tracked incrementally from `add` / `update` / `remove` deltas rather than by
|
|
8
|
+
* rescanning the grid, so cost is proportional to the update rather than to the row count.
|
|
9
|
+
*
|
|
10
|
+
* The typical consumer flags a tab while a background grid still holds rows needing
|
|
11
|
+
* attention:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* const tracker = new DatasourceRowMatchTracker(
|
|
15
|
+
* { rowIdField: 'ORDER_ID', stateField: 'ORDER_STATE', stateValue: 'PENDING' },
|
|
16
|
+
* (active) => { tab.alert = active; },
|
|
17
|
+
* );
|
|
18
|
+
* const stop = tracker.observe(datasourceElement);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Frameworks that surface the datasource events as props can feed the tracker directly with
|
|
22
|
+
* {@link DatasourceRowMatchTracker.handleDataChanged} and
|
|
23
|
+
* {@link DatasourceRowMatchTracker.handleDataCleared} instead of calling `observe`.
|
|
24
|
+
*
|
|
25
|
+
* Passing the same criteria to `createRowMatchClassRules` flashes the matching rows themselves, so
|
|
26
|
+
* a tab that says there is work to do leads to a grid that shows which rows it meant.
|
|
27
|
+
*
|
|
28
|
+
* Criteria are fixed for the tracker's life. To watch for something else, build a new tracker and
|
|
29
|
+
* reload the datasource, so rows already delivered are replayed against the new rules.
|
|
30
|
+
*
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
export class DatasourceRowMatchTracker {
|
|
34
|
+
constructor(criteria, onActiveChanged) {
|
|
35
|
+
this.criteria = criteria;
|
|
36
|
+
this.onActiveChanged = onActiveChanged;
|
|
37
|
+
this.matchedRowIds = new Set();
|
|
38
|
+
this.lastReportedActive = false;
|
|
39
|
+
}
|
|
40
|
+
/** True while at least one tracked row matches. */
|
|
41
|
+
get active() {
|
|
42
|
+
return this.matchedRowIds.size > 0;
|
|
43
|
+
}
|
|
44
|
+
/** How many tracked rows currently match. */
|
|
45
|
+
get matchCount() {
|
|
46
|
+
return this.matchedRowIds.size;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Forgets every tracked row. Call when the grid's filter or search criteria change, so a
|
|
50
|
+
* stale alert does not survive into a result set that no longer contains those rows.
|
|
51
|
+
*/
|
|
52
|
+
reset() {
|
|
53
|
+
this.matchedRowIds.clear();
|
|
54
|
+
this.report();
|
|
55
|
+
}
|
|
56
|
+
/** Applies a `datasource-data-changed` detail. */
|
|
57
|
+
handleDataChanged(detail) {
|
|
58
|
+
var _a;
|
|
59
|
+
const { add = [], update = [], remove = [] } = (_a = detail === null || detail === void 0 ? void 0 : detail.changes) !== null && _a !== void 0 ? _a : {};
|
|
60
|
+
for (const row of remove) {
|
|
61
|
+
this.syncRow(row, true);
|
|
62
|
+
}
|
|
63
|
+
for (const row of add) {
|
|
64
|
+
this.syncRow(row, false);
|
|
65
|
+
}
|
|
66
|
+
for (const row of update) {
|
|
67
|
+
this.syncRow(row, false);
|
|
68
|
+
}
|
|
69
|
+
this.report();
|
|
70
|
+
}
|
|
71
|
+
/** Applies a `datasource-data-cleared` event. */
|
|
72
|
+
handleDataCleared() {
|
|
73
|
+
this.reset();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Subscribes to the datasource events on `target`.
|
|
77
|
+
*
|
|
78
|
+
* @returns a function that removes the listeners.
|
|
79
|
+
*/
|
|
80
|
+
observe(target) {
|
|
81
|
+
const onChanged = (event) => {
|
|
82
|
+
this.handleDataChanged(event.detail);
|
|
83
|
+
};
|
|
84
|
+
const onCleared = () => {
|
|
85
|
+
this.handleDataCleared();
|
|
86
|
+
};
|
|
87
|
+
target.addEventListener(datasourceEventNames.dataChanged, onChanged);
|
|
88
|
+
target.addEventListener(datasourceEventNames.dataCleared, onCleared);
|
|
89
|
+
return () => {
|
|
90
|
+
target.removeEventListener(datasourceEventNames.dataChanged, onChanged);
|
|
91
|
+
target.removeEventListener(datasourceEventNames.dataCleared, onCleared);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
syncRow(row, removed) {
|
|
95
|
+
var _a;
|
|
96
|
+
if (!row) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const id = String((_a = row[this.criteria.rowIdField]) !== null && _a !== void 0 ? _a : '');
|
|
100
|
+
if (!id) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (removed || !matchesRowCriteria(this.criteria, row)) {
|
|
104
|
+
this.matchedRowIds.delete(id);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
this.matchedRowIds.add(id);
|
|
108
|
+
}
|
|
109
|
+
report() {
|
|
110
|
+
const active = this.active;
|
|
111
|
+
if (active === this.lastReportedActive) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
this.lastReportedActive = active;
|
|
115
|
+
this.onActiveChanged(active);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
function isEmpty(value) {
|
|
2
|
+
return value == null || value === '';
|
|
3
|
+
}
|
|
4
|
+
function asFiniteNumber(value) {
|
|
5
|
+
if (typeof value === 'number') {
|
|
6
|
+
return Number.isFinite(value) ? value : undefined;
|
|
7
|
+
}
|
|
8
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
9
|
+
const parsed = Number(value);
|
|
10
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
11
|
+
}
|
|
12
|
+
if (typeof value === 'boolean') {
|
|
13
|
+
return value ? 1 : 0;
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Equality that tolerates a string criterion against a number or boolean in the row (or the
|
|
19
|
+
* other way around), so a criteria authored as `'100'` still matches a numeric `100`.
|
|
20
|
+
*/
|
|
21
|
+
function valuesEqual(value, expected) {
|
|
22
|
+
if (value === expected) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
26
|
+
return String(value) === String(expected);
|
|
27
|
+
}
|
|
28
|
+
if (typeof expected === 'number' || typeof expected === 'boolean') {
|
|
29
|
+
return String(value) === String(expected);
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
function fieldMatches(value, expected, op = '==') {
|
|
34
|
+
if (expected === null) {
|
|
35
|
+
if (op === '==') {
|
|
36
|
+
return isEmpty(value);
|
|
37
|
+
}
|
|
38
|
+
if (op === '!=') {
|
|
39
|
+
return !isEmpty(value);
|
|
40
|
+
}
|
|
41
|
+
// Relational ops have no meaning against "empty".
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (op === '==') {
|
|
45
|
+
return valuesEqual(value, expected);
|
|
46
|
+
}
|
|
47
|
+
if (op === '!=') {
|
|
48
|
+
return !valuesEqual(value, expected);
|
|
49
|
+
}
|
|
50
|
+
const left = asFiniteNumber(value);
|
|
51
|
+
const right = asFiniteNumber(expected);
|
|
52
|
+
if (left === undefined || right === undefined) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
switch (op) {
|
|
56
|
+
case '>':
|
|
57
|
+
return left > right;
|
|
58
|
+
case '>=':
|
|
59
|
+
return left >= right;
|
|
60
|
+
case '<':
|
|
61
|
+
return left < right;
|
|
62
|
+
case '<=':
|
|
63
|
+
return left <= right;
|
|
64
|
+
default:
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Resolves either criteria form into a single OR-of-AND-groups representation. */
|
|
69
|
+
function resolveRowMatchGroups(criteria) {
|
|
70
|
+
var _a, _b;
|
|
71
|
+
if (criteria.anyOf !== undefined) {
|
|
72
|
+
return criteria.anyOf;
|
|
73
|
+
}
|
|
74
|
+
if (criteria.stateField === undefined) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
return [
|
|
78
|
+
[
|
|
79
|
+
{
|
|
80
|
+
field: criteria.stateField,
|
|
81
|
+
value: (_a = criteria.stateValue) !== null && _a !== void 0 ? _a : null,
|
|
82
|
+
op: criteria.stateOp,
|
|
83
|
+
},
|
|
84
|
+
...((_b = criteria.and) !== null && _b !== void 0 ? _b : []),
|
|
85
|
+
],
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* True when the row satisfies any resolved AND-group.
|
|
90
|
+
*
|
|
91
|
+
* @public
|
|
92
|
+
*/
|
|
93
|
+
export function matchesRowCriteria(criteria, row) {
|
|
94
|
+
if (!row) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return resolveRowMatchGroups(criteria).some((group) => group.every((condition) => fieldMatches(row[condition.field], condition.value, condition.op)));
|
|
98
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { alertFlashPhaseProperty } from '@genesislcap/foundation-ui';
|
|
1
2
|
import { css } from '@microsoft/fast-element';
|
|
2
3
|
import { errorHandlerDialogStyles } from './datasource/error-handler.dialog';
|
|
4
|
+
import { ROW_MATCH_ALERT_CELL_CLASS, ROW_MATCH_ALERT_CLASS } from './datasource/row-match.classes';
|
|
3
5
|
import { agExternalStockStyles } from './external';
|
|
4
6
|
/**
|
|
5
7
|
* Workaround for an AG Grid (v36, still present in v36.1.0) drag-ghost positioning bug in
|
|
@@ -30,6 +32,51 @@ export const agDragGhostShadowDomStyles = css `
|
|
|
30
32
|
height: 0;
|
|
31
33
|
}
|
|
32
34
|
`;
|
|
35
|
+
/**
|
|
36
|
+
* Flash for rows carrying the classes that `createRowMatchClassRules` and
|
|
37
|
+
* `createRowMatchCellClassRules` apply.
|
|
38
|
+
*
|
|
39
|
+
* These live in the grid's own stylesheet because AG Grid renders inside this shadow root, out of
|
|
40
|
+
* reach of an app's CSS. The phase comes from the document-level clock, which inherits through the
|
|
41
|
+
* shadow boundary, so a flashing row keeps step with the flashing tab that sent you looking for it.
|
|
42
|
+
*
|
|
43
|
+
* Any grid composition that does not include {@link foundationGridProStyles} needs to include this
|
|
44
|
+
* as well, otherwise its rows silently never flash.
|
|
45
|
+
*
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
export const rowMatchAlertStyles = css `
|
|
49
|
+
/* The :host prefix is needed to outweigh the AG theme's own .ag-theme-* .ag-row background. */
|
|
50
|
+
:host .ag-row.${ROW_MATCH_ALERT_CLASS} {
|
|
51
|
+
background-color: color-mix(
|
|
52
|
+
in srgb,
|
|
53
|
+
var(--row-alert-color, var(--error-fill-rest, #c62828))
|
|
54
|
+
calc(var(${alertFlashPhaseProperty}, 0) * 100%),
|
|
55
|
+
transparent
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* AG paints cell backgrounds over the row, so they have to stand aside for a row-wide flash. */
|
|
60
|
+
:host .ag-row.${ROW_MATCH_ALERT_CLASS} .ag-cell:not(.${ROW_MATCH_ALERT_CELL_CLASS}) {
|
|
61
|
+
background-color: transparent !important;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
:host .ag-cell.${ROW_MATCH_ALERT_CELL_CLASS} {
|
|
65
|
+
background-color: color-mix(
|
|
66
|
+
in srgb,
|
|
67
|
+
var(--row-alert-color, var(--error-fill-rest, #c62828))
|
|
68
|
+
calc(var(${alertFlashPhaseProperty}, 0) * 100%),
|
|
69
|
+
transparent
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Reduced motion stops the clock, leaving the phase at 0, so paint a steady alert instead. */
|
|
74
|
+
@media (prefers-reduced-motion: reduce) {
|
|
75
|
+
:host .ag-row.${ROW_MATCH_ALERT_CLASS}, :host .ag-cell.${ROW_MATCH_ALERT_CELL_CLASS} {
|
|
76
|
+
background-color: var(--row-alert-color, var(--error-fill-rest, #c62828));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
33
80
|
/**
|
|
34
81
|
* The Grid Pro styles.
|
|
35
82
|
* @public
|
|
@@ -39,6 +86,7 @@ export const foundationGridProStyles = css `
|
|
|
39
86
|
${agExternalStockStyles}
|
|
40
87
|
${errorHandlerDialogStyles}
|
|
41
88
|
${agDragGhostShadowDomStyles}
|
|
89
|
+
${rowMatchAlertStyles}
|
|
42
90
|
:host {
|
|
43
91
|
--datasource-error-background-color: var(--neutral-layer-4);
|
|
44
92
|
--datasource-error-background-opacity: 0.5;
|
package/dist/esm/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from './grid-pro.types';
|
|
|
12
12
|
export * from './grid-pro';
|
|
13
13
|
export * from './grid-pro-genesis-datasource';
|
|
14
14
|
export * from './multicolumn-dropdown';
|
|
15
|
+
export * from './row-alert';
|
|
15
16
|
export * from './state-persistence';
|
|
16
17
|
export * from './status-bar-components';
|
|
17
18
|
export * from './style';
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { DI } from '@microsoft/fast-foundation';
|
|
2
|
+
import { createRowMatchCellClassRules, createRowMatchClassRules, ROW_MATCH_ALERT_CELL_CLASS, ROW_MATCH_ALERT_CLASS, } from '../datasource/row-match.classes';
|
|
3
|
+
import { StatePersistence } from '../state-persistence/state-persistence';
|
|
4
|
+
import { logger } from '../utils';
|
|
5
|
+
import { defaultRowAlertColorPresets, defaultRowAlertPreferences, defaultRowAlertRatePresets, defaultRowAlertScopePresets, normalizeRowAlertPreferences, } from './row-alert.types';
|
|
6
|
+
/** CSS variable read by the row flash styles. */
|
|
7
|
+
const ROW_ALERT_COLOR_PROPERTY = '--row-alert-color';
|
|
8
|
+
/** CSS variable read by the shared flash clock, and so by every alerting surface on the page. */
|
|
9
|
+
const ALERT_FLASH_DURATION_PROPERTY = '--alert-flash-duration';
|
|
10
|
+
function resolveStatePersistence(options) {
|
|
11
|
+
if (options.statePersistence) {
|
|
12
|
+
return options.statePersistence;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return DI.getOrCreateDOMContainer().get(StatePersistence);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
logger.debug('Row alert preferences will not persist; no StatePersistence available', error);
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* One store per persistence key, so a page whose grids each build their own alert still presents
|
|
24
|
+
* a single preference: recolouring from one grid's menu is reflected in every other's.
|
|
25
|
+
*/
|
|
26
|
+
const storesByKey = new Map();
|
|
27
|
+
function createPreferenceStore(options) {
|
|
28
|
+
const { persistPreferencesKey, target, mirrorColorTo = [] } = options;
|
|
29
|
+
const statePersistence = persistPreferencesKey ? resolveStatePersistence(options) : undefined;
|
|
30
|
+
const subscribers = new Set();
|
|
31
|
+
let preferences = normalizeRowAlertPreferences(Object.assign(Object.assign({}, defaultRowAlertPreferences), options.preferences));
|
|
32
|
+
function applyStyles() {
|
|
33
|
+
const element = target !== null && target !== void 0 ? target : (typeof document === 'undefined' ? undefined : document.documentElement);
|
|
34
|
+
if (!element) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
element.style.setProperty(ROW_ALERT_COLOR_PROPERTY, preferences.color);
|
|
38
|
+
element.style.setProperty(ALERT_FLASH_DURATION_PROPERTY, `${preferences.durationMs}ms`);
|
|
39
|
+
for (const property of mirrorColorTo) {
|
|
40
|
+
element.style.setProperty(property, preferences.color);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function apply(next, persist) {
|
|
44
|
+
preferences = next;
|
|
45
|
+
applyStyles();
|
|
46
|
+
for (const subscriber of subscribers) {
|
|
47
|
+
subscriber(preferences);
|
|
48
|
+
}
|
|
49
|
+
if (persist && persistPreferencesKey && (statePersistence === null || statePersistence === void 0 ? void 0 : statePersistence.saveRowAlertPreferences)) {
|
|
50
|
+
statePersistence
|
|
51
|
+
.saveRowAlertPreferences(persistPreferencesKey, preferences)
|
|
52
|
+
.catch((error) => {
|
|
53
|
+
logger.warn('Could not save row alert preferences', error);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
applyStyles();
|
|
58
|
+
if (persistPreferencesKey && (statePersistence === null || statePersistence === void 0 ? void 0 : statePersistence.getRowAlertPreferences)) {
|
|
59
|
+
statePersistence
|
|
60
|
+
.getRowAlertPreferences(persistPreferencesKey)
|
|
61
|
+
.then((stored) => {
|
|
62
|
+
// Only adopt what was actually stored, so a partial entry keeps the caller's starting
|
|
63
|
+
// preferences for the rest rather than snapping them back to the library defaults.
|
|
64
|
+
apply(normalizeRowAlertPreferences(Object.assign(Object.assign({}, preferences), stored)), false);
|
|
65
|
+
})
|
|
66
|
+
.catch((error) => {
|
|
67
|
+
logger.warn('Could not load row alert preferences', error);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
get preferences() {
|
|
72
|
+
return preferences;
|
|
73
|
+
},
|
|
74
|
+
change(patch) {
|
|
75
|
+
apply(normalizeRowAlertPreferences(Object.assign(Object.assign({}, preferences), patch)), true);
|
|
76
|
+
},
|
|
77
|
+
subscribe(listener) {
|
|
78
|
+
subscribers.add(listener);
|
|
79
|
+
return () => {
|
|
80
|
+
subscribers.delete(listener);
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function resolvePreferenceStore(options) {
|
|
86
|
+
const { persistPreferencesKey } = options;
|
|
87
|
+
if (!persistPreferencesKey) {
|
|
88
|
+
return createPreferenceStore(options);
|
|
89
|
+
}
|
|
90
|
+
let store = storesByKey.get(persistPreferencesKey);
|
|
91
|
+
if (!store) {
|
|
92
|
+
store = createPreferenceStore(options);
|
|
93
|
+
storesByKey.set(persistPreferencesKey, store);
|
|
94
|
+
}
|
|
95
|
+
return store;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Builds the class rules that flash rows matching `criteria`, together with the context menu
|
|
99
|
+
* that lets a user recolor, retime, or narrow that flash to a single cell.
|
|
100
|
+
*
|
|
101
|
+
* The rules are created once and never replaced, because a grid backed by a Genesis datasource
|
|
102
|
+
* keeps the options it was created with — handing it new rules later would change nothing. Each
|
|
103
|
+
* rule instead asks, as it runs, whether its scope is the one currently selected, so switching
|
|
104
|
+
* target only needs the rows redrawn.
|
|
105
|
+
*
|
|
106
|
+
* ```ts
|
|
107
|
+
* const alert = createRowAlert(criteria, { persistPreferencesKey: 'orders.row-alert' });
|
|
108
|
+
* gridOptions = {
|
|
109
|
+
* rowClassRules: alert.rowClassRules,
|
|
110
|
+
* defaultColDef: alert.defaultColDef,
|
|
111
|
+
* getContextMenuItems: alert.getContextMenuItems,
|
|
112
|
+
* };
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* Alerts sharing a `persistPreferencesKey` share their preferences, so a page of grids that each
|
|
116
|
+
* watch for something different still answers to one setting.
|
|
117
|
+
*
|
|
118
|
+
* Pairs with {@link DatasourceRowMatchTracker}: give both the same criteria and a tab can say
|
|
119
|
+
* there is work to do while the grid shows which rows it meant.
|
|
120
|
+
*
|
|
121
|
+
* @public
|
|
122
|
+
*/
|
|
123
|
+
export function createRowAlert(criteria, options = {}) {
|
|
124
|
+
var _a, _b, _c, _d, _e;
|
|
125
|
+
const { colorPresets = defaultRowAlertColorPresets, ratePresets = defaultRowAlertRatePresets, menuLabel = 'Alert flash', } = options;
|
|
126
|
+
const store = resolvePreferenceStore(options);
|
|
127
|
+
const rowMatches = createRowMatchClassRules(criteria)[ROW_MATCH_ALERT_CLASS];
|
|
128
|
+
// Criteria that name no explicit flashField still need one for cell scope to mean anything, and
|
|
129
|
+
// the field the state is read from is the one that made the row interesting.
|
|
130
|
+
const cellMatches = createRowMatchCellClassRules(Object.assign(Object.assign({}, criteria), { flashField: (_b = (_a = criteria.flashField) !== null && _a !== void 0 ? _a : criteria.stateField) !== null && _b !== void 0 ? _b : (_e = (_d = (_c = criteria.anyOf) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.field }))[ROW_MATCH_ALERT_CELL_CLASS];
|
|
131
|
+
function presetItems(presets, selected, change, onChosen) {
|
|
132
|
+
return presets.map((preset) => ({
|
|
133
|
+
name: preset.label,
|
|
134
|
+
checked: preset.value === selected,
|
|
135
|
+
action: () => {
|
|
136
|
+
store.change(change(preset.value));
|
|
137
|
+
onChosen();
|
|
138
|
+
},
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
const rowAlert = {
|
|
142
|
+
rowClassRules: {
|
|
143
|
+
[ROW_MATCH_ALERT_CLASS]: (params) => store.preferences.scope === 'row' && rowMatches(params),
|
|
144
|
+
},
|
|
145
|
+
defaultColDef: {
|
|
146
|
+
cellClassRules: {
|
|
147
|
+
[ROW_MATCH_ALERT_CELL_CLASS]: (params) => store.preferences.scope === 'cell' && cellMatches(params),
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
get preferences() {
|
|
151
|
+
return store.preferences;
|
|
152
|
+
},
|
|
153
|
+
createMenuItem(api) {
|
|
154
|
+
// Color and rate reach the rows through CSS variables, but scope decides which rule
|
|
155
|
+
// applies, and rules are only consulted as a row renders.
|
|
156
|
+
const redrawRows = () => { var _a; return (_a = api === null || api === void 0 ? void 0 : api.redrawRows) === null || _a === void 0 ? void 0 : _a.call(api); };
|
|
157
|
+
return {
|
|
158
|
+
name: menuLabel,
|
|
159
|
+
subMenu: [
|
|
160
|
+
{
|
|
161
|
+
name: 'Flash target',
|
|
162
|
+
subMenu: presetItems(defaultRowAlertScopePresets, store.preferences.scope, (scope) => ({ scope }), redrawRows),
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'Color',
|
|
166
|
+
subMenu: presetItems(colorPresets, store.preferences.color, (color) => ({ color }), () => undefined),
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'Flash rate',
|
|
170
|
+
subMenu: presetItems(ratePresets, store.preferences.durationMs, (durationMs) => ({ durationMs }), () => undefined),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'Reset to defaults',
|
|
174
|
+
action: () => {
|
|
175
|
+
rowAlert.reset();
|
|
176
|
+
redrawRows();
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
getContextMenuItems(params) {
|
|
183
|
+
var _a;
|
|
184
|
+
return [...((_a = params.defaultItems) !== null && _a !== void 0 ? _a : []), 'separator', rowAlert.createMenuItem(params.api)];
|
|
185
|
+
},
|
|
186
|
+
setPreferences(patch) {
|
|
187
|
+
store.change(patch);
|
|
188
|
+
},
|
|
189
|
+
reset() {
|
|
190
|
+
store.change(defaultRowAlertPreferences);
|
|
191
|
+
},
|
|
192
|
+
subscribe(listener) {
|
|
193
|
+
return store.subscribe(listener);
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
return rowAlert;
|
|
197
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colors offered by default. Each resolves a design token so the menu follows the host
|
|
3
|
+
* application's theme, falling back to a fixed color for design systems that lack the token.
|
|
4
|
+
*
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export const defaultRowAlertColorPresets = [
|
|
8
|
+
{ id: 'error', label: 'Error', value: 'var(--error-fill-rest, #c62828)' },
|
|
9
|
+
{ id: 'warning', label: 'Warning', value: 'var(--warning-fill-rest, #ef6c00)' },
|
|
10
|
+
{ id: 'success', label: 'Success', value: 'var(--success-fill-rest, #2e7d32)' },
|
|
11
|
+
{ id: 'accent', label: 'Accent', value: 'var(--accent-fill-rest, #1565c0)' },
|
|
12
|
+
{ id: 'neutral', label: 'Neutral', value: 'var(--neutral-fill-rest, #546e7a)' },
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Flash rates offered by default.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export const defaultRowAlertRatePresets = [
|
|
20
|
+
{ id: 'slow', label: 'Slow', value: 1400 },
|
|
21
|
+
{ id: 'normal', label: 'Normal', value: 1000 },
|
|
22
|
+
{ id: 'fast', label: 'Fast', value: 500 },
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* Flash targets offered by default. Cell scope only has an effect when the criteria name a
|
|
26
|
+
* `flashField`.
|
|
27
|
+
*
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export const defaultRowAlertScopePresets = [
|
|
31
|
+
{ id: 'row', label: 'Whole row', value: 'row' },
|
|
32
|
+
{ id: 'cell', label: 'Alert field cell', value: 'cell' },
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Where preferences start before a user changes anything.
|
|
36
|
+
*
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
export const defaultRowAlertPreferences = {
|
|
40
|
+
color: defaultRowAlertColorPresets[0].value,
|
|
41
|
+
durationMs: defaultRowAlertRatePresets[1].value,
|
|
42
|
+
scope: 'row',
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Discards anything unusable from stored preferences, so a hand-edited or outdated entry falls
|
|
46
|
+
* back to the default rather than leaving alerts invisible.
|
|
47
|
+
*
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
export function normalizeRowAlertPreferences(value) {
|
|
51
|
+
const color = typeof (value === null || value === void 0 ? void 0 : value.color) === 'string' && value.color.trim().length > 0
|
|
52
|
+
? value.color
|
|
53
|
+
: defaultRowAlertPreferences.color;
|
|
54
|
+
const durationMs = typeof (value === null || value === void 0 ? void 0 : value.durationMs) === 'number' &&
|
|
55
|
+
Number.isFinite(value.durationMs) &&
|
|
56
|
+
value.durationMs > 0
|
|
57
|
+
? value.durationMs
|
|
58
|
+
: defaultRowAlertPreferences.durationMs;
|
|
59
|
+
const scope = (value === null || value === void 0 ? void 0 : value.scope) === 'row' || (value === null || value === void 0 ? void 0 : value.scope) === 'cell'
|
|
60
|
+
? value.scope
|
|
61
|
+
: defaultRowAlertPreferences.scope;
|
|
62
|
+
return { color, durationMs, scope };
|
|
63
|
+
}
|
|
@@ -75,6 +75,27 @@ export class KVStorageStatePersistence {
|
|
|
75
75
|
]);
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
|
+
getRowAlertPreferences(persistRowAlertKey) {
|
|
79
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
80
|
+
var _a;
|
|
81
|
+
const kvPreferences = yield this.kvStorage.get(persistRowAlertKey);
|
|
82
|
+
const storedPreferences = kvPreferences ? (_a = kvPreferences.kv) === null || _a === void 0 ? void 0 : _a.value : undefined;
|
|
83
|
+
if (storedPreferences) {
|
|
84
|
+
return JSON.parse(storedPreferences);
|
|
85
|
+
}
|
|
86
|
+
return {};
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
saveRowAlertPreferences(persistRowAlertKey, preferences) {
|
|
90
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
+
this.kvStorage.put([
|
|
92
|
+
{
|
|
93
|
+
key: persistRowAlertKey,
|
|
94
|
+
value: JSON.stringify(preferences),
|
|
95
|
+
},
|
|
96
|
+
]);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
78
99
|
}
|
|
79
100
|
__decorate([
|
|
80
101
|
KVStorage
|
|
@@ -50,6 +50,25 @@ export class LocalStorageStatePersistence {
|
|
|
50
50
|
this.session.setLocalStorageItem('gridOptions', JSON.stringify(existingOptions));
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
|
+
getRowAlertPreferences(persistRowAlertKey) {
|
|
54
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
const localStorageGridOptions = this.session.getLocalStorageItem('gridOptions');
|
|
56
|
+
if (localStorageGridOptions) {
|
|
57
|
+
const allGridOptions = JSON.parse(localStorageGridOptions);
|
|
58
|
+
if (persistRowAlertKey in allGridOptions) {
|
|
59
|
+
return JSON.parse(allGridOptions[persistRowAlertKey]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {};
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
saveRowAlertPreferences(persistRowAlertKey, preferences) {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
const existingOptions = JSON.parse(this.session.getLocalStorageItem('gridOptions') || '{}');
|
|
68
|
+
existingOptions[persistRowAlertKey] = JSON.stringify(preferences);
|
|
69
|
+
this.session.setLocalStorageItem('gridOptions', JSON.stringify(existingOptions));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
53
72
|
}
|
|
54
73
|
__decorate([
|
|
55
74
|
Session
|