aleman 1.2.6 → 1.3.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/ChangeLog +12 -0
- package/menu/addons/click.js +1 -0
- package/menu/addons/context-menu.js +1 -0
- package/menu/addons/down.js +11 -4
- package/menu/addons/enter.js +0 -4
- package/menu/addons/escape.js +1 -0
- package/menu/addons/item-click.js +1 -0
- package/menu/addons/left.js +1 -0
- package/menu/addons/right.js +4 -1
- package/menu/addons/submenu/index.js +1 -2
- package/menu/addons/up.js +22 -3
- package/menu/menu.css +1 -5
- package/menu/rules/index.js +1 -1
- package/menu/rules/select/index.js +64 -12
- package/menu/rules/submenu/fixture/last-selected-fix.js +53 -0
- package/menu/rules/submenu/fixture/last-selected.js +54 -0
- package/menu/rules/submenu/fixture/no-submenu-selected.js +17 -0
- package/menu/rules/submenu/index.js +34 -39
- package/menu/state.js +1 -0
- package/package.json +1 -1
package/ChangeLog
CHANGED
package/menu/addons/click.js
CHANGED
package/menu/addons/down.js
CHANGED
|
@@ -6,6 +6,8 @@ export const keys = [
|
|
|
6
6
|
];
|
|
7
7
|
export const preventDefault = true;
|
|
8
8
|
|
|
9
|
+
export const filter = ({state}) => state.command === 'show';
|
|
10
|
+
|
|
9
11
|
export const listener = ({state, options}) => {
|
|
10
12
|
let {
|
|
11
13
|
index,
|
|
@@ -15,20 +17,25 @@ export const listener = ({state, options}) => {
|
|
|
15
17
|
|
|
16
18
|
const {menu} = options;
|
|
17
19
|
const n = Object.keys(menu).length - 1;
|
|
20
|
+
|
|
21
|
+
if (!insideSubmenu && index < n)
|
|
22
|
+
++index;
|
|
23
|
+
|
|
18
24
|
const submenu = getSubmenu({
|
|
19
|
-
|
|
25
|
+
index,
|
|
20
26
|
options,
|
|
21
27
|
});
|
|
22
|
-
const submenuCount = Object.keys(submenu).length - 1;
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
++index;
|
|
29
|
+
const submenuCount = Object.keys(submenu).length - 1;
|
|
26
30
|
|
|
27
31
|
if (insideSubmenu && submenuIndex < submenuCount)
|
|
28
32
|
++submenuIndex;
|
|
29
33
|
|
|
34
|
+
const showSubmenu = submenuCount > -1;
|
|
35
|
+
|
|
30
36
|
return {
|
|
31
37
|
index,
|
|
32
38
|
submenuIndex,
|
|
39
|
+
showSubmenu,
|
|
33
40
|
};
|
|
34
41
|
};
|
package/menu/addons/enter.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import {run} from './run/index.js';
|
|
2
2
|
|
|
3
|
-
const isObject = (a) => a && typeof a === 'object';
|
|
4
|
-
const {values} = Object;
|
|
5
|
-
|
|
6
3
|
export const keys = ['Enter'];
|
|
7
4
|
|
|
8
5
|
export const filter = ({state}) => state.command === 'show';
|
|
@@ -22,4 +19,3 @@ export const listener = ({options, state}) => {
|
|
|
22
19
|
insideSubmenu: false,
|
|
23
20
|
};
|
|
24
21
|
};
|
|
25
|
-
|
package/menu/addons/escape.js
CHANGED
package/menu/addons/left.js
CHANGED
package/menu/addons/right.js
CHANGED
|
@@ -7,14 +7,17 @@ export const keys = [
|
|
|
7
7
|
export const preventDefault = true;
|
|
8
8
|
|
|
9
9
|
export const filter = ({state, options}) => {
|
|
10
|
+
const {index} = state;
|
|
10
11
|
const submenu = getSubmenu({
|
|
11
|
-
|
|
12
|
+
index,
|
|
12
13
|
options,
|
|
13
14
|
});
|
|
15
|
+
|
|
14
16
|
return Object.keys(submenu).length;
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
export const listener = () => ({
|
|
18
20
|
submenuIndex: 0,
|
|
19
21
|
insideSubmenu: true,
|
|
22
|
+
showSubmenu: true,
|
|
20
23
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const isObject = (a) => a && typeof a === 'object';
|
|
2
2
|
|
|
3
|
-
export function getSubmenu({
|
|
4
|
-
const {index} = state;
|
|
3
|
+
export function getSubmenu({index, options}) {
|
|
5
4
|
const {menu} = options;
|
|
6
5
|
const currentName = Object.keys(menu)[index];
|
|
7
6
|
const submenu = menu[currentName];
|
package/menu/addons/up.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {getSubmenu} from './submenu/index.js';
|
|
2
|
+
|
|
1
3
|
export const keys = [
|
|
2
4
|
'ArrowUp',
|
|
3
5
|
'k',
|
|
@@ -5,20 +7,37 @@ export const keys = [
|
|
|
5
7
|
|
|
6
8
|
export const preventDefault = true;
|
|
7
9
|
|
|
8
|
-
export const listener = ({state}) => {
|
|
10
|
+
export const listener = ({state, options}) => {
|
|
11
|
+
const {menu} = options;
|
|
9
12
|
let {
|
|
10
13
|
index,
|
|
11
14
|
insideSubmenu,
|
|
12
15
|
submenuIndex,
|
|
13
16
|
} = state;
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
const count = Object.keys(menu).length;
|
|
19
|
+
|
|
20
|
+
if (insideSubmenu && submenuIndex > 0)
|
|
16
21
|
--submenuIndex;
|
|
17
|
-
|
|
22
|
+
|
|
23
|
+
if (!insideSubmenu && index > 0)
|
|
18
24
|
--index;
|
|
19
25
|
|
|
26
|
+
if (index === -1)
|
|
27
|
+
index = count - 1;
|
|
28
|
+
|
|
29
|
+
const submenu = getSubmenu({
|
|
30
|
+
index,
|
|
31
|
+
options,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const submenuCount = Object.keys(submenu).length;
|
|
35
|
+
|
|
36
|
+
const showSubmenu = submenuCount > 0;
|
|
37
|
+
|
|
20
38
|
return {
|
|
21
39
|
index,
|
|
22
40
|
submenuIndex,
|
|
41
|
+
showSubmenu,
|
|
23
42
|
};
|
|
24
43
|
};
|
package/menu/menu.css
CHANGED
package/menu/rules/index.js
CHANGED
|
@@ -6,17 +6,29 @@ const {setLiteralValue} = operator;
|
|
|
6
6
|
|
|
7
7
|
export const report = () => `Select item`;
|
|
8
8
|
|
|
9
|
-
export const fix = ({path, prev, next}) => {
|
|
9
|
+
export const fix = ({path, current, prev, next, showSubmenu}) => {
|
|
10
10
|
const {value} = path.node;
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
if (!value.value.includes('menu-item-selected'))
|
|
13
|
+
setLiteralValue(value, `${value.value} menu-item-selected`);
|
|
14
|
+
|
|
15
|
+
addShowSubmenu(current, {
|
|
16
|
+
showSubmenu,
|
|
17
|
+
});
|
|
12
18
|
|
|
13
19
|
unselect(prev);
|
|
14
20
|
unselect(next);
|
|
21
|
+
removeShowSubmenu(next);
|
|
22
|
+
removeShowSubmenu(prev);
|
|
15
23
|
};
|
|
16
24
|
|
|
17
25
|
export const traverse = ({options, push}) => ({
|
|
18
26
|
JSXOpeningElement(path) {
|
|
19
|
-
const {
|
|
27
|
+
const {
|
|
28
|
+
name = 'menu',
|
|
29
|
+
index = 1,
|
|
30
|
+
showSubmenu,
|
|
31
|
+
} = options;
|
|
20
32
|
|
|
21
33
|
if (path.node.name.name !== 'li')
|
|
22
34
|
return;
|
|
@@ -42,19 +54,19 @@ export const traverse = ({options, push}) => ({
|
|
|
42
54
|
return;
|
|
43
55
|
|
|
44
56
|
for (const attr of current.get('openingElement.attributes')) {
|
|
45
|
-
const {name
|
|
57
|
+
const {name} = attr.node;
|
|
46
58
|
|
|
47
59
|
if (name.name !== 'className')
|
|
48
60
|
continue;
|
|
49
61
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
push({
|
|
63
|
+
path: attr,
|
|
64
|
+
current,
|
|
65
|
+
prev,
|
|
66
|
+
next,
|
|
67
|
+
showSubmenu,
|
|
68
|
+
});
|
|
69
|
+
break;
|
|
58
70
|
}
|
|
59
71
|
},
|
|
60
72
|
});
|
|
@@ -73,3 +85,43 @@ function unselect(path) {
|
|
|
73
85
|
setLiteralValue(value, value.value.replace(' menu-item-selected', ''));
|
|
74
86
|
}
|
|
75
87
|
}
|
|
88
|
+
|
|
89
|
+
function addShowSubmenu(path, {showSubmenu}) {
|
|
90
|
+
for (const attr of path.get('openingElement.attributes')) {
|
|
91
|
+
const {name, value} = attr.node;
|
|
92
|
+
|
|
93
|
+
if (name.name !== 'className')
|
|
94
|
+
continue;
|
|
95
|
+
|
|
96
|
+
const currentValue = value.value;
|
|
97
|
+
|
|
98
|
+
if (showSubmenu && !currentValue.includes('menu-submenu-show')) {
|
|
99
|
+
setLiteralValue(value, `${currentValue} menu-submenu-show`);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!showSubmenu && currentValue.includes('menu-submenu-show')) {
|
|
104
|
+
setLiteralValue(value, currentValue.replace('menu-submenu-show', ''));
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function removeShowSubmenu(path) {
|
|
111
|
+
if (!path)
|
|
112
|
+
return;
|
|
113
|
+
|
|
114
|
+
for (const attr of path.get('openingElement.attributes')) {
|
|
115
|
+
const {name, value} = attr.node;
|
|
116
|
+
|
|
117
|
+
if (name.name !== 'className')
|
|
118
|
+
continue;
|
|
119
|
+
|
|
120
|
+
const currentValue = value.value;
|
|
121
|
+
|
|
122
|
+
if (currentValue.includes('menu-submenu-show')) {
|
|
123
|
+
setLiteralValue(value, currentValue.replace(' menu-submenu-show', ''));
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// second selected
|
|
2
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
3
|
+
<li data-menu-path="Upload" data-name="menu-item" className="menu-item icon icon-view">
|
|
4
|
+
<label data-menu-path="Upload">Upload</label>
|
|
5
|
+
</li>
|
|
6
|
+
<li data-menu-path="New" data-name="menu-item" className="menu-item icon icon-edit menu-item-selected">
|
|
7
|
+
<label data-menu-path="New">New</label>
|
|
8
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
9
|
+
<li data-menu-path="New.File" data-name="menu-item" className="menu-item icon icon-view">
|
|
10
|
+
<label data-menu-path="New.File">File</label>
|
|
11
|
+
</li>
|
|
12
|
+
<li data-menu-path="New.Directory" data-name="menu-item" className='menu-item icon icon-edit'>
|
|
13
|
+
<label data-menu-path="New.Directory">Directory</label>
|
|
14
|
+
</li>
|
|
15
|
+
</ul>
|
|
16
|
+
</li>
|
|
17
|
+
</ul>;
|
|
18
|
+
|
|
19
|
+
// first selected
|
|
20
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
21
|
+
<li data-menu-path="Upload" data-name="menu-item" className="menu-item icon icon-view menu-item-selected">
|
|
22
|
+
<label data-menu-path="Upload">Upload</label>
|
|
23
|
+
</li>
|
|
24
|
+
<li data-menu-path="New" data-name="menu-item" className="menu-item icon icon-edit">
|
|
25
|
+
<label data-menu-path="New">New</label>
|
|
26
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
27
|
+
<li data-menu-path="New.File" data-name="menu-item" className="menu-item icon icon-view">
|
|
28
|
+
<label data-menu-path="New.File">File</label>
|
|
29
|
+
</li>
|
|
30
|
+
<li data-menu-path="New.Directory" data-name="menu-item" className="menu-item icon icon-edit">
|
|
31
|
+
<label data-menu-path="New.Directory">Directory</label>
|
|
32
|
+
</li>
|
|
33
|
+
</ul>
|
|
34
|
+
</li>
|
|
35
|
+
</ul>;
|
|
36
|
+
|
|
37
|
+
// second selected, submenu first selected
|
|
38
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
39
|
+
<li data-menu-path="Upload" data-name="menu-item" className="menu-item icon icon-view">
|
|
40
|
+
<label data-menu-path="Upload">Upload</label>
|
|
41
|
+
</li>
|
|
42
|
+
<li data-menu-path="New" data-name="menu-item" className="menu-item icon icon-edit menu-item-selected">
|
|
43
|
+
<label data-menu-path="New">New</label>
|
|
44
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
45
|
+
<li data-menu-path="New.File" data-name="menu-item" className='menu-item icon icon-view'>
|
|
46
|
+
<label data-menu-path="New.File">File</label>
|
|
47
|
+
</li>
|
|
48
|
+
<li data-menu-path="New.Directory" data-name="menu-item" className='menu-item icon icon-edit'>
|
|
49
|
+
<label data-menu-path="New.Directory">Directory</label>
|
|
50
|
+
</li>
|
|
51
|
+
</ul>
|
|
52
|
+
</li>
|
|
53
|
+
</ul>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// second selected
|
|
2
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
3
|
+
<li data-menu-path="Upload" data-name="menu-item" className="menu-item icon icon-view">
|
|
4
|
+
<label data-menu-path="Upload">Upload</label>
|
|
5
|
+
</li>
|
|
6
|
+
<li data-menu-path="New" data-name="menu-item" className="menu-item icon icon-edit menu-item-selected">
|
|
7
|
+
<label data-menu-path="New">New</label>
|
|
8
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
9
|
+
<li data-menu-path="New.File" data-name="menu-item" className="menu-item icon icon-view">
|
|
10
|
+
<label data-menu-path="New.File">File</label>
|
|
11
|
+
</li>
|
|
12
|
+
<li data-menu-path="New.Directory" data-name="menu-item" className="menu-item icon icon-edit menu-item-selected">
|
|
13
|
+
<label data-menu-path="New.Directory">Directory</label>
|
|
14
|
+
</li>
|
|
15
|
+
</ul>
|
|
16
|
+
</li>
|
|
17
|
+
</ul>;
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
// first selected
|
|
21
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
22
|
+
<li data-menu-path="Upload" data-name="menu-item" className="menu-item icon icon-view menu-item-selected">
|
|
23
|
+
<label data-menu-path="Upload">Upload</label>
|
|
24
|
+
</li>
|
|
25
|
+
<li data-menu-path="New" data-name="menu-item" className="menu-item icon icon-edit">
|
|
26
|
+
<label data-menu-path="New">New</label>
|
|
27
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
28
|
+
<li data-menu-path="New.File" data-name="menu-item" className="menu-item icon icon-view">
|
|
29
|
+
<label data-menu-path="New.File">File</label>
|
|
30
|
+
</li>
|
|
31
|
+
<li data-menu-path="New.Directory" data-name="menu-item" className="menu-item icon icon-edit">
|
|
32
|
+
<label data-menu-path="New.Directory">Directory</label>
|
|
33
|
+
</li>
|
|
34
|
+
</ul>
|
|
35
|
+
</li>
|
|
36
|
+
</ul>;
|
|
37
|
+
|
|
38
|
+
// second selected, submenu first selected
|
|
39
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
40
|
+
<li data-menu-path="Upload" data-name="menu-item" className="menu-item icon icon-view">
|
|
41
|
+
<label data-menu-path="Upload">Upload</label>
|
|
42
|
+
</li>
|
|
43
|
+
<li data-menu-path="New" data-name="menu-item" className="menu-item icon icon-edit menu-item-selected">
|
|
44
|
+
<label data-menu-path="New">New</label>
|
|
45
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
46
|
+
<li data-menu-path="New.File" data-name="menu-item" className="menu-item icon icon-view menu-item-selected">
|
|
47
|
+
<label data-menu-path="New.File">File</label>
|
|
48
|
+
</li>
|
|
49
|
+
<li data-menu-path="New.Directory" data-name="menu-item" className="menu-item icon icon-edit">
|
|
50
|
+
<label data-menu-path="New.Directory">Directory</label>
|
|
51
|
+
</li>
|
|
52
|
+
</ul>
|
|
53
|
+
</li>
|
|
54
|
+
</ul>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// first selected
|
|
2
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
3
|
+
<li data-menu-path="Upload" data-name="menu-item" className="menu-item icon icon-view menu-item-selected">
|
|
4
|
+
<label data-menu-path="Upload">Upload</label>
|
|
5
|
+
</li>
|
|
6
|
+
<li data-menu-path="New" data-name="menu-item" className="menu-item icon icon-edit">
|
|
7
|
+
<label data-menu-path="New">New</label>
|
|
8
|
+
<ul data-name="menu" class="menu menu-hidden">
|
|
9
|
+
<li data-menu-path="New.File" data-name="menu-item" className="menu-item icon icon-view">
|
|
10
|
+
<label data-menu-path="New.File">File</label>
|
|
11
|
+
</li>
|
|
12
|
+
<li data-menu-path="New.Directory" data-name="menu-item" className="menu-item icon icon-edit">
|
|
13
|
+
<label data-menu-path="New.Directory">Directory</label>
|
|
14
|
+
</li>
|
|
15
|
+
</ul>
|
|
16
|
+
</li>
|
|
17
|
+
</ul>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {operator, types} from 'putout';
|
|
2
|
+
import {checkDataName} from '../check-data-name.js';
|
|
2
3
|
|
|
3
4
|
const {isJSXElement} = types;
|
|
4
5
|
const {setLiteralValue} = operator;
|
|
5
6
|
|
|
6
|
-
export const report = () =>
|
|
7
|
+
export const report = ({insideSubmenu}) => `${insideSubmenu ? 'Show' : 'Hide'} submenu`;
|
|
7
8
|
|
|
8
9
|
export const fix = ({path, prev, next, insideSubmenu}) => {
|
|
9
10
|
const {value} = path.node;
|
|
@@ -17,8 +18,10 @@ export const fix = ({path, prev, next, insideSubmenu}) => {
|
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
if (!value.value.includes('menu-item-selected')) {
|
|
22
|
+
const newValue = `${value.value} menu-item-selected`;
|
|
23
|
+
setLiteralValue(value, newValue);
|
|
24
|
+
}
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
export const traverse = ({options, push}) => ({
|
|
@@ -38,7 +41,9 @@ export const traverse = ({options, push}) => ({
|
|
|
38
41
|
if (!isJSXElement(path.parentPath.parentPath))
|
|
39
42
|
return;
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
const openingElementPath = path.parentPath.parentPath.get('openingElement');
|
|
45
|
+
|
|
46
|
+
if (!checkDataName(openingElementPath))
|
|
42
47
|
return;
|
|
43
48
|
|
|
44
49
|
const children = path.parentPath
|
|
@@ -53,44 +58,17 @@ export const traverse = ({options, push}) => ({
|
|
|
53
58
|
if (!current)
|
|
54
59
|
return;
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
path: attr,
|
|
65
|
-
insideSubmenu,
|
|
66
|
-
prev,
|
|
67
|
-
next,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
if (!insideSubmenu && value.value.includes('menu-item-selected'))
|
|
71
|
-
push({
|
|
72
|
-
path: attr,
|
|
73
|
-
insideSubmenu,
|
|
74
|
-
prev,
|
|
75
|
-
next,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
61
|
+
const [currentClassPath] = getClassPath(current);
|
|
62
|
+
|
|
63
|
+
push({
|
|
64
|
+
path: currentClassPath,
|
|
65
|
+
insideSubmenu,
|
|
66
|
+
prev,
|
|
67
|
+
next,
|
|
68
|
+
});
|
|
78
69
|
},
|
|
79
70
|
});
|
|
80
71
|
|
|
81
|
-
function checkDataName(path) {
|
|
82
|
-
const attributes = path.get('openingElement.attributes');
|
|
83
|
-
|
|
84
|
-
for (const attr of attributes) {
|
|
85
|
-
const {name, value} = attr.node;
|
|
86
|
-
|
|
87
|
-
if (name.name === 'data-name')
|
|
88
|
-
return value.value === 'menu';
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
72
|
function isParentSelected(path) {
|
|
95
73
|
const attributes = path.get('openingElement.attributes');
|
|
96
74
|
|
|
@@ -118,3 +96,20 @@ function unselect(path) {
|
|
|
118
96
|
setLiteralValue(value, value.value.replace(' menu-item-selected', ''));
|
|
119
97
|
}
|
|
120
98
|
}
|
|
99
|
+
|
|
100
|
+
function getClassPath(path) {
|
|
101
|
+
if (!path)
|
|
102
|
+
return [null, ''];
|
|
103
|
+
|
|
104
|
+
for (const attr of path.get('openingElement.attributes')) {
|
|
105
|
+
const {name, value} = attr.node;
|
|
106
|
+
|
|
107
|
+
if (name.name !== 'className')
|
|
108
|
+
continue;
|
|
109
|
+
|
|
110
|
+
return [
|
|
111
|
+
attr,
|
|
112
|
+
value.value,
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
}
|
package/menu/state.js
CHANGED