From 4be9ce5513b47094e9fbedbd7a4772b87889a5c1 Mon Sep 17 00:00:00 2001 From: Daniel Kostro Date: Tue, 12 Sep 2023 16:55:51 +0200 Subject: [PATCH 1/8] UI: fix keydown focusInInput check when input is in a shadow dom --- .../src/modules/preview-web/PreviewWeb.test.ts | 8 ++++++-- .../src/modules/preview-web/PreviewWithSelection.tsx | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts index 41e735ebfddb..c8a6b1a89e64 100644 --- a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts +++ b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts @@ -3503,7 +3503,9 @@ describe('PreviewWeb', () => { const preview = await createAndRenderPreview(); preview.onKeydown({ - target: { tagName: 'div', getAttribute: jest.fn().mockReturnValue(null) }, + composedPath: jest + .fn() + .mockReturnValue([{ tagName: 'div', getAttribute: jest.fn().mockReturnValue(null) }]), } as any); expect(mockChannel.emit).toHaveBeenCalledWith(PREVIEW_KEYDOWN, expect.objectContaining({})); @@ -3514,7 +3516,9 @@ describe('PreviewWeb', () => { const preview = await createAndRenderPreview(); preview.onKeydown({ - target: { tagName: 'input', getAttribute: jest.fn().mockReturnValue(null) }, + composedPath: jest + .fn() + .mockReturnValue([{ tagName: 'input', getAttribute: jest.fn().mockReturnValue(null) }]), } as any); expect(mockChannel.emit).not.toHaveBeenCalledWith( diff --git a/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx b/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx index ca30544d7c49..b40545bcb4ba 100644 --- a/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx +++ b/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx @@ -45,7 +45,7 @@ import type { StorySpecifier } from '../store/StoryIndexStore'; const globalWindow = globalThis; function focusInInput(event: Event) { - const target = event.target as Element; + const target = event.composedPath()[0] as Element; return /input|textarea/i.test(target.tagName) || target.getAttribute('contenteditable') !== null; } From c297b5d78701fcbb3495a7b7038dc973f7f59063 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Tue, 19 Sep 2023 10:27:55 +0200 Subject: [PATCH 2/8] fix `sb add` adding the same addon even if already present --- code/lib/cli/src/add.ts | 28 +++++++++++++++++-- .../src/utils/get-storybook-info.ts | 2 +- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/code/lib/cli/src/add.ts b/code/lib/cli/src/add.ts index 71437b314b1c..4ebe59437493 100644 --- a/code/lib/cli/src/add.ts +++ b/code/lib/cli/src/add.ts @@ -1,6 +1,8 @@ -import { getStorybookInfo } from '@storybook/core-common'; +import { getStorybookInfo, loadAllPresets, serverRequire } from '@storybook/core-common'; import { readConfig, writeConfig } from '@storybook/csf-tools'; +import { isAbsolute, join, relative } from 'path'; import SemVer from 'semver'; +import dedent from 'ts-dedent'; import { JsPackageManagerFactory, @@ -38,6 +40,21 @@ const getVersionSpecifier = (addon: string) => { return groups ? [groups[1], groups[2]] : [addon, undefined]; }; +const requireMain = (configDir: string) => { + const absoluteConfigDir = isAbsolute(configDir) ? configDir : join(process.cwd(), configDir); + const mainFile = join(absoluteConfigDir, 'main'); + + return serverRequire(mainFile) ?? {}; +}; + +const checkInstalled = (addonName: string, main: any) => { + const existingAddon = main.addons?.find((entry: string | { name: string }) => { + const name = typeof entry === 'string' ? entry : entry.name; + return name?.endsWith(addonName); + }); + return !!existingAddon; +}; + /** * Install the given addon package and add it to main.js * @@ -60,9 +77,16 @@ export async function add( } const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr }); const packageJson = await packageManager.retrievePackageJson(); + const { mainConfig, configDir } = getStorybookInfo(packageJson); + + if (checkInstalled(addon, requireMain(configDir))) { + throw new Error(dedent` + Addon ${addon} is already installed; we skipped adding it to your ${mainConfig}. + `); + } + const [addonName, versionSpecifier] = getVersionSpecifier(addon); - const { mainConfig } = getStorybookInfo(packageJson); if (!mainConfig) { logger.error('Unable to find storybook main.js config'); return; diff --git a/code/lib/core-common/src/utils/get-storybook-info.ts b/code/lib/core-common/src/utils/get-storybook-info.ts index fe183d566b7c..8d97fed4d3ed 100644 --- a/code/lib/core-common/src/utils/get-storybook-info.ts +++ b/code/lib/core-common/src/utils/get-storybook-info.ts @@ -100,7 +100,7 @@ const getConfigInfo = (packageJson: PackageJson, configDir?: string) => { } return { - configDir, + configDir: storybookConfigDir, mainConfig: findConfigFile('main', storybookConfigDir), previewConfig: findConfigFile('preview', storybookConfigDir), managerConfig: findConfigFile('manager', storybookConfigDir), From cee6a1e1ad5aa60f395321d61ac44666460be59e Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Tue, 19 Sep 2023 12:25:57 +0200 Subject: [PATCH 3/8] cleanup --- code/lib/cli/src/add.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/lib/cli/src/add.ts b/code/lib/cli/src/add.ts index 4ebe59437493..8728da80ad5f 100644 --- a/code/lib/cli/src/add.ts +++ b/code/lib/cli/src/add.ts @@ -1,6 +1,6 @@ -import { getStorybookInfo, loadAllPresets, serverRequire } from '@storybook/core-common'; +import { getStorybookInfo, serverRequire } from '@storybook/core-common'; import { readConfig, writeConfig } from '@storybook/csf-tools'; -import { isAbsolute, join, relative } from 'path'; +import { isAbsolute, join } from 'path'; import SemVer from 'semver'; import dedent from 'ts-dedent'; From 16968f05a7499083b0de185b2007d9b734479ea6 Mon Sep 17 00:00:00 2001 From: Daniel Kostro Date: Tue, 19 Sep 2023 14:25:32 +0200 Subject: [PATCH 4/8] implement fallback to composedPath in the unlikely case that it doesn't exist or returns an empty array --- .../src/modules/preview-web/PreviewWeb.test.ts | 11 +++++++++++ .../src/modules/preview-web/PreviewWithSelection.tsx | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts index c8a6b1a89e64..8123020b86bb 100644 --- a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts +++ b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts @@ -3511,6 +3511,17 @@ describe('PreviewWeb', () => { expect(mockChannel.emit).toHaveBeenCalledWith(PREVIEW_KEYDOWN, expect.objectContaining({})); }); + it('emits PREVIEW_KEYDOWN for regular elements, fallback to event.target', async () => { + document.location.search = '?id=component-one--docs&viewMode=docs'; + const preview = await createAndRenderPreview(); + + preview.onKeydown({ + target: { tagName: 'div', getAttribute: jest.fn().mockReturnValue(null) }, + } as any); + + expect(mockChannel.emit).toHaveBeenCalledWith(PREVIEW_KEYDOWN, expect.objectContaining({})); + }); + it('does not emit PREVIEW_KEYDOWN for input elements', async () => { document.location.search = '?id=component-one--docs&viewMode=docs'; const preview = await createAndRenderPreview(); diff --git a/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx b/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx index b40545bcb4ba..73b58cc70ead 100644 --- a/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx +++ b/code/lib/preview-api/src/modules/preview-web/PreviewWithSelection.tsx @@ -45,7 +45,7 @@ import type { StorySpecifier } from '../store/StoryIndexStore'; const globalWindow = globalThis; function focusInInput(event: Event) { - const target = event.composedPath()[0] as Element; + const target = ((event.composedPath && event.composedPath()[0]) || event.target) as Element; return /input|textarea/i.test(target.tagName) || target.getAttribute('contenteditable') !== null; } From 80ee481f3eec833fd3c2a21be232ce80fd486f98 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 20 Sep 2023 11:28:07 +0200 Subject: [PATCH 5/8] Make Nextjs 13.5 work again by mapping it's dependencies to aliasses Co-authored-by: Yann Braga --- code/frameworks/nextjs/src/dependency-map.ts | 40 +++++++++++++++++++ code/frameworks/nextjs/src/preset.ts | 4 +- .../frameworks/nextjs/src/routing/webpack.tsx | 18 --------- 3 files changed, 42 insertions(+), 20 deletions(-) create mode 100644 code/frameworks/nextjs/src/dependency-map.ts delete mode 100644 code/frameworks/nextjs/src/routing/webpack.tsx diff --git a/code/frameworks/nextjs/src/dependency-map.ts b/code/frameworks/nextjs/src/dependency-map.ts new file mode 100644 index 000000000000..c60c99630027 --- /dev/null +++ b/code/frameworks/nextjs/src/dependency-map.ts @@ -0,0 +1,40 @@ +import type { Configuration as WebpackConfig } from 'webpack'; +import semver from 'semver'; +import { getNextjsVersion, addScopedAlias } from './utils'; + +const mapping = { + default: { + 'next/dist/shared/lib/router-context': 'next/dist/next-server/lib/router-context', + 'next/dist/shared/lib/head-manager-context': 'next/dist/shared/lib/head-manager-context', + 'next/dist/shared/lib/app-router-context': 'next/dist/shared/lib/app-router-context', + 'next/dist/shared/lib/hooks-client-context': 'next/dist/shared/lib/hooks-client-context', + }, + '11.1.0': { + 'next/dist/shared/lib/router-context': 'next/dist/shared/lib/router-context', + 'next/dist/shared/lib/head-manager-context': 'next/dist/shared/lib/head-manager-context', + 'next/dist/shared/lib/app-router-context': 'next/dist/shared/lib/app-router-context', + 'next/dist/shared/lib/hooks-client-context': 'next/dist/shared/lib/hooks-client-context', + }, + '13.5.1': { + 'next/dist/shared/lib/router-context': 'next/dist/shared/lib/router-context.shared-runtime', + 'next/dist/shared/lib/head-manager-context': + 'next/dist/shared/lib/head-manager-context.shared-runtime', + 'next/dist/shared/lib/app-router-context': + 'next/dist/shared/lib/app-router-context.shared-runtime', + 'next/dist/shared/lib/hooks-client-context': + 'next/dist/shared/lib/hooks-client-context.shared-runtime', + }, +}; + +export const configureAliasing = (baseConfig: WebpackConfig): void => { + const version = getNextjsVersion(); + const result: Record = { ...mapping.default }; + + if (semver.gte(version, '13.5.1')) { + Object.assign(result, mapping['13.5.1']); + } + + Object.entries(result).forEach(([name, alias]) => { + addScopedAlias(baseConfig, name, alias); + }); +}; diff --git a/code/frameworks/nextjs/src/preset.ts b/code/frameworks/nextjs/src/preset.ts index 4aa06d54c4d0..db1c276da9b4 100644 --- a/code/frameworks/nextjs/src/preset.ts +++ b/code/frameworks/nextjs/src/preset.ts @@ -7,7 +7,6 @@ import { getProjectRoot } from '@storybook/core-common'; import { configureConfig } from './config/webpack'; import { configureCss } from './css/webpack'; import { configureImports } from './imports/webpack'; -import { configureRouting } from './routing/webpack'; import { configureStyledJsx } from './styledJsx/webpack'; import { configureImages } from './images/webpack'; import { configureRuntimeNextjsVersionResolution } from './utils'; @@ -17,6 +16,7 @@ import TransformFontImports from './font/babel'; import { configureNextFont } from './font/webpack/configureNextFont'; import nextBabelPreset from './babel/preset'; import { configureNodePolyfills } from './nodePolyfills/webpack'; +import { configureAliasing } from './dependency-map'; export const addons: PresetProperty<'addons', StorybookConfig> = [ dirname(require.resolve(join('@storybook/preset-react-webpack', 'package.json'))), @@ -143,13 +143,13 @@ export const webpackFinal: StorybookConfig['webpackFinal'] = async (baseConfig, configDir: options.configDir, }); + configureAliasing(baseConfig); configureNextFont(baseConfig); configureNextImport(baseConfig); configureRuntimeNextjsVersionResolution(baseConfig); configureImports({ baseConfig, configDir: options.configDir }); configureCss(baseConfig, nextConfig); configureImages(baseConfig, nextConfig); - configureRouting(baseConfig); configureStyledJsx(baseConfig); configureNodePolyfills(baseConfig); diff --git a/code/frameworks/nextjs/src/routing/webpack.tsx b/code/frameworks/nextjs/src/routing/webpack.tsx deleted file mode 100644 index c0d245219742..000000000000 --- a/code/frameworks/nextjs/src/routing/webpack.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Configuration as WebpackConfig } from 'webpack'; -import semver from 'semver'; -import { addScopedAlias, getNextjsVersion } from '../utils'; - -export const configureRouting = (baseConfig: WebpackConfig): void => { - // here we resolve the router context path with the installed version of Next.js - const routerContextPath = getRouterContextPath(); - addScopedAlias(baseConfig, routerContextPath); -}; - -const getRouterContextPath = () => { - const version = getNextjsVersion(); - if (semver.gte(version, '11.1.0')) { - return 'next/dist/shared/lib/router-context'; - } - - return 'next/dist/next-server/lib/router-context'; -}; From 1ce0548b2543e87945356659d0aa5895f93f985f Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 20 Sep 2023 12:15:20 +0200 Subject: [PATCH 6/8] improve the mapping of versions so we can easily change which versions get which aliases --- code/frameworks/nextjs/src/config/webpack.ts | 16 ++++++++++-- code/frameworks/nextjs/src/dependency-map.ts | 26 +++++++++----------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/code/frameworks/nextjs/src/config/webpack.ts b/code/frameworks/nextjs/src/config/webpack.ts index 13c1a251cebe..f5e72bc360d8 100644 --- a/code/frameworks/nextjs/src/config/webpack.ts +++ b/code/frameworks/nextjs/src/config/webpack.ts @@ -5,6 +5,14 @@ import type { NextConfig } from 'next'; import { DefinePlugin } from 'webpack'; import { addScopedAlias, getNextjsVersion, resolveNextConfig } from '../utils'; +const tryResolve = (path: string) => { + try { + return require.resolve(path); + } catch (err) { + return false; + } +}; + export const configureConfig = async ({ baseConfig, nextConfigPath, @@ -17,8 +25,12 @@ export const configureConfig = async ({ const nextConfig = await resolveNextConfig({ baseConfig, nextConfigPath, configDir }); addScopedAlias(baseConfig, 'next/config'); - addScopedAlias(baseConfig, 'react', 'next/dist/compiled/react'); - addScopedAlias(baseConfig, 'react-dom', 'next/dist/compiled/react-dom'); + if (tryResolve('next/dist/compiled/react')) { + addScopedAlias(baseConfig, 'react', 'next/dist/compiled/react'); + } + if (tryResolve('next/dist/compiled/react-dom')) { + addScopedAlias(baseConfig, 'react-dom', 'next/dist/compiled/react-dom'); + } setupRuntimeConfig(baseConfig, nextConfig); return nextConfig; diff --git a/code/frameworks/nextjs/src/dependency-map.ts b/code/frameworks/nextjs/src/dependency-map.ts index c60c99630027..31342722fb50 100644 --- a/code/frameworks/nextjs/src/dependency-map.ts +++ b/code/frameworks/nextjs/src/dependency-map.ts @@ -2,20 +2,14 @@ import type { Configuration as WebpackConfig } from 'webpack'; import semver from 'semver'; import { getNextjsVersion, addScopedAlias } from './utils'; -const mapping = { - default: { - 'next/dist/shared/lib/router-context': 'next/dist/next-server/lib/router-context', - 'next/dist/shared/lib/head-manager-context': 'next/dist/shared/lib/head-manager-context', - 'next/dist/shared/lib/app-router-context': 'next/dist/shared/lib/app-router-context', - 'next/dist/shared/lib/hooks-client-context': 'next/dist/shared/lib/hooks-client-context', +const mapping: Record> = { + '<11.1.0': { + 'next/dist/next-server/lib/router-context': 'next/dist/next-server/lib/router-context', }, - '11.1.0': { + '>11.1.0': { 'next/dist/shared/lib/router-context': 'next/dist/shared/lib/router-context', - 'next/dist/shared/lib/head-manager-context': 'next/dist/shared/lib/head-manager-context', - 'next/dist/shared/lib/app-router-context': 'next/dist/shared/lib/app-router-context', - 'next/dist/shared/lib/hooks-client-context': 'next/dist/shared/lib/hooks-client-context', }, - '13.5.1': { + '>13.5.0': { 'next/dist/shared/lib/router-context': 'next/dist/shared/lib/router-context.shared-runtime', 'next/dist/shared/lib/head-manager-context': 'next/dist/shared/lib/head-manager-context.shared-runtime', @@ -28,11 +22,13 @@ const mapping = { export const configureAliasing = (baseConfig: WebpackConfig): void => { const version = getNextjsVersion(); - const result: Record = { ...mapping.default }; + const result: Record = {}; - if (semver.gte(version, '13.5.1')) { - Object.assign(result, mapping['13.5.1']); - } + Object.keys(mapping).forEach((key) => { + if (semver.intersects(version, key)) { + Object.assign(result, mapping[key]); + } + }); Object.entries(result).forEach(([name, alias]) => { addScopedAlias(baseConfig, name, alias); From afe4c537810f28191168b5b19a0c33fa366c386f Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 20 Sep 2023 12:55:24 +0200 Subject: [PATCH 7/8] Apply suggestions from code review Co-authored-by: Yann Braga --- code/frameworks/nextjs/src/dependency-map.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/frameworks/nextjs/src/dependency-map.ts b/code/frameworks/nextjs/src/dependency-map.ts index 31342722fb50..70ad2ece94e0 100644 --- a/code/frameworks/nextjs/src/dependency-map.ts +++ b/code/frameworks/nextjs/src/dependency-map.ts @@ -6,10 +6,10 @@ const mapping: Record> = { '<11.1.0': { 'next/dist/next-server/lib/router-context': 'next/dist/next-server/lib/router-context', }, - '>11.1.0': { + '>=11.1.0': { 'next/dist/shared/lib/router-context': 'next/dist/shared/lib/router-context', }, - '>13.5.0': { + '>=13.5.0': { 'next/dist/shared/lib/router-context': 'next/dist/shared/lib/router-context.shared-runtime', 'next/dist/shared/lib/head-manager-context': 'next/dist/shared/lib/head-manager-context.shared-runtime', From 64d4536e73f90233949ed77870ca15ea4b79968d Mon Sep 17 00:00:00 2001 From: storybook-bot <32066757+storybook-bot@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:59:34 +0000 Subject: [PATCH 8/8] Update CHANGELOG.md for v7.4.3 [skip ci] --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26e490d7c6d2..966bf5ce9a52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 7.4.3 + +- CLI: Fix `sb add` adding duplicative entries - [#24229](https://github.com/storybookjs/storybook/pull/24229), thanks [@ndelangen](https://github.com/ndelangen)! +- NextJS: Add compatibility with nextjs `13.5` - [#24239](https://github.com/storybookjs/storybook/pull/24239), thanks [@ndelangen](https://github.com/ndelangen)! +- NextJS: Aliases `react` and `react-dom` like `next.js` does - [#23671](https://github.com/storybookjs/storybook/pull/23671), thanks [@sookmax](https://github.com/sookmax)! +- Types: Allow `null` in value of `experimental_updateStatus` to clear status - [#24206](https://github.com/storybookjs/storybook/pull/24206), thanks [@ndelangen](https://github.com/ndelangen)! + ## 7.4.2 - Addon API: Improve the updateStatus API - [#24007](https://github.com/storybookjs/storybook/pull/24007), thanks [@ndelangen](https://github.com/ndelangen)!