aleman 1.0.3 → 1.0.5

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.
Files changed (54) hide show
  1. package/index.html +6 -4
  2. package/lib/addons/fixture/start-click-fix.html +0 -0
  3. package/lib/addons/fixture/start-click.html +1 -0
  4. package/lib/addons/fixture/stop-click-fix.html +0 -0
  5. package/lib/addons/fixture/stop-click.html +1 -0
  6. package/lib/addons/input-change.js +1 -2
  7. package/lib/addons/menu-item-click.js +27 -0
  8. package/lib/addons/menu-mouse-over.js +9 -0
  9. package/lib/addons/{menu-click.js → show-menu-click.js} +4 -1
  10. package/lib/addons/stop-click.js +1 -1
  11. package/lib/aleman/add-listeners.js +23 -2
  12. package/lib/aleman/aleman.js +28 -4
  13. package/lib/aleman/render.js +25 -4
  14. package/lib/globals/menu/click.js +21 -0
  15. package/lib/globals/menu/context-menu.js +13 -0
  16. package/lib/globals/menu/down.js +21 -0
  17. package/lib/globals/menu/enter.js +33 -0
  18. package/lib/globals/menu/escape.js +19 -0
  19. package/lib/globals/menu/f9.js +22 -0
  20. package/lib/globals/menu/menu.js +31 -0
  21. package/lib/globals/menu/up.js +16 -0
  22. package/lib/globals/menu.js +17 -0
  23. package/lib/main.js +14 -5
  24. package/lib/menu/menu.js +4 -0
  25. package/lib/rules/README.md +4 -1
  26. package/lib/rules/build-menu/fixture/build-menu-fix.js +8 -0
  27. package/lib/rules/build-menu/fixture/build-menu.js +1 -0
  28. package/lib/rules/build-menu/fixture/built.js +8 -0
  29. package/lib/rules/build-menu/index.js +66 -0
  30. package/lib/rules/check-data-name.js +12 -0
  31. package/lib/rules/index.js +8 -2
  32. package/lib/rules/select/fixture/no-data-name.js +4 -0
  33. package/lib/rules/select/fixture/no-next.js +5 -0
  34. package/lib/rules/select/fixture/no-parent.js +2 -0
  35. package/lib/rules/select/fixture/select-fix.js +8 -0
  36. package/lib/rules/select/fixture/select.js +8 -0
  37. package/lib/rules/select/fixture/wrong-data-name.js +4 -0
  38. package/lib/rules/select/index.js +61 -0
  39. package/lib/rules/set-value/fixture/no-data-name.js +1 -0
  40. package/lib/rules/set-value/fixture/not-label.js +1 -0
  41. package/lib/rules/unselect/fixture/no-data-name.js +4 -0
  42. package/lib/rules/unselect/fixture/no-parent.js +2 -0
  43. package/lib/rules/unselect/fixture/unselect-fix.js +8 -0
  44. package/lib/rules/unselect/fixture/unselect.js +8 -0
  45. package/lib/rules/unselect/index.js +39 -0
  46. package/lib/rules/unselect-all/fixture/no-data-name.js +4 -0
  47. package/lib/rules/unselect-all/fixture/no-parent.js +2 -0
  48. package/lib/rules/{select-next/fixture/select-next-fix.js → unselect-all/fixture/unselect-all-fix.js} +2 -2
  49. package/lib/rules/unselect-all/fixture/unselect-all.js +8 -0
  50. package/lib/rules/unselect-all/index.js +46 -0
  51. package/package.json +12 -2
  52. package/lib/globals/menu-next.js +0 -20
  53. package/lib/rules/select-next/fixture/select-next.js +0 -8
  54. package/lib/rules/select-next/index.js +0 -69
package/index.html CHANGED
@@ -3,7 +3,6 @@
3
3
 
4
4
  <section data-name="hydrate">
5
5
  <button data-name="menu-toggler">Menu</button>
6
- <button data-name="select-toggler">Select</button>
7
6
  <button data-name=start>hello</button>
8
7
  <button data-name=stop>world</button>
9
8
  <label>
@@ -11,7 +10,7 @@
11
10
  </label>
12
11
  <label data-name="value"></label>
13
12
  <ul data-name="menu" class="menu menu-hidden" style="left: 0; top: 20px;">
14
- <li data-name="js-menu-item" class="menu-item icon icon-view menu-item-selected"><label data-menu-path="View">View</label></li><li data-name="js-menu-item" class="menu-item icon icon-edit"><label data-menu-path="Edit">Edit</label></li><li data-name="js-menu-item" class="menu-item icon icon-rename"><label data-menu-path="Rename">Rename</label></li><li data-name="js-menu-item" class="menu-item icon icon-delete"><label data-menu-path="Delete">Delete</label></li><li data-name="js-menu-item" class="menu-item icon icon-pack"><label data-menu-path="Pack">Pack</label></li><li data-name="js-menu-item" class="menu-item icon icon-extract"><label data-menu-path="Extract">Extract</label></li><li data-name="js-menu-item" class="menu-item icon icon-download"><label data-menu-path="Download">Download</label></li><li data-name="js-menu-item" class="menu-item icon icon-upload-to-cloud"><label data-menu-path="Upload To Cloud">Upload To Cloud</label></li><li data-name="js-menu-item" class="menu-item icon icon-cut"><label data-menu-path="Cut">Cut</label></li><li data-name="js-menu-item" class="menu-item icon icon-copy"><label data-menu-path="Copy">Copy</label></li><li data-name="js-menu-item" class="menu-item icon icon-paste"><label data-menu-path="Paste">Paste</label></li><li data-name="js-menu-item" class="menu-item menu-submenu icon icon-new" data-menu="js-submenu"><label data-menu-path="New">New</label><ul data-name="js-menu" class="menu menu-hidden"><li data-name="js-menu-item" class="menu-item icon icon-file"><label data-menu-path="New.File">File</label></li><li data-name="js-menu-item" class="menu-item icon icon-directory"><label data-menu-path="New.Directory">Directory</label></li></ul></li><li data-name="js-menu-item" class="menu-item icon icon-upload"><label data-menu-path="Upload">Upload</label></li><li data-name="js-menu-item" class="menu-item icon icon-upload-from-cloud"><label data-menu-path="Upload From Cloud">Upload From Cloud</label></li><li data-name="js-menu-item" class="menu-item icon icon-toggle-file-selection"><label data-menu-path="Toggle File Selection">Toggle File Selection</label></li><li data-name="js-menu-item" class="menu-item icon icon-unselect-all"><label data-menu-path="(Un)Select All">(Un)Select All</label></li></ul>
13
+ </ul>
15
14
  </section>
16
15
 
17
16
 
@@ -19,7 +18,10 @@
19
18
  <script type="importmap">
20
19
  {
21
20
  "imports": {
22
- "putout": "https://esm.sh/@putout/bundle"
23
- }
21
+ "putout": "https://esm.sh/@putout/bundle",
22
+ "@putout/processor-html": "https://esm.sh/@putout/processor-html",
23
+ "html-escaper": "https://esm.sh/html-escaper",
24
+ "once": "https://esm.sh/once"
25
+ }
24
26
  }
25
27
  </script>
File without changes
@@ -0,0 +1 @@
1
+ <button data-name="start">world</button>
File without changes
@@ -0,0 +1 @@
1
+ <button data-name="stop">world</button>
@@ -4,7 +4,7 @@ export const events = [
4
4
  'change',
5
5
  ];
6
6
 
7
- export const filter = ({state, event}) => {
7
+ export const filter = ({event}) => {
8
8
  const {key} = event;
9
9
  const keys = [
10
10
  'Shift',
@@ -37,4 +37,3 @@ function isTextSelected(input) {
37
37
 
38
38
  return !input.selectionStart && input.selectionEnd === input.value.length;
39
39
  }
40
-
@@ -0,0 +1,27 @@
1
+ import {menu} from '../menu/menu.js';
2
+ import {hideMenu} from '../globals/menu/menu.js';
3
+
4
+ export const name = 'menu';
5
+ export const events = ['click'];
6
+ export const listener = ({event, render, element, state}) => {
7
+ const index = element.index ?? -1;
8
+
9
+ const menuItemElement = document.elementFromPoint(event.clientX, event.clientY);
10
+ const name = menuItemElement.textContent.trim();
11
+ const fn = menu[name];
12
+
13
+ fn();
14
+
15
+ hideMenu({
16
+ render,
17
+ state,
18
+ });
19
+
20
+ render('unselect', {
21
+ index: index - 1,
22
+ });
23
+
24
+ render('select', {
25
+ index,
26
+ });
27
+ };
@@ -0,0 +1,9 @@
1
+ import {select} from '../globals/menu/menu.js';
2
+
3
+ export const name = 'menu';
4
+ export const events = ['mouseenter'];
5
+ export const listener = ({render, storage}) => {
6
+ storage.set('index', -1);
7
+ render('unselect-all');
8
+ };
9
+
@@ -1,9 +1,12 @@
1
+ import {menu} from '../menu/menu.js';
2
+
1
3
  export const name = 'menu-toggler';
2
4
  export const events = ['click'];
3
5
  export const listener = ({render, element}) => {
4
6
  const command = element.command === 'show' ? 'hide' : 'show';
5
7
 
6
- render('menu', {
8
+ render(['build-menu', 'menu'], {
7
9
  command,
10
+ menu,
8
11
  });
9
12
  };
@@ -1,7 +1,7 @@
1
1
  export const name = 'stop';
2
2
  export const events = ['click'];
3
3
  export const listener = ({render}) => {
4
- return render('remove-button', {
4
+ render('remove-button', {
5
5
  name: 'stop',
6
6
  });
7
7
  };
@@ -1,5 +1,24 @@
1
1
  const isFn = (a) => typeof a === 'function';
2
- const {addEventListener} = document;
2
+ const {addEventListener} = globalThis.document || {};
3
+
4
+ export function addGlobalKeyListener({render, state, keys, storage}) {
5
+ const listeners = {};
6
+
7
+ for (const {key, listener, filter} of keys) {
8
+ listeners[key] = createListener({
9
+ name: 'document',
10
+ filter,
11
+ render,
12
+ listener,
13
+ storage,
14
+ state,
15
+ });
16
+ }
17
+
18
+ addEventListener('keydown', (event) => {
19
+ listeners[event.key]?.(event);
20
+ });
21
+ }
3
22
 
4
23
  export function addGlobalListeners({render, state, globals, storage}) {
5
24
  for (const {events, listener, filter} of globals) {
@@ -32,10 +51,11 @@ const createListener = ({name, filter = success, state, render, listener, storag
32
51
  event,
33
52
  element: createElement(name, storage),
34
53
  state,
54
+ storage,
35
55
  });
36
56
  };
37
57
 
38
- export function addListeners({render, addons, storage}) {
58
+ export function addListeners({render, addons, storage, state}) {
39
59
  for (const {name, events, listener, filter} of addons) {
40
60
  for (const event of events) {
41
61
  const element = document.querySelector(`[data-name='${name}']`);
@@ -49,6 +69,7 @@ export function addListeners({render, addons, storage}) {
49
69
  listener,
50
70
  filter,
51
71
  storage,
72
+ state,
52
73
  }));
53
74
  }
54
75
  }
@@ -1,14 +1,17 @@
1
1
  import {
2
2
  addListeners,
3
3
  addGlobalListeners,
4
+ addGlobalKeyListener,
4
5
  } from './add-listeners.js';
5
6
  import {createRender} from './render.js';
6
7
 
7
- export const hydrate = ({element, rules, addons, globals}) => {
8
+ const isKey = ({key}) => key;
9
+ const notKey = ({key}) => !key;
10
+
11
+ export const hydrate = ({element, rules, addons, globals, state = {}}) => {
8
12
  element = element || document.querySelector('[data-name="hydrate"]') || document.body;
9
13
  const html = `<section>${element.innerHTML}</section>`;
10
14
  const storage = new Map();
11
- const state = {};
12
15
 
13
16
  const render = createRender(html, {
14
17
  rules,
@@ -18,6 +21,27 @@ export const hydrate = ({element, rules, addons, globals}) => {
18
21
  state,
19
22
  });
20
23
 
21
- addListeners({render, addons, storage, state});
22
- addGlobalListeners({render, globals, storage, state});
24
+ addListeners({
25
+ render,
26
+ addons,
27
+ storage,
28
+ state,
29
+ });
30
+
31
+ const keys = globals.filter(isKey);
32
+ const other = globals.filter(notKey);
33
+
34
+ addGlobalListeners({
35
+ render,
36
+ globals: other,
37
+ storage,
38
+ state,
39
+ });
40
+
41
+ addGlobalKeyListener({
42
+ render,
43
+ keys,
44
+ storage,
45
+ state,
46
+ });
23
47
  };
@@ -2,16 +2,17 @@ import {
2
2
  parse,
3
3
  transform,
4
4
  print,
5
- } from 'https://esm.sh/@putout/bundle';
6
- import {branch, merge} from 'https://esm.sh/@putout/processor-html@14.0.2';
7
- import {escape} from 'https://esm.sh/html-escaper';
5
+ findPlaces,
6
+ } from 'putout';
7
+ import {branch, merge} from '@putout/processor-html';
8
+ import {escape} from 'html-escaper';
8
9
  import {addListeners} from './add-listeners.js';
9
10
 
10
11
  const {isArray} = Array;
11
12
  const maybeArray = (a) => isArray(a) ? a : [a];
12
13
  const {assign, entries} = Object;
13
14
 
14
- export const createRender = (html, {state, storage, rules, addons, element}) => {
15
+ export const createRender = (html, {headless, state, storage, rules, addons, element}) => {
15
16
  const {source} = branch(html)[0];
16
17
  const ast = parse(source);
17
18
 
@@ -37,6 +38,20 @@ export const createRender = (html, {state, storage, rules, addons, element}) =>
37
38
  }
38
39
  }
39
40
 
41
+ const places = findPlaces(ast, source, {
42
+ rules: currentRules,
43
+ plugins,
44
+ });
45
+
46
+ if (!places.length) {
47
+ restoreState({
48
+ state,
49
+ element,
50
+ storage,
51
+ });
52
+ return;
53
+ }
54
+
40
55
  transform(ast, '', {
41
56
  rules: currentRules,
42
57
  plugins,
@@ -55,11 +70,17 @@ export const createRender = (html, {state, storage, rules, addons, element}) =>
55
70
  const suffix = '<\\section>';
56
71
 
57
72
  element.innerHTML = merge('', [code]).slice(prefix.length, -suffix.length);
73
+
74
+ if (headless)
75
+ return;
76
+
58
77
  addListeners({
59
78
  render,
60
79
  addons,
61
80
  storage,
81
+ state,
62
82
  });
83
+
63
84
  restoreState({
64
85
  state,
65
86
  element,
@@ -0,0 +1,21 @@
1
+ import {
2
+ hideMenu,
3
+ filter as menuFilter,
4
+ } from '../menu/menu.js';
5
+
6
+ export const events = ['click'];
7
+ export const filter = ({event, state}) => {
8
+ if (!menuFilter({state}))
9
+ return false;
10
+
11
+ const {target} = event;
12
+
13
+ return target.dataset.name !== 'menu-toggler';
14
+ };
15
+
16
+ export const listener = ({render, state}) => {
17
+ hideMenu({
18
+ render,
19
+ state,
20
+ });
21
+ };
@@ -0,0 +1,13 @@
1
+ import {showMenu} from '../menu/menu.js';
2
+
3
+ export const events = [
4
+ 'contextmenu',
5
+ ];
6
+
7
+ export const listener = ({render, state, event}) => {
8
+ event.preventDefault();
9
+ showMenu({
10
+ render,
11
+ state,
12
+ });
13
+ };
@@ -0,0 +1,21 @@
1
+ import {menu} from '../../menu/menu.js';
2
+ import {select} from './menu.js';
3
+
4
+ const {keys} = Object;
5
+
6
+ export const key = 'ArrowDown';
7
+
8
+ export {filter} from './menu.js';
9
+
10
+ export const listener = ({render, element}) => {
11
+ let index = element.index ?? -1;
12
+ const n = keys(menu).length - 1;
13
+
14
+ if (index < n)
15
+ ++index;
16
+
17
+ select({
18
+ render,
19
+ index,
20
+ });
21
+ };
@@ -0,0 +1,33 @@
1
+ import {menu} from '../../menu/menu.js';
2
+ import {hideMenu, select} from './menu.js';
3
+
4
+ const {values} = Object;
5
+
6
+ export const key = 'Enter';
7
+
8
+ export {filter} from './menu.js';
9
+
10
+ export const listener = ({render, element, state}) => {
11
+ let index = element.index ?? -1;
12
+
13
+ run({
14
+ index,
15
+ render,
16
+ state,
17
+ });
18
+ index = -1;
19
+
20
+ select({
21
+ render,
22
+ index,
23
+ });
24
+ };
25
+
26
+ function run({index, render, state}) {
27
+ const fn = values(menu)[index];
28
+ fn();
29
+ hideMenu({
30
+ render,
31
+ state,
32
+ });
33
+ }
@@ -0,0 +1,19 @@
1
+ import {hideMenu, select} from './menu.js';
2
+
3
+ export const key = 'Escape';
4
+
5
+ export {filter} from './menu.js';
6
+
7
+ export const listener = ({render, state}) => {
8
+ const index = -1;
9
+
10
+ hideMenu({
11
+ render,
12
+ state,
13
+ });
14
+
15
+ select({
16
+ render,
17
+ index,
18
+ });
19
+ };
@@ -0,0 +1,22 @@
1
+ import {select, showMenu} from './menu.js';
2
+
3
+ export const key = 'F9';
4
+
5
+ export const filter = ({state}) => {
6
+ const {command} = state['menu-toggler'];
7
+ return command !== 'show';
8
+ };
9
+
10
+ export const listener = ({render, state}) => {
11
+ const index = -1;
12
+
13
+ showMenu({
14
+ render,
15
+ state,
16
+ });
17
+
18
+ select({
19
+ render,
20
+ index,
21
+ });
22
+ };
@@ -0,0 +1,31 @@
1
+ import {menu} from '../../menu/menu.js';
2
+
3
+ export const filter = ({state}) => {
4
+ const {command} = state['menu-toggler'];
5
+ return command !== 'hide';
6
+ };
7
+
8
+ export const select = ({render, index}) => {
9
+ render('unselect', {
10
+ index: index - 1,
11
+ });
12
+
13
+ render('select', {
14
+ index,
15
+ });
16
+ };
17
+
18
+ export function hideMenu({render, state}) {
19
+ state['menu-toggler'].command = 'hide';
20
+ render('menu', {
21
+ command: 'hide',
22
+ });
23
+ }
24
+
25
+ export function showMenu({render, state}) {
26
+ state['menu-toggler'].command = 'show';
27
+ render(['build-menu', 'menu'], {
28
+ command: 'show',
29
+ menu,
30
+ });
31
+ }
@@ -0,0 +1,16 @@
1
+ import {select} from './menu.js';
2
+
3
+ export const key = 'ArrowUp';
4
+
5
+ export {filter} from './menu.js';
6
+ export const listener = ({render, element}) => {
7
+ let index = element.index ?? -1;
8
+
9
+ if (index)
10
+ --index;
11
+
12
+ select({
13
+ render,
14
+ index,
15
+ });
16
+ };
@@ -0,0 +1,17 @@
1
+ import * as enter from './menu/enter.js';
2
+ import * as escape from './menu/escape.js';
3
+ import * as f9 from './menu/f9.js';
4
+ import * as up from './menu/up.js';
5
+ import * as down from './menu/down.js';
6
+ import * as click from './menu/click.js';
7
+ import * as contextMenu from './menu/context-menu.js';
8
+
9
+ export default [
10
+ enter,
11
+ escape,
12
+ f9,
13
+ up,
14
+ down,
15
+ click,
16
+ contextMenu,
17
+ ];
package/lib/main.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import * as start from './addons/start-click.js';
2
2
  import * as stop from './addons/stop-click.js';
3
3
  import * as inputChange from './addons/input-change.js';
4
- import * as menuClick from './addons/menu-click.js';
5
- import * as menuNext from './globals/menu-next.js';
4
+ import * as menuClick from './addons/show-menu-click.js';
5
+ import * as menuItemClick from './addons/menu-item-click.js';
6
+ import * as menuMouseOver from './addons/menu-mouse-over.js';
7
+ import menu from './globals/menu.js';
6
8
  import {hydrate} from './aleman/aleman.js';
7
9
  import {rules} from './rules/index.js';
8
10
 
@@ -11,13 +13,20 @@ const addons = [
11
13
  stop,
12
14
  inputChange,
13
15
  menuClick,
16
+ menuItemClick,
17
+ menuMouseOver,
14
18
  ];
15
19
 
16
- const globals = [
17
- menuNext,
18
- ];
20
+ const globals = [...menu];
21
+
22
+ const state = {
23
+ 'menu-toggler': {
24
+ command: 'hide',
25
+ },
26
+ };
19
27
 
20
28
  hydrate({
29
+ state,
21
30
  rules,
22
31
  addons,
23
32
  globals,
@@ -0,0 +1,4 @@
1
+ export const menu = {
2
+ hello: () => alert('hello'),
3
+ world: () => alert('world'),
4
+ };
@@ -3,4 +3,7 @@
3
3
  - ✅[`set-value`](https://putout.cloudcmd.io/#/gist/29850822bca99f83dbea1b17fda61904/6cdec0ef8c1903cbc9b9df6dfcde6056b8165386);
4
4
  - ✅[`remove-button`](https://putout.cloudcmd.io/#/gist/fa2b037a629306cbd43cf42c457c08b9/0cac9c986141d69f9747538816e66b14c4cbd256);
5
5
  - ✅[`show-menu`](https://putout.cloudcmd.io/#/gist/884fd40c07b94951de1a9ce99afab015/21d4881b44955b1426f80e87c76b7ad86eeea464);
6
- - ✅[`select-next`](https://putout.cloudcmd.io/#/gist/10891aa58ff2ecfa1728ed19aeb2e3f2/ce536fb0b414faefbae5d0c8a094517beb1c606e);
6
+ - ✅[`select`](https://putout.cloudcmd.io/#/gist/10891aa58ff2ecfa1728ed19aeb2e3f2/ce536fb0b414faefbae5d0c8a094517beb1c606e);
7
+ - ✅[`unselect`](https://putout.cloudcmd.io/#/gist/aa6006f83aebcb70362a65b3349f8ddd/df1a06b94ccce3134b2246892702bf63dff79145);
8
+ - ✅[`unselect-all`](https://putout.cloudcmd.io/#/gist/ca24f199f508cccb34b11baf726eba83/96f08fa91331ac6265d9f5ff48692c9d4ad1b98b);
9
+ - ✅[`build-menu`](https://putout.cloudcmd.io/#/gist/329dccd5fdc7f8b220be79af405dc9bb/b56df53f52bbe2a300ede38a96d1d2242e60679f);
@@ -0,0 +1,8 @@
1
+ <ul data-name="menu" class="menu menu-hidden">
2
+ <li data-name='menu-item' className='menu-item'>
3
+ <label>hello</label>
4
+ </li>
5
+ <li data-name='menu-item' className='menu-item'>
6
+ <label>world</label>
7
+ </li>
8
+ </ul>;
@@ -0,0 +1 @@
1
+ <ul data-name="menu" class="menu menu-hidden"></ul>;
@@ -0,0 +1,8 @@
1
+ <ul data-name="menu" class="menu menu-hidden">
2
+ <li data-name='menu-item' class='menu-item'>
3
+ <label>hello</label>
4
+ </li>
5
+ <li data-name='menu-item' class='menu-item'>
6
+ <label>world</label>
7
+ </li>
8
+ </ul>;
@@ -0,0 +1,66 @@
1
+ import {types, template} from 'putout';
2
+
3
+ const {entries} = Object;
4
+ const noop = () => {};
5
+ const {jsxText} = types;
6
+
7
+ export const report = () => `Build menu`;
8
+
9
+ const createMenuItem = template(`
10
+ <li data-name="menu-item" className="menu-item">
11
+ <label>NAME</label>
12
+ </li>
13
+ `);
14
+
15
+ const NEWLINE = jsxText('\n');
16
+ const INDENT = jsxText(' ');
17
+
18
+ const DefaultMenu = {
19
+ hello: noop,
20
+ world: noop,
21
+ };
22
+
23
+ export const fix = ({path, menu}) => {
24
+ const items = [];
25
+
26
+ for (const [key] of entries(menu)) {
27
+ const menuItem = createMenuItem();
28
+
29
+ menuItem.children[1].children[0].value = key;
30
+ items.push(INDENT, menuItem);
31
+ }
32
+
33
+ items.push(NEWLINE);
34
+ path.parentPath.node.children = items;
35
+ };
36
+
37
+ export const traverse = ({options, push}) => ({
38
+ JSXOpeningElement(path) {
39
+ const {
40
+ name = 'menu',
41
+ menu = DefaultMenu,
42
+ } = options;
43
+
44
+ if (!checkDataName(path, name))
45
+ return;
46
+
47
+ if (path.parentPath.node.children.length)
48
+ return;
49
+
50
+ push({
51
+ path,
52
+ menu,
53
+ });
54
+ },
55
+ });
56
+
57
+ function checkDataName(path, dataName) {
58
+ const {attributes} = path.node;
59
+
60
+ for (const {name, value} of attributes) {
61
+ if (name.name === 'data-name')
62
+ return value.value === dataName;
63
+ }
64
+
65
+ return false;
66
+ }
@@ -0,0 +1,12 @@
1
+ export function checkDataName(path) {
2
+ const attributes = path.get('openingElement.attributes');
3
+
4
+ for (const attr of attributes) {
5
+ const {name, value} = attr.node;
6
+
7
+ if (name.name === 'data-name')
8
+ return value.value === 'menu';
9
+ }
10
+
11
+ return false;
12
+ }
@@ -1,4 +1,7 @@
1
- import * as selectNext from './select-next/index.js';
1
+ import * as unselectAll from './unselect-all/index.js';
2
+ import * as buildMenu from './build-menu/index.js';
3
+ import * as select from './select/index.js';
4
+ import * as unselect from './unselect/index.js';
2
5
  import * as menu from './menu/index.js';
3
6
  import * as removeButton from './remove-button/index.js';
4
7
  import * as setValue from './set-value/index.js';
@@ -7,5 +10,8 @@ export const rules = {
7
10
  'set-value': setValue,
8
11
  'remove-button': removeButton,
9
12
  'menu': menu,
10
- 'select-next': selectNext,
13
+ 'select': select,
14
+ 'build-menu': buildMenu,
15
+ 'unselect': unselect,
16
+ 'unselect-all': unselectAll,
11
17
  };
@@ -0,0 +1,4 @@
1
+ <ul className="hello">
2
+ <li className="menu menu-hidden">
3
+ </li>
4
+ </ul>;
@@ -0,0 +1,5 @@
1
+ <ul data-name="menu" class="menu menu-hidden">
2
+ <li data-name="menu-item" className="menu-item icon icon-view menu-item-selected">
3
+ <label data-menu-path="View">View</label>
4
+ </li>
5
+ </ul>;
@@ -0,0 +1,2 @@
1
+ <li className="menu menu-hidden">
2
+ </li>;
@@ -0,0 +1,8 @@
1
+ <ul data-name="menu" class="menu menu-hidden">
2
+ <li data-name="menu-item" className="menu-item icon icon-view menu-item-selected">
3
+ <label data-menu-path="View">View</label>
4
+ </li>
5
+ <li data-name="menu-item" className='menu-item icon icon-edit menu-item-selected'>
6
+ <label data-menu-path="Edit">Edit</label>
7
+ </li>
8
+ </ul>;
@@ -0,0 +1,8 @@
1
+ <ul data-name="menu" class="menu menu-hidden">
2
+ <li data-name="menu-item" className="menu-item icon icon-view menu-item-selected">
3
+ <label data-menu-path="View">View</label>
4
+ </li>
5
+ <li data-name="menu-item" className="menu-item icon icon-edit">
6
+ <label data-menu-path="Edit">Edit</label>
7
+ </li>
8
+ </ul>;
@@ -0,0 +1,4 @@
1
+ <ul data-name="hello" className="hello">
2
+ <li className="menu menu-hidden">
3
+ </li>
4
+ </ul>;
@@ -0,0 +1,61 @@
1
+ import {operator, types} from 'putout';
2
+
3
+ const {isJSXElement} = types;
4
+ const {setLiteralValue} = operator;
5
+
6
+ export const report = () => `Select item`;
7
+
8
+ export const fix = (path) => {
9
+ const {value} = path.node;
10
+ setLiteralValue(value, `${value.value} menu-item-selected`);
11
+ };
12
+
13
+ export const traverse = ({options, push}) => ({
14
+ JSXOpeningElement(path) {
15
+ if (path.node.name.name !== 'li')
16
+ return;
17
+
18
+ const {index = 1} = options;
19
+
20
+ if (!isJSXElement(path.parentPath.parentPath))
21
+ return;
22
+
23
+ if (!checkDataName(path.parentPath.parentPath))
24
+ return;
25
+
26
+ const children = path.parentPath
27
+ .parentPath
28
+ .get('children')
29
+ .filter(isJSXElement);
30
+
31
+ const next = children[index];
32
+
33
+ if (!next)
34
+ return;
35
+
36
+ for (const attr of next.get('openingElement.attributes')) {
37
+ const {name, value} = attr.node;
38
+
39
+ if (name.name !== 'className')
40
+ continue;
41
+
42
+ if (!value.value.includes('menu-item-selected')) {
43
+ push(attr);
44
+ break;
45
+ }
46
+ }
47
+ },
48
+ });
49
+
50
+ function checkDataName(path) {
51
+ const attributes = path.get('openingElement.attributes');
52
+
53
+ for (const attr of attributes) {
54
+ const {name, value} = attr.node;
55
+
56
+ if (name.name === 'data-name')
57
+ return value.value === 'menu';
58
+ }
59
+
60
+ return false;
61
+ }
@@ -0,0 +1 @@
1
+ <label className="hello">x</label>;
@@ -0,0 +1 @@
1
+ <section data-name="x">x</section>;
@@ -0,0 +1,4 @@
1
+ <ul className="hello">
2
+ <li className="menu menu-hidden">
3
+ </li>
4
+ </ul>;
@@ -0,0 +1,2 @@
1
+ <li className="menu menu-hidden">
2
+ </li>;
@@ -0,0 +1,8 @@
1
+ <ul data-name="menu" class="menu menu-hidden">
2
+ <li data-name="menu-item" className='menu-item icon icon-view'>
3
+ <label data-menu-path="View">View</label>
4
+ </li>
5
+ <li data-name="menu-item" className="menu-item icon icon-edit">
6
+ <label data-menu-path="Edit">Edit</label>
7
+ </li>
8
+ </ul>;
@@ -0,0 +1,8 @@
1
+ <ul data-name="menu" class="menu menu-hidden">
2
+ <li data-name="menu-item" className="menu-item icon icon-view menu-item-selected">
3
+ <label data-menu-path="View">View</label>
4
+ </li>
5
+ <li data-name="menu-item" className="menu-item icon icon-edit">
6
+ <label data-menu-path="Edit">Edit</label>
7
+ </li>
8
+ </ul>;
@@ -0,0 +1,39 @@
1
+ import {operator, types} from 'putout';
2
+ import {checkDataName} from '../check-data-name.js';
3
+
4
+ const {isJSXElement} = types;
5
+ const {setLiteralValue} = operator;
6
+
7
+ export const report = () => `Select next item`;
8
+
9
+ export const fix = ({path}) => {
10
+ const {value} = path.node;
11
+ const newValue = value.value.replace(/\s?menu-item-selected/, '');
12
+
13
+ setLiteralValue(value, newValue);
14
+ };
15
+
16
+ export const traverse = ({push}) => ({
17
+ JSXOpeningElement(path) {
18
+ if (path.node.name.name !== 'li')
19
+ return;
20
+
21
+ if (!isJSXElement(path.parentPath.parentPath))
22
+ return;
23
+
24
+ if (!checkDataName(path.parentPath.parentPath))
25
+ return;
26
+
27
+ for (const attr of path.get('attributes')) {
28
+ const {name, value} = attr.node;
29
+
30
+ if (name.name !== 'className')
31
+ continue;
32
+
33
+ if (value.value.includes('menu-item-selected'))
34
+ push({
35
+ path: attr,
36
+ });
37
+ }
38
+ },
39
+ });
@@ -0,0 +1,4 @@
1
+ <ul className="hello">
2
+ <li className="menu menu-hidden">
3
+ </li>
4
+ </ul>;
@@ -0,0 +1,2 @@
1
+ <li className="menu menu-hidden">
2
+ </li>;
@@ -1,8 +1,8 @@
1
1
  <ul data-name="menu" class="menu menu-hidden">
2
- <li data-name="menu-item" class='menu-item icon icon-view'>
2
+ <li data-name="menu-item" className='menu-item icon icon-view'>
3
3
  <label data-menu-path="View">View</label>
4
4
  </li>
5
- <li data-name="menu-item" class='menu-item icon icon-edit menu-item-selected'>
5
+ <li data-name="menu-item" className='menu-item icon icon-edit'>
6
6
  <label data-menu-path="Edit">Edit</label>
7
7
  </li>
8
8
  </ul>;
@@ -0,0 +1,8 @@
1
+ <ul data-name="menu" class="menu menu-hidden">
2
+ <li data-name="menu-item" className="menu-item icon icon-view menu-item-selected">
3
+ <label data-menu-path="View">View</label>
4
+ </li>
5
+ <li data-name="menu-item" className="menu-item icon icon-edit menu-item-selected">
6
+ <label data-menu-path="Edit">Edit</label>
7
+ </li>
8
+ </ul>;
@@ -0,0 +1,46 @@
1
+ import {operator, types} from 'putout';
2
+ import {checkDataName} from '../check-data-name.js';
3
+
4
+ const {isJSXElement} = types;
5
+ const {setLiteralValue} = operator;
6
+
7
+ export const report = () => `Unselect all`;
8
+
9
+ export const fix = ({path}) => {
10
+ const {value} = path.node;
11
+ const newValue = value.value.replace(/\s?menu-item-selected/, '');
12
+
13
+ setLiteralValue(value, newValue);
14
+ };
15
+
16
+ export const traverse = ({push}) => ({
17
+ JSXOpeningElement(path) {
18
+ if (path.node.name.name !== 'li')
19
+ return;
20
+
21
+ if (!isJSXElement(path.parentPath.parentPath))
22
+ return;
23
+
24
+ if (!checkDataName(path.parentPath.parentPath))
25
+ return;
26
+
27
+ const children = path.parentPath
28
+ .parentPath
29
+ .get('children')
30
+ .filter(isJSXElement);
31
+
32
+ for (const child of children) {
33
+ for (const attr of child.get('openingElement.attributes')) {
34
+ const {name, value} = attr.node;
35
+
36
+ if (name.name !== 'className')
37
+ continue;
38
+
39
+ if (value.value.includes('menu-item-selected'))
40
+ push({
41
+ path: attr,
42
+ });
43
+ }
44
+ }
45
+ },
46
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aleman",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
6
6
  "description": "🐊Putout-based framework for web",
@@ -13,6 +13,10 @@
13
13
  "type": "git",
14
14
  "url": "git+https://github.com/coderaiser/putout.git"
15
15
  },
16
+ "imports": {
17
+ "#test": "./test/test.js",
18
+ "#rules": "./lib/rules/index.js"
19
+ },
16
20
  "scripts": {
17
21
  "test": "madrun test",
18
22
  "watch:test": "madrun watch:test",
@@ -24,6 +28,9 @@
24
28
  "report": "madrun report"
25
29
  },
26
30
  "dependencies": {
31
+ "@putout/processor-html": "^14.0.2",
32
+ "html-escaper": "^3.0.3",
33
+ "once": "^1.4.0",
27
34
  "putout": "^40.5.1"
28
35
  },
29
36
  "keywords": [
@@ -39,8 +46,11 @@
39
46
  "eslint": "^9.0.0",
40
47
  "eslint-plugin-n": "^17.0.0",
41
48
  "eslint-plugin-putout": "^28.0.0",
49
+ "just-kebab-case": "^4.2.0",
42
50
  "madrun": "^11.0.0",
43
- "nodemon": "^3.0.1"
51
+ "nodemon": "^3.0.1",
52
+ "supertape": "^11.3.0",
53
+ "try-catch": "^3.0.1"
44
54
  },
45
55
  "license": "MIT",
46
56
  "engines": {
@@ -1,20 +0,0 @@
1
- export const events = ['keydown'];
2
-
3
- export const filter = ({event, state}) => {
4
- if (event.key !== 'ArrowDown')
5
- return false;
6
-
7
- const {command} = state['menu-toggler'] || {
8
- command: 'show',
9
- };
10
-
11
- return command !== 'hide';
12
- };
13
- export const listener = ({state, render, element}) => {
14
- const index = element.index || 0;
15
-
16
- render('select-next', {
17
- index: index + 1,
18
- });
19
- };
20
-
@@ -1,8 +0,0 @@
1
- <ul data-name="menu" class="menu menu-hidden">
2
- <li data-name="menu-item" class="menu-item icon icon-view menu-item-selected">
3
- <label data-menu-path="View">View</label>
4
- </li>
5
- <li data-name="menu-item" class="menu-item icon icon-edit">
6
- <label data-menu-path="Edit">Edit</label>
7
- </li>
8
- </ul>
@@ -1,69 +0,0 @@
1
- import {operator, types} from 'putout';
2
-
3
- const {isJSXElement} = types;
4
- const {setLiteralValue} = operator;
5
-
6
- export const report = () => `Select next item`;
7
-
8
- export const fix = ({path, next}) => {
9
- const {value} = path.node;
10
- const newValue = value.value.replace(/\s?menu-item-selected/, '');
11
-
12
- setLiteralValue(value, newValue);
13
-
14
- for (const {name, value} of next.node.openingElement.attributes) {
15
- if (name.name !== 'className')
16
- continue;
17
-
18
- setLiteralValue(value, `${value.value} menu-item-selected`);
19
- }
20
- };
21
-
22
- export const traverse = ({options, push}) => ({
23
- JSXOpeningElement(path) {
24
- if (path.node.name.name !== 'li')
25
- return;
26
-
27
- const {index = 1} = options;
28
-
29
- const attributes = path.parentPath.parentPath.get('openingElement.attributes');
30
-
31
- for (const attr of attributes) {
32
- const {name, value} = attr.node;
33
-
34
- if (name.name !== 'data-name')
35
- continue;
36
-
37
- if (value.value !== 'menu')
38
- return;
39
-
40
- break;
41
- }
42
-
43
- const children = path.parentPath
44
- .parentPath
45
- .get('children')
46
- .filter(isJSXElement);
47
-
48
- if (index !== children.indexOf(path.parentPath) + 1)
49
- return;
50
-
51
- const next = children[index];
52
-
53
- if (!next)
54
- return;
55
-
56
- for (const attr of path.get('attributes')) {
57
- const {name, value} = attr.node;
58
-
59
- if (name.name !== 'className')
60
- continue;
61
-
62
- if (value.value.includes('menu-item-selected'))
63
- push({
64
- path: attr,
65
- next,
66
- });
67
- }
68
- },
69
- });