mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-18 19:46:36 +00:00
extract ContextMenuInner.svelte
This commit is contained in:
parent
92d1eba45c
commit
3308fd4116
3 changed files with 146 additions and 110 deletions
|
@ -1,4 +1,6 @@
|
|||
<script>
|
||||
import ContextMenuInner from "./ContextMenuInner.svelte";
|
||||
|
||||
/**
|
||||
* Set to `true` to open the menu
|
||||
* Either `x` and `y` must be greater than zero
|
||||
|
@ -14,91 +16,29 @@
|
|||
/** Obtain a reference to the unordered list HTML element */
|
||||
export let ref = null;
|
||||
|
||||
import {
|
||||
setContext,
|
||||
getContext,
|
||||
afterUpdate,
|
||||
createEventDispatcher,
|
||||
} from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
import { getContext } from "svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const position = writable([x, y]);
|
||||
const currentIndex = writable(-1);
|
||||
const hasPopup = writable(false);
|
||||
const ctx = getContext("ContextMenu");
|
||||
|
||||
let options = [];
|
||||
let direction = 1;
|
||||
let prevX = 0;
|
||||
let prevY = 0;
|
||||
let focusIndex = -1;
|
||||
let level;
|
||||
$: level = !ctx ? 1 : 2;
|
||||
|
||||
function close() {
|
||||
open = false;
|
||||
x = 0;
|
||||
y = 0;
|
||||
prevX = 0;
|
||||
prevY = 0;
|
||||
focusIndex = -1;
|
||||
}
|
||||
|
||||
setContext("ContextMenu", {
|
||||
currentIndex,
|
||||
position,
|
||||
close,
|
||||
setPopup: (popup) => {
|
||||
hasPopup.set(popup);
|
||||
},
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
if (open) {
|
||||
options = [...ref.querySelectorAll("li[data-nested='false']")];
|
||||
|
||||
if (level === 1) {
|
||||
if (prevX !== x || prevY !== y) ref.focus();
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
function onContextMenu(e) {
|
||||
if (level > 1) return;
|
||||
open = true;
|
||||
x = e.x;
|
||||
y = e.y;
|
||||
}
|
||||
|
||||
dispatch("open");
|
||||
} else {
|
||||
dispatch("close");
|
||||
}
|
||||
|
||||
if (!$hasPopup && options[focusIndex]) options[focusIndex].focus();
|
||||
});
|
||||
|
||||
$: level = !ctx ? 1 : 2;
|
||||
$: currentIndex.set(focusIndex);
|
||||
</script>
|
||||
|
||||
<svelte:window
|
||||
on:contextmenu|preventDefault="{(e) => {
|
||||
if (level > 1) return;
|
||||
|
||||
const { height, width } = ref.getBoundingClientRect();
|
||||
|
||||
if (open || x === 0) {
|
||||
if (window.innerWidth - width < e.x) {
|
||||
x = e.x - width;
|
||||
} else {
|
||||
x = e.x;
|
||||
}
|
||||
}
|
||||
|
||||
if (open || y === 0) {
|
||||
if (window.innerHeight - height < e.y) {
|
||||
y = e.y - height;
|
||||
} else {
|
||||
y = e.y;
|
||||
}
|
||||
}
|
||||
position.set([x, y]);
|
||||
open = true;
|
||||
}}"
|
||||
on:contextmenu|preventDefault="{onContextMenu}"
|
||||
on:click="{(e) => {
|
||||
if (!open) return;
|
||||
if (e.target.contains(ref)) close();
|
||||
|
@ -108,41 +48,6 @@
|
|||
}}"
|
||||
/>
|
||||
|
||||
<ul
|
||||
bind:this="{ref}"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
data-direction="{direction}"
|
||||
data-level="{level}"
|
||||
class:bx--context-menu="{true}"
|
||||
class:bx--context-menu--open="{open}"
|
||||
class:bx--context-menu--invisible="{open && x === 0 && y === 0}"
|
||||
class:bx--context-menu--root="{level === 1}"
|
||||
{...$$restProps}
|
||||
style="left: {x}px; top: {y}px; {$$restProps.style}"
|
||||
on:click
|
||||
on:click="{({ target }) => {
|
||||
const closestOption = target.closest('[tabindex]');
|
||||
|
||||
if (closestOption && closestOption.getAttribute('role') !== 'menuitem') {
|
||||
close();
|
||||
}
|
||||
}}"
|
||||
on:keydown
|
||||
on:keydown="{(e) => {
|
||||
if (open) e.preventDefault();
|
||||
if ($hasPopup) return;
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
if (focusIndex < options.length - 1) focusIndex++;
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
if (focusIndex === -1) {
|
||||
focusIndex = options.length - 1;
|
||||
} else {
|
||||
if (focusIndex > 0) focusIndex--;
|
||||
}
|
||||
}
|
||||
}}"
|
||||
>
|
||||
<ContextMenuInner bind:open bind:ref x="{x}" y="{y}">
|
||||
<slot />
|
||||
</ul>
|
||||
</ContextMenuInner>
|
||||
|
|
126
src/ContextMenu/ContextMenuInner.svelte
Normal file
126
src/ContextMenu/ContextMenuInner.svelte
Normal file
|
@ -0,0 +1,126 @@
|
|||
<script>
|
||||
/**
|
||||
* Set to `true` to open the menu
|
||||
* Either `x` and `y` must be greater than zero
|
||||
*/
|
||||
export let open = false;
|
||||
|
||||
/** Specify the horizontal offset of the menu position */
|
||||
export let x = 0;
|
||||
|
||||
/** Specify the vertical offset of the menu position */
|
||||
export let y = 0;
|
||||
|
||||
/** Obtain a reference to the unordered list HTML element */
|
||||
export let ref = null;
|
||||
|
||||
import {
|
||||
setContext,
|
||||
getContext,
|
||||
afterUpdate,
|
||||
createEventDispatcher,
|
||||
} from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const position = writable([x, y]);
|
||||
const dimensions = writable([0, 0]);
|
||||
const currentIndex = writable(-1);
|
||||
const hasPopup = writable(false);
|
||||
const ctx = getContext("ContextMenu");
|
||||
|
||||
let options = [];
|
||||
let direction = 1;
|
||||
let prevX = 0;
|
||||
let prevY = 0;
|
||||
let focusIndex = -1;
|
||||
let level;
|
||||
|
||||
$: level = !ctx ? 1 : 2;
|
||||
$: $position = [x, y];
|
||||
$: $currentIndex = focusIndex;
|
||||
$: if (open && level === 1 && ref != null) {
|
||||
const { height, width } = ref.getBoundingClientRect();
|
||||
$dimensions = [width, height];
|
||||
|
||||
// if the menu is too far to the right, display it on the left side of the cursor
|
||||
if (window.innerWidth - width < x) x -= width;
|
||||
|
||||
// if the menu is too far down, display it above the cursor
|
||||
if (window.innerHeight - height < y) y -= height;
|
||||
}
|
||||
|
||||
function close() {
|
||||
open = false;
|
||||
x = 0;
|
||||
y = 0;
|
||||
prevX = 0;
|
||||
prevY = 0;
|
||||
focusIndex = -1;
|
||||
}
|
||||
|
||||
setContext("ContextMenu", {
|
||||
currentIndex,
|
||||
position,
|
||||
dimensions,
|
||||
close,
|
||||
setPopup: (popup) => ($hasPopup = popup),
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
if (open) {
|
||||
options = [...ref.querySelectorAll("li[data-nested='false']")];
|
||||
|
||||
if (level === 1) {
|
||||
if (prevX !== x || prevY !== y) ref.focus();
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
}
|
||||
|
||||
dispatch("open");
|
||||
} else {
|
||||
dispatch("close");
|
||||
}
|
||||
|
||||
if (!$hasPopup && options[focusIndex]) options[focusIndex].focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
<ul
|
||||
bind:this="{ref}"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
data-direction="{direction}"
|
||||
data-level="{level}"
|
||||
class:bx--context-menu="{true}"
|
||||
class:bx--context-menu--open="{open}"
|
||||
class:bx--context-menu--invisible="{open && x === 0 && y === 0}"
|
||||
class:bx--context-menu--root="{level === 1}"
|
||||
{...$$restProps}
|
||||
style="left: {x}px; top: {y}px; {$$restProps.style}"
|
||||
on:click
|
||||
on:click="{({ target }) => {
|
||||
const closestOption = target.closest('[tabindex]');
|
||||
|
||||
if (closestOption && closestOption.getAttribute('role') !== 'menuitem') {
|
||||
close();
|
||||
}
|
||||
}}"
|
||||
on:keydown
|
||||
on:keydown="{(e) => {
|
||||
if (open) e.preventDefault();
|
||||
if ($hasPopup) return;
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
if (focusIndex < options.length - 1) focusIndex++;
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
if (focusIndex === -1) {
|
||||
focusIndex = options.length - 1;
|
||||
} else {
|
||||
if (focusIndex > 0) focusIndex--;
|
||||
}
|
||||
}
|
||||
}}"
|
||||
>
|
||||
<slot />
|
||||
</ul>
|
|
@ -59,6 +59,7 @@
|
|||
const ctxRadioGroup = getContext("ContextMenuRadioGroup");
|
||||
|
||||
const rootMenuPosition = ctx.position;
|
||||
const rootMenuDimensions = ctx.dimensions;
|
||||
|
||||
// "moderate-01" duration (ms) from Carbon motion recommended for small expansion, short distance movements
|
||||
const moderate01 = 150;
|
||||
|
@ -120,11 +121,15 @@
|
|||
$: ctx.setPopup(submenuOpen);
|
||||
$: if (submenuOpen) {
|
||||
const rootMenuX = $rootMenuPosition[0];
|
||||
const rootMenuWidth = $rootMenuDimensions[0];
|
||||
const { width, y } = ref.getBoundingClientRect();
|
||||
let x = rootMenuX + width;
|
||||
|
||||
if (window.innerWidth - rootMenuX < width) {
|
||||
let x;
|
||||
if (window.innerWidth < rootMenuX + rootMenuWidth + width) {
|
||||
// submenu is too far to the right, so we display it on the left side
|
||||
x = rootMenuX - width;
|
||||
} else {
|
||||
x = rootMenuX + width;
|
||||
}
|
||||
|
||||
submenuPosition = [x, y];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue