wx-svelte-menu 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/license.txt +21 -0
- package/package.json +43 -0
- package/readme.md +60 -0
- package/src/components/ActionMenu.svelte +97 -0
- package/src/components/ContextMenu.svelte +31 -0
- package/src/components/DropDownMenu.svelte +40 -0
- package/src/components/Menu.svelte +113 -0
- package/src/components/MenuBar.svelte +99 -0
- package/src/components/MenuItem.svelte +81 -0
- package/src/helpers/index.js +37 -0
- package/src/index.js +8 -0
- package/whatsnew.md +72 -0
package/license.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 XB Software Sp. z o.o.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wx-svelte-menu",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"productTag": "menu",
|
|
5
|
+
"productTrial": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"build:dist": "vite build --mode dist",
|
|
10
|
+
"build:tests": "vite build --mode test",
|
|
11
|
+
"lint": "yarn eslint ./demos ./src --ext .svelte,.ts,.js",
|
|
12
|
+
"start": "vite --open=/demos/",
|
|
13
|
+
"start:tests": "vite --open=/tests/ --host 0.0.0.0 --port 5100 --mode test",
|
|
14
|
+
"test": "true",
|
|
15
|
+
"test:cypress": "cypress run -P ./ --config \"baseUrl=http://localhost:5100/tests\""
|
|
16
|
+
},
|
|
17
|
+
"svelte": "src/index.js",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"svelte": "./src/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/svar-widgets/menu.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://forum.svar.dev"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://svar.dev/svelte/core/",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"wx-svelte-core": "1.3.0",
|
|
35
|
+
"wx-lib-dom": "0.6.0"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"src",
|
|
39
|
+
"readme.md",
|
|
40
|
+
"whatsnew.md",
|
|
41
|
+
"license.txt"
|
|
42
|
+
]
|
|
43
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
### SVAR Menu for Svelte
|
|
2
|
+
|
|
3
|
+
SVAR Menu provides ready to use control for creating context and popup menus
|
|
4
|
+
|
|
5
|
+
### Useful Links
|
|
6
|
+
|
|
7
|
+
- [Documentation](https://docs.svar.dev/svelte/core/overview)
|
|
8
|
+
- [How to start guide](https://docs.svar.dev/svelte/core/getting_started/)
|
|
9
|
+
- [Demos](https://docs.svar.dev/svelte/menu/samples/#/base/willow)
|
|
10
|
+
|
|
11
|
+
### License
|
|
12
|
+
|
|
13
|
+
SVAR Menu for Svelte is available under MIT license.
|
|
14
|
+
|
|
15
|
+
### How to Use
|
|
16
|
+
|
|
17
|
+
To use the widget, simply import the package and include the component in your Svelte file:
|
|
18
|
+
|
|
19
|
+
```svelte
|
|
20
|
+
<script>
|
|
21
|
+
import { Menu } from "wx-svelte-menu";
|
|
22
|
+
|
|
23
|
+
function onClick(item) {
|
|
24
|
+
const action = ev.detail.action;
|
|
25
|
+
message = action ? `clicked on ${action.id}` : "closed";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const options = [
|
|
29
|
+
{ id: "edit-cut", text: "Cut", icon: "wxi wxi-content-cut" },
|
|
30
|
+
{ id: "edit-copy", text: "Copy", icon: "wxi wxi-content-copy" },
|
|
31
|
+
{
|
|
32
|
+
id: "edit-paste",
|
|
33
|
+
text: "Paste",
|
|
34
|
+
icon: "wxi wxi-content-paste",
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<Menu {options} on:click={clicked} />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### How to Modify
|
|
43
|
+
|
|
44
|
+
Typically, you don't need to modify the code. However, if you wish to do so, follow these steps:
|
|
45
|
+
|
|
46
|
+
1. Run `yarn` to install dependencies. Note that this project is a monorepo using `yarn` workspaces, so npm will not work
|
|
47
|
+
2. Start the project in development mode with `yarn start`
|
|
48
|
+
|
|
49
|
+
### Run Tests
|
|
50
|
+
|
|
51
|
+
To run the test:
|
|
52
|
+
|
|
53
|
+
1. Start the test examples with:
|
|
54
|
+
```sh
|
|
55
|
+
yarn start:tests
|
|
56
|
+
```
|
|
57
|
+
2. In a separate console, run the end-to-end tests with:
|
|
58
|
+
```sh
|
|
59
|
+
yarn test:cypress
|
|
60
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { Portal } from "wx-svelte-core";
|
|
3
|
+
import { id } from "wx-lib-dom";
|
|
4
|
+
import Menu from "./Menu.svelte";
|
|
5
|
+
import { filterMenu } from "../helpers";
|
|
6
|
+
|
|
7
|
+
import { createEventDispatcher } from "svelte";
|
|
8
|
+
const dispatch = createEventDispatcher();
|
|
9
|
+
|
|
10
|
+
const SLOTS = $$props.$$slots;
|
|
11
|
+
|
|
12
|
+
export let options;
|
|
13
|
+
export let at = "bottom";
|
|
14
|
+
export let resolver = null;
|
|
15
|
+
export let dataKey = "contextId";
|
|
16
|
+
export let filter = null;
|
|
17
|
+
export let css = "";
|
|
18
|
+
export const handler = show;
|
|
19
|
+
|
|
20
|
+
var filteredOptions;
|
|
21
|
+
$: filteredOptions = options;
|
|
22
|
+
|
|
23
|
+
var item = null;
|
|
24
|
+
var parent = null;
|
|
25
|
+
let left = 0,
|
|
26
|
+
top = 0;
|
|
27
|
+
|
|
28
|
+
function onClick(ev) {
|
|
29
|
+
parent = null;
|
|
30
|
+
dispatch("click", ev.detail);
|
|
31
|
+
}
|
|
32
|
+
function getDataAttr(node, name) {
|
|
33
|
+
let v = null;
|
|
34
|
+
while (node && node.dataset && !v) {
|
|
35
|
+
v = node.dataset[name];
|
|
36
|
+
node = node.parentNode;
|
|
37
|
+
}
|
|
38
|
+
return v ? id(v) : null;
|
|
39
|
+
}
|
|
40
|
+
function show(ev, obj) {
|
|
41
|
+
if (!ev) {
|
|
42
|
+
parent = null;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (ev.defaultPrevented) return;
|
|
47
|
+
|
|
48
|
+
const target = ev.target;
|
|
49
|
+
if (target && target.dataset && target.dataset.menuIgnore) return;
|
|
50
|
+
|
|
51
|
+
left = ev.clientX + 1;
|
|
52
|
+
top = ev.clientY + 1;
|
|
53
|
+
|
|
54
|
+
item = typeof obj !== "undefined" ? obj : getDataAttr(target, dataKey);
|
|
55
|
+
if (resolver) {
|
|
56
|
+
item = resolver(item, ev);
|
|
57
|
+
if (!item) return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (item !== null && filter) {
|
|
61
|
+
filteredOptions = filterMenu(options, v => filter(v, item));
|
|
62
|
+
}
|
|
63
|
+
parent = target;
|
|
64
|
+
|
|
65
|
+
ev.preventDefault();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// about #key in markup below
|
|
69
|
+
// we need to be sure that menu is closed before it is shown again
|
|
70
|
+
// its the only way to reinit click-outside
|
|
71
|
+
// otherwise, the menu will be hidden as click-ouside occurs after show call
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
{#if SLOTS && SLOTS.default}
|
|
75
|
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
76
|
+
<div on:click={handler} data-menu-ignore="true">
|
|
77
|
+
<slot />
|
|
78
|
+
</div>
|
|
79
|
+
{/if}
|
|
80
|
+
|
|
81
|
+
{#if parent}
|
|
82
|
+
<Portal let:mount>
|
|
83
|
+
{#key parent}
|
|
84
|
+
<Menu
|
|
85
|
+
{css}
|
|
86
|
+
{at}
|
|
87
|
+
{top}
|
|
88
|
+
{left}
|
|
89
|
+
{mount}
|
|
90
|
+
{parent}
|
|
91
|
+
context={item}
|
|
92
|
+
on:click={onClick}
|
|
93
|
+
options={filteredOptions}
|
|
94
|
+
/>
|
|
95
|
+
{/key}
|
|
96
|
+
</Portal>
|
|
97
|
+
{/if}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import ActionMenu from "./ActionMenu.svelte";
|
|
3
|
+
|
|
4
|
+
const SLOTS = $$props.$$slots;
|
|
5
|
+
|
|
6
|
+
export let handler = null;
|
|
7
|
+
|
|
8
|
+
export let options;
|
|
9
|
+
export let at = "bottom";
|
|
10
|
+
export let resolver = null;
|
|
11
|
+
export let dataKey = "contextId";
|
|
12
|
+
export let filter = null;
|
|
13
|
+
export let css = "";
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
{#if SLOTS && SLOTS.default}
|
|
17
|
+
<div on:contextmenu={handler} data-menu-ignore="true">
|
|
18
|
+
<slot />
|
|
19
|
+
</div>
|
|
20
|
+
{/if}
|
|
21
|
+
|
|
22
|
+
<ActionMenu
|
|
23
|
+
{css}
|
|
24
|
+
{at}
|
|
25
|
+
{options}
|
|
26
|
+
{resolver}
|
|
27
|
+
{dataKey}
|
|
28
|
+
{filter}
|
|
29
|
+
bind:handler
|
|
30
|
+
on:click
|
|
31
|
+
/>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { Portal } from "wx-svelte-core";
|
|
3
|
+
import Menu from "./Menu.svelte";
|
|
4
|
+
import { createEventDispatcher } from "svelte";
|
|
5
|
+
const dispatch = createEventDispatcher();
|
|
6
|
+
|
|
7
|
+
export let options;
|
|
8
|
+
export let at = "bottom";
|
|
9
|
+
export let css = "";
|
|
10
|
+
|
|
11
|
+
export const handler = ev => {
|
|
12
|
+
parent = ev.target;
|
|
13
|
+
ev.preventDefault();
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
var parent = null;
|
|
17
|
+
function onClick(ev) {
|
|
18
|
+
parent = null;
|
|
19
|
+
dispatch("click", ev.detail);
|
|
20
|
+
}
|
|
21
|
+
function show(ev) {
|
|
22
|
+
let target = ev.target;
|
|
23
|
+
while (!target.dataset.menuIgnore) {
|
|
24
|
+
parent = target;
|
|
25
|
+
target = target.parentNode;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
31
|
+
<div on:click={show} data-menu-ignore="true">
|
|
32
|
+
<slot />
|
|
33
|
+
</div>
|
|
34
|
+
{#if parent}
|
|
35
|
+
<Portal let:mount>
|
|
36
|
+
{#key parent}
|
|
37
|
+
<Menu {css} {at} {mount} {parent} {options} on:click={onClick} />
|
|
38
|
+
{/key}
|
|
39
|
+
</Portal>
|
|
40
|
+
{/if}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { clickOutside, calculatePosition } from "wx-lib-dom";
|
|
3
|
+
import { onMount, createEventDispatcher } from "svelte";
|
|
4
|
+
|
|
5
|
+
import MenuItem from "./MenuItem.svelte";
|
|
6
|
+
import { prepareMenuData } from "../helpers";
|
|
7
|
+
|
|
8
|
+
const dispatch = createEventDispatcher();
|
|
9
|
+
|
|
10
|
+
export let options;
|
|
11
|
+
$: prepareMenuData(options);
|
|
12
|
+
|
|
13
|
+
export let left = 0;
|
|
14
|
+
export let top = 0;
|
|
15
|
+
export let at = "bottom";
|
|
16
|
+
export let parent = null;
|
|
17
|
+
export let mount = null;
|
|
18
|
+
export let context = null;
|
|
19
|
+
export let css = "";
|
|
20
|
+
|
|
21
|
+
let x = -10000;
|
|
22
|
+
let y = -10000;
|
|
23
|
+
let index = 0;
|
|
24
|
+
let width;
|
|
25
|
+
|
|
26
|
+
let self;
|
|
27
|
+
let showSub;
|
|
28
|
+
let activeItem;
|
|
29
|
+
|
|
30
|
+
function updatePosition() {
|
|
31
|
+
const result = calculatePosition(self, parent, at, left, top);
|
|
32
|
+
x = result.x || x;
|
|
33
|
+
y = result.y || y;
|
|
34
|
+
index = result.index;
|
|
35
|
+
width = result?.width || width;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// unfortunately svelte doesn't guarantee that afterUpdate of child component
|
|
39
|
+
// will be called after onMount in the parent one, so to be sure that Portal is already
|
|
40
|
+
// moved menu to the correct parent we are registering single time handler
|
|
41
|
+
if (mount) mount(updatePosition);
|
|
42
|
+
onMount(updatePosition);
|
|
43
|
+
$: updatePosition(parent);
|
|
44
|
+
|
|
45
|
+
function onLeave() {
|
|
46
|
+
showSub = false;
|
|
47
|
+
}
|
|
48
|
+
function cancel() {
|
|
49
|
+
dispatch("click", { action: null });
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<div
|
|
54
|
+
use:clickOutside={{ callback: cancel, modal: true }}
|
|
55
|
+
bind:this={self}
|
|
56
|
+
data-wx-menu="true"
|
|
57
|
+
class="wx-menu {css}"
|
|
58
|
+
style="top:{y}px;left:{x}px;width:{width};"
|
|
59
|
+
on:mouseleave={onLeave}
|
|
60
|
+
>
|
|
61
|
+
{#each options as item (item.id)}
|
|
62
|
+
{#if item.type === "separator"}
|
|
63
|
+
<div class="wx-separator" />
|
|
64
|
+
{:else}
|
|
65
|
+
<MenuItem
|
|
66
|
+
{item}
|
|
67
|
+
bind:showSub
|
|
68
|
+
bind:activeItem
|
|
69
|
+
on:click={ev => {
|
|
70
|
+
if (!item.data && !ev.defaultPrevented) {
|
|
71
|
+
const pack = { context, action: item, event: ev };
|
|
72
|
+
if (item.handler) item.handler(pack);
|
|
73
|
+
dispatch("click", pack);
|
|
74
|
+
|
|
75
|
+
// it is a rare case when we need to stop event bubbling
|
|
76
|
+
// clicking on menu is isolated action which must not affect any other elements on the page
|
|
77
|
+
ev.stopPropagation();
|
|
78
|
+
}
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
{/if}
|
|
82
|
+
{#if item.data && showSub === item.id}
|
|
83
|
+
<svelte:self
|
|
84
|
+
{css}
|
|
85
|
+
options={item.data}
|
|
86
|
+
at="right-overlap"
|
|
87
|
+
parent={activeItem}
|
|
88
|
+
{context}
|
|
89
|
+
on:click
|
|
90
|
+
/>
|
|
91
|
+
{/if}
|
|
92
|
+
{/each}
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<style>
|
|
96
|
+
.wx-menu {
|
|
97
|
+
position: absolute;
|
|
98
|
+
box-shadow: var(--wx-shadow-light);
|
|
99
|
+
|
|
100
|
+
min-width: 125px;
|
|
101
|
+
display: flex;
|
|
102
|
+
flex-direction: column;
|
|
103
|
+
z-index: 20;
|
|
104
|
+
border-radius: var(--wx-border-radius);
|
|
105
|
+
background-color: var(--wx-background);
|
|
106
|
+
padding: 4px 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.wx-separator {
|
|
110
|
+
width: 100%;
|
|
111
|
+
border-top: var(--wx-border-medium);
|
|
112
|
+
}
|
|
113
|
+
</style>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { createEventDispatcher } from "svelte";
|
|
3
|
+
|
|
4
|
+
import ActionMenu from "./ActionMenu.svelte";
|
|
5
|
+
import { prepareMenuData } from "../helpers";
|
|
6
|
+
|
|
7
|
+
const dispatch = createEventDispatcher();
|
|
8
|
+
|
|
9
|
+
export let css = "";
|
|
10
|
+
export let menuCss = "";
|
|
11
|
+
export let options;
|
|
12
|
+
$: prepareMenuData(options);
|
|
13
|
+
|
|
14
|
+
let active;
|
|
15
|
+
let menuOptions = [];
|
|
16
|
+
let activate = null;
|
|
17
|
+
|
|
18
|
+
function doClick(ev) {
|
|
19
|
+
active = null;
|
|
20
|
+
dispatch("click", ev.detail);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function setMenu(ev, item, trigger) {
|
|
24
|
+
// if the item has a submenu, show it and enable hover mode
|
|
25
|
+
if (item.data && item.data.length) {
|
|
26
|
+
if (active && trigger) {
|
|
27
|
+
// second click on item with submenu disables hover mode
|
|
28
|
+
active = null;
|
|
29
|
+
} else {
|
|
30
|
+
menuOptions = item.data;
|
|
31
|
+
active = item.id;
|
|
32
|
+
activate(ev, item);
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
// hide the submenu
|
|
36
|
+
activate(null);
|
|
37
|
+
// if it was the click action, dispatch it and end hover mode
|
|
38
|
+
if (trigger) {
|
|
39
|
+
dispatch("click", { action: item });
|
|
40
|
+
active = null;
|
|
41
|
+
} else {
|
|
42
|
+
// do not remove active flag, to preserve the hover mode
|
|
43
|
+
active = -1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function onHover(ev, item) {
|
|
49
|
+
if (active) setMenu(ev, item);
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<div class="wx-menubar {css}">
|
|
54
|
+
{#each options as item (item.id)}
|
|
55
|
+
<button
|
|
56
|
+
class="wx-item {active === item.id ? 'wx-active' : ''}"
|
|
57
|
+
on:mouseenter={ev => onHover(ev, item)}
|
|
58
|
+
on:click={ev => setMenu(ev, item, true)}>{item.text}</button
|
|
59
|
+
>
|
|
60
|
+
{/each}
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<ActionMenu
|
|
64
|
+
css={menuCss}
|
|
65
|
+
on:click={doClick}
|
|
66
|
+
options={menuOptions}
|
|
67
|
+
bind:handler={activate}
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
<style>
|
|
71
|
+
.wx-menubar {
|
|
72
|
+
display: flex;
|
|
73
|
+
position: relative;
|
|
74
|
+
width: fit-content;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.wx-item {
|
|
78
|
+
background-color: transparent;
|
|
79
|
+
border: none;
|
|
80
|
+
color: var(--wx-color-font);
|
|
81
|
+
box-sizing: border-box;
|
|
82
|
+
height: 36px;
|
|
83
|
+
line-height: 30px;
|
|
84
|
+
padding: 2px 12px;
|
|
85
|
+
font-family: var(--wx-font-family);
|
|
86
|
+
font-weight: var(--wx-font-weight);
|
|
87
|
+
font-size: var(--wx-font-size);
|
|
88
|
+
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
outline: none;
|
|
91
|
+
white-space: nowrap;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.wx-active,
|
|
95
|
+
.wx-item:hover {
|
|
96
|
+
background-color: var(--wx-background-alt);
|
|
97
|
+
border-radius: var(--wx-button-border-radius);
|
|
98
|
+
}
|
|
99
|
+
</style>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getItemHandler } from "../helpers";
|
|
3
|
+
|
|
4
|
+
export let item;
|
|
5
|
+
export let showSub = false;
|
|
6
|
+
export let activeItem = null;
|
|
7
|
+
|
|
8
|
+
function onHover() {
|
|
9
|
+
showSub = item.data ? item.id : false;
|
|
10
|
+
activeItem = this;
|
|
11
|
+
}
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
15
|
+
<div
|
|
16
|
+
class="wx-item {item.css || ''}"
|
|
17
|
+
data-id={item.id}
|
|
18
|
+
on:mouseenter={onHover}
|
|
19
|
+
on:click
|
|
20
|
+
>
|
|
21
|
+
{#if item.icon}<i class="wx-icon {item.icon}" />{/if}
|
|
22
|
+
{#if item.type}
|
|
23
|
+
<svelte:component this={getItemHandler(item.type)} {item} />
|
|
24
|
+
{:else}<span class="wx-value"> {item.text} </span>{/if}
|
|
25
|
+
{#if item.subtext}<span class="wx-subtext">{item.subtext}</span>{/if}
|
|
26
|
+
{#if item.data}<i class="wx-sub-icon wxi-angle-right" />{/if}
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<style>
|
|
30
|
+
.wx-item {
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
box-sizing: border-box;
|
|
34
|
+
height: 36px;
|
|
35
|
+
line-height: 36px;
|
|
36
|
+
padding: 2px 12px;
|
|
37
|
+
font-family: var(--wx-font-family);
|
|
38
|
+
font-weight: var(--wx-font-weight);
|
|
39
|
+
font-size: var(--wx-font-size);
|
|
40
|
+
background-color: var(--wx-background);
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.wx-item:hover {
|
|
45
|
+
background: var(--wx-background-alt);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.wx-item:first-child {
|
|
49
|
+
border-top-left-radius: inherit;
|
|
50
|
+
border-top-right-radius: inherit;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.wx-item:last-child {
|
|
54
|
+
border-bottom-left-radius: inherit;
|
|
55
|
+
border-bottom-right-radius: inherit;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.wx-value {
|
|
59
|
+
flex-grow: 1;
|
|
60
|
+
white-space: nowrap;
|
|
61
|
+
color: var(--wx-color-font);
|
|
62
|
+
}
|
|
63
|
+
.wx-icon,
|
|
64
|
+
.wx-sub-icon {
|
|
65
|
+
vertical-align: middle;
|
|
66
|
+
height: inherit;
|
|
67
|
+
line-height: inherit;
|
|
68
|
+
font-size: var(--wx-icon-size);
|
|
69
|
+
color: var(--wx-icon-color);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.wx-icon {
|
|
73
|
+
margin-right: 8px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.wx-subtext {
|
|
77
|
+
color: var(--wx-color-font-disabled);
|
|
78
|
+
margin-left: 20px;
|
|
79
|
+
white-space: nowrap;
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function walkData(data, cb) {
|
|
2
|
+
data.forEach(a => {
|
|
3
|
+
cb(a);
|
|
4
|
+
if (a.data && a.data.length) walkData(a.data, cb);
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function filterMenu(data, cb) {
|
|
9
|
+
const out = [];
|
|
10
|
+
data.forEach(a => {
|
|
11
|
+
if (a.data) {
|
|
12
|
+
const sub = filterMenu(a.data, cb);
|
|
13
|
+
if (sub.length) out.push({ ...a, data: sub });
|
|
14
|
+
} else {
|
|
15
|
+
if (cb(a)) out.push(a);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let uid = 1;
|
|
23
|
+
export function prepareMenuData(data) {
|
|
24
|
+
walkData(data, a => {
|
|
25
|
+
a.id = a.id || uid++;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const handlers = {};
|
|
32
|
+
export function getItemHandler(type) {
|
|
33
|
+
return handlers[type];
|
|
34
|
+
}
|
|
35
|
+
export function registerMenuItem(type, handler) {
|
|
36
|
+
handlers[type] = handler;
|
|
37
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import Menu from "./components/Menu.svelte";
|
|
2
|
+
import MenuBar from "./components/MenuBar.svelte";
|
|
3
|
+
import DropDownMenu from "./components/DropDownMenu.svelte";
|
|
4
|
+
import ContextMenu from "./components/ContextMenu.svelte";
|
|
5
|
+
import ActionMenu from "./components/ActionMenu.svelte";
|
|
6
|
+
|
|
7
|
+
export { registerMenuItem } from "./helpers";
|
|
8
|
+
export { Menu, MenuBar, DropDownMenu, ContextMenu, ActionMenu };
|
package/whatsnew.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
### 1.3.0
|
|
2
|
+
|
|
3
|
+
- [dev] public release, using core@1.3.0
|
|
4
|
+
|
|
5
|
+
### 1.2.4
|
|
6
|
+
|
|
7
|
+
- [update] ability to define custom css class for top menu and submenu's containers
|
|
8
|
+
- [fix] incorrect z-index when shown from popup
|
|
9
|
+
|
|
10
|
+
### 1.2.3
|
|
11
|
+
|
|
12
|
+
- [update] when menu is closed it doesn't affect other popup elements
|
|
13
|
+
- [fix] regression in repositioning menu when clicking on next active area
|
|
14
|
+
|
|
15
|
+
### 1.2.1
|
|
16
|
+
|
|
17
|
+
- [fix] regression in popup closing in some cases
|
|
18
|
+
|
|
19
|
+
### 1.2.0
|
|
20
|
+
|
|
21
|
+
- [deps] uses core@1.2.0
|
|
22
|
+
|
|
23
|
+
### 1.1.1
|
|
24
|
+
|
|
25
|
+
- [fix] incorrect positioning in "point" mode when menu initialized not as child of document.body
|
|
26
|
+
- [fix] submenus lost context values
|
|
27
|
+
|
|
28
|
+
### 1.1.0
|
|
29
|
+
|
|
30
|
+
- [update] visual improvements
|
|
31
|
+
- [fix] incorrect auto-position logic
|
|
32
|
+
|
|
33
|
+
### 0.0.1-rc21
|
|
34
|
+
|
|
35
|
+
- [fix] space between icon and text
|
|
36
|
+
|
|
37
|
+
### 0.0.1-rc20
|
|
38
|
+
|
|
39
|
+
- [add] auto-fit for sub menus
|
|
40
|
+
|
|
41
|
+
### 0.0.1-rc19
|
|
42
|
+
|
|
43
|
+
- [add] MenuBar - top level menu bar
|
|
44
|
+
|
|
45
|
+
### 0.0.1-rc18
|
|
46
|
+
|
|
47
|
+
- menu doesn't require that all items have an unique IDs ( will generate missed ids on its own )
|
|
48
|
+
|
|
49
|
+
### 0.0.1-rc17
|
|
50
|
+
|
|
51
|
+
- fix position in scrolled container
|
|
52
|
+
- temporary fix for cypress tests
|
|
53
|
+
|
|
54
|
+
### 0.0.1-rc11
|
|
55
|
+
|
|
56
|
+
- updated styles
|
|
57
|
+
- bottom-fit mode
|
|
58
|
+
|
|
59
|
+
### 0.0.1-rc10
|
|
60
|
+
|
|
61
|
+
#### Fixes
|
|
62
|
+
|
|
63
|
+
- incorrect position in scrollable container
|
|
64
|
+
- activation area can't contain complex markup inside
|
|
65
|
+
- missed hover styling
|
|
66
|
+
|
|
67
|
+
#### New features
|
|
68
|
+
|
|
69
|
+
- ability to init menu in some specific container
|
|
70
|
+
- ability to define subtext ( hotkey, etc. )
|
|
71
|
+
- ability to use custom components as menu items
|
|
72
|
+
- ability to use left and bottom-left position strategies
|