aleman 2.0.2 → 2.0.4
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 +27 -0
- package/example/lunes/index.html +3 -0
- package/example/lunes/index.js +22 -0
- package/example/menu/index.html +1 -0
- package/example/menu/index.js +13 -2
- package/lunes/actions/index.js +7 -0
- package/lunes/actions/up.json +5 -0
- package/lunes/compiler/compiler.js +5 -0
- package/lunes/engine/engine.js +16 -0
- package/lunes/engine/listen.js +61 -0
- package/lunes/engine/position/get-position.js +35 -0
- package/lunes/hydrate-menu.js +34 -0
- package/lunes/importmap.js +9 -0
- package/lunes/lunes.js +102 -0
- package/lunes/menu.css +65 -0
- package/lunes/render/render.js +16 -0
- package/lunes/render/rules/README.md +3 -0
- package/lunes/render/rules/build-menu/fixture/build-menu-fix.js +22 -0
- package/lunes/render/rules/build-menu/fixture/build-menu.js +9 -0
- package/lunes/render/rules/build-menu/fixture/no-cursor-fix.js +9 -0
- package/lunes/render/rules/build-menu/fixture/no-cursor.js +9 -0
- package/lunes/render/rules/build-menu/index.js +132 -0
- package/lunes/render/rules/index.js +5 -0
- package/lunes/state/convert-menu-to-state/convert-menu-to-state.js +21 -0
- package/lunes/state/rules/README.md +5 -0
- package/lunes/state/rules/add-cursor/fixture/add-cursor.js +9 -0
- package/lunes/state/rules/add-cursor/fixture/down-fix.js +9 -0
- package/lunes/state/rules/add-cursor/fixture/down.js +9 -0
- package/lunes/state/rules/add-cursor/fixture/up-fix.js +9 -0
- package/lunes/state/rules/add-cursor/fixture/up.js +9 -0
- package/lunes/state/rules/add-cursor/index.js +34 -0
- package/lunes/state/rules/apply-visibility/fixture/apply-visibility-fix.js +9 -0
- package/lunes/state/rules/apply-visibility/fixture/apply-visibility.js +9 -0
- package/lunes/state/rules/apply-visibility/index.js +29 -0
- package/lunes/state/rules/cursor.js +77 -0
- package/lunes/state/rules/move-cursor/fixture/move-cursor-fix.js +9 -0
- package/lunes/state/rules/move-cursor/fixture/move-cursor.js +9 -0
- package/lunes/state/rules/move-cursor/index.js +49 -0
- package/lunes/state/state.js +34 -0
- package/menu/addons/set-position/set-position.js +1 -1
- package/menu/hydrate-menu.js +0 -2
- package/menu/importmap.js +1 -1
- package/nemo/addons/escape/escape.js +3 -9
- package/nemo/hydrate-menu.js +0 -2
- package/nemo/importmap.js +1 -1
- package/nemo/rules/index.js +0 -16
- package/package.json +3 -2
- package/nemo/addons/up.json +0 -5
- package/nemo/rules/hide-submenu/fixture/hide-submenu-fix.js +0 -17
- package/nemo/rules/hide-submenu/fixture/hide-submenu.js +0 -18
- package/nemo/rules/hide-submenu/fixture/no-menu.js +0 -16
- package/nemo/rules/hide-submenu/fixture/show-submenu.js +0 -18
- package/nemo/rules/hide-submenu/index.js +0 -35
- package/nemo/rules/menu/fixture/hide-menu-fix.js +0 -3
- package/nemo/rules/menu/fixture/hide-menu.js +0 -3
- package/nemo/rules/menu/fixture/no-menu.js +0 -3
- package/nemo/rules/menu/fixture/show-menu-fix.js +0 -1
- package/nemo/rules/menu/fixture/show-menu.js +0 -1
- package/nemo/rules/menu/index.js +0 -40
- package/nemo/rules/select/fixture/no-current.js +0 -8
- package/nemo/rules/select/fixture/no-data-name.js +0 -4
- package/nemo/rules/select/fixture/no-index.js +0 -8
- package/nemo/rules/select/fixture/no-next.js +0 -5
- package/nemo/rules/select/fixture/no-parent.js +0 -2
- package/nemo/rules/select/fixture/select-fix.js +0 -8
- package/nemo/rules/select/fixture/select.js +0 -8
- package/nemo/rules/select/fixture/submenu-fix.js +0 -17
- package/nemo/rules/select/fixture/submenu.js +0 -18
- package/nemo/rules/select/fixture/wrong-data-name.js +0 -4
- package/nemo/rules/select/index.js +0 -94
- package/nemo/rules/set-position/fixture/no-style-fix.js +0 -2
- package/nemo/rules/set-position/fixture/no-style.js +0 -2
- package/nemo/rules/set-position/fixture/not-menu.js +0 -2
- package/nemo/rules/set-position/fixture/same-position.js +0 -2
- package/nemo/rules/set-position/fixture/set-position-fix.js +0 -2
- package/nemo/rules/set-position/fixture/set-position.js +0 -2
- package/nemo/rules/set-position/index.js +0 -83
- package/nemo/rules/submenu/fixture/last-selected-fix.js +0 -53
- package/nemo/rules/submenu/fixture/last-selected.js +0 -54
- package/nemo/rules/submenu/fixture/li.js +0 -3
- package/nemo/rules/submenu/fixture/no-submenu-selected.js +0 -17
- package/nemo/rules/submenu/fixture/no-submenu.js +0 -54
- package/nemo/rules/submenu/fixture/not-menu.js +0 -17
- package/nemo/rules/submenu/fixture/submenu-fix.js +0 -53
- package/nemo/rules/submenu/fixture/submenu.js +0 -54
- package/nemo/rules/submenu/index.js +0 -80
- package/nemo/rules/unselect-all/fixture/hide.js +0 -8
- package/nemo/rules/unselect-all/fixture/nested-fix.js +0 -17
- package/nemo/rules/unselect-all/fixture/nested.js +0 -18
- package/nemo/rules/unselect-all/fixture/no-data-name.js +0 -4
- package/nemo/rules/unselect-all/fixture/no-parent.js +0 -2
- package/nemo/rules/unselect-all/fixture/unselect-all-fix.js +0 -8
- package/nemo/rules/unselect-all/fixture/unselect-all.js +0 -8
- package/nemo/rules/unselect-all/index.js +0 -51
- package/nemo/rules/unselect-all-submenu-items/fixture/show-submenu-fix.js +0 -33
- package/nemo/rules/unselect-all-submenu-items/fixture/show-submenu.js +0 -34
- package/nemo/rules/unselect-all-submenu-items/fixture/unselect-all-submenu-items-fix.js +0 -33
- package/nemo/rules/unselect-all-submenu-items/fixture/unselect-all-submenu-items.js +0 -33
- package/nemo/rules/unselect-all-submenu-items/index.js +0 -30
- package/nemo/rules/unselect-wrongly-selected/fixture/hide.js +0 -17
- package/nemo/rules/unselect-wrongly-selected/fixture/unselect-wrongly-selected-fix.js +0 -17
- package/nemo/rules/unselect-wrongly-selected/fixture/unselect-wrongly-selected.js +0 -17
- package/nemo/rules/unselect-wrongly-selected/index.js +0 -39
- /package/{menu/addons/set-position → lunes/engine/position}/calculate.js +0 -0
package/ChangeLog
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
2026.05.27, v2.0.4
|
|
2
|
+
|
|
3
|
+
fix:
|
|
4
|
+
- 79ad4aa aleman: menu: rm afterHydrate
|
|
5
|
+
- 98c1d80 example: menu
|
|
6
|
+
|
|
7
|
+
feature:
|
|
8
|
+
- fe3cf8c lunes: Escape
|
|
9
|
+
- b592739 lunes: apply-visibility: add
|
|
10
|
+
- f2b6589 lunes: render
|
|
11
|
+
- 17ac3a2 lunes: event loop
|
|
12
|
+
- af911e2 lunes: hydrate
|
|
13
|
+
- ed3bf43 lunes: cursor
|
|
14
|
+
- 6408584 lunes: build-menu: open/close -> on/off
|
|
15
|
+
|
|
16
|
+
2026.05.24, v2.0.3
|
|
17
|
+
|
|
18
|
+
feature:
|
|
19
|
+
- 5afafd1 importmap: @putout/bundle@5.5.0
|
|
20
|
+
- 1c86e51 lunes: move-cursor: options: cursor
|
|
21
|
+
- d862a0e lunes: build-menu: position
|
|
22
|
+
- 54353ab lunes: build-menu: add
|
|
23
|
+
- f2f2e3a aleman: lunes: put cursor to AST
|
|
24
|
+
- 0274408 lunes: rules -> state
|
|
25
|
+
- d2a55a8 aleman: lunes: add
|
|
26
|
+
- ac3c8a1 nemo: rules: simplify
|
|
27
|
+
|
|
1
28
|
2026.05.20, v2.0.2
|
|
2
29
|
|
|
3
30
|
feature:
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {createMenu} from '../../lunes/lunes.js';
|
|
2
|
+
|
|
3
|
+
const menuData = {
|
|
4
|
+
hello: () => alert('x'),
|
|
5
|
+
world: () => alert('y'),
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const options = {
|
|
9
|
+
name: 'menu',
|
|
10
|
+
infiniteScroll: true,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const {name} = document.body.dataset;
|
|
14
|
+
const menu = await createMenu(name, options, menuData);
|
|
15
|
+
|
|
16
|
+
globalThis.addEventListener('keydown', (event) => {
|
|
17
|
+
if (event.key === 'F9')
|
|
18
|
+
menu.show();
|
|
19
|
+
|
|
20
|
+
if (event.key === 'Escape')
|
|
21
|
+
menu.hide();
|
|
22
|
+
});
|
package/example/menu/index.html
CHANGED
package/example/menu/index.js
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import {createMenu} from '
|
|
1
|
+
import {createMenu} from '../../menu/menu.js';
|
|
2
2
|
|
|
3
3
|
const menuData = {
|
|
4
4
|
hello: () => alert('x'),
|
|
5
5
|
world: () => alert('y'),
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const options = {
|
|
9
|
+
name: 'menu',
|
|
10
|
+
infiniteScroll: true,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const {name} = document.body.dataset;
|
|
14
|
+
const menu = await createMenu(name, options, menuData);
|
|
15
|
+
|
|
16
|
+
globalThis.addEventListener('keydown', (event) => {
|
|
17
|
+
if (event.key === 'F9')
|
|
18
|
+
menu.show();
|
|
19
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {listen} from './listen.js';
|
|
2
|
+
|
|
3
|
+
export const createLoop = (element, {actions, compile}) => {
|
|
4
|
+
setTimeout(async () => {
|
|
5
|
+
await loop({
|
|
6
|
+
actions,
|
|
7
|
+
compile,
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const loop = async ({actions, compile}) => {
|
|
13
|
+
for await (const [operation, cursor] of listen(actions)) {
|
|
14
|
+
compile(operation, cursor);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export const listen = (events = ['click'], overrides = {}) => ({
|
|
2
|
+
[Symbol.asyncIterator]() {
|
|
3
|
+
const {
|
|
4
|
+
addEventListener = globalThis.addEventListener,
|
|
5
|
+
removeEventListener = globalThis.removeEventListener,
|
|
6
|
+
} = overrides;
|
|
7
|
+
|
|
8
|
+
const queue = [];
|
|
9
|
+
let resolveNext;
|
|
10
|
+
|
|
11
|
+
const handlers = events.map((event) => {
|
|
12
|
+
const fn = () => {
|
|
13
|
+
if (resolveNext) {
|
|
14
|
+
resolveNext({
|
|
15
|
+
value: event,
|
|
16
|
+
done: false,
|
|
17
|
+
});
|
|
18
|
+
resolveNext = null;
|
|
19
|
+
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
queue.push(event);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
addEventListener(event, fn);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
event,
|
|
30
|
+
fn,
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const cleanup = () => {
|
|
35
|
+
for (const h of handlers) {
|
|
36
|
+
removeEventListener(h.event, h.fn);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
next() {
|
|
42
|
+
if (queue.length)
|
|
43
|
+
return Promise.resolve({
|
|
44
|
+
value: queue.shift(),
|
|
45
|
+
done: false,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
resolveNext = resolve;
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
return() {
|
|
54
|
+
cleanup();
|
|
55
|
+
return Promise.resolve({
|
|
56
|
+
done: true,
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {calculate} from './calculate.js';
|
|
2
|
+
|
|
3
|
+
export const getPosition = (name, event) => {
|
|
4
|
+
const element = document.querySelector(`[data-name="${name}"]`);
|
|
5
|
+
const heightMenu = getMenuHeight(element);
|
|
6
|
+
const widthMenu = getMenuWidth(element);
|
|
7
|
+
const {innerHeight, innerWidth} = globalThis;
|
|
8
|
+
|
|
9
|
+
const {x, y} = calculate(event, {
|
|
10
|
+
heightMenu,
|
|
11
|
+
widthMenu,
|
|
12
|
+
innerWidth,
|
|
13
|
+
innerHeight,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const left = x;
|
|
17
|
+
const top = y;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
position: {
|
|
21
|
+
left,
|
|
22
|
+
top,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function getMenuHeight(element) {
|
|
28
|
+
const {height} = getComputedStyle(element);
|
|
29
|
+
return parseInt(height, 10);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getMenuWidth(element) {
|
|
33
|
+
const {width} = getComputedStyle(element);
|
|
34
|
+
return parseInt(width, 10);
|
|
35
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {createState} from './state/state.js';
|
|
2
|
+
import {render} from './render/render.js';
|
|
3
|
+
import {actions} from './actions/index.js';
|
|
4
|
+
import {createLoop} from './engine/engine.js';
|
|
5
|
+
import {createCompiler} from './compiler/compiler.js';
|
|
6
|
+
|
|
7
|
+
export const hydrateMenu = (elementName, {hydrateElement, menu}) => {
|
|
8
|
+
const {commit} = createState(menu);
|
|
9
|
+
const compile = createCompiler(hydrateElement, {
|
|
10
|
+
commit,
|
|
11
|
+
render,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
createLoop(hydrateElement, {
|
|
15
|
+
compile,
|
|
16
|
+
actions,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
show: (top = 0, left = 0) => {
|
|
21
|
+
compile('show', {
|
|
22
|
+
position: {
|
|
23
|
+
top,
|
|
24
|
+
left,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
hide: () => {
|
|
29
|
+
compile('hide', {
|
|
30
|
+
position: {},
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const importmap = {
|
|
2
|
+
imports: {
|
|
3
|
+
'putout': 'https://esm.sh/@putout/bundle@5.5.0',
|
|
4
|
+
'@putout/processor-html': 'https://esm.sh/@putout/processor-html',
|
|
5
|
+
'fullstore': 'https://esm.sh/fullstore',
|
|
6
|
+
'jessy': 'https://esm.sh/jessy',
|
|
7
|
+
'#aleman': '../aleman/aleman.js',
|
|
8
|
+
},
|
|
9
|
+
};
|
package/lunes/lunes.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const {stringify} = JSON;
|
|
2
|
+
|
|
3
|
+
export const createMenu = async (elementName, options, menu) => {
|
|
4
|
+
options.name = options.name || 'menu';
|
|
5
|
+
const {name} = options;
|
|
6
|
+
|
|
7
|
+
const hydrateElement = createHydrate(name);
|
|
8
|
+
|
|
9
|
+
await createMap();
|
|
10
|
+
await loadStyle();
|
|
11
|
+
|
|
12
|
+
const {hydrateMenu} = await import('./hydrate-menu.js');
|
|
13
|
+
|
|
14
|
+
return hydrateMenu(elementName, {
|
|
15
|
+
hydrateElement,
|
|
16
|
+
options,
|
|
17
|
+
menu,
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
async function loadStyle() {
|
|
22
|
+
const name = 'aleman-menu-style';
|
|
23
|
+
|
|
24
|
+
if (findByName(name))
|
|
25
|
+
return;
|
|
26
|
+
|
|
27
|
+
const [error] = await tryToCatch(importCSS);
|
|
28
|
+
|
|
29
|
+
if (!error)
|
|
30
|
+
return;
|
|
31
|
+
|
|
32
|
+
createLink();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function createLink() {
|
|
36
|
+
const link = document.createElement('link');
|
|
37
|
+
|
|
38
|
+
link.rel = 'stylesheet';
|
|
39
|
+
link.href = new URL('menu.css', import.meta.url).pathname;
|
|
40
|
+
document.head.appendChild(link);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function importCSS() {
|
|
44
|
+
const content = await import('./menu.css', {
|
|
45
|
+
with: {
|
|
46
|
+
type: 'css',
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const style = document.createElement('style');
|
|
51
|
+
|
|
52
|
+
style.dataset.name = name;
|
|
53
|
+
|
|
54
|
+
for (const rule of content.default.cssRules) {
|
|
55
|
+
style.innerHTML += rule.cssText;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
document.head.appendChild(style);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createHydrate(name) {
|
|
62
|
+
const hydrateElement = findByName(name);
|
|
63
|
+
|
|
64
|
+
if (hydrateElement)
|
|
65
|
+
return hydrateElement;
|
|
66
|
+
|
|
67
|
+
const section = document.createElement('section');
|
|
68
|
+
|
|
69
|
+
section.dataset.name = `aleman-hydrate-${name}`;
|
|
70
|
+
section.innerHTML = `<ul data-name="${name}" class="menu menu-hidden"></ul>`;
|
|
71
|
+
document.body.append(section);
|
|
72
|
+
|
|
73
|
+
return section;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function createMap() {
|
|
77
|
+
const name = 'aleman-importmap';
|
|
78
|
+
|
|
79
|
+
if (findByName(name))
|
|
80
|
+
return;
|
|
81
|
+
|
|
82
|
+
const script = document.createElement('script');
|
|
83
|
+
|
|
84
|
+
const {importmap} = await import('./importmap.js');
|
|
85
|
+
|
|
86
|
+
script.dataset.name = name;
|
|
87
|
+
script.type = 'importmap';
|
|
88
|
+
script.innerHTML = stringify(importmap, null, 4);
|
|
89
|
+
document.body.append(script);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function findByName(name) {
|
|
93
|
+
return document.querySelector(`[data-name=${name}]`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function tryToCatch(fn, args) {
|
|
97
|
+
try {
|
|
98
|
+
return [null, await fn(...args)];
|
|
99
|
+
} catch(error) {
|
|
100
|
+
return [error];
|
|
101
|
+
}
|
|
102
|
+
}
|
package/lunes/menu.css
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
.menu-hidden {
|
|
2
|
+
display: none;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.menu {
|
|
6
|
+
position : absolute;
|
|
7
|
+
z-index : 2;
|
|
8
|
+
padding : 0;
|
|
9
|
+
font-size : 14px;
|
|
10
|
+
list-style : none;
|
|
11
|
+
color : #222;
|
|
12
|
+
background : rgb(255 255 255 / 90%);
|
|
13
|
+
border-color : rgb(49 123 249 / 40%);
|
|
14
|
+
border-style : solid;
|
|
15
|
+
border-width : 1px;
|
|
16
|
+
border-radius : 5px;
|
|
17
|
+
-webkit-user-select : none;
|
|
18
|
+
-moz-user-select : none;
|
|
19
|
+
-ms-user-select : none;
|
|
20
|
+
user-select : none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.menu-button {
|
|
24
|
+
background: white;
|
|
25
|
+
border: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.menu-item {
|
|
29
|
+
position: relative;
|
|
30
|
+
padding: 3px 20px;
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.menu-item::after {
|
|
35
|
+
content: attr(data-key);
|
|
36
|
+
float: right;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.menu-item:hover, .menu-item-selected {
|
|
40
|
+
background-color: #e3f2ff;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.menu-item > .menu {
|
|
44
|
+
top: 0;
|
|
45
|
+
left: 100%;
|
|
46
|
+
display: none;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.menu-submenu-show > .menu, .menu-item:hover > .menu {
|
|
50
|
+
display: block;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.menu-submenu > label::after {
|
|
54
|
+
display: block;
|
|
55
|
+
float: right;
|
|
56
|
+
width: 0;
|
|
57
|
+
height: 0;
|
|
58
|
+
margin-top: 3px;
|
|
59
|
+
margin-right: -10px;
|
|
60
|
+
border-color: transparent;
|
|
61
|
+
border-left-color: rgb(49 123 249);
|
|
62
|
+
border-style: solid;
|
|
63
|
+
border-width: 5px 0 5px 5px;
|
|
64
|
+
content: ' ';
|
|
65
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {putout} from 'putout';
|
|
2
|
+
import {merge} from '@putout/processor-html';
|
|
3
|
+
import * as buildMenu from './rules/build-menu/index.js';
|
|
4
|
+
|
|
5
|
+
export const render = (state, options) => {
|
|
6
|
+
const {code} = putout(state, {
|
|
7
|
+
rules: {
|
|
8
|
+
'lunes/build-menu': ['on', options],
|
|
9
|
+
},
|
|
10
|
+
plugins: [
|
|
11
|
+
['lunes/build-menu', buildMenu],
|
|
12
|
+
],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return merge('', [code]);
|
|
16
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<ul className="menu" style="left: 100px; top: 20px">
|
|
2
|
+
<li data-name="menu-item" className="menu-item">
|
|
3
|
+
<label>view</label>
|
|
4
|
+
</li>
|
|
5
|
+
<li data-name="menu-item" className="menu-item">
|
|
6
|
+
<label>edit</label>
|
|
7
|
+
</li>
|
|
8
|
+
<li data-name="menu-item" className="menu-item">
|
|
9
|
+
<label>rename</label>
|
|
10
|
+
</li>
|
|
11
|
+
<li data-name="menu-item" className="menu-item menu-item-selected">
|
|
12
|
+
<label>new</label>
|
|
13
|
+
<ul className="menu">
|
|
14
|
+
<li data-name="menu-item" className="menu-item">
|
|
15
|
+
<label>file</label>
|
|
16
|
+
</li>
|
|
17
|
+
<li data-name="menu-item" className="menu-item">
|
|
18
|
+
<label>directory</label>
|
|
19
|
+
</li>
|
|
20
|
+
</ul>
|
|
21
|
+
</li>
|
|
22
|
+
</ul>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
template,
|
|
3
|
+
types,
|
|
4
|
+
operator,
|
|
5
|
+
} from 'putout';
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
isExpressionStatement,
|
|
9
|
+
isObjectProperty,
|
|
10
|
+
jsxText,
|
|
11
|
+
isArrayExpression,
|
|
12
|
+
} = types;
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
replaceWith,
|
|
16
|
+
removeClassName,
|
|
17
|
+
setAttributeValue,
|
|
18
|
+
} = operator;
|
|
19
|
+
|
|
20
|
+
const isOn = ({node}) => {
|
|
21
|
+
if (isObjectProperty(node))
|
|
22
|
+
return node.value.value;
|
|
23
|
+
|
|
24
|
+
return node.elements[0].value === 'on';
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const isSelected = ({node}) => {
|
|
28
|
+
if (isObjectProperty(node))
|
|
29
|
+
return node.value.value;
|
|
30
|
+
|
|
31
|
+
return node.elements[1].value === 'cursor';
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const report = () => `Build menu`;
|
|
35
|
+
export const include = () => [
|
|
36
|
+
'ObjectProperty',
|
|
37
|
+
'ArrayExpression',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const createMenuItem = (path) => {
|
|
41
|
+
const {name} = path.node.key;
|
|
42
|
+
const valuePath = path.get('value');
|
|
43
|
+
|
|
44
|
+
if (isArrayExpression(valuePath)) {
|
|
45
|
+
const objectPath = valuePath.get('elements').at(-1);
|
|
46
|
+
const children = [];
|
|
47
|
+
|
|
48
|
+
for (const prop of objectPath.get('properties').map(createMenuItem)) {
|
|
49
|
+
children.push(prop);
|
|
50
|
+
children.push(jsxText('\n'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const submenu = createSubmenu(valuePath);
|
|
54
|
+
|
|
55
|
+
submenu.children[3].children = [
|
|
56
|
+
jsxText('\n'),
|
|
57
|
+
...children,
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
return submenu;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const classSelected = isSelected(path) ? 'menu-item-selected' : '';
|
|
64
|
+
|
|
65
|
+
const node = template.ast.fresh(`
|
|
66
|
+
<li data-name="menu-item" className="menu-item${classSelected}">
|
|
67
|
+
<label>${name}</label>
|
|
68
|
+
</li>
|
|
69
|
+
`);
|
|
70
|
+
|
|
71
|
+
return node;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const createUL = (path) => {
|
|
75
|
+
const node = template.ast.fresh(`
|
|
76
|
+
<ul className="menu menu-hidden"></ul>
|
|
77
|
+
`);
|
|
78
|
+
|
|
79
|
+
if (isOn(path))
|
|
80
|
+
removeClassName(node, 'menu-hidden');
|
|
81
|
+
|
|
82
|
+
return node;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const createSubmenu = (path) => {
|
|
86
|
+
const {name} = path.parentPath.node.key;
|
|
87
|
+
const menuHidden = isOn(path) ? '' : ' menu-hidden';
|
|
88
|
+
const classSelected = isSelected(path) ? ' menu-item-selected' : '';
|
|
89
|
+
|
|
90
|
+
const node = template.ast.fresh(`
|
|
91
|
+
<li data-name="menu-item" className="menu-item${classSelected}">
|
|
92
|
+
<label>${name}</label>
|
|
93
|
+
<ul className="menu${menuHidden}"></ul>
|
|
94
|
+
</li>
|
|
95
|
+
`);
|
|
96
|
+
|
|
97
|
+
return node;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const fix = (path, {options}) => {
|
|
101
|
+
const {position} = options;
|
|
102
|
+
const {top, left} = position;
|
|
103
|
+
|
|
104
|
+
if (isObjectProperty(path)) {
|
|
105
|
+
replaceWith(path, createMenuItem(path));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isArrayExpression(path)) {
|
|
110
|
+
const ul = createUL(path);
|
|
111
|
+
|
|
112
|
+
if (isExpressionStatement(path.parentPath)) {
|
|
113
|
+
replaceWith(path, ul);
|
|
114
|
+
setAttributeValue(ul, 'style', `left: ${left}px; top: ${top}px`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const {properties} = path.node.elements.at(-1);
|
|
118
|
+
const children = [];
|
|
119
|
+
|
|
120
|
+
for (const prop of properties) {
|
|
121
|
+
children.push(prop);
|
|
122
|
+
children.push(jsxText('\n'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
ul.children = [
|
|
126
|
+
jsxText('\n'),
|
|
127
|
+
...children,
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const {entries} = Object;
|
|
2
|
+
const isObject = (a) => a && typeof a === 'object';
|
|
3
|
+
|
|
4
|
+
export function convertMenuToState(menu, {submenu} = {}) {
|
|
5
|
+
const result = [];
|
|
6
|
+
|
|
7
|
+
for (const [key, value] of entries(menu)) {
|
|
8
|
+
if (!isObject(value)) {
|
|
9
|
+
result.push(key);
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
result.push(`${key}: ${convertMenuToState(value, {
|
|
14
|
+
submenu: true,
|
|
15
|
+
})}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const noCursor = submenu ? '' : `'no-cursor', `;
|
|
19
|
+
|
|
20
|
+
return `['off', ${noCursor} {${result.join(', ')}}]`;
|
|
21
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
## Rules
|
|
2
|
+
|
|
3
|
+
- [add-cursor](https://putout.cloudcmd.io/#/gist/0d0be0b7d19949be48c4122416654936/0cf8e1c512a0503f8c4bd0abf2af221b6680630b);
|
|
4
|
+
- [move-cursor](https://putout.cloudcmd.io/#/gist/54ff76b9673e0caf216b0f0f33d9f8bb/dad16ba0f861073a3ab7549e553dd2b97d252729);
|
|
5
|
+
- [apply-visibility](https://putout.cloudcmd.io/#/gist/7b97aaec7fcc518de4f6eef4ecc3dd58/f38db01fb9af051d49bd114bfb7bb04d2298c501);
|