diff --git a/src/components/ItaliaTheme/Blocks/Listing/Commons/utils.js b/src/components/ItaliaTheme/Blocks/Listing/Commons/utils.js index 82282bd32..949c7ef7c 100644 --- a/src/components/ItaliaTheme/Blocks/Listing/Commons/utils.js +++ b/src/components/ItaliaTheme/Blocks/Listing/Commons/utils.js @@ -1,4 +1,8 @@ import { useRef, useEffect } from 'react'; +import { + NextArrow, + PrevArrow, +} from 'design-comuni-plone-theme/components/ItaliaTheme'; export const getCategory = (item, show_type, show_section, props) => { let cat = []; @@ -17,19 +21,11 @@ export const getCategory = (item, show_type, show_section, props) => { } return null; }; -export const visibleSlideTitle = (selector) => { - // Needed to deal with react-slick duplicating a lot of slides - // when used in infinite mode. It's an useless and counterproductive - // thing to do on their part, there are multiple issues opened. - // The lib is not actually mantained so... - return Array.from(document.querySelectorAll(selector)).find((e) => { - const rect = e.getBoundingClientRect(); - return rect.left >= 0 && rect.right <= window.innerWidth; - }); -}; -export const useSlider = (userAutoplay) => { +export const useSlider = (userAutoplay, block_id) => { const slider = useRef(null); + const sliderContainer = document.getElementById('outside-slider-' + block_id); + const sliderElement = document.querySelector(`#slider_${block_id}`); const onIntersection = (entries, opt) => { entries.forEach((entry) => entry.target.classList.toggle('visible', entry.isIntersecting), @@ -39,49 +35,119 @@ export const useSlider = (userAutoplay) => { root: null, threshold: 0.5, }); - if (document.querySelector('.block.listing.slider')) - observer.observe(document.querySelector('.block.listing.slider')); + + if (sliderContainer) observer.observe(sliderContainer); useEffect(() => { return () => observer.disconnect(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const focusNext = (currentSlide) => { - const sliderElement = document.querySelector('.block.listing.slider'); if (!sliderElement) return; - const sliderIsVisible = sliderElement.classList.contains('visible'); + const sliderIsVisible = sliderContainer.classList.contains('visible'); if (!sliderIsVisible) { slider.current.slickPause(); return; } - const slide = sliderElement.querySelectorAll( - `a.slide-link[data-slide="${currentSlide}"]`, + const slide = sliderElement.querySelector( + `#slider_${block_id} .slick-slide[data-index="${currentSlide}"]`, ); - if ((userAutoplay && !slide) || (userAutoplay && !slide.length > 0)) return; - - // Custom handling of focus for a11y - const link = visibleSlideTitle( - `a.slide-link[data-slide="${currentSlide}"]`, - ); + if (userAutoplay && !slide) return; - if (!link || document.activeElement === link) { + if (!slide || document.activeElement === slide) { return; } // eslint-disable-next-line no-unused-expressions else if ( // if the focus was already on a slide, move it to the current one - Array.from(document.querySelectorAll('.slick-slide')).some((el) => - el.contains(document.activeElement), - ) + Array.from( + document.querySelectorAll(`#slider_${block_id} .slick-slide`), + ).some((el) => el.contains(document.activeElement)) ) { - link.focus(); + slide.focus(); } }; + const visibleSlide = (selector) => { + // Needed to deal with react-slick duplicating a lot of slides + // when used in infinite mode. It's an useless and counterproductive + // thing to do on their part, there are multiple issues opened. + // The lib is not actually mantained so... + + return Array.from(document.querySelectorAll(selector)).find((e) => { + const slick_slide = e.closest('.slick-slide'); + return !slick_slide.classList.contains('slick-cloned'); + }); + }; + + const SliderNextArrow = (props) => { + // Custom handling of focus for a11y + const { className, style, onClick, currentSlide } = props; + const handleClick = (options) => { + onClick(options, false); + }; + const handleKeyboardUsers = (e) => { + if (e.key === 'Tab' && e.shiftKey) { + e.stopPropagation(); + e.preventDefault(); + + const slide = visibleSlide( + `#slider_${block_id} .slick-slide[data-index="${currentSlide}"]`, + ); + slide && slide.focus(); + } + }; + + return ( + + ); + }; + + const SliderPrevArrow = (props) => { + // Custom handling of focus for a11y + const { className, style, onClick, focusNext, currentSlide, slideCount } = + props; + const handleClick = (options) => { + onClick(options, false); + }; + const handleKeyboardUsers = (e) => { + if (e.key === 'Tab' && !e.shiftKey) { + e.stopPropagation(); + e.preventDefault(); + + if (currentSlide < slideCount) { + const slide = visibleSlide( + `#slider_${block_id} .slick-slide[data-index="${currentSlide}"]`, + ); + + slide && slide.focus(); + } else focusNext(0, block_id); + } + }; + return ( + + ); + }; + return { slider, focusNext, + visibleSlide, + SliderNextArrow, + SliderPrevArrow, }; }; diff --git a/src/components/ItaliaTheme/Blocks/Listing/SimpleCard/Card/SimpleCardDefault.jsx b/src/components/ItaliaTheme/Blocks/Listing/SimpleCard/Card/SimpleCardDefault.jsx index 5d3ed23a9..5e1f26be9 100644 --- a/src/components/ItaliaTheme/Blocks/Listing/SimpleCard/Card/SimpleCardDefault.jsx +++ b/src/components/ItaliaTheme/Blocks/Listing/SimpleCard/Card/SimpleCardDefault.jsx @@ -56,6 +56,7 @@ const SimpleCardDefault = (props) => { id_lighthouse, rrule, index, + handleKeyboardUsers = () => {}, //for slider template handler } = props; const getItemClass = (item) => { @@ -119,6 +120,7 @@ const SimpleCardDefault = (props) => { item={!isEditMode ? item : null} href={isEditMode ? '#' : null} data-element={id_lighthouse} + onKeyDown={handleKeyboardUsers} > {itemTitle} diff --git a/src/components/ItaliaTheme/Blocks/Listing/SliderTemplate.jsx b/src/components/ItaliaTheme/Blocks/Listing/SliderTemplate.jsx index 1fe281dfb..335615e7c 100644 --- a/src/components/ItaliaTheme/Blocks/Listing/SliderTemplate.jsx +++ b/src/components/ItaliaTheme/Blocks/Listing/SliderTemplate.jsx @@ -10,19 +10,13 @@ import { SingleSlideWrapper, CarouselWrapper, ButtonPlayPause, - NextArrow, - PrevArrow, } from 'design-comuni-plone-theme/components/ItaliaTheme'; -import { - useSlider, - visibleSlideTitle, -} from 'design-comuni-plone-theme/components/ItaliaTheme/Blocks/Listing/Commons/utils'; +import { useSlider } from 'design-comuni-plone-theme/components/ItaliaTheme/Blocks/Listing/Commons/utils'; import React, { useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; -import { v4 as uuid } from 'uuid'; import config from '@plone/volto/registry'; const messages = defineMessages({ @@ -41,67 +35,17 @@ const messages = defineMessages({ }, }); -function SliderNextArrow(props) { - // Custom handling of focus for a11y - const { className, style, onClick, currentSlide, id } = props; - const handleClick = (options) => { - onClick(options, false); - }; - const handleKeyboardUsers = (e) => { - if (e.key === 'Tab' && e.shiftKey) { - e.stopPropagation(); - e.preventDefault(); - - const link = visibleSlideTitle( - `a.slide-link[data-slide="${currentSlide}"]`, - ); - link && link.focus(); - } - }; - - return ( - - ); -} - -function SliderPrevArrow(props) { - // Custom handling of focus for a11y - const { className, style, onClick, focusNext, currentSlide, slideCount, id } = - props; - const handleClick = (options) => { - onClick(options, false); - }; - const handleKeyboardUsers = (e) => { - if (e.key === 'Tab' && !e.shiftKey) { - e.stopPropagation(); - e.preventDefault(); - if (currentSlide < slideCount) { - const link = visibleSlideTitle( - `a.slide-link[data-slide="${currentSlide}"]`, - ); - link && link.focus(); - } else focusNext(0); - } - }; - return ( - - ); -} - const Slide = (props) => { - const { item, index, appearance, appearanceProp, block_id } = props; + const { + item, + index, + nextIndex, + prevIndex, + appearance, + appearanceProp, + block_id, + } = props; + const handleKeyboardUsers = (e) => { const { key, shiftKey } = e; if (key === 'Tab') { @@ -112,10 +56,21 @@ const Slide = (props) => { // if (userAutoplay) setUserAutoplay(false); // slider.current.slickPause(); let elementToFocus; + if (shiftKey) { - elementToFocus = document.getElementById('sliderPrevArrow_' + block_id); + elementToFocus = + prevIndex != null + ? document.querySelector( + `#slider_${block_id} .slick-slide[data-index="${prevIndex}"]`, + ) + : document.getElementById('sliderPrevArrow_' + block_id); } else - elementToFocus = document.getElementById('sliderNextArrow_' + block_id); + elementToFocus = + nextIndex != null + ? document.querySelector( + `#slider_${block_id} .slick-slide[data-index="${nextIndex}"]`, + ) + : document.getElementById('sliderNextArrow_' + block_id); elementToFocus.focus(); } }; @@ -154,9 +109,10 @@ const SliderTemplate = ({ autoplay_speed = 2, //seconds slide_appearance = 'default', reactSlick, - ...appearanceProp + block, + ...otherProps }) => { - const block_id = uuid(); + const block_id = block; const intl = useIntl(); const [userAutoplay, setUserAutoplay] = useState(autoplay); @@ -165,7 +121,11 @@ const SliderTemplate = ({ ? items.length : parseInt(slidesToShow); const Slider = reactSlick.default; - const { slider, focusNext } = useSlider(userAutoplay); + const { slider, focusNext, SliderNextArrow, SliderPrevArrow } = useSlider( + userAutoplay, + block_id, + ); + const toggleAutoplay = () => { if (!slider?.current) return; if (userAutoplay) { @@ -237,20 +197,8 @@ const SliderTemplate = ({ focusOnSelect: false, draggable: true, accessibility: true, - nextArrow: ( - - ), - prevArrow: ( - - ), + nextArrow: , + prevArrow: , appendDots: renderCustomDots, // Custom handling of focus for a11y afterChange: focusNext, @@ -271,6 +219,7 @@ const SliderTemplate = ({ 'no-margin': full_width, ['appearance_' + slide_appearance]: slide_appearance, })} + id={'slider_' + block_id} > {title && ( @@ -305,6 +254,8 @@ const SliderTemplate = ({ ); diff --git a/src/components/ItaliaTheme/View/Commons/RenderBlocks.jsx b/src/components/ItaliaTheme/View/Commons/RenderBlocks.jsx index 06d5c319a..258221c5e 100644 --- a/src/components/ItaliaTheme/View/Commons/RenderBlocks.jsx +++ b/src/components/ItaliaTheme/View/Commons/RenderBlocks.jsx @@ -16,6 +16,13 @@ const messages = defineMessages({ defaultMessage: 'Blocco sconosciuto', }, }); +const Wrapper = ({ block, id, children }) => { + return block['@type'] === 'listing' && block.variation === 'slider' ? ( +
{children}
+ ) : ( + <>{children} + ); +}; /** * RenderBlocks view component class. * @function RenderBlocks @@ -54,15 +61,21 @@ const RenderBlocks = ({ {map(items, (block) => { const blockType = blockContent[blocksFieldname]?.[block]?.['@type']; const Block = config.blocks.blocksConfig[blockType]?.['view'] || null; + if (Block != null) { return ( - + + + ); } else { return ( diff --git a/src/customizations/volto/components/manage/Blocks/Listing/ListingBody.jsx b/src/customizations/volto/components/manage/Blocks/Listing/ListingBody.jsx index ea9dd328c..c6b86975d 100644 --- a/src/customizations/volto/components/manage/Blocks/Listing/ListingBody.jsx +++ b/src/customizations/volto/components/manage/Blocks/Listing/ListingBody.jsx @@ -5,6 +5,7 @@ CUSTOMIZATIONS: - 'background class' and 'block class' - 'background class' and 'block class' logic for search block - search block integration +- pass 'block' prop to listing variation */ import React from 'react'; import { FormattedMessage, injectIntl } from 'react-intl'; @@ -37,7 +38,9 @@ const ListingBody = React.memo( loadingQuery, listingRef, additionalFilters, + block, } = props; + let ListingBodyTemplate; let templateConfig; // Legacy support if template is present @@ -97,6 +100,7 @@ const ListingBody = React.memo( // Also need to purge title from searchblock schema, it's the name of the listing template used const listingBodyProps = variation?.['@type'] !== 'search' ? data : { ...variation, title: '' }; + return (
{loadingQuery && ( @@ -114,6 +118,7 @@ const ListingBody = React.memo(