mirror of
https://github.com/BloopAI/vibe-kanban-web-companion.git
synced 2026-08-23 11:58:32 +00:00
Fix lint issues
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
"build": "turbo run build --filter=click-to-*-component",
|
||||
"env": "env",
|
||||
"dev": "turbo run dev --parallel",
|
||||
"lint": "turbo run lint",
|
||||
"lint": "turbo run lint --filter=click-to-*-component",
|
||||
"prerelease": "turbo run build",
|
||||
"release": "changeset publish"
|
||||
},
|
||||
|
||||
@@ -29,282 +29,276 @@ import { getPropsForInstance } from './getPropsForInstance.js'
|
||||
import { getReactInstancesForElement } from './getReactInstancesForElement.js'
|
||||
import { getSourceForInstance } from './getSourceForInstance.js'
|
||||
|
||||
export const ContextMenu = React.forwardRef(
|
||||
(
|
||||
/** @type {Props} */
|
||||
props,
|
||||
ref
|
||||
) => {
|
||||
const { onClose, pathModifier } = props
|
||||
// @ts-expect-error
|
||||
export const ContextMenu = React.forwardRef((props, ref) => {
|
||||
// @ts-expect-error
|
||||
const { onClose, pathModifier } = props
|
||||
|
||||
const [target, setTarget] = React.useState(
|
||||
/** @type {HTMLElement | null} */
|
||||
(null)
|
||||
)
|
||||
const [target, setTarget] = React.useState(
|
||||
/** @type {HTMLElement | null} */
|
||||
(null)
|
||||
)
|
||||
|
||||
const arrowRef = React.useRef(
|
||||
/** @type {HTMLElement | null} */
|
||||
(null)
|
||||
)
|
||||
const arrowRef = React.useRef(
|
||||
/** @type {HTMLElement | null} */
|
||||
(null)
|
||||
)
|
||||
|
||||
const [activeIndex, setActiveIndex] = React.useState(
|
||||
/** @type {number | null} */
|
||||
(null)
|
||||
)
|
||||
const [activeIndex, setActiveIndex] = React.useState(
|
||||
/** @type {number | null} */
|
||||
(null)
|
||||
)
|
||||
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
const listItemsRef = React.useRef(
|
||||
/** @type {Array<HTMLButtonElement | null>} */
|
||||
([])
|
||||
)
|
||||
const listItemsRef = React.useRef(
|
||||
/** @type {Array<HTMLButtonElement | null>} */
|
||||
([])
|
||||
)
|
||||
|
||||
const {
|
||||
x,
|
||||
y,
|
||||
reference,
|
||||
floating,
|
||||
strategy,
|
||||
refs,
|
||||
update,
|
||||
context,
|
||||
placement,
|
||||
middlewareData: { arrow: { x: arrowX, y: arrowY } = {} },
|
||||
} = useFloating({
|
||||
open,
|
||||
onOpenChange(open) {
|
||||
setOpen(open)
|
||||
const {
|
||||
x,
|
||||
y,
|
||||
reference,
|
||||
floating,
|
||||
strategy,
|
||||
refs,
|
||||
update,
|
||||
context,
|
||||
placement,
|
||||
middlewareData: { arrow: { x: arrowX, y: arrowY } = {} },
|
||||
} = useFloating({
|
||||
open,
|
||||
onOpenChange(open) {
|
||||
setOpen(open)
|
||||
|
||||
if (!open) onClose?.()
|
||||
},
|
||||
middleware: [
|
||||
offset({ mainAxis: 5, alignmentAxis: 4 }),
|
||||
flip(),
|
||||
shift(),
|
||||
arrow({ element: arrowRef }),
|
||||
],
|
||||
placement: 'right',
|
||||
})
|
||||
if (!open) onClose?.()
|
||||
},
|
||||
middleware: [
|
||||
offset({ mainAxis: 5, alignmentAxis: 4 }),
|
||||
flip(),
|
||||
shift(),
|
||||
arrow({ element: arrowRef }),
|
||||
],
|
||||
placement: 'right',
|
||||
})
|
||||
|
||||
const { getFloatingProps, getItemProps } = useInteractions([
|
||||
useRole(context, { role: 'menu' }),
|
||||
useDismiss(context),
|
||||
useListNavigation(context, {
|
||||
listRef: listItemsRef,
|
||||
activeIndex,
|
||||
onNavigate: setActiveIndex,
|
||||
focusItemOnOpen: false,
|
||||
}),
|
||||
])
|
||||
const { getFloatingProps, getItemProps } = useInteractions([
|
||||
useRole(context, { role: 'menu' }),
|
||||
useDismiss(context),
|
||||
useListNavigation(context, {
|
||||
listRef: listItemsRef,
|
||||
activeIndex,
|
||||
onNavigate: setActiveIndex,
|
||||
focusItemOnOpen: false,
|
||||
}),
|
||||
])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (open && refs.reference.current && refs.floating.current) {
|
||||
return autoUpdate(refs.reference.current, refs.floating.current, update)
|
||||
}
|
||||
}, [open, update, refs.reference, refs.floating])
|
||||
React.useEffect(() => {
|
||||
if (open && refs.reference.current && refs.floating.current) {
|
||||
return autoUpdate(refs.reference.current, refs.floating.current, update)
|
||||
}
|
||||
}, [open, update, refs.reference, refs.floating])
|
||||
|
||||
const mergedReferenceRef = React.useMemo(
|
||||
() => mergeRefs([ref, reference]),
|
||||
[reference, ref]
|
||||
)
|
||||
const mergedReferenceRef = React.useMemo(
|
||||
() => mergeRefs([ref, reference]),
|
||||
[reference, ref]
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
function onContextMenu(
|
||||
/** @type {MouseEvent} */
|
||||
e
|
||||
) {
|
||||
if (!e.altKey) {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
mergedReferenceRef({
|
||||
getBoundingClientRect() {
|
||||
return {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: e.clientY,
|
||||
right: e.clientX,
|
||||
bottom: e.clientY,
|
||||
left: e.clientX,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
setOpen(true)
|
||||
|
||||
if (e.target instanceof HTMLElement) setTarget(e.target)
|
||||
React.useEffect(() => {
|
||||
function onContextMenu(
|
||||
/** @type {MouseEvent} */
|
||||
e
|
||||
) {
|
||||
if (!e.altKey) {
|
||||
return
|
||||
}
|
||||
|
||||
document.addEventListener('contextmenu', onContextMenu)
|
||||
return () => {
|
||||
document.removeEventListener('contextmenu', onContextMenu)
|
||||
}
|
||||
}, [mergedReferenceRef])
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (open) {
|
||||
refs.floating.current?.focus()
|
||||
}
|
||||
}, [open, refs.floating])
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (!arrowRef.current) return
|
||||
|
||||
const staticSide = {
|
||||
top: 'bottom',
|
||||
right: 'left',
|
||||
bottom: 'top',
|
||||
left: 'right',
|
||||
}[placement.split('-')[0]]
|
||||
|
||||
Object.assign(arrowRef.current.style, {
|
||||
display: 'block',
|
||||
left: arrowX != null ? `${arrowX}px` : '',
|
||||
top: arrowY != null ? `${arrowY}px` : '',
|
||||
right: '',
|
||||
bottom: '',
|
||||
[staticSide]: '-4px',
|
||||
e.preventDefault()
|
||||
mergedReferenceRef({
|
||||
getBoundingClientRect() {
|
||||
return {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: e.clientY,
|
||||
right: e.clientX,
|
||||
bottom: e.clientY,
|
||||
left: e.clientX,
|
||||
}
|
||||
},
|
||||
})
|
||||
}, [arrowX, arrowY, placement])
|
||||
|
||||
if (!target) {
|
||||
return null
|
||||
setOpen(true)
|
||||
|
||||
if (e.target instanceof HTMLElement) setTarget(e.target)
|
||||
}
|
||||
|
||||
const instances = getReactInstancesForElement(target).filter((instance) =>
|
||||
getSourceForInstance(instance)
|
||||
)
|
||||
document.addEventListener('contextmenu', onContextMenu)
|
||||
return () => {
|
||||
document.removeEventListener('contextmenu', onContextMenu)
|
||||
}
|
||||
}, [mergedReferenceRef])
|
||||
|
||||
return html`
|
||||
<style key="click-to-component-contextmenu-style">
|
||||
#floating-ui-root > div {
|
||||
z-index: 2147483647;
|
||||
}
|
||||
React.useLayoutEffect(() => {
|
||||
if (open) {
|
||||
refs.floating.current?.focus()
|
||||
}
|
||||
}, [open, refs.floating])
|
||||
|
||||
[data-click-to-component-contextmenu],
|
||||
[data-click-to-component-contextmenu] * {
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
React.useLayoutEffect(() => {
|
||||
if (!arrowRef.current) return
|
||||
|
||||
[data-click-to-component-contextmenu] {
|
||||
all: unset;
|
||||
outline: 0;
|
||||
background: white;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
overflow: visible;
|
||||
padding: 5px;
|
||||
font-size: 13px;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
const staticSide = {
|
||||
top: 'bottom',
|
||||
right: 'left',
|
||||
bottom: 'top',
|
||||
left: 'right',
|
||||
}[placement.split('-')[0]]
|
||||
|
||||
--shadow-color: 0deg 0% 0%;
|
||||
--shadow-elevation-low: 0px -1px 0.8px hsl(var(--shadow-color) / 0.1),
|
||||
0px -1.2px 0.9px -2.5px hsl(var(--shadow-color) / 0.07),
|
||||
0px -3px 2.3px -5px hsl(var(--shadow-color) / 0.03);
|
||||
Object.assign(arrowRef.current.style, {
|
||||
display: 'block',
|
||||
left: arrowX != null ? `${arrowX}px` : '',
|
||||
top: arrowY != null ? `${arrowY}px` : '',
|
||||
right: '',
|
||||
bottom: '',
|
||||
[staticSide]: '-4px',
|
||||
})
|
||||
}, [arrowX, arrowY, placement])
|
||||
|
||||
--shadow-elevation-medium: 0px 1px 0.8px
|
||||
hsl(var(--shadow-color) / 0.11),
|
||||
0px 1.5px 1.1px -1.7px hsl(var(--shadow-color) / 0.08),
|
||||
0px 5.1px 3.8px -3.3px hsl(var(--shadow-color) / 0.05),
|
||||
0px 15px 11.3px -5px hsl(var(--shadow-color) / 0.03);
|
||||
--shadow-elevation-high: 0px 1px 0.8px hsl(var(--shadow-color) / 0.1),
|
||||
0px 1.1px 0.8px -0.7px hsl(var(--shadow-color) / 0.09),
|
||||
0px 2.1px 1.6px -1.4px hsl(var(--shadow-color) / 0.07),
|
||||
0px 4.9px 3.7px -2.1px hsl(var(--shadow-color) / 0.06),
|
||||
0px 10.1px 7.6px -2.9px hsl(var(--shadow-color) / 0.05),
|
||||
0px 18.9px 14.2px -3.6px hsl(var(--shadow-color) / 0.04),
|
||||
0px 31.9px 23.9px -4.3px hsl(var(--shadow-color) / 0.02),
|
||||
0px 50px 37.5px -5px hsl(var(--shadow-color) / 0.01);
|
||||
if (!target) {
|
||||
return null
|
||||
}
|
||||
|
||||
box-shadow: var(--shadow-elevation-high);
|
||||
filter: drop-shadow(0px 0px 0.5px rgba(0 0 0 / 50%));
|
||||
}
|
||||
const instances = getReactInstancesForElement(target).filter((instance) =>
|
||||
getSourceForInstance(instance)
|
||||
)
|
||||
|
||||
[data-click-to-component-contextmenu] button {
|
||||
all: unset;
|
||||
outline: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
max-width: 50vw;
|
||||
}
|
||||
return html`
|
||||
<style key="click-to-component-contextmenu-style">
|
||||
#floating-ui-root > div {
|
||||
z-index: 2147483647;
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button:focus,
|
||||
[data-click-to-component-contextmenu] button:not([disabled]):active {
|
||||
cursor: pointer;
|
||||
background: royalblue;
|
||||
color: white;
|
||||
box-shadow: var(--shadow-elevation-medium);
|
||||
}
|
||||
[data-click-to-component-contextmenu],
|
||||
[data-click-to-component-contextmenu] * {
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button:focus code,
|
||||
[data-click-to-component-contextmenu]
|
||||
button:not([disabled]):active
|
||||
code {
|
||||
color: white;
|
||||
}
|
||||
[data-click-to-component-contextmenu] {
|
||||
all: unset;
|
||||
outline: 0;
|
||||
background: white;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
overflow: visible;
|
||||
padding: 5px;
|
||||
font-size: 13px;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
|
||||
[data-click-to-component-contextmenu] button > * + * {
|
||||
margin-top: 3px;
|
||||
}
|
||||
--shadow-color: 0deg 0% 0%;
|
||||
--shadow-elevation-low: 0px -1px 0.8px hsl(var(--shadow-color) / 0.1),
|
||||
0px -1.2px 0.9px -2.5px hsl(var(--shadow-color) / 0.07),
|
||||
0px -3px 2.3px -5px hsl(var(--shadow-color) / 0.03);
|
||||
|
||||
[data-click-to-component-contextmenu] button code {
|
||||
color: royalblue;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
'Liberation Mono', 'Courier New', monospace;
|
||||
}
|
||||
--shadow-elevation-medium: 0px 1px 0.8px hsl(var(--shadow-color) / 0.11),
|
||||
0px 1.5px 1.1px -1.7px hsl(var(--shadow-color) / 0.08),
|
||||
0px 5.1px 3.8px -3.3px hsl(var(--shadow-color) / 0.05),
|
||||
0px 15px 11.3px -5px hsl(var(--shadow-color) / 0.03);
|
||||
--shadow-elevation-high: 0px 1px 0.8px hsl(var(--shadow-color) / 0.1),
|
||||
0px 1.1px 0.8px -0.7px hsl(var(--shadow-color) / 0.09),
|
||||
0px 2.1px 1.6px -1.4px hsl(var(--shadow-color) / 0.07),
|
||||
0px 4.9px 3.7px -2.1px hsl(var(--shadow-color) / 0.06),
|
||||
0px 10.1px 7.6px -2.9px hsl(var(--shadow-color) / 0.05),
|
||||
0px 18.9px 14.2px -3.6px hsl(var(--shadow-color) / 0.04),
|
||||
0px 31.9px 23.9px -4.3px hsl(var(--shadow-color) / 0.02),
|
||||
0px 50px 37.5px -5px hsl(var(--shadow-color) / 0.01);
|
||||
|
||||
[data-click-to-component-contextmenu] button code var {
|
||||
background: rgba(0 0 0 / 5%);
|
||||
cursor: help;
|
||||
border-radius: 3px;
|
||||
padding: 3px 6px;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system,
|
||||
BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
box-shadow: var(--shadow-elevation-high);
|
||||
filter: drop-shadow(0px 0px 0.5px rgba(0 0 0 / 50%));
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button cite {
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 11px;
|
||||
opacity: 0.5;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
'Liberation Mono', 'Courier New', monospace;
|
||||
}
|
||||
[data-click-to-component-contextmenu] button {
|
||||
all: unset;
|
||||
outline: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
max-width: 50vw;
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button cite data::after {
|
||||
content: attr(value);
|
||||
float: right;
|
||||
padding-left: 15px;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system,
|
||||
BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
[data-click-to-component-contextmenu] button:focus,
|
||||
[data-click-to-component-contextmenu] button:not([disabled]):active {
|
||||
cursor: pointer;
|
||||
background: royalblue;
|
||||
color: white;
|
||||
box-shadow: var(--shadow-elevation-medium);
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu-arrow] {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background: inherit;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
</style>
|
||||
[data-click-to-component-contextmenu] button:focus code,
|
||||
[data-click-to-component-contextmenu] button:not([disabled]):active code {
|
||||
color: white;
|
||||
}
|
||||
|
||||
${open &&
|
||||
html`
|
||||
[data-click-to-component-contextmenu] button > * + * {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button code {
|
||||
color: royalblue;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
'Liberation Mono', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button code var {
|
||||
background: rgba(0 0 0 / 5%);
|
||||
cursor: help;
|
||||
border-radius: 3px;
|
||||
padding: 3px 6px;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
|
||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button cite {
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 11px;
|
||||
opacity: 0.5;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
'Liberation Mono', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button cite data::after {
|
||||
content: attr(value);
|
||||
float: right;
|
||||
padding-left: 15px;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
|
||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu-arrow] {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background: inherit;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
</style>
|
||||
|
||||
${open &&
|
||||
html`
|
||||
<${FloatingOverlay} key="click-to-component-overlay" lockScroll>
|
||||
<${FloatingFocusManager} context=${context}>
|
||||
<dialog
|
||||
@@ -377,6 +371,5 @@ export const ContextMenu = React.forwardRef(
|
||||
</${FloatingFocusManager}>
|
||||
</${FloatingOverlay}>
|
||||
`}
|
||||
`
|
||||
}
|
||||
)
|
||||
`
|
||||
})
|
||||
|
||||
14
packages/click-to-react-component/src/types.d.ts
vendored
14
packages/click-to-react-component/src/types.d.ts
vendored
@@ -1,12 +1,12 @@
|
||||
export { ClickToComponent } from './ClickToComponent';
|
||||
export { ClickToComponent } from './ClickToComponent'
|
||||
|
||||
export type Editor = 'vscode' | 'vscode-insiders' | 'cursor';
|
||||
export type Editor = 'vscode' | 'vscode-insiders' | 'cursor' | string
|
||||
|
||||
export type PathModifier = (path: string) => string;
|
||||
export type PathModifier = (path: string) => string
|
||||
|
||||
export type ClickToComponentProps = {
|
||||
editor?: Editor;
|
||||
pathModifier?: PathModifier;
|
||||
editor?: Editor
|
||||
pathModifier?: PathModifier
|
||||
}
|
||||
|
||||
export type Coords = [MouseEvent['pageX'], MouseEvent['pageY']]
|
||||
@@ -14,6 +14,6 @@ export type Coords = [MouseEvent['pageX'], MouseEvent['pageY']]
|
||||
export type Target = HTMLElement
|
||||
|
||||
export type ContextMenuProps = {
|
||||
onClose?: () => void;
|
||||
pathModifier?: PathModifier;
|
||||
onClose?: () => void
|
||||
pathModifier?: PathModifier
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user