diff --git a/.distignore b/.distignore index 2b85c7775612..99e10329d4bb 100644 --- a/.distignore +++ b/.distignore @@ -40,6 +40,7 @@ web-stories-scraper .npmpackagejsonlintrc.json .npmrc .nvmrc +.oxlintrc.json .phpstorm.config.js .phpstorm.meta.php .phpunit.result.cache diff --git a/.eslintrc b/.eslintrc index d687e90727ae..657a0ef25401 100644 --- a/.eslintrc +++ b/.eslintrc @@ -20,6 +20,7 @@ "jsx-a11y", "markdown", "react", + "react-compiler", "react-hooks", "styled-components-a11y" ], @@ -214,6 +215,15 @@ "react/no-unused-prop-types": "error", "react/react-in-jsx-scope": "off", "react/self-closing-comp": "error", + "react-compiler/react-compiler": [ + "error", + { + "environment": { + "enableTreatRefLikeIdentifiersAsRefs": true, + "validateRefAccessDuringRender": false + } + } + ], "import/no-extraneous-dependencies": "error", "import/no-unresolved": "error", "import/order": [ @@ -422,6 +432,7 @@ "rules": { "@eslint-community/eslint-comments/require-description": "off", "react/prop-types": "off", + "react-compiler/react-compiler": "off", "jest/no-hooks": "off", "jest/no-untyped-mock-factory": "off", "jest/max-expects": "off", diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 000000000000..a2713a8f92ec --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,13 @@ +{ + "settings": { + "jsdoc": { + "tagNamePreference": { + "returns": "return", + "yields": "yield" + } + } + }, + "rules": { + "no-unused-vars": "off" + } +} diff --git a/.storybook/stories/playground/dashboard/index.js b/.storybook/stories/playground/dashboard/index.js index e0424d9b601f..3ae89f7047dd 100644 --- a/.storybook/stories/playground/dashboard/index.js +++ b/.storybook/stories/playground/dashboard/index.js @@ -17,7 +17,7 @@ /** * External dependencies */ -import { useRef } from 'react'; +import { useRef, useEffect } from 'react'; import { toId } from '@storybook/csf'; import { Dashboard, InterfaceSkeleton } from '@googleforcreators/dashboard'; @@ -102,10 +102,12 @@ const getAuthors = () => Promise.resolve([{ name: 'Author', id: 1 }]); const useClearHash = () => { const isHashCleaned = useRef(false); - if (!isHashCleaned.current) { - window.location.hash = '/'; - isHashCleaned.current = true; - } + useEffect(() => { + if (!isHashCleaned.current) { + window.location.hash = '/'; + isHashCleaned.current = true; + } + }, []); }; export const _default = { diff --git a/.storybook/stories/playground/story-editor/header/buttons/preview.js b/.storybook/stories/playground/story-editor/header/buttons/preview.js index ddd0f452ef52..83c99ff27bed 100644 --- a/.storybook/stories/playground/story-editor/header/buttons/preview.js +++ b/.storybook/stories/playground/story-editor/header/buttons/preview.js @@ -68,7 +68,7 @@ function PreviewButton() { ` ); } - } catch (e) { + } catch { // Not interested in the error. } }; diff --git a/babel.config.cjs b/babel.config.cjs index 9f3e1d3dbc00..7ecd390b8d10 100644 --- a/babel.config.cjs +++ b/babel.config.cjs @@ -46,6 +46,7 @@ module.exports = function (api) { '@babel/preset-typescript', ], plugins: [ + ['babel-plugin-react-compiler', { target: '17' }], '@wordpress/babel-plugin-import-jsx-pragma', [ 'babel-plugin-styled-components', diff --git a/package-lock.json b/package-lock.json index 5413d0477047..c515372011ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,6 +76,7 @@ "ajv-formats": "^3.0.1", "babel-jest": "^29.6.1", "babel-loader": "^9.2.1", + "babel-plugin-react-compiler": "0.0.0-experimental-63cca73-20250106", "babel-plugin-styled-components": "^2.1.4", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "browserslist": "^4.24.4", @@ -100,6 +101,7 @@ "eslint-plugin-oxlint": "^0.15.5", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.37.3", + "eslint-plugin-react-compiler": "0.0.0-experimental-63cca73-20250106", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-security": "^3.0.1", "eslint-plugin-styled-components-a11y": "^2.2.0", @@ -135,6 +137,7 @@ "postcss-syntax": "^0.36.2", "prettier": "^3.4.2", "puppeteer": "^23.11.1", + "react-compiler-runtime": "0.0.0-experimental-63cca73-20250106", "react-refresh": "^0.16.0", "react-test-renderer": "^17.0.2", "rollup": "^2.79.2", @@ -1017,6 +1020,24 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -11878,6 +11899,16 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/babel-plugin-react-compiler": { + "version": "0.0.0-experimental-63cca73-20250106", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-0.0.0-experimental-63cca73-20250106.tgz", + "integrity": "sha512-XYdCAnSJbMGLlrbxI9jCKsM+pIVJnbeHkNJxqIT8jTzw1aHXO6Udv/WCsIQvGdwpKsl4gcDdtRYTTgpoeeOSJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.19.0" + } + }, "node_modules/babel-plugin-styled-components": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", @@ -15987,6 +16018,27 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, + "node_modules/eslint-plugin-react-compiler": { + "version": "0.0.0-experimental-63cca73-20250106", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-0.0.0-experimental-63cca73-20250106.tgz", + "integrity": "sha512-lhEPJoBlmTJvcezxc+nqHGFm9l7ZKZlWQ/sZR7tBYtRSdTB4et+Js8OVYIGAfPoDwY2za/v9A/3gVM2ZXb7Fzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "hermes-parser": "^0.25.1", + "zod": "^3.22.4", + "zod-validation-error": "^3.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" + }, + "peerDependencies": { + "eslint": ">=7" + } + }, "node_modules/eslint-plugin-react-hooks": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz", @@ -19319,6 +19371,23 @@ "tslib": "^2.0.3" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/highlight-words-core": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/highlight-words-core/-/highlight-words-core-1.2.2.tgz", @@ -29541,6 +29610,16 @@ "react-dom": ">=16.8.0" } }, + "node_modules/react-compiler-runtime": { + "version": "0.0.0-experimental-63cca73-20250106", + "resolved": "https://registry.npmjs.org/react-compiler-runtime/-/react-compiler-runtime-0.0.0-experimental-63cca73-20250106.tgz", + "integrity": "sha512-u26xZxhdUTaA3/A/9nT/fbQ8iBQ3sAxJN+8Cspe06YERZHEJd4H81RITuKBfdYjyv6VYYWRkBMXo9ES1odbiZw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental" + } + }, "node_modules/react-css-styled": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/react-css-styled/-/react-css-styled-1.1.9.tgz", @@ -36461,6 +36540,19 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-validation-error": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.4.0.tgz", + "integrity": "sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.18.0" + } + }, "packages/activation-notice": { "name": "@web-stories-wp/activation-notice", "license": "Apache-2.0", diff --git a/package.json b/package.json index 85c5e50a4362..5318ae57eff8 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "ajv-formats": "^3.0.1", "babel-jest": "^29.6.1", "babel-loader": "^9.2.1", + "babel-plugin-react-compiler": "0.0.0-experimental-63cca73-20250106", "babel-plugin-styled-components": "^2.1.4", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "browserslist": "^4.24.4", @@ -124,6 +125,7 @@ "eslint-plugin-oxlint": "^0.15.5", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.37.3", + "eslint-plugin-react-compiler": "0.0.0-experimental-63cca73-20250106", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-security": "^3.0.1", "eslint-plugin-styled-components-a11y": "^2.2.0", @@ -159,6 +161,7 @@ "postcss-syntax": "^0.36.2", "prettier": "^3.4.2", "puppeteer": "^23.11.1", + "react-compiler-runtime": "0.0.0-experimental-63cca73-20250106", "react-refresh": "^0.16.0", "react-test-renderer": "^17.0.2", "rollup": "^2.79.2", @@ -221,9 +224,9 @@ "lint:css:js:fix": "stylelint \"**/*.js\" --fix", "lint:css:css": "stylelint \"**/*.css\"", "lint:css:css:fix": "stylelint \"**/*.css\" --fix", - "lint:js": "oxlint && eslint .", - "lint:js:fix": "oxlint --fix && eslint --fix .", - "lint:js:report": "oxlint && eslint --output-file build/lint-js-report.json --format json .", + "lint:js": "oxlint -c=.oxlintrc.json --tsconfig=tsconfig.json --ignore-pattern=@types --react-perf-plugin && eslint .", + "lint:js:fix": "oxlint --fix -c=.oxlintrc.json --tsconfig=tsconfig.json --ignore-pattern=@types --react-perf-plugin && eslint --fix .", + "lint:js:report": "oxlint -c=.oxlintrc.json --tsconfig=tsconfig.json --ignore-pattern=@types --react-perf-plugin && eslint --output-file build/lint-js-report.json --format json .", "lint:package-json": "npmPkgJsonLint .", "lint:php": "composer phpcs", "lint:php:fix": "composer phpcbf", diff --git a/packages/dashboard/src/app/api/useStoryApi.js b/packages/dashboard/src/app/api/useStoryApi.js index 9cca0e5c039a..1428842f945c 100644 --- a/packages/dashboard/src/app/api/useStoryApi.js +++ b/packages/dashboard/src/app/api/useStoryApi.js @@ -51,13 +51,13 @@ const useStoryApi = () => { const [state, dispatch] = useReducer(storyReducer, defaultStoriesState); const { apiCallbacks } = useConfig(); - const isMounted = useRef(false); + const isMountedRef = useRef(false); useEffect(() => { - isMounted.current = true; + isMountedRef.current = true; return () => { - isMounted.current = false; + isMountedRef.current = false; }; }, []); @@ -78,7 +78,7 @@ const useStoryApi = () => { const { stories, fetchedStoryIds, totalPages, totalStoriesByStatus } = await apiCallbacks.fetchStories(queryParams); - if (!isMounted.current) { + if (!isMountedRef.current) { return; } diff --git a/packages/dashboard/src/app/views/exploreTemplates/content/templateGridView.js b/packages/dashboard/src/app/views/exploreTemplates/content/templateGridView.js index 4a37c7882317..194c9b9ee3e6 100644 --- a/packages/dashboard/src/app/views/exploreTemplates/content/templateGridView.js +++ b/packages/dashboard/src/app/views/exploreTemplates/content/templateGridView.js @@ -48,7 +48,7 @@ function TemplateGridView({ const { isRTL, apiCallbacks } = useConfig(); const containerRef = useRef(); const gridRef = useRef(); - const itemRefs = useRef({}); + const itemsRef = useRef({}); const [activeGridItemId, setActiveGridItemId] = useState(null); const { handleDetailsToggle, createStoryFromTemplate } = templateActions || {}; @@ -58,7 +58,7 @@ function TemplateGridView({ useGridViewKeys({ containerRef, gridRef, - itemRefs, + itemRefs: itemsRef, isRTL, currentItemId: activeGridItemId, items: filteredTemplates, @@ -69,7 +69,7 @@ function TemplateGridView({ // for legibility, it's based on the FOCUS_TEMPLATE_CLASS useEffect(() => { if (activeGridItemId) { - itemRefs.current?.[activeGridItemId] + itemsRef.current?.[activeGridItemId] ?.querySelector(`.${FOCUS_TEMPLATE_CLASS}`) ?.focus(); } @@ -97,7 +97,7 @@ function TemplateGridView({ key={slug} posterSrc={posterSrc} ref={(el) => { - itemRefs.current[id] = el; + itemsRef.current[id] = el; }} slug={slug} status={status} diff --git a/packages/dashboard/src/app/views/myStories/content/storiesView/index.js b/packages/dashboard/src/app/views/myStories/content/storiesView/index.js index 0c27f6868694..b5c6caea41a7 100644 --- a/packages/dashboard/src/app/views/myStories/content/storiesView/index.js +++ b/packages/dashboard/src/app/views/myStories/content/storiesView/index.js @@ -113,7 +113,7 @@ function StoriesView({ loading, storyActions, stories, view }) { const handleOnDeleteStory = useCallback(() => { trackEvent('delete_story'); storyActions.trashStory(activeStory); - setFocusedStory({ id: activeStory.id, isDeleted: true }); + setFocusedStory({ id: activeStory?.id, isDeleted: true }); setActiveDialog(''); }, [storyActions, activeStory]); @@ -162,6 +162,11 @@ function StoriesView({ loading, storyActions, stories, view }) { [showSnackbar] ); + const handleCloseConfirmDialog = useCallback(() => { + setFocusedStory({ id: activeStory?.id }); + setActiveDialog(''); + }, [activeStory]); + const menuItems = STORY_CONTEXT_MENU_ITEMS.map((item) => { switch (item?.value) { case STORY_CONTEXT_MENU_ACTIONS.COPY_STORY_LINK: @@ -257,10 +262,7 @@ function StoriesView({ loading, storyActions, stories, view }) { isOpen contentLabel={__('Dialog to confirm deleting a story', 'web-stories')} title={__('Delete Story', 'web-stories')} - onClose={() => { - setFocusedStory({ id: activeStory.id }); - setActiveDialog(''); - }} + onClose={handleCloseConfirmDialog} secondaryText={__('Cancel', 'web-stories')} secondaryRest={{ ['aria-label']: sprintf( diff --git a/packages/dashboard/src/app/views/myStories/content/storyGridView/index.js b/packages/dashboard/src/app/views/myStories/content/storyGridView/index.js index 606044a686d3..9cc3fc3d0733 100644 --- a/packages/dashboard/src/app/views/myStories/content/storyGridView/index.js +++ b/packages/dashboard/src/app/views/myStories/content/storyGridView/index.js @@ -62,7 +62,7 @@ const StoryGridView = ({ const { isRTL } = useConfig(); const containerRef = useRef(); const gridRef = useRef(); - const itemRefs = useRef({}); + const itemsRef = useRef({}); const [activeGridItemId, setActiveGridItemId] = useState(); const activeGridItemIdRef = useRef(); const gridItemIds = useMemo(() => stories.map(({ id }) => id), [stories]); @@ -70,7 +70,7 @@ const StoryGridView = ({ useGridViewKeys({ containerRef, gridRef, - itemRefs, + itemRefs: itemsRef, isRTL, currentItemId: activeGridItemId, items: stories, @@ -90,7 +90,7 @@ const StoryGridView = ({ const newFocusId = returnStoryFocusId?.value; setActiveGridItemId(newFocusId); // grab the menu button and refocus - const firstFocusableElement = itemRefs.current?.[ + const firstFocusableElement = itemsRef.current?.[ newFocusId ]?.querySelectorAll(['button', 'a'])?.[0]; @@ -171,7 +171,7 @@ const StoryGridView = ({ onFocus={() => setActiveGridItemId(story.id)} isActive={activeGridItemId === story.id} ref={(el) => { - itemRefs.current[story.id] = el; + itemsRef.current[story.id] = el; }} key={story.id} pageSize={pageSize} diff --git a/packages/dashboard/src/app/views/myStories/index.js b/packages/dashboard/src/app/views/myStories/index.js index 0797034ff964..97b1a5228b4f 100644 --- a/packages/dashboard/src/app/views/myStories/index.js +++ b/packages/dashboard/src/app/views/myStories/index.js @@ -79,13 +79,13 @@ function MyStoriesView() { ); const { apiCallbacks, canViewDefaultTemplates } = useConfig(); - const isMounted = useRef(false); + const isMountedRef = useRef(false); useEffect(() => { - isMounted.current = true; + isMountedRef.current = true; return () => { - isMounted.current = false; + isMountedRef.current = false; }; }, []); diff --git a/packages/dashboard/src/components/cardGallery/index.js b/packages/dashboard/src/components/cardGallery/index.js index 8810f7e108b6..47386b21c433 100644 --- a/packages/dashboard/src/components/cardGallery/index.js +++ b/packages/dashboard/src/components/cardGallery/index.js @@ -55,7 +55,7 @@ function CardGallery({ galleryPosters, isRTL, galleryLabel }) { const [focusedGridItemIndex, setFocusedGridItemIndex] = useState(); const containerRef = useRef(); const gridRef = useRef(); - const posterRefs = useRef({}); + const postersRef = useRef({}); const handleMiniCardClick = useCallback((index) => { setSelectedGridItemIndex(index); @@ -77,7 +77,7 @@ function CardGallery({ galleryPosters, isRTL, galleryLabel }) { useGridViewKeys({ containerRef, gridRef, - itemRefs: posterRefs, + itemRefs: postersRef, isRTL, currentItemId: focusedGridItemIndex, items: galleryPosters, @@ -96,7 +96,7 @@ function CardGallery({ galleryPosters, isRTL, galleryLabel }) {
{ - posterRefs.current[index] = el; + postersRef.current[index] = el; }} onFocus={() => handleGalleryItemFocus(index)} > diff --git a/packages/dashboard/src/components/interfaceSkeleton/index.js b/packages/dashboard/src/components/interfaceSkeleton/index.js index b0779e19ef02..811e839ad5d1 100644 --- a/packages/dashboard/src/components/interfaceSkeleton/index.js +++ b/packages/dashboard/src/components/interfaceSkeleton/index.js @@ -72,9 +72,11 @@ const InterfaceSkeleton = ({ additionalRoutes }) => { addInitialFetchListener, }) ); - const isFirstLoadOnMyStories = useRef(currentPath === APP_ROUTES.DASHBOARD); + const isFirstLoadOnMyStoriesRef = useRef( + currentPath === APP_ROUTES.DASHBOARD + ); const [isRedirectComplete, setIsRedirectComplete] = useState( - !isFirstLoadOnMyStories.current + !isFirstLoadOnMyStoriesRef.current ); // Direct user to templates on first load if they @@ -83,11 +85,11 @@ const InterfaceSkeleton = ({ additionalRoutes }) => { return addInitialFetchListener?.((storyStatuses) => { if ( storyStatuses?.all <= 0 && - isFirstLoadOnMyStories.current && + isFirstLoadOnMyStoriesRef.current && canViewDefaultTemplates ) { push(APP_ROUTES.TEMPLATES_GALLERY); - isFirstLoadOnMyStories.current = false; + isFirstLoadOnMyStoriesRef.current = false; } setIsRedirectComplete(true); }); diff --git a/packages/dashboard/src/karma/integrationLayerTesting/config.karma.js b/packages/dashboard/src/karma/integrationLayerTesting/config.karma.js index f8d5854aac4b..94930da53c69 100644 --- a/packages/dashboard/src/karma/integrationLayerTesting/config.karma.js +++ b/packages/dashboard/src/karma/integrationLayerTesting/config.karma.js @@ -31,7 +31,7 @@ describe('Integration Layer tests : EditorConfig Params :', () => { MINIMUM_CONFIG[key] = undefined; } MINIMUM_CONFIG.apiCallbacks = { - fetchStories: () => new Promise.resolve({}), + fetchStories: () => Promise.resolve({}), }; }); diff --git a/packages/dashboard/src/utils/useStoryView.js b/packages/dashboard/src/utils/useStoryView.js index 9ba2a8f5c1d8..6ad2b96c9be3 100644 --- a/packages/dashboard/src/utils/useStoryView.js +++ b/packages/dashboard/src/utils/useStoryView.js @@ -50,7 +50,7 @@ export default function useStoryView({ const [sort, _setSort] = useState(sortObject); const [filters, _setFilters] = useState(filtersObject); const [page, setPage] = useState(1); - const showStoriesWhileLoading = useRef(false); + const showStoriesWhileLoadingRef = useRef(false); const [initialPageReady, setInitialPageReady] = useState(false); const { pageSize } = usePagePreviewSize({ @@ -90,7 +90,7 @@ export default function useStoryView({ }, [viewStyle, setViewStyle]); const requestNextPage = useCallback(() => { - showStoriesWhileLoading.current = true; + showStoriesWhileLoadingRef.current = true; setPageClamped(page + 1); }, [page, setPageClamped]); @@ -111,7 +111,7 @@ export default function useStoryView({ useEffect(() => { // reset ref state after request is finished if (!isLoading) { - showStoriesWhileLoading.current = false; + showStoriesWhileLoadingRef.current = false; } }, [isLoading]); @@ -156,7 +156,7 @@ export default function useStoryView({ requestNextPage, }, initialPageReady, - showStoriesWhileLoading, + showStoriesWhileLoading: showStoriesWhileLoadingRef, }), [ viewStyle, diff --git a/packages/design-system/src/components/contextMenu/components/subMenuTrigger.tsx b/packages/design-system/src/components/contextMenu/components/subMenuTrigger.tsx index 283aaf8ff327..aece08f26e3e 100644 --- a/packages/design-system/src/components/contextMenu/components/subMenuTrigger.tsx +++ b/packages/design-system/src/components/contextMenu/components/subMenuTrigger.tsx @@ -62,7 +62,7 @@ function SubMenuTrigger({ ...buttonProps }: SubMenuTriggerProps) { const ref = useRef(null); - const pointerTracker = useRef<{ x?: number; y?: number }>({}); + const pointerTrackerRef = useRef<{ x?: number; y?: number }>({}); const { setFocusedId } = useContextMenu(({ actions }) => ({ setFocusedId: actions.setFocusedId, @@ -70,7 +70,7 @@ function SubMenuTrigger({ const pointerIsOutside = (node: Element) => { const { x, y, width, height } = node.getBoundingClientRect(); - const { x: pointerX, y: pointerY } = pointerTracker.current; + const { x: pointerX, y: pointerY } = pointerTrackerRef.current; if (pointerX === undefined || pointerY === undefined) { return true; } @@ -118,8 +118,8 @@ function SubMenuTrigger({ const onPointerMove = (e: Event) => { if (e instanceof PointerEvent) { // Track the pointer when moving inside the menu while the submenu is open. - pointerTracker.current.x = e.clientX; - pointerTracker.current.y = e.clientY; + pointerTrackerRef.current.x = e.clientX; + pointerTrackerRef.current.y = e.clientY; maybeCloseSubMenu(); } }; @@ -162,7 +162,7 @@ function SubMenuTrigger({ onPointerEnter={openSubMenu} onPointerLeave={() => { // Reset tracker in case we moved out of the menu fully. - pointerTracker.current = {}; + pointerTrackerRef.current = {}; maybeCloseSubMenu(); }} onClick={(e: MouseEvent) => e.preventDefault()} diff --git a/packages/design-system/src/components/contextMenu/menu.tsx b/packages/design-system/src/components/contextMenu/menu.tsx index c6faf29416d4..99402aafeaee 100644 --- a/packages/design-system/src/components/contextMenu/menu.tsx +++ b/packages/design-system/src/components/contextMenu/menu.tsx @@ -240,7 +240,7 @@ const Menu = forwardRef< const keyOut = isHorizontal ? KEYS.UP : isRTL ? KEYS.RIGHT : KEYS.LEFT; // Maybe move from submenu to parent menu. - if (isSubMenu && keyOut === key && parentMenuRef.current) { + if (isSubMenu && keyOut === key && parentMenuRef?.current) { // Get the button with expanded popup. const parentButton = parentMenuRef.current.querySelector( 'button[aria-expanded="true"]' diff --git a/packages/design-system/src/components/datalist/list/list.tsx b/packages/design-system/src/components/datalist/list/list.tsx index 713512c56b8d..b39a1777beae 100644 --- a/packages/design-system/src/components/datalist/list/list.tsx +++ b/packages/design-system/src/components/datalist/list/list.tsx @@ -81,7 +81,7 @@ function OptionListWithRef( const listRef = useForwardedRef(forwardedListRef); const optionsRef = useRef<(HTMLLIElement | null)[]>([]); const [focusIndex, setFocusIndex] = useState(-1); - const userSeenOptions = useRef([]); + const userSeenOptionsRef = useRef([]); /* * KEYWORD FILTERING @@ -134,11 +134,11 @@ function OptionListWithRef( .filter((t): t is HTMLElement => t instanceof HTMLElement) .map((target) => target.dataset.option) .filter((o): o is string => Boolean(o)); - userSeenOptions.current = addUniqueEntries( - userSeenOptions.current, + userSeenOptionsRef.current = addUniqueEntries( + userSeenOptionsRef.current, ...newlySeenOptions ); - onObserve(userSeenOptions.current); + onObserve(userSeenOptionsRef.current); } }, { diff --git a/packages/design-system/src/components/input/useNumericInput.tsx b/packages/design-system/src/components/input/useNumericInput.tsx index 7829d7835717..eb6da40d2044 100644 --- a/packages/design-system/src/components/input/useNumericInput.tsx +++ b/packages/design-system/src/components/input/useNumericInput.tsx @@ -16,7 +16,7 @@ /** * External dependencies */ -import Big from 'big.js'; +import { Big } from 'big.js'; import { useCallback, useEffect, @@ -45,8 +45,8 @@ const useNumericInput = ({ }: UseNumericInputProps) => { const _inputRef = useRef(null); const inputRef = ref && 'current' in ref ? ref : _inputRef; - const oldValue = useRef(value); - const revertToOriginal = useRef(false); + const oldValueRef = useRef(value); + const revertToOriginalRef = useRef(false); const [currentValue, setCurrentValue] = useState(value); const options = useMemo( () => ({ allowEmpty, isFloat, padZero, max, min }), @@ -58,9 +58,9 @@ const useNumericInput = ({ */ const handleBlur = useCallback( (ev: unknown) => { - let newValue = parseInput(oldValue.current, options); + let newValue = parseInput(oldValueRef.current, options); - if (!revertToOriginal.current && newValue !== null) { + if (!revertToOriginalRef.current && newValue !== null) { const parsedValue = parseInput(currentValue, options); if (parsedValue !== null) { @@ -70,11 +70,11 @@ const useNumericInput = ({ newValue = parseInput(currentValue, options); } - revertToOriginal.current = false; + revertToOriginalRef.current = false; if (newValue !== null) { // Set newly updated value. setCurrentValue(newValue); - if (newValue !== oldValue.current) { + if (newValue !== oldValueRef.current) { onChange(ev, newValue); } } else if (min !== undefined) { @@ -205,14 +205,14 @@ const useNumericInput = ({ * Blur input and revert value to original value */ const handleEsc = useCallback(() => { - setCurrentValue(oldValue.current); - revertToOriginal.current = true; + setCurrentValue(oldValueRef.current); + revertToOriginalRef.current = true; inputRef?.current?.blur(); }, [inputRef]); useEffect(() => { // update internal value when `value` prop changes - oldValue.current = value; + oldValueRef.current = value; setCurrentValue(value); }, [value]); @@ -225,7 +225,7 @@ const useNumericInput = ({ handleChange, handleEsc, handleKeyUpAndDown, - isIndeterminate: oldValue.current === currentValue, + isIndeterminate: oldValueRef.current === currentValue, }; }; diff --git a/packages/design-system/src/components/popup/popup.tsx b/packages/design-system/src/components/popup/popup.tsx index 36f11985b70e..cb3e8b3b0495 100644 --- a/packages/design-system/src/components/popup/popup.tsx +++ b/packages/design-system/src/components/popup/popup.tsx @@ -82,12 +82,12 @@ function Popup({ offset: Offset; height: number | null; } | null>(null); - const isMounted = useRef(false); + const isMountedRef = useRef(false); const popup = useRef(null); const positionPopup = useCallback( (evt?: unknown) => { - if (!isMounted.current || !anchor?.current) { + if (!isMountedRef.current || !anchor?.current) { return; } // If scrolling within the popup, ignore. @@ -182,9 +182,9 @@ function Popup({ ); useEffect(() => { - isMounted.current = true; + isMountedRef.current = true; return () => { - isMounted.current = false; + isMountedRef.current = false; }; }, []); @@ -202,18 +202,18 @@ function Popup({ if (!isOpen) { return undefined; } - isMounted.current = true; + isMountedRef.current = true; positionPopup(); // Adjust the position when scrolling. document.addEventListener('scroll', positionPopup, true); return () => { document.removeEventListener('scroll', positionPopup, true); - isMounted.current = false; + isMountedRef.current = false; }; }, [isOpen, positionPopup]); useLayoutEffect(() => { - if (!isMounted.current) { + if (!isMountedRef.current) { return; } diff --git a/packages/design-system/src/components/tooltip/tooltip.tsx b/packages/design-system/src/components/tooltip/tooltip.tsx index a68bae5db5c0..63dc37688a05 100644 --- a/packages/design-system/src/components/tooltip/tooltip.tsx +++ b/packages/design-system/src/components/tooltip/tooltip.tsx @@ -131,11 +131,11 @@ function Tooltip({ const tooltipRef = useRef(null); const placementRef = useRef(placement); const [dynamicPlacement, setDynamicPlacement] = useState(placement); - const isMounted = useRef(false); + const isMountedRef = useRef(false); const [popupState, setPopupState] = useState<{ offset?: Offset }>({}); - const isPopupMounted = useRef(false); - const popup = useRef(null); + const isPopupMountedRef = useRef(false); + const popupRef = useRef(null); const isOpen = Boolean(shown && (shortcut || title)); const [dynamicOffset, setDynamicOffset] = useState<{ x?: number }>({}); @@ -160,7 +160,7 @@ function Tooltip({ ); const positionPopup = useCallback(() => { - if (!isPopupMounted.current || !anchorRef?.current) { + if (!isPopupMountedRef.current || !anchorRef?.current) { return; } setPopupState({ @@ -169,7 +169,7 @@ function Tooltip({ placement: dynamicPlacement, spacing, anchor: getAnchor(), - popup, + popup: popupRef, isRTL, ignoreMaxOffsetY: true, }) @@ -191,7 +191,7 @@ function Tooltip({ const shouldMoveToTop = dynamicPlacement.startsWith('bottom') && neededVerticalSpace >= window.innerHeight; - // We can sometimes render a tooltip too far to the left, ie. in RTL mode, or with the wp-admin sidenav. + // We can sometimes render a tooltip too far to the left, i.e. in RTL mode, or with the wp-admin sidenav. // When that is the case, let's update the offset. const isOverFlowingLeft = Math.trunc(left) < (isRTL ? 0 : leftOffset); // The getOffset util has a maxOffset that prevents the tooltip from being render too far to the right. However, when @@ -239,10 +239,10 @@ function Tooltip({ const resetPlacement = useDebouncedCallback(() => { setDynamicPlacement(placementRef.current); }, 100); - const delay = useRef | null>(null); + const delayRef = useRef | null>(null); const onHover = useCallback(() => { const handle = () => { - if (!isMounted.current) { + if (!isMountedRef.current) { return; } @@ -255,11 +255,11 @@ function Tooltip({ // Show instantly handle(); } - if (delay.current) { - clearTimeout(delay.current); + if (delayRef.current) { + clearTimeout(delayRef.current); } // Invoke in DELAY_MS - delay.current = setTimeout(handle, DELAY_MS); + delayRef.current = setTimeout(handle, DELAY_MS); } else { handle(); } @@ -267,8 +267,8 @@ function Tooltip({ const onHoverOut = useCallback(() => { setShown(false); resetPlacement(); - if (isDelayed && delay.current) { - clearTimeout(delay.current); + if (isDelayed && delayRef.current) { + clearTimeout(delayRef.current); if (shown) { lastVisibleDelayedTooltip = performance.now(); } @@ -276,18 +276,18 @@ function Tooltip({ }, [resetPlacement, isDelayed, shown]); useEffect(() => { - isMounted.current = true; + isMountedRef.current = true; return () => { - isMounted.current = false; + isMountedRef.current = false; }; }, []); useEffect(() => { - isPopupMounted.current = true; + isPopupMountedRef.current = true; return () => { - isPopupMounted.current = false; + isPopupMountedRef.current = false; }; }, []); @@ -295,19 +295,19 @@ function Tooltip({ if (!isOpen) { return undefined; } - isPopupMounted.current = true; + isPopupMountedRef.current = true; positionPopup(); // Adjust the position when scrolling. document.addEventListener('scroll', positionPopup, true); return () => { document.removeEventListener('scroll', positionPopup, true); - isPopupMounted.current = false; + isPopupMountedRef.current = false; }; }, [isOpen, positionPopup]); useLayoutEffect(() => { - if (!isPopupMounted.current) { + if (!isPopupMountedRef.current) { return; } @@ -339,7 +339,7 @@ function Tooltip({ {popupState?.offset && isOpen ? createPortal( (ref: React.ForwardedRef) { const wrappedRef = useRef<{ current: T | null }>({ current: null }); - const reference = useRef(null); + const referenceRef = useRef(null); Object.defineProperty(wrappedRef.current, 'current', { - get: () => reference.current, + get: () => referenceRef.current, set: (value: T | null) => { - if (!Object.is(reference.current, value)) { - reference.current = value; + if (!Object.is(referenceRef.current, value)) { + referenceRef.current = value; if (!ref) { return; } if (typeof ref === 'function') { - ref(reference.current); + ref(referenceRef.current); } else { - ref.current = reference.current; + ref.current = referenceRef.current; } } }, diff --git a/packages/e2e-test-utils/src/customFonts.js b/packages/e2e-test-utils/src/customFonts.js index 1fa34217a4ba..cf026178e222 100644 --- a/packages/e2e-test-utils/src/customFonts.js +++ b/packages/e2e-test-utils/src/customFonts.js @@ -59,7 +59,7 @@ export const getFontList = async () => { ) ); return parseText(optionsText); - } catch (e) { + } catch { return []; } }; diff --git a/packages/element-library/src/media/edit.tsx b/packages/element-library/src/media/edit.tsx index bf1eaf8f6b73..e3c7dded8b00 100644 --- a/packages/element-library/src/media/edit.tsx +++ b/packages/element-library/src/media/edit.tsx @@ -153,29 +153,29 @@ function MediaEdit({ const [cropBox, setCropBox] = useState(null); const elementRef = useRef(null); - const isUpdatedLocally = useRef(false); - const lastLocalProperties = useRef>({ scale } as Partial); + const isUpdatedLocallyRef = useRef(false); + const lastLocalPropertiesRef = useRef>({ scale } as Partial); const updateLocalProperties = useCallback( (properties: Partial | ((p: Partial) => Partial)) => { - lastLocalProperties.current = { - ...lastLocalProperties.current, + lastLocalPropertiesRef.current = { + ...lastLocalPropertiesRef.current, ...(typeof properties === 'function' - ? properties(lastLocalProperties.current) + ? properties(lastLocalPropertiesRef.current) : properties), }; - isUpdatedLocally.current = true; - setLocalProperties(lastLocalProperties.current); + isUpdatedLocallyRef.current = true; + setLocalProperties(lastLocalPropertiesRef.current); }, [setLocalProperties] ); const updateProperties = useCallback(() => { - if (!isUpdatedLocally.current) { + if (!isUpdatedLocallyRef.current) { return; } - isUpdatedLocally.current = false; - const properties: Partial = lastLocalProperties.current; + isUpdatedLocallyRef.current = false; + const properties: Partial = lastLocalPropertiesRef.current; updateElementById({ elementId: id, properties }); }, [id, updateElementById]); diff --git a/packages/karma-puppeteer-client/src/client.js b/packages/karma-puppeteer-client/src/client.js index e7cdd6c4452a..6547baacc108 100644 --- a/packages/karma-puppeteer-client/src/client.js +++ b/packages/karma-puppeteer-client/src/client.js @@ -14,7 +14,7 @@ * limitations under the License. */ -(function (global) { +(function () { 'use strict'; function noCleanup() {} diff --git a/packages/rich-text/src/provider.tsx b/packages/rich-text/src/provider.tsx index 4acd951f7ed3..cd936797e301 100644 --- a/packages/rich-text/src/provider.tsx +++ b/packages/rich-text/src/provider.tsx @@ -58,7 +58,7 @@ export interface RichTextProviderProps { } function RichTextProvider({ children, editingState }: RichTextProviderProps) { const [editorState, setEditorState] = useState(null); - const lastKnownStyle = useRef(null); + const lastKnownStyleRef = useRef(null); const selectionInfo = useMemo(() => { if (editorState) { @@ -81,7 +81,7 @@ function RichTextProvider({ children, editingState }: RichTextProviderProps) { if (selection) { state = EditorState.forceSelection(state, selection); } - lastKnownStyle.current = state.getCurrentInlineStyle(); + lastKnownStyleRef.current = state.getCurrentInlineStyle(); setEditorState(state); }, [editingState, setEditorState] @@ -100,14 +100,14 @@ function RichTextProvider({ children, editingState }: RichTextProviderProps) { let filteredState = getFilteredState(newEditorState, editorState); const isEmpty = filteredState?.getCurrentContent().getPlainText('') === ''; - if (isEmpty && lastKnownStyle.current) { + if (isEmpty && lastKnownStyleRef.current) { // Copy last known current style as inline style filteredState = EditorState.setInlineStyleOverride( filteredState, - lastKnownStyle.current + lastKnownStyleRef.current ); } else { - lastKnownStyle.current = filteredState.getCurrentInlineStyle(); + lastKnownStyleRef.current = filteredState.getCurrentInlineStyle(); } setEditorState(filteredState); }, diff --git a/packages/rich-text/src/useSelectionManipulation.ts b/packages/rich-text/src/useSelectionManipulation.ts index 21710136c05d..481a3e8e9cc5 100644 --- a/packages/rich-text/src/useSelectionManipulation.ts +++ b/packages/rich-text/src/useSelectionManipulation.ts @@ -37,14 +37,14 @@ function useSelectionManipulation( editorState: EditorState | null, setEditorState: Dispatch> ): Record { - const lastKnownState = useRef(null); - const lastKnownSelection = useRef(null); + const lastKnownStateRef = useRef(null); + const lastKnownSelectionRef = useRef(null); useEffect(() => { - lastKnownState.current = editorState; + lastKnownStateRef.current = editorState; if (!editorState) { - lastKnownSelection.current = null; + lastKnownSelectionRef.current = null; } else if (editorState.getSelection().getHasFocus()) { - lastKnownSelection.current = editorState.getSelection(); + lastKnownSelectionRef.current = editorState.getSelection(); } }, [editorState]); @@ -53,8 +53,8 @@ function useSelectionManipulation( updater: (state: EditorState | null) => EditorState, shouldForceFocus = true ) => { - const oldState = lastKnownState.current; - const selection = lastKnownSelection.current; + const oldState = lastKnownStateRef.current; + const selection = lastKnownSelectionRef.current; const workingState = shouldForceFocus && oldState && selection ? EditorState.forceSelection(oldState, selection) diff --git a/packages/stories-block/src/block/block-types/single-story/edit.js b/packages/stories-block/src/block/block-types/single-story/edit.js index 0c4c515f702b..0a771e0ece77 100644 --- a/packages/stories-block/src/block/block-types/single-story/edit.js +++ b/packages/stories-block/src/block/block-types/single-story/edit.js @@ -28,8 +28,7 @@ import { trackEvent } from '@googleforcreators/tracking'; import { useCallback, useEffect, useRef, useState } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; import { ResizableBox } from '@wordpress/components'; -import * as compose from '@wordpress/compose'; -import { withViewportMatch } from '@wordpress/viewport'; +import { useViewportMatch } from '@wordpress/compose'; import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { store as blockEditorStore } from '@wordpress/block-editor'; @@ -53,7 +52,6 @@ function StoryEmbedEdit({ setAttributes, className, isSelected, - _isResizable, context = {}, }) { const { @@ -86,9 +84,7 @@ function StoryEmbedEdit({ const showLoadingIndicator = isFetchingData; const showPlaceholder = !localURL || !outerURL || editingURL || cannotEmbed; - const isResizable = compose.useViewportMatch - ? compose.useViewportMatch('medium') - : _isResizable; + const isResizable = useViewportMatch('medium'); const ref = useRef(); @@ -390,11 +386,10 @@ StoryEmbedEdit.propTypes = { setAttributes: PropTypes.func.isRequired, className: PropTypes.string.isRequired, isSelected: PropTypes.bool, - _isResizable: PropTypes.bool, context: PropTypes.shape({ postType: PropTypes.string, postId: PropTypes.number, }), }; -export default withViewportMatch({ _isResizable: 'medium' })(StoryEmbedEdit); +export default StoryEmbedEdit; diff --git a/packages/stories-block/src/block/block-types/single-story/editInLoop.js b/packages/stories-block/src/block/block-types/single-story/editInLoop.js index fbca4998eba3..4f1e67e53237 100644 --- a/packages/stories-block/src/block/block-types/single-story/editInLoop.js +++ b/packages/stories-block/src/block/block-types/single-story/editInLoop.js @@ -25,8 +25,7 @@ import classNames from 'classnames'; */ import { useEffect, useRef } from '@wordpress/element'; import { Placeholder, ResizableBox } from '@wordpress/components'; -import * as compose from '@wordpress/compose'; -import { withViewportMatch } from '@wordpress/viewport'; +import { useViewportMatch } from '@wordpress/compose'; import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { store as blockEditorStore, BlockIcon } from '@wordpress/block-editor'; @@ -50,7 +49,6 @@ function StoryEmbedEditInLoop({ setAttributes, className, isSelected, - _isResizable, context = {}, }) { const { @@ -95,9 +93,7 @@ function StoryEmbedEditInLoop({ }; }, []); - const isResizable = compose.useViewportMatch - ? compose.useViewportMatch('medium') - : _isResizable; + const isResizable = useViewportMatch('medium'); const ref = useRef(); @@ -271,13 +267,10 @@ StoryEmbedEditInLoop.propTypes = { setAttributes: PropTypes.func.isRequired, className: PropTypes.string.isRequired, isSelected: PropTypes.bool, - _isResizable: PropTypes.bool, context: PropTypes.shape({ postType: PropTypes.string, postId: PropTypes.number, }), }; -export default withViewportMatch({ _isResizable: 'medium' })( - StoryEmbedEditInLoop -); +export default StoryEmbedEditInLoop; diff --git a/packages/stories-block/src/block/components/storyPicker/selectStories.js b/packages/stories-block/src/block/components/storyPicker/selectStories.js index cee6bb9e72a0..ec3e75743d7b 100644 --- a/packages/stories-block/src/block/components/storyPicker/selectStories.js +++ b/packages/stories-block/src/block/components/storyPicker/selectStories.js @@ -134,7 +134,7 @@ function SelectStories({ const [authorKeyword, setAuthorKeyword] = useState(''); const [order, setOrder] = useState('desc'); const [orderBy, setOrderBy] = useState('modified'); - const nextPage = useRef(1); + const nextPageRef = useRef(1); const { authors } = useSelect( (select) => { @@ -160,17 +160,17 @@ function SelectStories({ search: searchKeyword || undefined, order, orderby: orderBy, - page: nextPage.current, + page: nextPageRef.current, }); }, [searchKeyword, currentAuthor, fetchStories, order, orderBy]); useEffect(() => { - nextPage.current = 1; + nextPageRef.current = 1; fetchSelectedStories(); }, [searchKeyword, currentAuthor, order, orderBy, fetchSelectedStories]); const onLoadMoreClick = useCallback(() => { - nextPage.current++; + nextPageRef.current++; fetchSelectedStories(); }, [fetchSelectedStories]); diff --git a/packages/stories-block/src/block/components/storyPicker/storyPicker.js b/packages/stories-block/src/block/components/storyPicker/storyPicker.js index 3a548347f319..f6fd6e057c72 100644 --- a/packages/stories-block/src/block/components/storyPicker/storyPicker.js +++ b/packages/stories-block/src/block/components/storyPicker/storyPicker.js @@ -99,7 +99,7 @@ function StoryPicker({ setStories((existingStories) => page === 1 ? response.body : [...existingStories, ...response.body] ); - } catch (err) { + } catch { setLoadingState('error'); createErrorNotice(__('Unable to load stories', 'web-stories'), { type: 'snackbar', diff --git a/packages/story-editor/src/app/api/apiProvider.tsx b/packages/story-editor/src/app/api/apiProvider.tsx index e23580815446..e90cb7bf8199 100644 --- a/packages/story-editor/src/app/api/apiProvider.tsx +++ b/packages/story-editor/src/app/api/apiProvider.tsx @@ -44,25 +44,32 @@ const filterTemplates = (templates: Template[], search: string): Template[] => { function APIProvider({ children }: PropsWithChildren>) { const { apiCallbacks: actions, cdnURL } = useConfig(); - const pageTemplates = useRef([]); + const pageTemplatesRef = useRef([]); - actions.getPageTemplates = useCallback( - async (search: string) => { - // check if pageTemplates have been loaded yet - if (pageTemplates.current.length === 0) { - pageTemplates.current = filterTemplates( - await getAllTemplates({ - cdnURL, - }), - search - ); - } - return filterTemplates(pageTemplates.current, search); - }, - [cdnURL] - ); + const newActions = { + ...actions, + getPageTemplates: useCallback( + async (search: string) => { + // check if pageTemplates have been loaded yet + if (pageTemplatesRef.current.length === 0) { + pageTemplatesRef.current = filterTemplates( + await getAllTemplates({ + cdnURL, + }), + search + ); + } + return filterTemplates(pageTemplatesRef.current, search); + }, + [cdnURL] + ), + }; - return {children}; + return ( + + {children} + + ); } export default APIProvider; diff --git a/packages/story-editor/src/app/canvas/canvasProvider.tsx b/packages/story-editor/src/app/canvas/canvasProvider.tsx index 18e1471330c6..e5f513fb8aeb 100644 --- a/packages/story-editor/src/app/canvas/canvasProvider.tsx +++ b/packages/story-editor/src/app/canvas/canvasProvider.tsx @@ -56,7 +56,7 @@ function CanvasProvider({ children }: PropsWithChildren) { const [boundingBoxes, setBoundingBoxes] = useState({}); const [lastSelectionEvent, setLastSelectionEvent] = useState(null); - const lastSelectedElementId = useRef(null); + const lastSelectedElementIdRef = useRef(null); const [canvasContainer, setCanvasContainer] = useState(null); const [pageContainer, setPageContainer] = useState(null); const [fullbleedContainer, setFullbleedContainer] = useState( @@ -158,10 +158,10 @@ function CanvasProvider({ children }: PropsWithChildren) { // Skip the focus that immediately follows mouse event. // Use the reference to the latest element because the events come in the // sequence in the same event loop. - if (lastSelectedElementId.current === elId && evt.type === 'focus') { + if (lastSelectedElementIdRef.current === elId && evt.type === 'focus') { return; } - lastSelectedElementId.current = elId; + lastSelectedElementIdRef.current = elId; if (evt.shiftKey) { toggleElementInSelection({ elementId: elId, withLinked: !evt.altKey }); } else { @@ -210,10 +210,10 @@ function CanvasProvider({ children }: PropsWithChildren) { clearEditing(); } if ( - lastSelectedElementId.current && - !selectedElementIds.includes(lastSelectedElementId.current) + lastSelectedElementIdRef.current && + !selectedElementIds.includes(lastSelectedElementIdRef.current) ) { - lastSelectedElementId.current = null; + lastSelectedElementIdRef.current = null; } }, [editingElement, selectedElementIds, clearEditing]); diff --git a/packages/story-editor/src/app/font/fontProvider.tsx b/packages/story-editor/src/app/font/fontProvider.tsx index 048df3a9764c..b507bbe8795f 100644 --- a/packages/story-editor/src/app/font/fontProvider.tsx +++ b/packages/story-editor/src/app/font/fontProvider.tsx @@ -167,22 +167,22 @@ function FontProvider({ children }: PropsWithChildren) { [recentFonts] ); - const menuFonts = useRef([]); + const menuFontsRef = useRef([]); const ensureMenuFontsLoaded = useCallback((fontsToLoad: string[]) => { const newMenuFonts = fontsToLoad.filter( - (fontName) => !menuFonts.current.includes(fontName) + (fontName) => !menuFontsRef.current.includes(fontName) ); if (!newMenuFonts?.length) { return; } - menuFonts.current = menuFonts.current.concat(newMenuFonts); + menuFontsRef.current = menuFontsRef.current.concat(newMenuFonts); // Create new in head with ref to new font families const families = encodeURIComponent(newMenuFonts.join('|')); const url = `${GOOGLE_MENU_FONT_URL}?family=${families}&subset=menu&display=swap`; loadStylesheet(url, 'web-stories-google-fonts-menu-css').catch(() => { // If they failed to load, remove from array again! - menuFonts.current = menuFonts.current.filter( + menuFontsRef.current = menuFontsRef.current.filter( (font) => !newMenuFonts.includes(font) ); }); diff --git a/packages/story-editor/src/app/history/historyProvider.tsx b/packages/story-editor/src/app/history/historyProvider.tsx index 1bd4e9d6c764..1fed04faf733 100644 --- a/packages/story-editor/src/app/history/historyProvider.tsx +++ b/packages/story-editor/src/app/history/historyProvider.tsx @@ -52,7 +52,7 @@ function HistoryProvider({ const [hasNewChanges, setHasNewChanges] = useState(false); const setPreventUnload = usePreventWindowUnload(); // The version number for the initially loaded (saved) state is 1. - const savedVersionNumber = useRef(1); + const savedVersionNumberRef = useRef(1); useEffect(() => { setPreventUnload('history', hasNewChanges); @@ -60,12 +60,12 @@ function HistoryProvider({ }, [setPreventUnload, hasNewChanges]); useEffect(() => { - setHasNewChanges(versionNumber !== savedVersionNumber.current); + setHasNewChanges(versionNumber !== savedVersionNumberRef.current); }, [versionNumber]); const resetNewChanges = useCallback(() => { // When new changes are saved, let's track which version was saved. - savedVersionNumber.current = versionNumber; + savedVersionNumberRef.current = versionNumber; setHasNewChanges(false); }, [versionNumber]); diff --git a/packages/story-editor/src/app/media/local/useContextValueProvider.js b/packages/story-editor/src/app/media/local/useContextValueProvider.js index e36a511f709e..04be9324f227 100644 --- a/packages/story-editor/src/app/media/local/useContextValueProvider.js +++ b/packages/story-editor/src/app/media/local/useContextValueProvider.js @@ -72,13 +72,13 @@ export default function useContextValueProvider(reducerState, reducerActions) { actions: { getMedia, updateMedia }, } = useAPI(); - const isMounted = useRef(false); + const isMountedRef = useRef(false); useEffect(() => { - isMounted.current = true; + isMountedRef.current = true; return () => { - isMounted.current = false; + isMountedRef.current = false; }; }, []); @@ -104,7 +104,7 @@ export default function useContextValueProvider(reducerState, reducerActions) { pagingNum: p, }) .then(({ data, headers }) => { - if (!isMounted.current) { + if (!isMountedRef.current) { return; } diff --git a/packages/story-editor/src/app/media/media3p/test/useContextValueProvider.js b/packages/story-editor/src/app/media/media3p/test/useContextValueProvider.js index 6b04eb8829de..80e4bc728f12 100644 --- a/packages/story-editor/src/app/media/media3p/test/useContextValueProvider.js +++ b/packages/story-editor/src/app/media/media3p/test/useContextValueProvider.js @@ -14,6 +14,11 @@ * limitations under the License. */ +/** + * External dependencies + */ +import { renderHook } from '@testing-library/react-hooks'; + /** * Internal dependencies */ @@ -28,16 +33,18 @@ describe('useContextValueProvider', () => { state: { media: [] }, }); - const value = useContextValueProvider( - { - selectedProvider: 'unsplash', - searchTerm: '', - unsplash: {}, - }, - { setSelectedProvider: () => {}, setSearchTerm: () => {} } + const { result } = renderHook(() => + useContextValueProvider( + { + selectedProvider: 'unsplash', + searchTerm: '', + unsplash: {}, + }, + { setSelectedProvider: () => {}, setSearchTerm: () => {} } + ) ); - expect(value).toStrictEqual( + expect(result.current).toStrictEqual( expect.objectContaining({ state: { selectedProvider: 'unsplash', searchTerm: '' }, actions: { diff --git a/packages/story-editor/src/app/media/media3p/useFetchCategoriesEffect.js b/packages/story-editor/src/app/media/media3p/useFetchCategoriesEffect.js index 993068bf7054..c5bc753224b4 100644 --- a/packages/story-editor/src/app/media/media3p/useFetchCategoriesEffect.js +++ b/packages/story-editor/src/app/media/media3p/useFetchCategoriesEffect.js @@ -63,7 +63,7 @@ export default function useFetchCategoriesEffect({ provider: PROVIDERS[provider].provider, }); fetchCategoriesSuccess({ provider, categories: newCategories }); - } catch (e) { + } catch { fetchCategoriesError({ provider }); showSnackbar({ message: PROVIDERS[provider].fetchCategoriesErrorMessage, diff --git a/packages/story-editor/src/app/media/uploadQueue/useMediaUploadQueue.ts b/packages/story-editor/src/app/media/uploadQueue/useMediaUploadQueue.ts index 81f319cc1b75..f4ae0d2354e0 100644 --- a/packages/story-editor/src/app/media/uploadQueue/useMediaUploadQueue.ts +++ b/packages/story-editor/src/app/media/uploadQueue/useMediaUploadQueue.ts @@ -105,7 +105,7 @@ function useMediaUploadQueue() { const { uploadVideoPoster } = useUploadVideoFrame({}); - const isMounted = useRef(false); + const isMountedRef = useRef(false); const currentTranscodingItem = useRef(null); const currentPosterGenerationItem = useRef(null); @@ -150,7 +150,7 @@ function useMediaUploadQueue() { const { resource: newResource, posterFile } = await getResourceFromLocalFile(file); - if (!isMounted.current) { + if (!isMountedRef.current) { return; } @@ -204,7 +204,7 @@ function useMediaUploadQueue() { height, }; - if (!isMounted.current) { + if (!isMountedRef.current) { return; } @@ -270,7 +270,7 @@ function useMediaUploadQueue() { try { const newFile = await convertGifToVideo(file); - if (!isMounted.current) { + if (!isMountedRef.current) { return; } @@ -304,7 +304,7 @@ function useMediaUploadQueue() { const { start, end } = trimData; const newFile = await trimVideo(file, start, end); - if (!isMounted.current) { + if (!isMountedRef.current) { return; } @@ -331,7 +331,7 @@ function useMediaUploadQueue() { try { const newFile = await stripAudioFromVideo(file); - if (!isMounted.current) { + if (!isMountedRef.current) { return; } @@ -364,7 +364,7 @@ function useMediaUploadQueue() { const newFile = await cropResource(file, additionalData.cropParams); const posterFile = await getFirstFrameOfVideo(newFile); - if (!isMounted.current) { + if (!isMountedRef.current) { return; } @@ -397,7 +397,7 @@ function useMediaUploadQueue() { try { const newFile = await transcodeVideo(file); - if (!isMounted.current) { + if (!isMountedRef.current) { return; } @@ -435,7 +435,7 @@ function useMediaUploadQueue() { additionalData )) as QueueItemResource; - if (!isMounted.current) { + if (!isMountedRef.current) { return; } @@ -455,7 +455,7 @@ function useMediaUploadQueue() { posterFile ); - if (!isMounted.current) { + if (!isMountedRef.current) { return; } @@ -483,7 +483,7 @@ function useMediaUploadQueue() { additionalData )) as ImageResource; - if (!isMounted.current) { + if (!isMountedRef.current) { return; } @@ -674,10 +674,10 @@ function useMediaUploadQueue() { }, [state.queue, uploadItem]); useEffect(() => { - isMounted.current = true; + isMountedRef.current = true; return () => { - isMounted.current = false; + isMountedRef.current = false; }; }, []); diff --git a/packages/story-editor/src/app/media/useMediaReducer.js b/packages/story-editor/src/app/media/useMediaReducer.js index 494d00595d6d..7d2bd509adb0 100644 --- a/packages/story-editor/src/app/media/useMediaReducer.js +++ b/packages/story-editor/src/app/media/useMediaReducer.js @@ -84,7 +84,7 @@ function useMediaReducer(reducer = rootReducer, actionsToWrap) { () => ({ local: localActionsToWrap, media3p: media3pActionsToWrap }), [] ); - actionsToWrap = actionsToWrap ?? defaultActionsToWrap; + const newActionsToWrap = actionsToWrap ?? defaultActionsToWrap; const initialValue = useMemo( () => reducer(undefined, { type: types.INITIAL_STATE }), @@ -93,8 +93,8 @@ function useMediaReducer(reducer = rootReducer, actionsToWrap) { const [state, dispatch] = useReducer(reducer, initialValue); const wrappedActions = useMemo( - () => wrapWithDispatch(actionsToWrap, dispatch), - [actionsToWrap] + () => wrapWithDispatch(newActionsToWrap, dispatch), + [newActionsToWrap] ); return { diff --git a/packages/story-editor/src/app/media/utils/useDetectBlurhash.js b/packages/story-editor/src/app/media/utils/useDetectBlurhash.js index c7d3fb77c6b7..6a3421735cc7 100644 --- a/packages/story-editor/src/app/media/utils/useDetectBlurhash.js +++ b/packages/story-editor/src/app/media/utils/useDetectBlurhash.js @@ -101,7 +101,7 @@ function useDetectBlurHash({ updateMediaElement }) { blurHash, }); } - } catch (error) { + } catch { // This might happen as an author when trying to updateMedia() that // was uploaded by someone else. // Do nothing with the error for now. @@ -130,7 +130,7 @@ function useDetectBlurHash({ updateMediaElement }) { if (posterResource) { imageSrc = getSmallestUrlForWidth(300, posterResource); } - } catch (error) { + } catch { // The user might not have the permission to access the video with context=edit. // This might happen as an author when the video // was uploaded by someone else. diff --git a/packages/story-editor/src/app/story/effects/useHashState.ts b/packages/story-editor/src/app/story/effects/useHashState.ts index 9e8d417b14f3..c1377b5b1945 100644 --- a/packages/story-editor/src/app/story/effects/useHashState.ts +++ b/packages/story-editor/src/app/story/effects/useHashState.ts @@ -64,7 +64,7 @@ function useHashState( _value = JSON.parse(decodeURI(paramValue)) as string; } } catch { - // @TODO Add some error handling + /* empty */ } return _value; }); diff --git a/packages/story-editor/src/components/autoSaveHandler/index.js b/packages/story-editor/src/components/autoSaveHandler/index.js index 9ca4a9b2b489..adbf758c732c 100644 --- a/packages/story-editor/src/components/autoSaveHandler/index.js +++ b/packages/story-editor/src/components/autoSaveHandler/index.js @@ -37,9 +37,9 @@ function AutoSaveHandler() { const isUploading = useIsUploadingToStory(); // Cache it to make it stable in terms of the below timeout - const cachedSaveStory = useRef(autoSave); + const cachedSaveStoryRef = useRef(autoSave); useEffect(() => { - cachedSaveStory.current = autoSave; + cachedSaveStoryRef.current = autoSave; }, [autoSave]); useEffect(() => { @@ -50,7 +50,7 @@ function AutoSaveHandler() { // back false after the save. // This timeout will thus be re-started when some new change occurs after an autosave. const timeout = setTimeout( - () => cachedSaveStory.current(), + () => cachedSaveStoryRef.current(), autoSaveInterval * 1000 ); diff --git a/packages/story-editor/src/components/canvas/editLayer.js b/packages/story-editor/src/components/canvas/editLayer.js index e344b673848b..b7b0c78af79e 100644 --- a/packages/story-editor/src/components/canvas/editLayer.js +++ b/packages/story-editor/src/components/canvas/editLayer.js @@ -106,19 +106,19 @@ function EditLayerForElement({ element, showOverflow }) { return () => focusCanvas(/* force */ false); }, [focusCanvas]); - const moveable = useRef(null); + const moveableRef = useRef(null); const setRef = useCallback( - (moveableRef) => { - moveable.current = moveableRef; - onMoveableMount?.(moveableRef); + (newMoveable) => { + moveableRef.current = newMoveable; + onMoveableMount?.(newMoveable); }, [onMoveableMount] ); const onResize = useCallback(() => { // Update moveable when resizing. - if (moveable.current) { - moveable.current.updateRect(); + if (moveableRef.current) { + moveableRef.current.updateRect(); } }, []); diff --git a/packages/story-editor/src/components/canvas/eyedropperLayer.js b/packages/story-editor/src/components/canvas/eyedropperLayer.js index eff556ca9de1..8c7cf1833a16 100644 --- a/packages/story-editor/src/components/canvas/eyedropperLayer.js +++ b/packages/story-editor/src/components/canvas/eyedropperLayer.js @@ -151,10 +151,10 @@ function EyedropperLayer() { const fullHeight = pageWidth / FULLBLEED_RATIO; const img = eyedropperImg; const imgRef = useRef(); - const magnifier = useRef(); - const magnifierInfo = useRef(); - const magnifierColor = useRef(); - const eyedropperCanvas = useRef(); + const magnifierRef = useRef(); + const magnifierInfoRef = useRef(); + const magnifierColorRef = useRef(); + const eyedropperCanvasRef = useRef(); const closeEyedropper = () => { setIsEyedropperActive(false); @@ -162,7 +162,7 @@ function EyedropperLayer() { setEyedropperPixelData(null); }; - useFocusOut(eyedropperCanvas, closeEyedropper, [isEyedropperActive, img]); + useFocusOut(eyedropperCanvasRef, closeEyedropper, [isEyedropperActive, img]); useGlobalKeyDownEffect('esc', closeEyedropper); @@ -185,7 +185,7 @@ function EyedropperLayer() { } const magnify = (x, y) => { - const canvas = magnifier.current; + const canvas = magnifierRef.current; if (canvas) { const ctx = canvas.getContext('2d'); @@ -227,14 +227,14 @@ function EyedropperLayer() { const y = (e.clientY - top) * (fullHeight / height); if (x < 0 || y < 0 || x > width || y > height) { - magnifierInfo.current.style.display = 'none'; + magnifierInfoRef.current.style.display = 'none'; return; } else { - magnifierInfo.current.style.display = 'block'; + magnifierInfoRef.current.style.display = 'block'; } // Move magnifier canvas. - magnifierInfo.current.style.transform = `translate(${ + magnifierInfoRef.current.style.transform = `translate(${ x - MAGNIFIER_SIZE / 2 }px, ${y}px)`; @@ -245,14 +245,14 @@ function EyedropperLayer() { const rgbaObject = getColorFromPixelData(eyedropperPixelData, x, y, width); const { r, g, b, a } = rgbaObject; const hex = rgba(r, g, b, a); - magnifierColor.current.style.background = `rgba(${r},${g},${b},${a})`; - magnifierColor.current.style.color = readableColor( + magnifierColorRef.current.style.background = `rgba(${r},${g},${b},${a})`; + magnifierColorRef.current.style.color = readableColor( hex, '#333', '#EDEDED', false ); - magnifierColor.current.innerText = hex; + magnifierColorRef.current.innerText = hex; }; const onClick = (e) => { @@ -278,17 +278,17 @@ function EyedropperLayer() { {/* Remove the safe zone so we don't have to move the canvas image up (we have fullbleed image). */} {/* eslint-disable-next-line styled-components-a11y/click-events-have-key-events, styled-components-a11y/no-static-element-interactions -- No pixel-by-pixel keyboard navigation. */} - + - + - + diff --git a/packages/story-editor/src/components/canvas/layout.js b/packages/story-editor/src/components/canvas/layout.js index dfb5ea75f78a..0f31354d156b 100644 --- a/packages/story-editor/src/components/canvas/layout.js +++ b/packages/story-editor/src/components/canvas/layout.js @@ -408,13 +408,13 @@ const PageArea = forwardRef(function PageArea( ); // We need to ref scroll, because scroll changes should not update a non-controlled layer - const scroll = useRef(); - scroll.current = { top: scrollTop, left: scrollLeft }; + const scrollRef = useRef(); + scrollRef.current = { top: scrollTop, left: scrollLeft }; // If zoom setting changes for a non-controlled layer, make sure to reset actual scroll inside container useEffect(() => { if (!isControlled) { - fullbleedRef.current.scrollTop = scroll.current.top; - fullbleedRef.current.scrollLeft = scroll.current.left; + fullbleedRef.current.scrollTop = scrollRef.current.top; + fullbleedRef.current.scrollLeft = scrollRef.current.left; } }, [isControlled, zoomSetting, fullbleedRef]); diff --git a/packages/story-editor/src/components/colorPicker/colorPicker.js b/packages/story-editor/src/components/colorPicker/colorPicker.js index 351cde0dee81..6cf6aa49de52 100644 --- a/packages/story-editor/src/components/colorPicker/colorPicker.js +++ b/packages/story-editor/src/components/colorPicker/colorPicker.js @@ -124,16 +124,16 @@ function ColorPicker({ // So, if the eyedropper is used from inside a floating menu color picker // the debounced onChange can never be seen. // this gives us a way to process that change when no longer mounted - const isMounted = useRef(true); + const isMountedRef = useRef(true); useEffect(() => { return () => { - isMounted.current = false; + isMountedRef.current = false; }; }, []); const handleColorChange = useCallback( (newColor) => { - if (isMounted.current) { + if (isMountedRef.current) { onDebouncedChange(newColor); } else { onChange(newColor); diff --git a/packages/story-editor/src/components/colorPicker/currentColorPicker.js b/packages/story-editor/src/components/colorPicker/currentColorPicker.js index 5075f922f17e..40fb54a90813 100644 --- a/packages/story-editor/src/components/colorPicker/currentColorPicker.js +++ b/packages/story-editor/src/components/colorPicker/currentColorPicker.js @@ -259,23 +259,23 @@ const CurrentColorPickerContext = createContext([false, false]); const DynamicImportWrapper = () => { return (...args) => { function DynamicFetcher({ showOpacity, hasEyedropper, ...props }) { - const isMounted = useRef(false); + const isMountedRef = useRef(false); const [Picker, setPicker] = useState(null); useEffect(() => { - isMounted.current = true; + isMountedRef.current = true; import( /* webpackChunkName: "chunk-react-color" */ /* webpackExports: "CustomPicker" */ '@hello-pangea/color-picker' ).then(({ CustomPicker }) => { - if (isMounted.current) { + if (isMountedRef.current) { setPicker({ component: CustomPicker(...args) }); } }); return () => { - isMounted.current = false; + isMountedRef.current = false; }; }, []); diff --git a/packages/story-editor/src/components/colorPicker/customColorPicker.js b/packages/story-editor/src/components/colorPicker/customColorPicker.js index d2b67aecfdd9..880805b348bc 100644 --- a/packages/story-editor/src/components/colorPicker/customColorPicker.js +++ b/packages/story-editor/src/components/colorPicker/customColorPicker.js @@ -65,10 +65,10 @@ function CustomColorPicker({ }, } = useColor(); - const isMounted = useRef(true); + const isMountedRef = useRef(true); useEffect(() => { return () => { - isMounted.current = false; + isMountedRef.current = false; }; }, []); @@ -96,7 +96,7 @@ function CustomColorPicker({ // check for unmount and if so, we know this change event // is from the eyedropper so we can just grab the rgb and // trigger the rest of the change for the element. - if (!isMounted.current && e?.rgb) { + if (!isMountedRef.current && e?.rgb) { handleColorChange({ color: e.rgb }); } }, diff --git a/packages/story-editor/src/components/colorPicker/gradientLine.js b/packages/story-editor/src/components/colorPicker/gradientLine.js index 83189b12850b..132adb14b1c9 100644 --- a/packages/story-editor/src/components/colorPicker/gradientLine.js +++ b/packages/story-editor/src/components/colorPicker/gradientLine.js @@ -96,7 +96,7 @@ function GradientLine({ useKeyMoveStop(line, onMove); useKeyAddStop(line, onAdd, stops, currentStopIndex); useKeyDeleteStop(line, onDelete); - const stopRefs = useKeyFocus(line, stops, currentStopIndex); + const stopsRef = useKeyFocus(line, stops, currentStopIndex); usePointerMoveStop(line, onMove); const tempPointerPosition = usePointerAddStop(line, onAdd); @@ -109,7 +109,8 @@ function GradientLine({ {stops.map(({ position, color }, index) => ( (stopRefs[index].current = ref)} + // eslint-disable-next-line react-compiler/react-compiler -- FIXME + ref={(ref) => (stopsRef[index].current = ref)} key={ // eslint-disable-next-line react/no-array-index-key -- Should be OK here. index diff --git a/packages/story-editor/src/components/colorPicker/usePointerMoveStop.js b/packages/story-editor/src/components/colorPicker/usePointerMoveStop.js index 0d69ae0fe3c5..6dd5e1d36072 100644 --- a/packages/story-editor/src/components/colorPicker/usePointerMoveStop.js +++ b/packages/story-editor/src/components/colorPicker/usePointerMoveStop.js @@ -26,17 +26,17 @@ import { LINE_LENGTH } from './constants'; import { getPageX, setPointerCapture, releasePointerCapture } from './utils'; function usePointerMoveStop(ref, onMove) { - const lastPageX = useRef(null); + const lastPageXRef = useRef(null); useLayoutEffect(() => { const node = ref.current; const onPointerMove = (evt) => { - const relativeDeltaX = getPageX(evt) - lastPageX.current; - lastPageX.current = getPageX(evt); + const relativeDeltaX = getPageX(evt) - lastPageXRef.current; + lastPageXRef.current = getPageX(evt); onMove(-relativeDeltaX / LINE_LENGTH); }; const onPointerUp = (evt) => { - lastPageX.current = null; + lastPageXRef.current = null; releasePointerCapture(evt); evt.target.removeEventListener('pointermove', onPointerMove); evt.target.removeEventListener('pointerup', onPointerUp); @@ -46,7 +46,7 @@ function usePointerMoveStop(ref, onMove) { if (evt.target === node) { return; } - lastPageX.current = getPageX(evt); + lastPageXRef.current = getPageX(evt); setPointerCapture(evt); evt.target.addEventListener('pointermove', onPointerMove); evt.target.addEventListener('pointerup', onPointerUp); diff --git a/packages/story-editor/src/components/errorBoundary/copyStoryDataToClipboard.js b/packages/story-editor/src/components/errorBoundary/copyStoryDataToClipboard.js index 23bb6e3512ef..fefaa2fae968 100644 --- a/packages/story-editor/src/components/errorBoundary/copyStoryDataToClipboard.js +++ b/packages/story-editor/src/components/errorBoundary/copyStoryDataToClipboard.js @@ -48,7 +48,7 @@ function CopyStoryDataToClipboard() { try { await navigator.clipboard.writeText(jsonStr); alert(__('Copied to clipboard', 'web-stories')); - } catch (err) { + } catch { alert(__('Failed to copy story data', 'web-stories')); } }, [pages, current, selection, story]); diff --git a/packages/story-editor/src/components/floatingMenu/elements/settings.js b/packages/story-editor/src/components/floatingMenu/elements/settings.js index 038aba9dea67..16ba6f81a3bf 100644 --- a/packages/story-editor/src/components/floatingMenu/elements/settings.js +++ b/packages/story-editor/src/components/floatingMenu/elements/settings.js @@ -87,7 +87,7 @@ function Settings() { // Record left position of this button in the parent design menu useEffect( - () => setOffsetLeft(buttonRef.current.parentNode.offsetLeft + OFFSET_X), + () => setOffsetLeft(buttonRef.current?.parentNode.offsetLeft + OFFSET_X), [] ); diff --git a/packages/story-editor/src/components/floatingMenu/elements/textAlign.js b/packages/story-editor/src/components/floatingMenu/elements/textAlign.js index fd8eb90dd566..4681a33ee4e0 100644 --- a/packages/story-editor/src/components/floatingMenu/elements/textAlign.js +++ b/packages/story-editor/src/components/floatingMenu/elements/textAlign.js @@ -105,10 +105,13 @@ function TextAlign() { const [offsetLeft, setOffsetLeft] = useState(0); // Record left position of this button in the parent design menu - useEffect( - () => setOffsetLeft(buttonRef.current.parentNode.offsetLeft + OFFSET_X), - [] - ); + useEffect(() => { + if (buttonRef.current) { + setOffsetLeft(buttonRef.current?.parentNode.offsetLeft + OFFSET_X); + } else { + setOffsetLeft(OFFSET_X); + } + }, []); // When menu has just opened, focus the current button in submenu const currentIconMounted = (node) => { diff --git a/packages/story-editor/src/components/floatingMenu/karma/utils.js b/packages/story-editor/src/components/floatingMenu/karma/utils.js index 5ccc3b5a536b..fd9562442629 100644 --- a/packages/story-editor/src/components/floatingMenu/karma/utils.js +++ b/packages/story-editor/src/components/floatingMenu/karma/utils.js @@ -21,7 +21,7 @@ export async function tabToCanvasFocusContainer(focusContainer, fixture) { // tab until focus reaches the canvas container let count = 0; - while (count < 15) { + while (count < 20) { // eslint-disable-next-line no-await-in-loop -- need to await key press await fixture.events.keyboard.press('tab'); @@ -32,7 +32,7 @@ export async function tabToCanvasFocusContainer(focusContainer, fixture) { count++; } - if (count >= 15) { + if (count >= 20) { throw new Error('Could not find focus container.'); } } diff --git a/packages/story-editor/src/components/floatingMenu/layer.js b/packages/story-editor/src/components/floatingMenu/layer.js index 2899dbe281b2..7d1fb194451f 100644 --- a/packages/story-editor/src/components/floatingMenu/layer.js +++ b/packages/story-editor/src/components/floatingMenu/layer.js @@ -76,7 +76,7 @@ function FloatingMenuLayer() { const [moveable, setMoveable] = useState(null); const menuRef = useRef(); - const workspaceSize = useRef(); + const workspaceSizeRef = useRef(); const [isDismissed, setDismissed] = useState(false); const handleDismiss = useCallback(() => setDismissed(true), []); @@ -102,7 +102,10 @@ function FloatingMenuLayer() { // Whenever the workspace resizes, update size useEffect(() => { - workspaceSize.current = { width: workspaceWidth, height: workspaceHeight }; + workspaceSizeRef.current = { + width: workspaceWidth, + height: workspaceHeight, + }; // Note that we don't have to manually update our position, because the selection // frame will already be updating because of the resize, so a DOM mutation is incoming. }, [workspaceWidth, workspaceHeight]); @@ -125,7 +128,7 @@ function FloatingMenuLayer() { // If the toolbar is positioned to the top, we keep it in a fixed position. const updatePosition = () => { const frameRect = moveable.getRect(); - const { width, height } = workspaceSize.current; + const { width, height } = workspaceSizeRef.current; if (floatingMenuPosition === TOOLBAR_POSITIONS.TOP) { menu.style.left = `clamp(0px, ${ width / 2 diff --git a/packages/story-editor/src/components/footer/carousel/carouselContext/carouselProvider.tsx b/packages/story-editor/src/components/footer/carousel/carouselContext/carouselProvider.tsx index 37e2149b61a2..bf9654caf652 100644 --- a/packages/story-editor/src/components/footer/carousel/carouselContext/carouselProvider.tsx +++ b/packages/story-editor/src/components/footer/carousel/carouselContext/carouselProvider.tsx @@ -52,7 +52,7 @@ function CarouselProvider({ ); const [listElement, setListElement] = useState(null); - const pageRefs = useRef>({}); + const pagesRef = useRef>({}); const numPages = pages.length; @@ -86,10 +86,10 @@ function CarouselProvider({ pageThumbMargin, }); - useCarouselKeys({ listElement, pageRefs }); + useCarouselKeys({ listElement, pageRefs: pagesRef }); const setPageRef = useCallback((page: Page, el: HTMLElement) => { - pageRefs.current[page.id] = el; + pagesRef.current[page.id] = el; }, []); const clickPage = useCallback( diff --git a/packages/story-editor/src/components/footer/gridview/gridView.js b/packages/story-editor/src/components/footer/gridview/gridView.js index 8654d0c09e98..ea9802e755ce 100644 --- a/packages/story-editor/src/components/footer/gridview/gridView.js +++ b/packages/story-editor/src/components/footer/gridview/gridView.js @@ -182,7 +182,7 @@ function GridView({ onClose }) { const handleClickPage = (page) => () => setCurrentPage({ pageId: page.id }); const gridRef = useRef(); - const pageRefs = useRef({}); + const pagesRef = useRef({}); const arrangeItem = useCallback( (focusedPageId, nextIndex) => { @@ -194,7 +194,7 @@ function GridView({ onClose }) { useGridViewKeys({ containerRef: wrapperRef, gridRef, - itemRefs: pageRefs, + itemRefs: pagesRef, isRTL, currentItemId: currentPageId, items: pages, @@ -254,7 +254,7 @@ function GridView({ onClose }) { { - pageRefs.current[page.id] = el; + pagesRef.current[page.id] = el; }} > `hierarchical_term_${name}`; * @param {string} option.label The label of the checkbox * @param {Function} option.onChange Change event handler * @param {boolean} option.checked The value of the checkbox - * @param {Object} option.optionRefs Ref used to store refs to checkboxes. + * @param {Object} option.optionRef Ref used to store refs to checkboxes. * @param {number} option.$level The indentation level. * @return {Node} The rendered option and children. */ -const Option = ({ optionRefs = { current: {} }, $level = 0, ...option }) => { +const Option = ({ optionRef = { current: {} }, $level = 0, ...option }) => { const { id, label, onBlur, onChange, onFocus, checked, value } = option; const optionId = buildOptionId(option.id); @@ -122,7 +122,7 @@ const Option = ({ optionRefs = { current: {} }, $level = 0, ...option }) => { { - optionRefs.current[id] = node; + optionRef.current[id] = node; }} value={value} checked={checked} @@ -141,7 +141,7 @@ const OptionPropType = { checked: PropTypes.bool, label: PropTypes.string.isRequired, $level: PropTypes.number, - optionRefs: PropTypes.shape({ + optionRef: PropTypes.shape({ current: PropTypes.shape({ [PropTypes.string.isRequired]: PropTypes.node, }), @@ -179,7 +179,7 @@ const HierarchicalInput = ({ // Focus handling const [focusedCheckboxId, setFocusedCheckboxId] = useState(-1); - const optionRefs = useRef({}); + const optionRef = useRef({}); /** * Handles listbox and checkbox focus. @@ -211,7 +211,7 @@ const HierarchicalInput = ({ setFocusedCheckboxId(firstCheckedOption ? firstCheckedOption : 0); } else { // else focus the previously focused option - optionRefs.current[focusedCheckboxId]?.focus(); + optionRef.current[focusedCheckboxId]?.focus(); } } }, @@ -295,7 +295,7 @@ const HierarchicalInput = ({ useEffect(() => { // only focus checkbox if focus is in the list if (checkboxListRef.current?.contains(document.activeElement)) { - optionRefs.current[focusedCheckboxId]?.focus(); + optionRef.current[focusedCheckboxId]?.focus(); } }, [focusedCheckboxId]); @@ -334,7 +334,7 @@ const HierarchicalInput = ({ key={option.id} {...option} onChange={handleCheckboxChange} - optionRefs={optionRefs} + optionRef={optionRef} /> )) ) : ( diff --git a/packages/story-editor/src/components/header/buttons/preview.js b/packages/story-editor/src/components/header/buttons/preview.js index 9c5f2750f6a4..2878a1d19fad 100644 --- a/packages/story-editor/src/components/header/buttons/preview.js +++ b/packages/story-editor/src/components/header/buttons/preview.js @@ -103,7 +103,7 @@ function PreviewButton({ forceIsSaving = false }) { ` ); } - } catch (e) { + } catch { // Ignore errors. Anything can happen with a popup. The errors // will be resolved after the story is saved. } diff --git a/packages/story-editor/src/components/header/buttons/publish.js b/packages/story-editor/src/components/header/buttons/publish.js index f44623417d06..10d21468af72 100644 --- a/packages/story-editor/src/components/header/buttons/publish.js +++ b/packages/story-editor/src/components/header/buttons/publish.js @@ -36,22 +36,23 @@ import { PublishModal } from '../../publishModal'; import ButtonWithChecklistWarning from './buttonWithChecklistWarning'; function PublishButton({ forceIsSaving }) { - const { date, storyId, saveStory, title, editLink, canPublish } = useStory( - ({ - state: { - story: { date, storyId, title, editLink }, - capabilities, - }, - actions: { saveStory }, - }) => ({ - date, - storyId, - saveStory, - title, - editLink, - canPublish: Boolean(capabilities?.publish), - }) - ); + const { date, storyId, saveStory, titleLength, editLink, canPublish } = + useStory( + ({ + state: { + story: { date, storyId, title, editLink }, + capabilities, + }, + actions: { saveStory }, + }) => ({ + date, + storyId, + saveStory, + titleLength: title?.length || 0, + editLink, + canPublish: Boolean(capabilities?.publish), + }) + ); const showPriorityIssues = useCheckpoint( ({ actions: { showPriorityIssues } }) => showPriorityIssues @@ -80,13 +81,13 @@ function PublishButton({ forceIsSaving }) { trackEvent('publish_story', { status: newStatus, - title_length: title.length, + title_length: titleLength, }); setShowDialog(false); saveStory({ status: newStatus }); refreshPostEditURL(); - }, [refreshPostEditURL, saveStory, hasFutureDate, title, canPublish]); + }, [refreshPostEditURL, saveStory, hasFutureDate, titleLength, canPublish]); const handlePublish = useCallback(() => { showPriorityIssues(); @@ -97,7 +98,7 @@ function PublishButton({ forceIsSaving }) { setShowDialog(false); if (focusPublishButton) { - publishButtonRef.current.focus(); + publishButtonRef.current?.focus(); } }, []); diff --git a/packages/story-editor/src/components/library/panes/media/common/innerElement.js b/packages/story-editor/src/components/library/panes/media/common/innerElement.js index 7f4fe3455852..7488a9d6cb4f 100644 --- a/packages/story-editor/src/components/library/panes/media/common/innerElement.js +++ b/packages/story-editor/src/components/library/panes/media/common/innerElement.js @@ -98,13 +98,13 @@ function InnerElement({ onClick, onLoad = noop, showVideoDetail, - mediaElement, + mediaElementRef, active, isMuted, }) { const newVideoPosterRef = useRef(null); // Track if we have already set the dragging resource. - const hasSetResourceTracker = useRef(null); + const hasSetResourceRef = useRef(null); // Note: This `useDropTargets` is purposefully separated from the one below since it // uses a custom function for checking for equality and is meant for `handleDrag` and `handleDrop` only. @@ -117,11 +117,11 @@ function InnerElement({ }), (prev, curr) => { // If we're dragging this element, always update the actions. - if (hasSetResourceTracker.current) { + if (hasSetResourceRef.current) { return false; // If we're rendering the first time, init `handleDrag` and `handleDrop`. - } else if (hasSetResourceTracker.current === null) { - hasSetResourceTracker.current = false; + } else if (hasSetResourceRef.current === null) { + hasSetResourceRef.current = false; return false; } // If the drop targets updated meanwhile, also update the actions, otherwise `handleDrag` won't consider those. @@ -156,8 +156,8 @@ function InnerElement({ }, [resource.poster]); const makeMediaVisible = () => { - if (mediaElement.current) { - mediaElement.current.style.opacity = 1; + if (mediaElementRef.current) { + mediaElementRef.current.style.opacity = 1; } onLoad(); }; @@ -206,17 +206,17 @@ function InnerElement({ if (type === ContentType.Image || type === ContentType.Sticker) { // eslint-disable-next-line styled-components-a11y/alt-text -- False positive. - media = ; + media = ; cloneProps.src = thumbnailURL; } else if ([ContentType.Video, ContentType.Gif].includes(type)) { media = ( <> {poster && !active ? ( /* eslint-disable-next-line styled-components-a11y/alt-text -- False positive. */ - + ) : ( // eslint-disable-next-line jsx-a11y/media-has-caption,styled-components-a11y/media-has-caption -- No captions/tracks because video is muted. -