feat: Add option to disable mousewheel carousel scrolling (#2263)
- Closes: #2220
This commit is contained in:
@@ -41,6 +41,7 @@ live:
|
||||
| `next_previous` | | Configures how the "Next & Previous" controls are shown on the `live` view. See below. |
|
||||
| `thumbnails` | | Configures how thumbnails are shown on the `live` view. See below. |
|
||||
| `timeline` | | Configures how the mini-timeline is shown on the `live` view. See below. |
|
||||
| `wheel` | `true` | Whether to allow mouse wheel to scroll through the carousel. |
|
||||
|
||||
### `next_previous`
|
||||
|
||||
@@ -206,6 +207,7 @@ live:
|
||||
next_previous:
|
||||
style: chevrons
|
||||
size: 48
|
||||
wheel: true
|
||||
ptz:
|
||||
mode: auto
|
||||
position: bottom-right
|
||||
|
||||
@@ -37,6 +37,7 @@ media_viewer:
|
||||
| `next_previous` | | Configures how the "Next & Previous" controls are shown on the media viewer. See below. |
|
||||
| `thumbnails` | | Configures how thumbnails are shown on the media viewer. See below. |
|
||||
| `timeline` | | Configures how the mini-timeline is shown on the media viewer. See below. |
|
||||
| `wheel` | `true` | Whether to allow mouse wheel to scroll through the carousel. |
|
||||
|
||||
### `next_previous`
|
||||
|
||||
@@ -183,6 +184,7 @@ media_viewer:
|
||||
next_previous:
|
||||
size: 48
|
||||
style: thumbnails
|
||||
wheel: true
|
||||
ptz:
|
||||
mode: off
|
||||
position: bottom-right
|
||||
|
||||
@@ -42,6 +42,9 @@ export class AdvancedCameraCardCarousel extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public plugins?: EmblaCarouselPlugins;
|
||||
|
||||
@property({ attribute: false })
|
||||
public wheelScrolling?: boolean;
|
||||
|
||||
@property({ attribute: false })
|
||||
public selected = 0;
|
||||
|
||||
@@ -121,6 +124,7 @@ export class AdvancedCameraCardCarousel extends LitElement {
|
||||
loop: this.loop,
|
||||
plugins: this.plugins,
|
||||
textDirection: getTextDirection(this),
|
||||
wheelScrolling: this.wheelScrolling,
|
||||
},
|
||||
);
|
||||
} else if (changedProps.has('selected')) {
|
||||
|
||||
@@ -339,6 +339,7 @@ export class AdvancedCameraCardLiveCarousel extends LitElement {
|
||||
this._getPlugins.bind(this),
|
||||
)}
|
||||
.selected=${this._getSelectedCameraIndex()}
|
||||
.wheelScrolling=${this.liveConfig?.controls.wheel}
|
||||
transitionEffect=${this._getTransitionEffect()}
|
||||
@advanced-camera-card:carousel:select=${this._setViewHandler.bind(this)}
|
||||
@advanced-camera-card:media:loaded=${() => {
|
||||
|
||||
@@ -339,6 +339,7 @@ export class AdvancedCameraCardViewerCarousel extends LitElement {
|
||||
.dragEnabled=${this.viewerConfig?.draggable ?? true}
|
||||
.plugins=${guard([this.viewerConfig, this._media], this._getPlugins.bind(this))}
|
||||
.selected=${this._selected}
|
||||
.wheelScrolling=${this.viewerConfig?.controls.wheel}
|
||||
transitionEffect=${this._getTransitionEffect()}
|
||||
@advanced-camera-card:carousel:select=${(ev: CustomEvent<CarouselSelected>) => {
|
||||
this._setViewSelectedIndex(ev.detail.index);
|
||||
|
||||
@@ -69,6 +69,7 @@ export const liveConfigDefault = {
|
||||
ptz: ptzControlsDefaults,
|
||||
thumbnails: liveThumbnailControlsDefaults,
|
||||
timeline: miniTimelineConfigDefault,
|
||||
wheel: true,
|
||||
},
|
||||
microphone: {
|
||||
...microphoneConfigDefault,
|
||||
@@ -121,6 +122,7 @@ export const liveConfigSchema = z
|
||||
liveConfigDefault.controls.thumbnails,
|
||||
),
|
||||
timeline: miniTimelineConfigSchema.default(liveConfigDefault.controls.timeline),
|
||||
wheel: z.boolean().default(liveConfigDefault.controls.wheel),
|
||||
})
|
||||
.default(liveConfigDefault.controls),
|
||||
display: viewDisplaySchema,
|
||||
|
||||
@@ -40,6 +40,7 @@ export const viewerConfigDefault = {
|
||||
...ptzControlsDefaults,
|
||||
mode: 'off' as const,
|
||||
},
|
||||
wheel: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -101,6 +102,7 @@ export const viewerConfigSchema = z
|
||||
timeline: miniTimelineConfigSchema.default(
|
||||
viewerConfigDefault.controls.timeline,
|
||||
),
|
||||
wheel: z.boolean().default(viewerConfigDefault.controls.wheel),
|
||||
})
|
||||
.default(viewerConfigDefault.controls),
|
||||
})
|
||||
|
||||
@@ -248,6 +248,8 @@ export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_STYLE =
|
||||
`${CONF_MEDIA_VIEWER}.controls.timeline.style` as const;
|
||||
export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_WINDOW_SECONDS =
|
||||
`${CONF_MEDIA_VIEWER}.controls.timeline.window_seconds` as const;
|
||||
export const CONF_MEDIA_VIEWER_CONTROLS_WHEEL =
|
||||
`${CONF_MEDIA_VIEWER}.controls.wheel` as const;
|
||||
export const CONF_MEDIA_VIEWER_ZOOMABLE = `${CONF_MEDIA_VIEWER}.zoomable` as const;
|
||||
|
||||
const CONF_LIVE = 'live' as const;
|
||||
@@ -271,6 +273,7 @@ export const CONF_LIVE_CONTROLS_PTZ_ORIENTATION =
|
||||
`${CONF_LIVE}.controls.ptz.orientation` as const;
|
||||
export const CONF_LIVE_CONTROLS_PTZ_POSITION =
|
||||
`${CONF_LIVE}.controls.ptz.position` as const;
|
||||
export const CONF_LIVE_CONTROLS_WHEEL = `${CONF_LIVE}.controls.wheel` as const;
|
||||
export const CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA_TYPE =
|
||||
`${CONF_LIVE}.controls.thumbnails.media_type` as const;
|
||||
export const CONF_LIVE_CONTROLS_THUMBNAILS_EVENTS_MEDIA_TYPE =
|
||||
|
||||
@@ -133,6 +133,7 @@ import {
|
||||
CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS,
|
||||
CONF_LIVE_CONTROLS_TIMELINE_STYLE,
|
||||
CONF_LIVE_CONTROLS_TIMELINE_WINDOW_SECONDS,
|
||||
CONF_LIVE_CONTROLS_WHEEL,
|
||||
CONF_LIVE_DISPLAY_GRID_COLUMNS,
|
||||
CONF_LIVE_DISPLAY_GRID_MAX_COLUMNS,
|
||||
CONF_LIVE_DISPLAY_GRID_SELECTED_POSITION,
|
||||
@@ -175,6 +176,7 @@ import {
|
||||
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_SHOW_RECORDINGS,
|
||||
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_STYLE,
|
||||
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_WINDOW_SECONDS,
|
||||
CONF_MEDIA_VIEWER_CONTROLS_WHEEL,
|
||||
CONF_MEDIA_VIEWER_DISPLAY_GRID_COLUMNS,
|
||||
CONF_MEDIA_VIEWER_DISPLAY_GRID_MAX_COLUMNS,
|
||||
CONF_MEDIA_VIEWER_DISPLAY_GRID_SELECTED_POSITION,
|
||||
@@ -2911,6 +2913,13 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
|
||||
label: localize('config.common.controls.builtin'),
|
||||
},
|
||||
)}
|
||||
${this._renderSwitch(
|
||||
CONF_LIVE_CONTROLS_WHEEL,
|
||||
this._defaults.live.controls.wheel,
|
||||
{
|
||||
label: localize('config.common.controls.wheel'),
|
||||
},
|
||||
)}
|
||||
${this._renderNextPreviousControls(
|
||||
MENU_LIVE_CONTROLS_NEXT_PREVIOUS,
|
||||
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||
@@ -3106,6 +3115,13 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
|
||||
label: localize('config.common.controls.builtin'),
|
||||
},
|
||||
)}
|
||||
${this._renderSwitch(
|
||||
CONF_MEDIA_VIEWER_CONTROLS_WHEEL,
|
||||
this._defaults.media_viewer.controls.wheel,
|
||||
{
|
||||
label: localize('config.common.controls.wheel'),
|
||||
},
|
||||
)}
|
||||
${this._renderNextPreviousControls(
|
||||
MENU_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS,
|
||||
CONF_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||
|
||||
@@ -263,7 +263,8 @@
|
||||
"seek-in-camera": "Cerca d'escombratge només a la càmera seleccionada",
|
||||
"seek-in-media": "Cerca d'escombratge només dins de l'element multimèdia seleccionat"
|
||||
}
|
||||
}
|
||||
},
|
||||
"wheel": ""
|
||||
},
|
||||
"display": {
|
||||
"editor_label": "Visualització",
|
||||
|
||||
@@ -263,7 +263,8 @@
|
||||
"seek-in-camera": "Pan seeks within selected camera only",
|
||||
"seek-in-media": "Pan seeks within selected media item only"
|
||||
}
|
||||
}
|
||||
},
|
||||
"wheel": "Scroll wheel scrolls media carousel"
|
||||
},
|
||||
"display": {
|
||||
"editor_label": "Display",
|
||||
|
||||
@@ -263,7 +263,8 @@
|
||||
"seek-in-camera": "Pan ne cherche que parmi la caméra sélectionnée",
|
||||
"seek-in-media": "Pan ne cherche que parmi les médias sélectionnés"
|
||||
}
|
||||
}
|
||||
},
|
||||
"wheel": ""
|
||||
},
|
||||
"display": {
|
||||
"editor_label": "Affichage",
|
||||
|
||||
@@ -263,7 +263,8 @@
|
||||
"seek-in-camera": "",
|
||||
"seek-in-media": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"wheel": ""
|
||||
},
|
||||
"display": {
|
||||
"editor_label": "",
|
||||
|
||||
@@ -263,7 +263,8 @@
|
||||
"seek-in-camera": "",
|
||||
"seek-in-media": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"wheel": ""
|
||||
},
|
||||
"display": {
|
||||
"editor_label": "",
|
||||
|
||||
@@ -263,7 +263,8 @@
|
||||
"seek-in-camera": "",
|
||||
"seek-in-media": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"wheel": ""
|
||||
},
|
||||
"display": {
|
||||
"editor_label": "",
|
||||
|
||||
@@ -26,6 +26,7 @@ export class CarouselController {
|
||||
protected _dragFree: boolean;
|
||||
protected _draggable: boolean;
|
||||
protected _textDirection: TextDirection;
|
||||
protected _wheelScrolling: boolean;
|
||||
|
||||
protected _plugins: EmblaCarouselPlugins;
|
||||
protected _carousel: EmblaCarouselType;
|
||||
@@ -48,6 +49,7 @@ export class CarouselController {
|
||||
dragFree?: boolean;
|
||||
plugins?: EmblaCarouselPlugins;
|
||||
textDirection?: TextDirection;
|
||||
wheelScrolling?: boolean;
|
||||
},
|
||||
) {
|
||||
this._root = root;
|
||||
@@ -60,6 +62,7 @@ export class CarouselController {
|
||||
this._draggable = options?.dragEnabled ?? true;
|
||||
this._plugins = options?.plugins ?? [];
|
||||
this._textDirection = options?.textDirection ?? 'ltr';
|
||||
this._wheelScrolling = options?.wheelScrolling ?? true;
|
||||
|
||||
this._carousel = this._createCarousel(getChildrenFromElement(this._parent));
|
||||
|
||||
@@ -144,7 +147,7 @@ export class CarouselController {
|
||||
},
|
||||
[
|
||||
...this._plugins,
|
||||
...(slides.length > 1
|
||||
...(slides.length > 1 && this._wheelScrolling
|
||||
? [
|
||||
WheelGesturesPlugin({
|
||||
// Whether the carousel is vertical or horizontal, interpret y-axis wheel
|
||||
|
||||
@@ -113,6 +113,7 @@ describe('config defaults', () => {
|
||||
style: 'ribbon',
|
||||
window_seconds: 3600,
|
||||
},
|
||||
wheel: true,
|
||||
},
|
||||
draggable: true,
|
||||
lazy_load: true,
|
||||
@@ -180,6 +181,7 @@ describe('config defaults', () => {
|
||||
style: 'ribbon',
|
||||
window_seconds: 3600,
|
||||
},
|
||||
wheel: true,
|
||||
},
|
||||
draggable: true,
|
||||
lazy_load: true,
|
||||
|
||||
@@ -239,6 +239,24 @@ describe('CarouselController', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should not include wheel plugin when wheelScrolling is false', () => {
|
||||
const children = createTestSlideNodes();
|
||||
const root = createRoot();
|
||||
const parent = createParent({ children: children });
|
||||
new CarouselController(root, parent, { wheelScrolling: false });
|
||||
|
||||
// Verify WheelGesturesPlugin is NOT present
|
||||
expect(EmblaCarousel).toBeCalledWith(
|
||||
root,
|
||||
expect.anything(),
|
||||
expect.not.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: 'wheelGestures',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should recreate carousel when children are added', () => {
|
||||
const children = createTestSlideNodes();
|
||||
const root = createRoot();
|
||||
|
||||
Reference in New Issue
Block a user