diff --git a/package.json b/package.json index e544c745549..3f5753d2195 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,8 @@ "bumpVersions": "node scripts/bumpVersions.js", "test:parcel": "jest --testMatch '**/*.parceltest.{ts,tsx}'", "blc:local": "npx git+ssh://git@github.com:ktabors/rsp-blc.git -u http://localhost:1234/", - "blc:prod": "npx git+ssh://git@github.com:ktabors/rsp-blc.git -u https://react-spectrum.adobe.com/" + "blc:prod": "npx git+ssh://git@github.com:ktabors/rsp-blc.git -u https://react-spectrum.adobe.com/", + "createRssFeed": "node scripts/createFeed.mjs" }, "workspaces": [ "packages/react-stately", @@ -210,6 +211,7 @@ "verdaccio": "^5.13.0", "walk-object": "^4.0.0", "wsrun": "^5.0.0", + "xml": "^1.0.1", "yargs": "^17.2.1" }, "resolutions": { diff --git a/packages/@adobe/spectrum-css-temp/components/menu/index.css b/packages/@adobe/spectrum-css-temp/components/menu/index.css index d15fbdbb353..d9f4477afd5 100644 --- a/packages/@adobe/spectrum-css-temp/components/menu/index.css +++ b/packages/@adobe/spectrum-css-temp/components/menu/index.css @@ -222,6 +222,7 @@ governing permissions and limitations under the License. justify-self: end; align-self: flex-start; padding-inline-start: var(--spectrum-global-dimension-size-250); + box-sizing: content-box; } .spectrum-Menu-icon { grid-area: icon; diff --git a/packages/@react-aria/focus/src/FocusScope.tsx b/packages/@react-aria/focus/src/FocusScope.tsx index f48af3ff981..f955e9c8995 100644 --- a/packages/@react-aria/focus/src/FocusScope.tsx +++ b/packages/@react-aria/focus/src/FocusScope.tsx @@ -12,7 +12,8 @@ import {FocusableElement, RefObject} from '@react-types/shared'; import {focusSafely} from './focusSafely'; -import {getOwnerDocument, useLayoutEffect} from '@react-aria/utils'; +import {getInteractionModality} from '@react-aria/interactions'; +import {getOwnerDocument, isAndroid, isChrome, useLayoutEffect} from '@react-aria/utils'; import {isElementVisible} from './isElementVisible'; import React, {ReactNode, useContext, useEffect, useMemo, useRef} from 'react'; @@ -381,8 +382,14 @@ function useFocusContainment(scopeRef: RefObject, contain?: bo cancelAnimationFrame(raf.current); } raf.current = requestAnimationFrame(() => { + // Patches infinite focus coersion loop for Android Talkback where the user isn't able to move the virtual cursor + // if within a containing focus scope. Bug filed against Chrome: https://issuetracker.google.com/issues/384844019. + // Note that this means focus can leave focus containing modals due to this, but it is isolated to Chrome Talkback. + let modality = getInteractionModality(); + let shouldSkipFocusRestore = (modality === 'virtual' || modality === null) && isAndroid() && isChrome(); + // Use document.activeElement instead of e.relatedTarget so we can tell if user clicked into iframe - if (ownerDocument.activeElement && shouldContainFocus(scopeRef) && !isElementInChildScope(ownerDocument.activeElement, scopeRef)) { + if (!shouldSkipFocusRestore && ownerDocument.activeElement && shouldContainFocus(scopeRef) && !isElementInChildScope(ownerDocument.activeElement, scopeRef)) { activeScope = scopeRef; if (ownerDocument.body.contains(e.target)) { focusedNode.current = e.target; diff --git a/packages/@react-spectrum/menu/test/MenuTrigger.test.js b/packages/@react-spectrum/menu/test/MenuTrigger.test.js index 80b3cba75a7..968add9fd0a 100644 --- a/packages/@react-spectrum/menu/test/MenuTrigger.test.js +++ b/packages/@react-spectrum/menu/test/MenuTrigger.test.js @@ -1123,4 +1123,3 @@ AriaMenuTests({ ) } }); - diff --git a/packages/@react-spectrum/s2/src/Button.tsx b/packages/@react-spectrum/s2/src/Button.tsx index ab0a67055c5..f1aefdbe703 100644 --- a/packages/@react-spectrum/s2/src/Button.tsx +++ b/packages/@react-spectrum/s2/src/Button.tsx @@ -154,7 +154,6 @@ const button = style {blogPages.map(page => )} +
+ + + RSS Link + + +
); } diff --git a/packages/dev/optimize-locales-plugin/LocalesPlugin.js b/packages/dev/optimize-locales-plugin/LocalesPlugin.js index ae559a5dd98..ae38f372826 100644 --- a/packages/dev/optimize-locales-plugin/LocalesPlugin.js +++ b/packages/dev/optimize-locales-plugin/LocalesPlugin.js @@ -10,6 +10,7 @@ * governing permissions and limitations under the License. */ const {createUnplugin} = require('unplugin'); +const path = require('path'); module.exports = createUnplugin(({locales}) => { locales = locales.map(l => new Intl.Locale(l)); @@ -24,7 +25,7 @@ module.exports = createUnplugin(({locales}) => { if (match) { let locale = new Intl.Locale(match[0]); if (!locales.some(l => localeMatches(locale, l))) { - return __dirname + '/empty.js'; + return path.join(__dirname, 'empty.js'); } } diff --git a/scripts/buildWebsite.js b/scripts/buildWebsite.js index 1ee1de53e6a..ab5c6d4998d 100644 --- a/scripts/buildWebsite.js +++ b/scripts/buildWebsite.js @@ -60,7 +60,8 @@ async function build() { name === 'tailwind-variants' || name === 'react' || name === 'react-dom' || - name === 'typescript' + name === 'typescript' || + name === 'xml' ) ), dependencies: { @@ -78,7 +79,8 @@ async function build() { scripts: { // Add a public url if provided via arg (for verdaccio prod doc website build since we want a commit hash) build: `DOCS_ENV=production PARCEL_WORKER_BACKEND=process GIT_HASH=${gitHash} parcel build 'docs/*/*/docs/*.mdx' 'docs/react-aria-components/docs/**/*.mdx' 'packages/dev/docs/pages/**/*.mdx' ${publicUrlFlag}`, - postinstall: 'patch-package' + postinstall: 'patch-package', + createRssFeed: "node scripts/createFeed.mjs" }, '@parcel/transformer-css': packageJSON['@parcel/transformer-css'] }; @@ -134,6 +136,7 @@ async function build() { fs.copySync(path.join(__dirname, '..', '.yarn', 'releases'), path.join(dir, '.yarn', 'releases')); fs.copySync(path.join(__dirname, '..', '.yarn', 'plugins'), path.join(dir, '.yarn', 'plugins')); fs.copySync(path.join(__dirname, '..', '.yarnrc.yml'), path.join(dir, '.yarnrc.yml')); + fs.copySync(path.join(__dirname, '..', 'scripts', 'createFeed.mjs'), path.join(dir, 'scripts', 'createFeed.mjs')); // Delete mdx files from dev/docs that shouldn't go out yet. let devPkg = JSON.parse(fs.readFileSync(path.join(dir, 'packages/dev/docs/package.json'), 'utf8')); @@ -170,8 +173,14 @@ async function build() { // Build the website await run('yarn', ['build'], {cwd: dir, stdio: 'inherit'}); + // Generate the rss file for the release notes and blog + await run('yarn', ['createRssFeed', 'releases'], {cwd: dir, stdio: 'inherit'}); + await run('yarn', ['createRssFeed', 'blog'], {cwd: dir, stdio: 'inherit'}); + // Copy the build back into dist, and delete the temp dir. fs.copySync(path.join(dir, 'dist'), path.join(__dirname, '..', 'dist', 'production', 'docs')); + fs.copySync(path.join(dir, 'scripts', 'releases-feed.rss'), path.join(__dirname, '..', 'dist', 'production', 'docs', 'releases', 'releases-feed.rss')) + fs.copySync(path.join(dir, 'scripts', 'blog-feed.rss'), path.join(__dirname, '..', 'dist', 'production', 'docs', 'blog', 'blog-feed.rss')) fs.removeSync(dir); } diff --git a/scripts/createFeed.mjs b/scripts/createFeed.mjs new file mode 100644 index 00000000000..9e151946d35 --- /dev/null +++ b/scripts/createFeed.mjs @@ -0,0 +1,134 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import glob from 'glob'; +import fs from 'fs'; +import xml from "xml"; +import process from 'process'; +import {parseArgs} from 'node:util'; + +let options = { + releases: { + type: 'string' + }, + blog: { + type: 'string' + } +}; + +(async function createRssFeed() { + + let args = parseArgs({options, allowPositionals: true}); + + if (args.positionals.length < 1) { + console.error('Expected at least one argument'); + process.exit(1); + } + + let type = args.positionals[0]; + if (type !== 'releases' && type !== 'blog') { + console.error('Expected argument to be either releases or blog'); + process.exit(1); + } + + let titleType = type === 'releases' ? 'Releases' : 'Blog' + let posts = getFeed(type); + const feedObject = { + rss: [ + {_attr: { + version: "2.0", + "xmlns:atom": "http://www.w3.org/2005/Atom", + }, + }, + {channel: [ + {"atom:link": { + _attr: { + href: `https://react-spectrum.adobe.com/${type}/${type}-feed.rss`, + rel: "self", + type: "application/rss+xml", + }, + }, + }, + {title: `Adobe React Spectrum ${titleType}`}, + {link: `https://react-spectrum.adobe.com/${type}`}, + {description: "A collection of libraries and tools that help you build adaptive, accessible, and robust user experiences."}, + {language: "en-US"}, + ...buildFeed(type, posts) + ], + }, + ], + }; + + const feed = xml(feedObject, {declaration: true}); + await fs.writeFile(`scripts/${type}-feed.rss`, feed, (err) => console.log(err === null ? 'Success' : err)); +})(); + +function getFeed(type) { + let files = glob.sync(`packages/dev/docs/pages/${type}/*.mdx`, {ignore: [`packages/dev/docs/pages/${type}/index.mdx`]}); + let posts = []; + + for (let file of files) { + let contents = fs.readFileSync(file, 'utf8').split("\n"); + + let date = ''; + let description = ''; + let title = ''; + let index = 0; + while (date === '' || description === '' || title === '' && index < contents.length) { + if (contents[index].startsWith('description')) { + description = contents[index].replace('description:', '').trim(); + } else if (contents[index].startsWith('date:')) { + date = contents[index].replace('date:', '').trim(); + } else if (contents[index].startsWith('#')) { + title = contents[index].replace('#', '').trim(); + } + index++; + } + + let f = file.split('/'); + let fileName = f[f.length - 1].replace('.mdx', ''); + + let post = {date: date, description: description, title: title, fileName: fileName}; + posts.push(post); + } + + posts = posts.sort((a, b) => a.date < b.date ? 1 : -1).slice(0, 5); + return posts; +} + +function buildFeed(type, posts) { + const feedItems = []; + + feedItems.push( + ...posts.map(function (post) { + const feedItem = { + item: [ + {title: post.title}, + {pubDate: new Date(post.date).toUTCString(),}, + {guid: [ + { _attr: { isPermaLink: true } }, + `https://react-spectrum.adobe.com/${type}/${post.fileName}.html`, + ], + }, + {description: { + _cdata: post.description, + }, + }, + ], + }; + return feedItem; + }) + ); + + return feedItems; +} + diff --git a/yarn.lock b/yarn.lock index 0886efe92c5..7f47e45bd49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29324,6 +29324,7 @@ __metadata: verdaccio: "npm:^5.13.0" walk-object: "npm:^4.0.0" wsrun: "npm:^5.0.0" + xml: "npm:^1.0.1" yargs: "npm:^17.2.1" languageName: unknown linkType: soft