본문 바로가기
카테고리 없음

t

by keisoft 2026. 9. 17.

원인은 `sheet-pointer-input.js`의 `onPointerDown`이 `closest('.bds-sheet-cell')`를 못 찾으면 `if (!args) return`으로 .NET 호출 자체를 안 하기 때문입니다. 즉 no-data 영역 우클릭은 **PointerDown이 아예 안 들어오고, 직전 헤더 클릭 값이 그대로 남아있어서** 메뉴에 계속 보입니다.

## 수정 — `Datasheet.razor.cs` 3군데

```csharp
private bool _pointerHitCell;

private void HandleCellMouseDown(object? sender, SheetPointerEventArgs args)
{
    _pointerHitCell = true;          // ← 추가
    LastPointerRow = args.Row;
    LastPointerCol = args.Col;
    ...
}

// 새 핸들러
private Task<bool> HandleWindowMouseDown(MouseEventArgs e)
{
    if (!_pointerHitCell)            // 셀/헤더가 아닌 곳을 눌렀다 → 좌표 무효화
    {
        LastPointerRow = -1;
        LastPointerCol = -1;
    }
    _pointerHitCell = false;
    return Task.FromResult(false);
}
```

```csharp
private async Task AddWindowEventsAsync()
{
    ...
    await _windowEventService.RegisterMouseEvent("mouseup", HandleWindowMouseUp);
    await _windowEventService.RegisterMouseEvent("mousedown", HandleWindowMouseDown); // ← 추가
}
```

## 왜 순서가 안전한가

이벤트 순서가 `시트 요소 pointerdown` → `window mousedown` → `contextmenu` 입니다. 헤더 우클릭이면 플래그가 먼저 true가 되어 리셋을 건너뛰고, no-data 영역이면 pointerdown이 없어서 window mousedown에서 -1로 초기화된 뒤 메뉴가 열립니다. 메뉴 항목 조건(`LastPointerRow == -1 && LastPointerCol >= 0`)은 그대로 두면 됩니다.

참고로 JS에서 `if (!args)`일 때 row/col을 -1/-1로 리포트하는 방법도 있지만, 그 값이 `HandlePointerDown`까지 흘러가 `ClearSelections()`와 에디터 Accept 경로를 타므로 권장하지 않습니다.




맞습니다, 앞의 window mousedown 방식은 틀렸습니다. 메뉴 팝오버는 `_sheetContainer` 밖에 있어서 메뉴 항목을 누르는 순간 리셋이 걸리고, 재렌더로 항목이 사라져 OnClick이 안 탑니다. 상태를 클릭마다 갱신할 게 아니라 **우클릭(메뉴가 열리는 그 순간)에만 딱 한 번 기록**해야 합니다.

## 0. 되돌리기

`_pointerHitCell`, `HandleWindowMouseDown`, `RegisterMouseEvent("mousedown", ...)`, 그리고 `HandleCellMouseDown` 안의 `LastPointerRow/Col` 대입을 모두 제거하세요.

## 1. `wwwroot/js/menu-target.js`

```js
handleContextMenu(e) {
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.hasAttribute("contentEditable"))
        return

    e.preventDefault()

    const cell = e.target.closest('.bds-sheet-cell')
    const hasPos = cell && cell.dataset.row !== undefined && cell.dataset.col !== undefined

    this.dotnetHelper.invokeMethodAsync(this.handlerName,
        {clientX: e.clientX, clientY: e.clientY},
        hasPos ? parseInt(cell.dataset.row) : -1,
        hasPos ? parseInt(cell.dataset.col) : -1)
}
```

## 2. `Menu/SheetMenuTarget.razor`

```csharp
@using BlazorDatasheet.DataStructures.Geometry

[Parameter] public EventCallback<CellPosition> OnContextMenuTarget { get; set; }

[JSInvokable(nameof(TargetOnContextMenu))]
public async Task TargetOnContextMenu(MouseEventArgs obj, int row, int col)
{
    if (Trigger != MenuTrigger.OnContextMenu)
        return;

    if (OnContextMenuTarget.HasDelegate)
        await OnContextMenuTarget.InvokeAsync(new CellPosition(row, col));

    await ShowMenu(MenuTrigger.OnContextMenu, obj);
}
```

## 3. `Datasheet.razor` / `Datasheet.razor.cs`

```razor
<SheetMenuTarget MenuId="SelectionMenu"
                 MenuData="_sheet"
                 OnContextMenuTarget="HandleContextMenuTarget"
                 Margin="0"
                 ... 나머지 그대로 />
```

```csharp
public int LastPointerRow { get; private set; } = -1;
public int LastPointerCol { get; private set; } = -1;

private void HandleContextMenuTarget(CellPosition pos)
{
    LastPointerRow = pos.row;
    LastPointerCol = pos.col;
}
```

이제 값이 바뀌는 시점은 우클릭 한 번뿐입니다. 헤더 우클릭이면 `(-1, col)`, no-data 영역이면 `.bds-sheet-cell`이 없으므로 `(-1, -1)`로 들어와 항목이 숨겨지고, 메뉴 항목 클릭 시에는 아무것도 갱신되지 않아 OnClick이 정상적으로 발생합니다. 페이지 쪽 조건식(`LastPointerRow == -1 && LastPointerCol >= 0`)은 그대로 두시면 됩니다.

한 가지: 헤더의 캐럿(▾) 버튼 메뉴는 `MenuTrigger.OnClick` 경로라 이 값이 갱신되지 않습니다. 안 쓰신다면 `MenuOptions.HeaderMenuEnabled = false`로 끄는 편이 혼선이 없습니다.