@vuu-ui/vuu-table-extras 3.5.16 → 3.5.17
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/package.json +12 -12
- package/src/cell-renderers/background-cell/BackgroundCell.css.js +85 -4
- package/src/cell-renderers/background-cell/BackgroundCell.js +13 -5
- package/src/cell-renderers/background-cell/BackgroundKeyframes.css.js +124 -0
- package/src/cell-renderers/background-cell/useDirection.js +83 -14
- package/src/column-menu/ColumnMenu.js +42 -13
- package/types/cell-renderers/background-cell/useDirection.d.ts +30 -1
- package/types/column-menu/ColumnMenu.d.ts +13 -1
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.5.
|
|
2
|
+
"version": "3.5.17",
|
|
3
3
|
"author": "heswell",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"license": "Apache-2.0",
|
|
@@ -11,19 +11,19 @@
|
|
|
11
11
|
"lezer-generate:column": "lezer-generator --output ./src/column-expression-input/column-language-parser/generated/column-parser.js ./src/column-expression-input/column-language-parser/grammar/column.grammar"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@vuu-ui/vuu-filter-types": "3.5.
|
|
15
|
-
"@vuu-ui/vuu-protocol-types": "3.5.
|
|
14
|
+
"@vuu-ui/vuu-filter-types": "3.5.17",
|
|
15
|
+
"@vuu-ui/vuu-protocol-types": "3.5.17"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@vuu-ui/vuu-codemirror": "3.5.
|
|
19
|
-
"@vuu-ui/vuu-data-editing": "3.5.
|
|
20
|
-
"@vuu-ui/vuu-data-react": "3.5.
|
|
21
|
-
"@vuu-ui/vuu-data-types": "3.5.
|
|
22
|
-
"@vuu-ui/vuu-table-types": "3.5.
|
|
23
|
-
"@vuu-ui/vuu-popups": "3.5.
|
|
24
|
-
"@vuu-ui/vuu-table": "3.5.
|
|
25
|
-
"@vuu-ui/vuu-utils": "3.5.
|
|
26
|
-
"@vuu-ui/vuu-ui-controls": "3.5.
|
|
18
|
+
"@vuu-ui/vuu-codemirror": "3.5.17",
|
|
19
|
+
"@vuu-ui/vuu-data-editing": "3.5.17",
|
|
20
|
+
"@vuu-ui/vuu-data-react": "3.5.17",
|
|
21
|
+
"@vuu-ui/vuu-data-types": "3.5.17",
|
|
22
|
+
"@vuu-ui/vuu-table-types": "3.5.17",
|
|
23
|
+
"@vuu-ui/vuu-popups": "3.5.17",
|
|
24
|
+
"@vuu-ui/vuu-table": "3.5.17",
|
|
25
|
+
"@vuu-ui/vuu-utils": "3.5.17",
|
|
26
|
+
"@vuu-ui/vuu-ui-controls": "3.5.17",
|
|
27
27
|
"@lezer/lr": "1.4.2",
|
|
28
28
|
"@salt-ds/core": "1.54.1",
|
|
29
29
|
"@salt-ds/lab": "1.0.0-alpha.83",
|
|
@@ -38,22 +38,103 @@ const css = `
|
|
|
38
38
|
background-color: #0000;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
.vuuBackgroundCell-backgroundOnly.
|
|
41
|
+
.vuuBackgroundCell-backgroundOnly.vuuBackgroundCell-hotUp, .vuuBackgroundCell-arrowBackground.vuuBackgroundCell-hotUp {
|
|
42
|
+
background-color: var(--vuuBackgroundCell-hotUp-background, color-mix(in srgb, var(--salt-sentiment-positive-foreground-informative) 30%, transparent));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.vuuBackgroundCell-backgroundOnly.vuuBackgroundCell-hotDown, .vuuBackgroundCell-arrowBackground.vuuBackgroundCell-hotDown {
|
|
46
|
+
background-color: var(--vuuBackgroundCell-hotDown-background, color-mix(in srgb, var(--salt-sentiment-negative-foreground-informative) 30%, transparent));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.vuuBackgroundCell-backgroundOnly.vuuBackgroundCell-coolUp > .vuuBackgroundCell-flash {
|
|
50
|
+
background-color: var(--vuuBackgroundCell-hotUp-background, color-mix(in srgb, var(--salt-sentiment-positive-foreground-informative) 30%, transparent));
|
|
51
|
+
animation: vuucoolup var(--vuuBackgroundCell-cool-duration, 2s);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.vuuBackgroundCell-backgroundOnly.vuuBackgroundCell-coolDown > .vuuBackgroundCell-flash {
|
|
55
|
+
background-color: var(--vuuBackgroundCell-hotDown-background, color-mix(in srgb, var(--salt-sentiment-negative-foreground-informative) 30%, transparent));
|
|
56
|
+
animation: vuucooldown var(--vuuBackgroundCell-cool-duration, 2s);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.vuuBackgroundCell-arrowBackground.vuuBackgroundCell-coolUp {
|
|
60
|
+
animation: vuucoolbgup var(--vuuBackgroundCell-cool-duration, 2s);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.vuuBackgroundCell-arrowBackground.vuuBackgroundCell-coolDown {
|
|
64
|
+
animation: vuucoolbgdown var(--vuuBackgroundCell-cool-duration, 2s);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.vuuBackgroundCell-hotUp > .vuuBackgroundCell-arrow {
|
|
68
|
+
color: var(--salt-sentiment-positive-foreground-informative);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.vuuBackgroundCell-hotDown > .vuuBackgroundCell-arrow {
|
|
72
|
+
color: var(--salt-sentiment-negative-foreground-informative);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.vuuBackgroundCell-flash {
|
|
76
|
+
opacity: 0;
|
|
77
|
+
pointer-events: none;
|
|
78
|
+
position: absolute;
|
|
79
|
+
inset: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.vuuBackgroundCell-backgroundOnly.up1 > .vuuBackgroundCell-flash, .vuuBackgroundCell-backgroundOnly.up2 > .vuuBackgroundCell-flash {
|
|
83
|
+
background-color: var(--salt-sentiment-positive-foreground-informative);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.vuuBackgroundCell-backgroundOnly.down1 > .vuuBackgroundCell-flash, .vuuBackgroundCell-backgroundOnly.down2 > .vuuBackgroundCell-flash {
|
|
87
|
+
background-color: var(--salt-sentiment-negative-foreground-informative);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.vuuBackgroundCell-backgroundOnly.up1 > .vuuBackgroundCell-flash {
|
|
91
|
+
animation: vuuflashup1 var(--background-animation-duration);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.vuuBackgroundCell-backgroundOnly.up2 > .vuuBackgroundCell-flash {
|
|
95
|
+
animation: vuuflashup2 var(--background-animation-duration);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.vuuBackgroundCell-backgroundOnly.down1 > .vuuBackgroundCell-flash {
|
|
99
|
+
animation: vuuflashdown1 var(--background-animation-duration);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.vuuBackgroundCell-backgroundOnly.down2 > .vuuBackgroundCell-flash {
|
|
103
|
+
animation: vuuflashdown2 var(--background-animation-duration);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.vuuBackgroundCell-backgroundOnly.up1 > .vuuBackgroundCell-value {
|
|
107
|
+
animation: vuuflashtextup1 var(--background-animation-duration);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.vuuBackgroundCell-backgroundOnly.up2 > .vuuBackgroundCell-value {
|
|
111
|
+
animation: vuuflashtextup2 var(--background-animation-duration);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.vuuBackgroundCell-backgroundOnly.down1 > .vuuBackgroundCell-value {
|
|
115
|
+
animation: vuuflashtextdown1 var(--background-animation-duration);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.vuuBackgroundCell-backgroundOnly.down2 > .vuuBackgroundCell-value {
|
|
119
|
+
animation: vuuflashtextdown2 var(--background-animation-duration);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.vuuBackgroundCell-arrowBackground.up1 {
|
|
42
123
|
animation-duration: var(--background-animation-duration);
|
|
43
124
|
animation-name: vuubgup1;
|
|
44
125
|
}
|
|
45
126
|
|
|
46
|
-
.vuuBackgroundCell-
|
|
127
|
+
.vuuBackgroundCell-arrowBackground.up2 {
|
|
47
128
|
animation-duration: var(--background-animation-duration);
|
|
48
129
|
animation-name: vuubgup2;
|
|
49
130
|
}
|
|
50
131
|
|
|
51
|
-
.vuuBackgroundCell-
|
|
132
|
+
.vuuBackgroundCell-arrowBackground.down1 {
|
|
52
133
|
animation-duration: var(--background-animation-duration);
|
|
53
134
|
animation-name: vuubgdown1;
|
|
54
135
|
}
|
|
55
136
|
|
|
56
|
-
.vuuBackgroundCell-
|
|
137
|
+
.vuuBackgroundCell-arrowBackground.down2 {
|
|
57
138
|
animation-duration: var(--background-animation-duration);
|
|
58
139
|
animation-name: vuubgdown2;
|
|
59
140
|
}
|
|
@@ -36,25 +36,33 @@ const BackgroundCell_BackgroundCell = /*#__PURE__*/ memo(function({ column, data
|
|
|
36
36
|
const { name, type, valueFormatter } = column;
|
|
37
37
|
const value = dataRow[name];
|
|
38
38
|
const flashStyle = getFlashStyle(type);
|
|
39
|
-
const direction = useDirection(dataRow.key, value, column);
|
|
40
|
-
const arrow = flashStyle === FlashStyle.ArrowOnly || flashStyle === FlashStyle.ArrowBackground ? direction === UP1 || direction === UP2 ? CHAR_ARROW_UP : direction === DOWN1 || direction === DOWN2 ? CHAR_ARROW_DOWN : null : null;
|
|
39
|
+
const { direction, hot, cooling } = useDirection(dataRow.key, value, column);
|
|
40
|
+
const arrow = flashStyle === FlashStyle.ArrowOnly || flashStyle === FlashStyle.ArrowBackground ? "up" === hot || direction === UP1 || direction === UP2 ? CHAR_ARROW_UP : "down" === hot || direction === DOWN1 || direction === DOWN2 ? CHAR_ARROW_DOWN : null : null;
|
|
41
41
|
const dirClass = direction ? " " + direction : "";
|
|
42
42
|
const className = clsx(classBase, dirClass, {
|
|
43
43
|
[`${classBase}-backgroundOnly`]: flashStyle === FlashStyle.BackgroundOnly,
|
|
44
44
|
[`${classBase}-arrowOnly`]: flashStyle === FlashStyle.ArrowOnly,
|
|
45
|
-
[`${classBase}-arrowBackground`]: flashStyle === FlashStyle.ArrowBackground
|
|
45
|
+
[`${classBase}-arrowBackground`]: flashStyle === FlashStyle.ArrowBackground,
|
|
46
|
+
[`${classBase}-hotUp`]: "up" === hot,
|
|
47
|
+
[`${classBase}-hotDown`]: "down" === hot,
|
|
48
|
+
[`${classBase}-coolUp`]: "up" === cooling,
|
|
49
|
+
[`${classBase}-coolDown`]: "down" === cooling
|
|
46
50
|
});
|
|
51
|
+
const formattedValue = valueFormatter(value);
|
|
47
52
|
return /*#__PURE__*/ jsxs("div", {
|
|
48
53
|
className: className,
|
|
49
54
|
tabIndex: -1,
|
|
50
55
|
children: [
|
|
51
|
-
/*#__PURE__*/ jsx("
|
|
56
|
+
flashStyle === FlashStyle.BackgroundOnly ? /*#__PURE__*/ jsx("span", {
|
|
57
|
+
"aria-hidden": true,
|
|
58
|
+
className: `${classBase}-flash`
|
|
59
|
+
}) : /*#__PURE__*/ jsx("div", {
|
|
52
60
|
className: `${classBase}-arrow`,
|
|
53
61
|
children: arrow
|
|
54
62
|
}),
|
|
55
63
|
/*#__PURE__*/ jsx("span", {
|
|
56
64
|
className: `${classBase}-value`,
|
|
57
|
-
children:
|
|
65
|
+
children: formattedValue
|
|
58
66
|
})
|
|
59
67
|
]
|
|
60
68
|
});
|
|
@@ -1,4 +1,128 @@
|
|
|
1
1
|
const css = `
|
|
2
|
+
@keyframes vuucoolup {
|
|
3
|
+
from {
|
|
4
|
+
opacity: 1;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
to {
|
|
8
|
+
opacity: 0;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@keyframes vuucooldown {
|
|
13
|
+
from {
|
|
14
|
+
opacity: 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
to {
|
|
18
|
+
opacity: 0;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@keyframes vuucoolbgup {
|
|
23
|
+
from {
|
|
24
|
+
background-color: var(--vuuBackgroundCell-hotUp-background, color-mix(in srgb, var(--salt-sentiment-positive-foreground-informative) 30%, transparent));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
to {
|
|
28
|
+
background-color: #0000;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@keyframes vuucoolbgdown {
|
|
33
|
+
from {
|
|
34
|
+
background-color: var(--vuuBackgroundCell-hotDown-background, color-mix(in srgb, var(--salt-sentiment-negative-foreground-informative) 30%, transparent));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
to {
|
|
38
|
+
background-color: #0000;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@keyframes vuuflashtextup1 {
|
|
43
|
+
0% {
|
|
44
|
+
color: var(--vuuBackgroundCell-flash-color, white);
|
|
45
|
+
animation-timing-function: step-end;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
33% {
|
|
49
|
+
color: var(--vuuBackgroundCell-color, var(--salt-content-primary-foreground));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@keyframes vuuflashtextup2 {
|
|
54
|
+
0% {
|
|
55
|
+
color: var(--vuuBackgroundCell-flash-color, white);
|
|
56
|
+
animation-timing-function: step-end;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
33% {
|
|
60
|
+
color: var(--vuuBackgroundCell-color, var(--salt-content-primary-foreground));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@keyframes vuuflashtextdown1 {
|
|
65
|
+
0% {
|
|
66
|
+
color: var(--vuuBackgroundCell-flash-color, white);
|
|
67
|
+
animation-timing-function: step-end;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
33% {
|
|
71
|
+
color: var(--vuuBackgroundCell-color, var(--salt-content-primary-foreground));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@keyframes vuuflashtextdown2 {
|
|
76
|
+
0% {
|
|
77
|
+
color: var(--vuuBackgroundCell-flash-color, white);
|
|
78
|
+
animation-timing-function: step-end;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
33% {
|
|
82
|
+
color: var(--vuuBackgroundCell-color, var(--salt-content-primary-foreground));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@keyframes vuuflashup1 {
|
|
87
|
+
from {
|
|
88
|
+
opacity: 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
to {
|
|
92
|
+
opacity: 0;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@keyframes vuuflashup2 {
|
|
97
|
+
from {
|
|
98
|
+
opacity: 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
to {
|
|
102
|
+
opacity: 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@keyframes vuuflashdown1 {
|
|
107
|
+
from {
|
|
108
|
+
opacity: 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
to {
|
|
112
|
+
opacity: 0;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@keyframes vuuflashdown2 {
|
|
117
|
+
from {
|
|
118
|
+
opacity: 1;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
to {
|
|
122
|
+
opacity: 0;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
2
126
|
@keyframes vuubgup1 {
|
|
3
127
|
0% {
|
|
4
128
|
background-color: var(--salt-sentiment-positive-foreground-informative);
|
|
@@ -1,25 +1,94 @@
|
|
|
1
1
|
import { getMovingValueDirection, isTypeDescriptor, isValidNumber } from "@vuu-ui/vuu-utils";
|
|
2
|
-
import { useEffect, useRef } from "react";
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
const DEFAULT_MIN_FLASH_INTERVAL = 1000;
|
|
4
|
+
const DEFAULT_HOT_INTERVAL = 500;
|
|
5
|
+
const DEFAULT_HOT_EXIT_DELAY = 1000;
|
|
6
|
+
const isUp = (direction)=>"up1" === direction || "up2" === direction;
|
|
7
|
+
const isDown = (direction)=>"down1" === direction || "down2" === direction;
|
|
8
|
+
const getRendererSetting = ({ type }, name, defaultValue)=>{
|
|
9
|
+
if (isTypeDescriptor(type) && type.renderer) {
|
|
10
|
+
const value = type.renderer[name];
|
|
11
|
+
if ("number" == typeof value) return value;
|
|
12
|
+
}
|
|
13
|
+
return defaultValue;
|
|
14
|
+
};
|
|
15
|
+
const COOL_DOWN_RESOLUTION = 100;
|
|
16
|
+
const coolDowns = new Map();
|
|
17
|
+
let coolDownTimer;
|
|
18
|
+
const runCoolDowns = ()=>{
|
|
19
|
+
const now = performance.now();
|
|
20
|
+
for (const [owner, entry] of coolDowns)if (entry.until <= now) {
|
|
21
|
+
coolDowns.delete(owner);
|
|
22
|
+
entry.coolDown();
|
|
23
|
+
}
|
|
24
|
+
if (0 === coolDowns.size && void 0 !== coolDownTimer) {
|
|
25
|
+
clearInterval(coolDownTimer);
|
|
26
|
+
coolDownTimer = void 0;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const scheduleCoolDown = (owner, until, coolDown)=>{
|
|
30
|
+
const entry = coolDowns.get(owner);
|
|
31
|
+
if (entry) entry.until = until;
|
|
32
|
+
else coolDowns.set(owner, {
|
|
33
|
+
until,
|
|
34
|
+
coolDown
|
|
35
|
+
});
|
|
36
|
+
coolDownTimer ??= setInterval(runCoolDowns, COOL_DOWN_RESOLUTION);
|
|
37
|
+
};
|
|
9
38
|
function useDirection(key, value, column) {
|
|
10
39
|
const ref = useRef(void 0);
|
|
11
|
-
const [
|
|
40
|
+
const [, setCoolDownCount] = useState(0);
|
|
41
|
+
const prev = ref.current;
|
|
42
|
+
const sameCell = prev?.key === key && prev?.column === column;
|
|
12
43
|
const { type: dataType } = column;
|
|
13
44
|
const decimals = isTypeDescriptor(dataType) ? dataType.formatting?.decimals : void 0;
|
|
14
|
-
const
|
|
45
|
+
const move = prev && sameCell && isValidNumber(value) && isValidNumber(prev.value) ? getMovingValueDirection(value, prev.lastMove, prev.value, decimals) : "";
|
|
46
|
+
const changed = "" !== move;
|
|
47
|
+
const now = performance.now();
|
|
48
|
+
const hotInterval = getRendererSetting(column, "hotInterval", DEFAULT_HOT_INTERVAL);
|
|
49
|
+
let hotUntil = prev && sameCell ? prev.hotUntil : 0;
|
|
50
|
+
let hotDirection = prev && sameCell ? prev.hotDirection : void 0;
|
|
51
|
+
if (prev && changed && now - prev.lastChange < hotInterval) {
|
|
52
|
+
hotUntil = now + getRendererSetting(column, "hotExitDelay", DEFAULT_HOT_EXIT_DELAY);
|
|
53
|
+
hotDirection = isUp(move) ? "up" : "down";
|
|
54
|
+
}
|
|
55
|
+
const hot = now < hotUntil ? hotDirection : void 0;
|
|
56
|
+
const wasHot = sameCell && prev?.hot !== void 0;
|
|
57
|
+
let direction = "";
|
|
58
|
+
let flashStart = 0;
|
|
59
|
+
let cooling;
|
|
60
|
+
if (hot) ;
|
|
61
|
+
else if (prev && wasHot && !changed) cooling = prev.hot;
|
|
62
|
+
else if (prev && sameCell) {
|
|
63
|
+
const sameDirection = isUp(move) && isUp(prev.direction) || isDown(move) && isDown(prev.direction);
|
|
64
|
+
const keepCurrentFlash = sameDirection && now - prev.flashStart < getRendererSetting(column, "minFlashInterval", DEFAULT_MIN_FLASH_INTERVAL);
|
|
65
|
+
direction = keepCurrentFlash ? prev.direction : move;
|
|
66
|
+
flashStart = keepCurrentFlash ? prev.flashStart : move ? now : 0;
|
|
67
|
+
}
|
|
68
|
+
const lastMove = changed ? move : prev?.lastMove ?? "";
|
|
69
|
+
const lastChange = changed ? now : prev && sameCell ? prev.lastChange : 0;
|
|
15
70
|
useEffect(()=>{
|
|
16
|
-
ref.current =
|
|
71
|
+
ref.current = {
|
|
17
72
|
key,
|
|
18
73
|
value,
|
|
19
74
|
column,
|
|
20
|
-
|
|
21
|
-
|
|
75
|
+
lastMove,
|
|
76
|
+
direction,
|
|
77
|
+
flashStart,
|
|
78
|
+
lastChange,
|
|
79
|
+
hotUntil,
|
|
80
|
+
hotDirection,
|
|
81
|
+
hot
|
|
82
|
+
};
|
|
83
|
+
if (hot) scheduleCoolDown(ref, hotUntil, ()=>setCoolDownCount((n)=>n + 1));
|
|
22
84
|
});
|
|
23
|
-
|
|
85
|
+
useEffect(()=>()=>{
|
|
86
|
+
coolDowns.delete(ref);
|
|
87
|
+
}, []);
|
|
88
|
+
return {
|
|
89
|
+
direction,
|
|
90
|
+
hot,
|
|
91
|
+
cooling
|
|
92
|
+
};
|
|
24
93
|
}
|
|
25
|
-
export { useDirection };
|
|
94
|
+
export { DEFAULT_HOT_EXIT_DELAY, DEFAULT_HOT_INTERVAL, DEFAULT_MIN_FLASH_INTERVAL, useDirection };
|
|
@@ -4,7 +4,7 @@ import { useComponentCssInjection } from "@salt-ds/styles";
|
|
|
4
4
|
import { useWindow } from "@salt-ds/window";
|
|
5
5
|
import { IconButton } from "@vuu-ui/vuu-ui-controls";
|
|
6
6
|
import clsx from "clsx";
|
|
7
|
-
import { useCallback } from "react";
|
|
7
|
+
import { useCallback, useLayoutEffect, useRef, useState } from "react";
|
|
8
8
|
import { useTableContext } from "../table-provider/TableProvider.js";
|
|
9
9
|
import { buildAggregationMenuItems, buildGroupMenu, buildPinMenuItems, buildSortMenu, buildVisibilityMenuItems, getColumnMenuActionType } from "./column-menu-utils.js";
|
|
10
10
|
import ColumnMenu from "./ColumnMenu.css";
|
|
@@ -17,14 +17,49 @@ const defaultColumnMenuPermissions = {
|
|
|
17
17
|
allowRemove: true,
|
|
18
18
|
allowPin: true
|
|
19
19
|
};
|
|
20
|
-
const
|
|
20
|
+
const stopPropagation = (evt)=>{
|
|
21
|
+
evt.stopPropagation();
|
|
22
|
+
};
|
|
23
|
+
const ColumnMenu_ColumnMenu = (props)=>{
|
|
21
24
|
const targetWindow = useWindow();
|
|
22
25
|
useComponentCssInjection({
|
|
23
26
|
testId: "vuu-column-menu",
|
|
24
27
|
css: ColumnMenu,
|
|
25
28
|
window: targetWindow
|
|
26
29
|
});
|
|
30
|
+
const [mountReason, setMountReason] = useState();
|
|
31
|
+
const mountOnKeyboardFocus = useCallback((evt)=>{
|
|
32
|
+
if (evt.currentTarget.matches(":focus-visible")) setMountReason("focus");
|
|
33
|
+
}, []);
|
|
34
|
+
const mountOnClick = useCallback((evt)=>{
|
|
35
|
+
evt.stopPropagation();
|
|
36
|
+
setMountReason("click");
|
|
37
|
+
}, []);
|
|
38
|
+
return mountReason ? /*#__PURE__*/ jsx(ColumnMenuContent, {
|
|
39
|
+
...props,
|
|
40
|
+
focusTriggerOnMount: "focus" === mountReason,
|
|
41
|
+
openOnMount: "click" === mountReason
|
|
42
|
+
}) : /*#__PURE__*/ jsx(ColumnMenuButton, {
|
|
43
|
+
className: props.className,
|
|
44
|
+
onClick: mountOnClick,
|
|
45
|
+
onFocus: mountOnKeyboardFocus
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
const ColumnMenuButton = ({ className, ...props })=>/*#__PURE__*/ jsx(IconButton, {
|
|
49
|
+
appearance: "transparent",
|
|
50
|
+
"data-embedded": true,
|
|
51
|
+
icon: "more-vert",
|
|
52
|
+
sentiment: "neutral",
|
|
53
|
+
"aria-label": "Open Column Menu",
|
|
54
|
+
...props,
|
|
55
|
+
className: clsx(classBase, className)
|
|
56
|
+
});
|
|
57
|
+
const ColumnMenuContent = ({ className, column, focusTriggerOnMount, openOnMount, menuActionHandler: menuActionHandlerProp, menuPermissions: { allowSort, allowGroup, allowAggregation, allowHide, allowRemove, allowPin } = defaultColumnMenuPermissions })=>{
|
|
27
58
|
const { dataSource, menuActionHandler } = useTableContext(true);
|
|
59
|
+
const triggerRef = useRef(null);
|
|
60
|
+
useLayoutEffect(()=>{
|
|
61
|
+
if (focusTriggerOnMount) triggerRef.current?.focus();
|
|
62
|
+
}, []);
|
|
28
63
|
const menuActionClickHandler = useCallback((evt)=>{
|
|
29
64
|
const columnMenuActionType = getColumnMenuActionType(evt.target);
|
|
30
65
|
if (menuActionHandlerProp?.(columnMenuActionType, column) !== true) return menuActionHandler(columnMenuActionType, column);
|
|
@@ -38,20 +73,14 @@ const ColumnMenu_ColumnMenu = ({ className, column, menuActionHandler: menuActio
|
|
|
38
73
|
const aggregationMenu = buildAggregationMenuItems(column, dataSource, menuActionClickHandler, allowAggregation);
|
|
39
74
|
const visibilityMenuItems = buildVisibilityMenuItems(column, menuActionClickHandler, allowHide, allowRemove);
|
|
40
75
|
const pinMenu = buildPinMenuItems(column, menuActionClickHandler, allowPin);
|
|
41
|
-
const handleClick = useCallback((evt)=>{
|
|
42
|
-
evt.stopPropagation();
|
|
43
|
-
}, []);
|
|
44
76
|
return /*#__PURE__*/ jsxs(Menu, {
|
|
77
|
+
defaultOpen: openOnMount,
|
|
45
78
|
children: [
|
|
46
79
|
/*#__PURE__*/ jsx(MenuTrigger, {
|
|
47
|
-
children: /*#__PURE__*/ jsx(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
icon: "more-vert",
|
|
52
|
-
sentiment: "neutral",
|
|
53
|
-
"aria-label": "Open Column Menu",
|
|
54
|
-
onClick: handleClick
|
|
80
|
+
children: /*#__PURE__*/ jsx(ColumnMenuButton, {
|
|
81
|
+
className: className,
|
|
82
|
+
onClick: stopPropagation,
|
|
83
|
+
ref: triggerRef
|
|
55
84
|
})
|
|
56
85
|
}),
|
|
57
86
|
/*#__PURE__*/ jsxs(MenuPanel, {
|
|
@@ -1,3 +1,32 @@
|
|
|
1
1
|
import { RuntimeColumnDescriptor } from "@vuu-ui/vuu-table-types";
|
|
2
2
|
import { valueChangeDirection } from "@vuu-ui/vuu-utils";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* A flash is not restarted if the cell flashed in the same direction more
|
|
5
|
+
* recently than this. While a cell keeps changing, its current flash keeps
|
|
6
|
+
* fading rather than restarting on every change. Restarting animations is
|
|
7
|
+
* the main cost of flashing cells at high update rates and adds little
|
|
8
|
+
* information when a value changes several times a second. A change of
|
|
9
|
+
* direction always flashes immediately. Configurable per column with
|
|
10
|
+
* renderer.minFlashInterval (ms), 0 restarts on every change.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEFAULT_MIN_FLASH_INTERVAL = 1000;
|
|
13
|
+
/**
|
|
14
|
+
* A cell whose value changes again within this many ms of its previous
|
|
15
|
+
* change is 'hot': instead of flashing it shows a steady tint in the
|
|
16
|
+
* direction of its latest move, with no animation. Configurable per column
|
|
17
|
+
* with renderer.hotInterval (ms), 0 disables the hot state.
|
|
18
|
+
*/
|
|
19
|
+
export declare const DEFAULT_HOT_INTERVAL = 500;
|
|
20
|
+
/**
|
|
21
|
+
* A hot cell cools down after this many ms without a change: its pale tint
|
|
22
|
+
* fades out. It does not flash, a bold flash always means a move after a
|
|
23
|
+
* quiet spell. Configurable with renderer.hotExitDelay (ms).
|
|
24
|
+
*/
|
|
25
|
+
export declare const DEFAULT_HOT_EXIT_DELAY = 1000;
|
|
26
|
+
export type HotDirection = "up" | "down";
|
|
27
|
+
export declare function useDirection(key: string, value: unknown, column: RuntimeColumnDescriptor): {
|
|
28
|
+
direction: valueChangeDirection;
|
|
29
|
+
hot?: HotDirection;
|
|
30
|
+
/** a hot cell that has just gone quiet, its tint fades out */
|
|
31
|
+
cooling?: HotDirection;
|
|
32
|
+
};
|
|
@@ -7,4 +7,16 @@ export interface ColumnMenuProps extends HTMLAttributes<HTMLSpanElement> {
|
|
|
7
7
|
menuActionHandler?: MenuActionHandler<ColumnMenuActionType, ColumnDescriptor>;
|
|
8
8
|
menuPermissions?: ColumnMenuPermissions;
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Renders only the trigger button until a user shows interest in the menu.
|
|
12
|
+
* A table has a column menu in every header cell, and the full menu (popup,
|
|
13
|
+
* menu items, table context) is only needed for the one a user opens.
|
|
14
|
+
*
|
|
15
|
+
* A click mounts the full menu, already open. Keyboard focus mounts it
|
|
16
|
+
* without opening it, and passes focus on to the new trigger (mounting
|
|
17
|
+
* replaces the button), so the key press that follows reaches the real menu
|
|
18
|
+
* trigger and opens the menu with focus inside it. Focus from a mouse click
|
|
19
|
+
* is not :focus-visible and is left to the click, so the button is not
|
|
20
|
+
* replaced between mouse down and mouse up.
|
|
21
|
+
*/
|
|
22
|
+
export declare const ColumnMenu: (props: ColumnMenuProps) => import("react/jsx-runtime").JSX.Element;
|