Skip to content

Commit 2ebbd50

Browse files
authored
Block: Improve carousel UX (#14030)
1 parent a86a3b7 commit 2ebbd50

File tree

22 files changed

+457
-220
lines changed

22 files changed

+457
-220
lines changed

.eslintrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"",
5959
{
6060
"pattern": " \\* Copyright \\d{4} Google LLC",
61-
"template": " * Copyright 2024 Google LLC"
61+
"template": " * Copyright 2025 Google LLC"
6262
},
6363
" *",
6464
" * Licensed under the Apache License, Version 2.0 (the \"License\");",

babel.config.cjs

+7
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ module.exports = function (api) {
3333
targets,
3434
useBuiltIns: 'usage',
3535
corejs: require('core-js/package.json').version,
36+
// Remove some unnecessary polyfills, similar to how Gutenberg is handling that.
37+
// See https://github.com/WordPress/gutenberg/blob/e95970d888c309274e24324d593c77c536c9f1d8/packages/babel-preset-default/polyfill-exclusions.js.
38+
exclude: [
39+
'es.array.push',
40+
/^es(next)?\.set\./,
41+
/^es(next)?\.iterator\./,
42+
],
3643
},
3744
],
3845
[

includes/Assets.php

+9-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,15 @@ public function register_style( string $style_handle, $src, array $deps = [], $v
302302
*/
303303
public function register_script( string $script_handle, $src, array $deps = [], $ver = false, bool $in_footer = false, bool $with_i18n = true ): bool {
304304
if ( ! isset( $this->register_scripts[ $script_handle ] ) ) {
305-
$this->register_scripts[ $script_handle ] = wp_register_script( $script_handle, $src, $deps, $ver, $in_footer );
305+
$this->register_scripts[ $script_handle ] = wp_register_script(
306+
$script_handle,
307+
$src,
308+
$deps,
309+
$ver,
310+
[
311+
'in_footer' => $in_footer,
312+
]
313+
);
306314

307315
if ( $src && $with_i18n ) {
308316
wp_set_script_translations( $script_handle, 'web-stories' );

includes/Renderer/Stories/Carousel_Renderer.php

+17-9
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ public function init(): void {
6666
public function load_assets(): void {
6767
parent::load_assets();
6868

69-
$this->assets->register_script_asset( self::SCRIPT_HANDLE );
70-
$this->assets->register_style_asset( self::SCRIPT_HANDLE );
69+
$this->assets->register_script_asset( self::SCRIPT_HANDLE, [], false );
7170

7271
wp_localize_script(
7372
self::SCRIPT_HANDLE,
@@ -98,13 +97,22 @@ public function render( array $args = [] ): string {
9897
ob_start();
9998
?>
10099
<div class="<?php echo esc_attr( $container_classes ); ?>" data-id="<?php echo esc_attr( (string) $this->instance_id ); ?>">
101-
<div class="web-stories-list__inner-wrapper <?php echo esc_attr( 'carousel-' . $this->instance_id ); ?>" style="<?php echo esc_attr( $container_styles ); ?>">
100+
<div
101+
class="web-stories-list__inner-wrapper <?php echo esc_attr( 'carousel-' . $this->instance_id ); ?>"
102+
style="<?php echo esc_attr( $container_styles ); ?>"
103+
>
102104
<?php
105+
$this->maybe_render_archive_link();
106+
103107
if ( ! $this->context->is_amp() ) {
104-
$this->assets->enqueue_script_asset( self::SCRIPT_HANDLE );
105-
$this->assets->enqueue_style_asset( self::SCRIPT_HANDLE );
108+
$this->assets->enqueue_script( self::SCRIPT_HANDLE );
106109
?>
107-
<div class="web-stories-list__carousel <?php echo esc_attr( $this->get_view_type() ); ?>" data-id="<?php echo esc_attr( 'carousel-' . $this->instance_id ); ?>">
110+
<div
111+
class="web-stories-list__carousel <?php echo esc_attr( $this->get_view_type() ); ?>"
112+
data-id="<?php echo esc_attr( 'carousel-' . $this->instance_id ); ?>"
113+
data-prev="<?php esc_attr_e( 'Previous', 'web-stories' ); ?>"
114+
data-next="<?php esc_attr_e( 'Next', 'web-stories' ); ?>"
115+
>
108116
<?php
109117
array_map(
110118
function () {
@@ -140,7 +148,6 @@ function () {
140148
</amp-carousel>
141149
<?php
142150
}
143-
$this->maybe_render_archive_link();
144151
?>
145152
</div>
146153
</div>
@@ -164,13 +171,14 @@ function () {
164171
*
165172
* @since 1.5.0
166173
*
167-
* @return array<string,array<string,bool>> Carousel settings.
174+
* @return array<string,array<string,bool>|string> Carousel settings.
168175
*/
169176
protected function get_carousel_settings(): array {
170177
return [
171-
'config' => [
178+
'config' => [
172179
'isRTL' => is_rtl(),
173180
],
181+
'publicPath' => $this->assets->get_base_url( 'assets/js/' ),
174182
];
175183
}
176184
}

package-lock.json

+11-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/glider/package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
"npm": ">= 7.3"
2525
},
2626
"type": "module",
27-
"main": "./src/index.js",
27+
"main": "./src/index.ts",
2828
"dependencies": {
2929
"glider-js": "^1.7.9"
3030
},
31-
"devDependencies": {}
31+
"devDependencies": {
32+
"@types/glider-js" : "^1.7.11"
33+
}
3234
}

packages/glider/src/index.js renamed to packages/glider/src/index.ts

+52-35
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,26 @@ import 'glider-js/glider.css';
2525
*
2626
* Glider-JS doesn't support RTL at the moment, this is to add basic
2727
* functioning support for the nav arrows as otherwise the nav arrows
28-
* becomes useless on RTL sites.
28+
* become useless on RTL sites.
2929
*
3030
* @todo Maybe replace glider-js with other lightweight lib which has RTL support. or Replace it with 'amp-carousel' once we have the support.
31-
* @param {Object|string} slide Slide arrow string based on action.
32-
* @param {boolean} dot Is dot navigation action.
33-
* @param {Object} e Event object.
34-
* @return {boolean} Navigation done.
31+
* @param slideIndex Slide arrow string based on action.
32+
* @param isActuallyDotIndex Is dot navigation action.
33+
* @param e Event object.
34+
* @return Navigation done.
3535
*/
36-
Glider.prototype.scrollItem = function (slide, dot, e) {
37-
// glider-js doesn't seem to pass right amount of arguments.
38-
if (e === undefined && dot?.target) {
39-
e = dot;
40-
dot = false;
36+
Glider.prototype.scrollItem = function (
37+
slideIndex: number,
38+
isActuallyDotIndex: boolean,
39+
e: Event
40+
) {
41+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Workaround
42+
// @ts-ignore
43+
if (e === undefined && isActuallyDotIndex?.target) {
44+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Workaround
45+
// @ts-ignore
46+
e = isActuallyDotIndex;
47+
isActuallyDotIndex = false;
4148
}
4249

4350
if (e === undefined) {
@@ -50,17 +57,21 @@ Glider.prototype.scrollItem = function (slide, dot, e) {
5057
}
5158

5259
// Somehow slidesToScroll and slidesToShow can end up being 0.
53-
this.opt.slidesToScroll = Math.max(1, this.opt.slidesToScroll);
54-
this.opt.slidesToShow = Math.max(1, this.opt.slidesToShow);
60+
this.opt.slidesToScroll = Math.max(1, this.opt.slidesToScroll as number);
61+
this.opt.slidesToShow = Math.max(1, this.opt.slidesToShow as number);
5562
// This will also cause this.itemWidth to be Infinity because division by zero returns Infinity in JS.
5663
// Update this.itemWidth with actual value in this case.
5764
if (this.itemWidth === Number.POSITIVE_INFINITY) {
5865
// It's a sibling.
59-
const carouselWrapper = e.target.parentElement.querySelector(
66+
const carouselWrapper = (
67+
e.target as HTMLElement
68+
).parentElement?.querySelector(
6069
'.web-stories-list__carousel'
61-
);
70+
) as HTMLElement;
6271
const itemStyle = window.getComputedStyle(
63-
carouselWrapper.querySelector('.web-stories-list__story')
72+
carouselWrapper.querySelector(
73+
'.web-stories-list__story'
74+
) as unknown as HTMLElement
6475
);
6576

6677
this.itemWidth =
@@ -69,56 +80,62 @@ Glider.prototype.scrollItem = function (slide, dot, e) {
6980
Number.parseFloat(itemStyle.marginRight));
7081
}
7182

72-
const originalSlide = slide;
83+
const originalSlide = slideIndex;
7384
++this.animate_id;
7485

75-
if (dot === true) {
76-
slide = slide * this.containerWidth;
77-
slide = Math.round(slide / this.itemWidth) * this.itemWidth;
86+
if (isActuallyDotIndex === true) {
87+
slideIndex = slideIndex * this.containerWidth;
88+
slideIndex = Math.round(slideIndex / this.itemWidth) * this.itemWidth;
7889
} else {
79-
if (typeof slide === 'string') {
80-
const backwards = slide === 'prev';
90+
if (typeof slideIndex === 'string') {
91+
const backwards = slideIndex === 'prev';
8192

8293
// use precise location if fractional slides are on
8394
if (this.opt.slidesToScroll % 1 || this.opt.slidesToShow % 1) {
84-
slide = this.getCurrentSlide();
95+
slideIndex = this.getCurrentSlide();
8596
} else {
86-
slide = !isNaN(this.slide) ? this.slide : 0;
97+
slideIndex = !isNaN(this.slide) ? this.slide : 0;
8798
}
8899

89100
if (backwards) {
90-
slide -= this.opt.slidesToScroll;
101+
slideIndex -= this.opt.slidesToScroll;
91102
} else {
92-
slide += this.opt.slidesToScroll;
103+
slideIndex += this.opt.slidesToScroll;
93104
}
94105

95106
if (this.opt.rewind) {
96-
const scrollLeft = this.ele.scrollLeft;
97-
slide =
107+
const scrollLeft = (this.ele as HTMLElement)
108+
.scrollLeft as unknown as number;
109+
slideIndex =
98110
backwards && !scrollLeft
99111
? this.slides.length
100112
: !backwards &&
101113
scrollLeft + this.containerWidth >= Math.floor(this.trackWidth)
102114
? 0
103-
: slide;
115+
: slideIndex;
104116
}
105117
}
106118

107-
slide = Math.min(slide, this.slides.length);
119+
slideIndex = Math.min(slideIndex, this.slides.length);
108120

109-
this.slide = slide;
110-
slide = this.itemWidth * slide;
121+
this.slide = slideIndex;
122+
slideIndex = this.itemWidth * slideIndex;
111123
}
112124

113125
this.scrollTo(
114-
slide,
115-
this.opt.duration * Math.abs(this.ele.scrollLeft - slide),
116-
function () {
126+
slideIndex,
127+
this.opt.duration *
128+
Math.abs((this.ele as HTMLElement).scrollLeft - slideIndex),
129+
function (this: Glider<HTMLElement>) {
117130
this.updateControls();
118131
this.emit('animated', {
119132
value: originalSlide,
120133
type:
121-
typeof originalSlide === 'string' ? 'arrow' : dot ? 'dot' : 'slide',
134+
typeof originalSlide === 'string'
135+
? 'arrow'
136+
: isActuallyDotIndex
137+
? 'dot'
138+
: 'slide',
122139
});
123140
}
124141
);

packages/glider/tsconfig.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "../../tsconfig.shared.json",
3+
"compilerOptions": {
4+
"rootDir": "src",
5+
"declarationDir": "dist-types"
6+
},
7+
"references": [],
8+
"include": ["src/**/*"]
9+
}

packages/stories-block/src/block/block-types/latest-stories/edit.js

+15-34
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ import { __ } from '@wordpress/i18n';
2626
import { addQueryArgs } from '@wordpress/url';
2727
import { Button, Placeholder } from '@wordpress/components';
2828
import { BlockIcon } from '@wordpress/block-editor';
29-
import { useSelect } from '@wordpress/data';
30-
import { store as coreStore } from '@wordpress/core-data';
29+
import { useEntityRecords } from '@wordpress/core-data';
3130

3231
/**
3332
* Internal dependencies
@@ -49,57 +48,39 @@ function LatestStoriesEdit({ attributes, setAttributes }) {
4948
const { numOfStories, order, orderby, archiveLinkLabel, authors, taxQuery } =
5049
attributes;
5150

52-
/**
53-
* Fetch stories based on the query.
54-
*
55-
* @return {void}
56-
*/
57-
const { isFetchingStories, fetchedStories } = useSelect(
58-
(select) => {
59-
const { getEntityRecords, isResolving } = select(coreStore);
60-
const newQuery = {
61-
per_page: 20,
62-
_embed: 'author,wp:featuredmedia',
63-
orderby: orderby || 'modified',
64-
order: order || 'desc',
65-
author: authors || undefined,
66-
...taxQuery,
67-
};
68-
69-
return {
70-
fetchedStories:
71-
getEntityRecords('postType', 'web-story', newQuery) || [],
72-
isFetchingStories:
73-
isResolving('postType', 'web-story', newQuery) || false,
74-
};
75-
},
76-
[order, orderby, authors, taxQuery]
77-
);
51+
const data = useEntityRecords('postType', 'web-story', {
52+
per_page: 20,
53+
_embed: 'author,wp:featuredmedia',
54+
orderby: orderby || 'modified',
55+
order: order || 'desc',
56+
author: authors || undefined,
57+
...taxQuery,
58+
});
7859

7960
const viewAllLabel = archiveLinkLabel
8061
? archiveLinkLabel
8162
: __('View All Stories', 'web-stories');
8263

8364
const storiesToDisplay =
84-
fetchedStories.length > numOfStories
85-
? fetchedStories.slice(0, numOfStories)
86-
: fetchedStories;
65+
data.records?.length > numOfStories
66+
? data.records.slice(0, numOfStories)
67+
: data.records;
8768

8869
return (
8970
<>
9071
<StoriesInspectorControls
9172
attributes={attributes}
9273
setAttributes={setAttributes}
9374
/>
94-
{isFetchingStories && <StoriesLoading />}
95-
{!isFetchingStories && Boolean(storiesToDisplay?.length) && (
75+
{!data.hasResolved && <StoriesLoading />}
76+
{data.hasResolved && Boolean(storiesToDisplay?.length) && (
9677
<StoriesPreview
9778
attributes={attributes}
9879
stories={storiesToDisplay}
9980
viewAllLabel={viewAllLabel}
10081
/>
10182
)}
102-
{!isFetchingStories && !storiesToDisplay?.length && (
83+
{data.hasResolved && !storiesToDisplay?.length && (
10384
<Placeholder
10485
icon={<BlockIcon icon={<WebStoriesLogo />} showColors />}
10586
label={__('Latest Stories', 'web-stories')}

0 commit comments

Comments
 (0)