Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
"clean": "pnpm -r run clean",
"clean:deep": "pnpm -r run clean:deep",
"example": "node ./utils/ExampleRunner/example-runner-cli.js",
"example:share": "cross-env CS3D_ALLOW_LAN=1 node ./utils/ExampleRunner/example-runner-cli.js",
"tunnel": "cloudflared tunnel --url http://localhost:3000",
"all-examples": "node ./utils/ExampleRunner/build-all-examples-cli.js --fromRoot",
"build-all-examples": "cross-env NODE_OPTIONS=--max_old_space_size=32896 node ./utils/ExampleRunner/build-all-examples-cli.js --build --fromRoot || (echo 'Build failed - likely due to memory constraints' && exit 1)",
"serve-static-examples": "pnpm dlx serve .static-examples --listen 3333",
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/RenderingEngine/BaseRenderingEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import viewportTypeUsesCustomRenderingPipeline, {
viewportUsesCustomRenderingPipeline,
} from './helpers/viewportTypeUsesCustomRenderingPipeline';
import getOrCreateCanvas from './helpers/getOrCreateCanvas';
import {
setElementTouchActionNone,
restoreElementTouchAction,
} from './helpers/elementTouchAction';
import {
getShouldUseCPURendering,
getUseGenericViewport,
Expand Down Expand Up @@ -567,6 +571,10 @@ abstract class BaseRenderingEngine {
// Make the element not focusable, we use this for modifier keys to work
element.tabIndex = -1;

// Deliver touch input to cornerstone tools instead of the browser
// (scroll, pinch-zoom, double-tap zoom). Restored in _resetViewport.
setElementTouchActionNone(element);

const canvas = getOrCreateCanvas(element);

// Add a viewport with no offset
Expand Down Expand Up @@ -718,6 +726,7 @@ abstract class BaseRenderingEngine {

element.removeAttribute('data-viewport-uid');
element.removeAttribute('data-rendering-engine-uid');
restoreElementTouchAction(element);

// clear drawing
const context = canvas.getContext('2d');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import viewportTypeUsesCustomRenderingPipeline, {
import getOrCreateCanvas, {
updateCanvasSizeAndAspectRatio,
} from './helpers/getOrCreateCanvas';
import { setElementTouchActionNone } from './helpers/elementTouchAction';
import type * as EventTypes from '../types/EventTypes';
import type {
ViewportInput,
Expand Down Expand Up @@ -92,6 +93,10 @@ class ContextPoolRenderingEngine extends BaseRenderingEngine {

element.tabIndex = -1;

// Deliver touch input to cornerstone tools instead of the browser
// (scroll, pinch-zoom, double-tap zoom). Restored in _resetViewport.
setElementTouchActionNone(element);

// Assign viewport to a context
// Stack viewports can be distributed across contexts. Planar Next may mount
// volume-backed GPU renderings, which cannot safely share volume textures
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/RenderingEngine/TiledRenderingEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type IStackViewport from '../types/IStackViewport';
import type IVolumeViewport from '../types/IVolumeViewport';
import { vtkOffscreenMultiRenderWindow } from './vtkClasses';
import { attachWebGLContextEvents } from './helpers/attachWebGLContextEvents';
import { setElementTouchActionNone } from './helpers/elementTouchAction';

import type * as EventTypes from '../types/EventTypes';
import type {
Expand Down Expand Up @@ -144,6 +145,10 @@ class TiledRenderingEngine extends BaseRenderingEngine {
// Make the element not focusable, we use this for modifier keys to work
element.tabIndex = -1;

// Deliver touch input to cornerstone tools instead of the browser
// (scroll, pinch-zoom, double-tap zoom). Restored in _resetViewport.
setElementTouchActionNone(element);

const { offScreenCanvasWidth, offScreenCanvasHeight, xOffset } =
offscreenCanvasProperties;

Expand Down
31 changes: 31 additions & 0 deletions packages/core/src/RenderingEngine/helpers/elementTouchAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const PRIOR_TOUCH_ACTION_ATTRIBUTE = 'data-prior-touch-action';

/**
* Sets `touch-action: none` on an enabled viewport element so the browser
* does not consume touch input (scrolling, pinch-zoom, double-tap zoom)
* before cornerstone tools receive the events. The prior inline value is
* stored on the element so disable can restore it.
*/
export function setElementTouchActionNone(element: HTMLDivElement): void {
if (!element.hasAttribute(PRIOR_TOUCH_ACTION_ATTRIBUTE)) {
element.setAttribute(
PRIOR_TOUCH_ACTION_ATTRIBUTE,
element.style.touchAction
);
}
element.style.touchAction = 'none';
}

/**
* Restores the inline `touch-action` value the element had before
* setElementTouchActionNone. No-op for elements that were never enabled.
*/
export function restoreElementTouchAction(element: HTMLDivElement): void {
if (!element.hasAttribute(PRIOR_TOUCH_ACTION_ATTRIBUTE)) {
return;
}
element.style.touchAction = element.getAttribute(
PRIOR_TOUCH_ACTION_ATTRIBUTE
);
element.removeAttribute(PRIOR_TOUCH_ACTION_ATTRIBUTE);
}
4 changes: 3 additions & 1 deletion packages/core/src/utilities/getCurrentVolumeViewportSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ function getCurrentVolumeViewportSlice(viewport: IVolumeViewport) {
// Using glMatrix.equals() because the number may be not exactly equal to
// 1 due to rounding issues
if (!glMatrix.equals(1, maxIJKRowVec) || !glMatrix.equals(1, maxIJKColVec)) {
throw new Error('Livewire is not available for rotate/oblique viewports');
throw new Error(
'getCurrentVolumeViewportSlice is not available for rotate/oblique viewports'
);
}

const { voxelManager } = viewport.getImageData();
Expand Down
40 changes: 40 additions & 0 deletions packages/docs/docs/migration-guides/5x/1-migration-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,43 @@ The legacy `moduleResolution: "node"` (a.k.a. `node10`) does **not** map a
as `any` or fail to resolve. This is a **type-resolution** concern only —
runtime behavior is unaffected — but if you see missing types, switch to
`"bundler"`/`"node16"`/`"nodenext"`.

## Viewport elements set `touch-action: none`

### What Changed

The rendering engine now sets `touch-action: none` on every element it enables
as a viewport, and restores the element's prior inline value when the viewport
is disabled. Previously this was left to the application.

### Why This Matters

Without `touch-action: none`, the browser claims viewport gestures before
Cornerstone sees them: a one-finger drag scrolls the page instead of running the
active tool, a two-finger pinch zooms the document rather than the image, and a
double-tap triggers the browser's own zoom. Touch tools cannot work on an
element the browser is still handling, which is why this is applied
unconditionally rather than behind a configuration flag — there is no
useful behavior to preserve on the other side of the switch.

The visible consequence is that **dragging on a viewport no longer scrolls the
page** on touch devices. Applications that relied on a viewport being a valid
place to start a page scroll need to provide scrollable area around the
viewport instead.

Two notes on scope:

- Only viewport elements are affected. The rest of your layout is untouched.
- The value is applied inline, so it overrides a `touch-action` coming from a
CSS class for the duration that the viewport is enabled. On disable the
element's original inline value is restored, and any CSS-supplied value takes
effect again.

### Migration Guidance

- **Remove application-level workarounds.** If you set `touch-action: none` (or
attached `preventDefault` touch listeners) on viewport elements to get touch
tools working, that code is now redundant and can be deleted.
- **Check your scroll affordances on small screens.** If a page relied on
viewport drags to scroll, add padding, a scroll container, or a gutter outside
the viewport elements so the page remains scrollable on a phone or tablet.
Loading
Loading