From ea7a87551304d2c55ce85a9cc566bc0bd6b64929 Mon Sep 17 00:00:00 2001 From: Eugene Date: Fri, 27 Sep 2024 20:06:12 +0000 Subject: [PATCH] feat: Add white-label CSS decorator (#5312) * Firefox implementation * Simplify layout * Cleanup * Styles package * Fix component build:types step * Fix source maps * Use quoted placeholder * Add injected styles check * Add built-in decorator tests * Add component decorator to external fluent * Configure fluent decorator * Handle loader overflow * Lower Chrome required version * Move decorator to component * Add missing env * Add styles to workspaces * Bail if unable to create styles * Sort * Readability --------- Co-authored-by: William Wong --- .eslintrc.production.yml | 1 + CHANGELOG.md | 3 + ...lt-in-decorator-with-decorators-1-snap.png | Bin 0 -> 9409 bytes ...lt-in-decorator-with-decorators-2-snap.png | Bin 0 -> 13798 bytes ...-theme-applied-with-decorators-1-snap.png} | Bin ...-theme-applied-with-decorators-2-snap.png} | Bin ...ustomization.basicWebChat.restructure.html | 2 +- ...ecorator.html => withCustomDecorator.html} | 0 ...ithDecorator.js => withCustomDecorator.js} | 2 +- .../hooks.useInjectStyles.changeNonce.html | 4 +- .../hooks.useInjectStyles.changeRoot.html | 8 +- .../hooks.useInjectStyles.dupeElement.html | 4 +- __tests__/html/hooks.useInjectStyles.html | 6 +- __tests__/html/withBuiltinDecorator.html | 114 ++++++++++++++++++ __tests__/html/withBuiltinDecorator.js | 5 + package-lock.json | 34 ++++++ package.json | 1 + packages/api/src/StyleOptions.ts | 29 +++++ packages/api/src/defaultStyleOptions.ts | 7 +- .../AdaptiveCardHacks/private/closest.ts | 1 - .../Attachment/private/renderAdaptiveCard.ts | 1 - packages/bundle/src/index-es5.ts | 3 - packages/bundle/src/index-minimal.ts | 9 +- packages/bundle/src/index.ts | 3 - .../bundle/src/speech/createAudioContext.ts | 2 - packages/component/decorator.js | 3 + packages/component/package.json | 14 ++- packages/component/src/Composer.tsx | 6 +- .../src/Styles/CustomPropertyNames.ts | 3 + .../Styles/StyleSet/CSSCustomProperties.ts | 51 -------- .../component/src/Styles/createStyleSet.ts | 2 - packages/component/src/Styles/createStyles.ts | 5 + packages/component/src/Styles/index.ts | 3 + .../Styles/useCustomPropertiesClassName.ts | 67 ++++++++++ .../src/Utils/firstTabbableDescendant.js | 1 - packages/component/src/decorator/index.ts | 1 + .../decorator/private/BorderFlair.module.css | 6 +- .../src}/decorator/private/BorderFlair.tsx | 2 +- .../decorator/private/BorderLoader.module.css | 8 +- .../src}/decorator/private/BorderLoader.tsx | 2 +- .../src}/decorator/private/Decorator.tsx | 14 ++- .../src/decorator/private/createStyles.ts | 5 + packages/component/src/env.d.ts | 1 + .../hooks/internal/useObserveFocusVisible.ts | 1 - packages/component/src/tsconfig.json | 5 +- packages/component/tsup.config.ts | 14 ++- packages/fluent-theme/.eslintrc.yml | 1 - packages/fluent-theme/package.json | 4 +- .../src/components/decorator/index.ts | 1 - packages/fluent-theme/src/env.d.ts | 8 +- .../decorator.ts | 1 + .../src/private/FluentThemeProvider.tsx | 4 +- .../fluent-theme/src/styles/createStyles.ts | 12 +- packages/fluent-theme/src/styles/useStyles.ts | 21 +--- packages/fluent-theme/src/tsconfig.json | 22 ++-- packages/fluent-theme/tsup.config.ts | 28 ++--- packages/styles/.eslintrc.yml | 6 + packages/styles/.gitignore | 4 + packages/styles/README.md | 0 packages/styles/build.js | 3 + packages/styles/package.json | 87 +++++++++++++ packages/styles/react.js | 3 + packages/styles/src/build/index.ts | 1 + .../src/build/private/injectCSSPlugin.ts | 76 ++++++++++++ packages/styles/src/env.d.ts | 7 ++ packages/styles/src/index.ts | 1 + .../styles/src/private/makeCreateStyles.ts | 17 +++ packages/styles/src/react/index.ts | 1 + .../styles/src/react/private/useStyles.ts | 19 +++ packages/styles/src/ts-config/config.json | 5 + packages/styles/src/tsconfig.json | 12 ++ packages/styles/tsup.config.ts | 12 ++ 72 files changed, 640 insertions(+), 169 deletions(-) create mode 100644 __tests__/__image_snapshots__/html/with-builtin-decorator-js-built-in-decorator-with-decorators-1-snap.png create mode 100644 __tests__/__image_snapshots__/html/with-builtin-decorator-js-built-in-decorator-with-decorators-2-snap.png rename __tests__/__image_snapshots__/html/{with-decorator-js-fluent-theme-applied-with-decorators-1-snap.png => with-custom-decorator-js-fluent-theme-applied-with-decorators-1-snap.png} (100%) rename __tests__/__image_snapshots__/html/{with-decorator-js-fluent-theme-applied-with-decorators-2-snap.png => with-custom-decorator-js-fluent-theme-applied-with-decorators-2-snap.png} (100%) rename __tests__/html/fluentTheme/{withDecorator.html => withCustomDecorator.html} (100%) rename __tests__/html/fluentTheme/{withDecorator.js => withCustomDecorator.js} (63%) create mode 100644 __tests__/html/withBuiltinDecorator.html create mode 100644 __tests__/html/withBuiltinDecorator.js create mode 100644 packages/component/decorator.js delete mode 100644 packages/component/src/Styles/StyleSet/CSSCustomProperties.ts create mode 100644 packages/component/src/Styles/createStyles.ts create mode 100644 packages/component/src/Styles/index.ts create mode 100644 packages/component/src/Styles/useCustomPropertiesClassName.ts create mode 100644 packages/component/src/decorator/index.ts rename packages/{fluent-theme/src/components => component/src}/decorator/private/BorderFlair.module.css (98%) rename packages/{fluent-theme/src/components => component/src}/decorator/private/BorderFlair.tsx (93%) rename packages/{fluent-theme/src/components => component/src}/decorator/private/BorderLoader.module.css (87%) rename packages/{fluent-theme/src/components => component/src}/decorator/private/BorderLoader.tsx (88%) rename packages/{fluent-theme/src/components => component/src}/decorator/private/Decorator.tsx (57%) create mode 100644 packages/component/src/decorator/private/createStyles.ts create mode 100644 packages/component/src/env.d.ts delete mode 100644 packages/fluent-theme/src/components/decorator/index.ts create mode 100644 packages/fluent-theme/src/external.umd/botframework-webchat-component/decorator.ts create mode 100644 packages/styles/.eslintrc.yml create mode 100644 packages/styles/.gitignore create mode 100644 packages/styles/README.md create mode 100644 packages/styles/build.js create mode 100644 packages/styles/package.json create mode 100644 packages/styles/react.js create mode 100644 packages/styles/src/build/index.ts create mode 100644 packages/styles/src/build/private/injectCSSPlugin.ts create mode 100644 packages/styles/src/env.d.ts create mode 100644 packages/styles/src/index.ts create mode 100644 packages/styles/src/private/makeCreateStyles.ts create mode 100644 packages/styles/src/react/index.ts create mode 100644 packages/styles/src/react/private/useStyles.ts create mode 100644 packages/styles/src/ts-config/config.json create mode 100644 packages/styles/src/tsconfig.json create mode 100644 packages/styles/tsup.config.ts diff --git a/.eslintrc.production.yml b/.eslintrc.production.yml index 288e26d8c4..80d8870da2 100644 --- a/.eslintrc.production.yml +++ b/.eslintrc.production.yml @@ -8,6 +8,7 @@ overrides: no-restricted-globals: off rules: + dot-notation: off no-restricted-globals: - error - name: cancelAnimationFrame diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e67b87464..13722a8a27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,9 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/ - Added `nonce` for Fluent and `react-scroll-to-bottom` injected styles, in PR [#5196](https://github.com/microsoft/BotFramework-WebChat/pull/5196), by [@OEvgeny](https://github.com/OEvgeny) - Updated `react-scroll-to-bottom` to version `4.2.1-main.53844f5`, in PR [#5196](https://github.com/microsoft/BotFramework-WebChat/pull/5196), by [@OEvgeny](https://github.com/OEvgeny) - Updated `react-film` to version `3.1.1-main.f623bf6`, in PR [#5196](https://github.com/microsoft/BotFramework-WebChat/pull/5196), by [@OEvgeny](https://github.com/OEvgeny) +- (Experimental) Added CSS decorator support into Web Chat white-label experience, in PR [#5312](https://github.com/microsoft/BotFramework-WebChat/pull/5312), by [@OEvgeny](https://github.com/OEvgeny) + - Introduced `WebChatDecorator` component for adding animated borders to activities, in PR [#5312](https://github.com/microsoft/BotFramework-WebChat/pull/5312) + - Added new style options `borderAnimationColor1`, `borderAnimationColor2`, and `borderAnimationColor3` for customizing decorator colors, in PR [#5312](https://github.com/microsoft/BotFramework-WebChat/pull/5312) ### Changed diff --git a/__tests__/__image_snapshots__/html/with-builtin-decorator-js-built-in-decorator-with-decorators-1-snap.png b/__tests__/__image_snapshots__/html/with-builtin-decorator-js-built-in-decorator-with-decorators-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..1c7cc325d7c39721b0a8c26fee6eef587fcf2aca GIT binary patch literal 9409 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 BfQSG9 literal 0 HcmV?d00001 diff --git a/__tests__/__image_snapshots__/html/with-builtin-decorator-js-built-in-decorator-with-decorators-2-snap.png b/__tests__/__image_snapshots__/html/with-builtin-decorator-js-built-in-decorator-with-decorators-2-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..53308298253006815b2c92b704886360600b587a GIT binary patch literal 13798 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 I{`m7h0Avo^8UO$Q literal 0 HcmV?d00001 diff --git a/__tests__/__image_snapshots__/html/with-decorator-js-fluent-theme-applied-with-decorators-1-snap.png b/__tests__/__image_snapshots__/html/with-custom-decorator-js-fluent-theme-applied-with-decorators-1-snap.png similarity index 100% rename from __tests__/__image_snapshots__/html/with-decorator-js-fluent-theme-applied-with-decorators-1-snap.png rename to __tests__/__image_snapshots__/html/with-custom-decorator-js-fluent-theme-applied-with-decorators-1-snap.png diff --git a/__tests__/__image_snapshots__/html/with-decorator-js-fluent-theme-applied-with-decorators-2-snap.png b/__tests__/__image_snapshots__/html/with-custom-decorator-js-fluent-theme-applied-with-decorators-2-snap.png similarity index 100% rename from __tests__/__image_snapshots__/html/with-decorator-js-fluent-theme-applied-with-decorators-2-snap.png rename to __tests__/__image_snapshots__/html/with-custom-decorator-js-fluent-theme-applied-with-decorators-2-snap.png diff --git a/__tests__/html/customization.basicWebChat.restructure.html b/__tests__/html/customization.basicWebChat.restructure.html index 014db57fda..736e906f0d 100644 --- a/__tests__/html/customization.basicWebChat.restructure.html +++ b/__tests__/html/customization.basicWebChat.restructure.html @@ -59,7 +59,7 @@ expect(container?.firstChild).not.toBeUndefined(); const composer = container.firstChild; - expect(getComputedStyle(composer).getPropertyValue('--webchat__color--accent')).toBe('#0063B1'); + expect(getComputedStyle(composer).getPropertyValue('--webchat__color--accent').trim()).toBe('#0063B1'); const surface = composer.children[1]; expect(surface.className).toContain(classes.surface); diff --git a/__tests__/html/fluentTheme/withDecorator.html b/__tests__/html/fluentTheme/withCustomDecorator.html similarity index 100% rename from __tests__/html/fluentTheme/withDecorator.html rename to __tests__/html/fluentTheme/withCustomDecorator.html diff --git a/__tests__/html/fluentTheme/withDecorator.js b/__tests__/html/fluentTheme/withCustomDecorator.js similarity index 63% rename from __tests__/html/fluentTheme/withDecorator.js rename to __tests__/html/fluentTheme/withCustomDecorator.js index 77832ec842..6911922f89 100644 --- a/__tests__/html/fluentTheme/withDecorator.js +++ b/__tests__/html/fluentTheme/withCustomDecorator.js @@ -1,5 +1,5 @@ /** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ describe('Fluent theme applied', () => { - test('with decorators', () => runHTML('fluentTheme/withDecorator')); + test('with decorators', () => runHTML('fluentTheme/withCustomDecorator')); }); diff --git a/__tests__/html/hooks.useInjectStyles.changeNonce.html b/__tests__/html/hooks.useInjectStyles.changeNonce.html index fb7a214575..ad66007d72 100644 --- a/__tests__/html/hooks.useInjectStyles.changeNonce.html +++ b/__tests__/html/hooks.useInjectStyles.changeNonce.html @@ -54,7 +54,7 @@ await renderWithFunction(() => useInjectStyles([styleElement], '1')); await host.snapshot(); - const allInjectedStyles1 = document.head.querySelectorAll('style:not([data-emotion])'); + const allInjectedStyles1 = document.head.querySelectorAll('style:not([data-emotion]):not([data-webchat-injected])'); expect(allInjectedStyles1).toHaveLength(1); expect(allInjectedStyles1[0]).toBe(styleElement); @@ -64,7 +64,7 @@ // No color should be applied as nonce don't match. await host.snapshot(); - const allInjectedStyles2 = document.head.querySelectorAll('style:not([data-emotion])'); + const allInjectedStyles2 = document.head.querySelectorAll('style:not([data-emotion]):not([data-webchat-injected])'); expect(allInjectedStyles2).toHaveLength(1); expect(allInjectedStyles2[0]).toBe(styleElement); diff --git a/__tests__/html/hooks.useInjectStyles.changeRoot.html b/__tests__/html/hooks.useInjectStyles.changeRoot.html index a14ecbaad4..25e88e30c2 100644 --- a/__tests__/html/hooks.useInjectStyles.changeRoot.html +++ b/__tests__/html/hooks.useInjectStyles.changeRoot.html @@ -52,16 +52,16 @@ await renderWithFunction(() => useInjectStyles([styleElement])); await host.snapshot(); - expect([...document.head.querySelectorAll('style:not([data-emotion])')]).toEqual([styleElement]); - expect([...document.body.querySelectorAll('style:not([data-emotion])')]).toEqual([]); + expect([...document.head.querySelectorAll('style:not([data-emotion]):not([data-webchat-injected])')]).toEqual([styleElement]); + expect([...document.body.querySelectorAll('style:not([data-emotion]):not([data-webchat-injected])')]).toEqual([]); await renderWithFunction(() => useInjectStyles([styleElement]), { styleOptions: { stylesRoot: document.body } }); await host.snapshot(); - expect([...document.head.querySelectorAll('style:not([data-emotion])')]).toEqual([]); - expect([...document.body.querySelectorAll('style:not([data-emotion])')]).toEqual([styleElement]); + expect([...document.head.querySelectorAll('style:not([data-emotion]):not([data-webchat-injected])')]).toEqual([]); + expect([...document.body.querySelectorAll('style:not([data-emotion]):not([data-webchat-injected])')]).toEqual([styleElement]); }); diff --git a/__tests__/html/hooks.useInjectStyles.dupeElement.html b/__tests__/html/hooks.useInjectStyles.dupeElement.html index ba8663bb40..ebf4fd35fc 100644 --- a/__tests__/html/hooks.useInjectStyles.dupeElement.html +++ b/__tests__/html/hooks.useInjectStyles.dupeElement.html @@ -52,7 +52,7 @@ await renderWithFunction(() => useInjectStyles([styleElement, styleElement])); await host.snapshot(); - const allInjectedStyles1 = document.head.querySelectorAll('style:not([data-emotion])'); + const allInjectedStyles1 = document.head.querySelectorAll('style:not([data-emotion]):not([data-webchat-injected])'); expect(allInjectedStyles1).toHaveLength(1); expect(allInjectedStyles1[0]).toBe(styleElement); @@ -60,7 +60,7 @@ await renderWithFunction(() => useInjectStyles([styleElement, styleElement])); await host.snapshot(); - const allInjectedStyles2 = document.head.querySelectorAll('style:not([data-emotion])'); + const allInjectedStyles2 = document.head.querySelectorAll('style:not([data-emotion]):not([data-webchat-injected])'); expect(allInjectedStyles2).toHaveLength(1); expect(allInjectedStyles2[0]).toBe(styleElement); diff --git a/__tests__/html/hooks.useInjectStyles.html b/__tests__/html/hooks.useInjectStyles.html index 4169135f62..ca9b43dc9c 100644 --- a/__tests__/html/hooks.useInjectStyles.html +++ b/__tests__/html/hooks.useInjectStyles.html @@ -56,7 +56,7 @@ await renderWithFunction(() => useInjectStyles([styleElement1])); await host.snapshot(); - const allInjectedStyles1 = document.head.querySelectorAll('style:not([data-emotion])'); + const allInjectedStyles1 = document.head.querySelectorAll('style:not([data-emotion]):not([data-webchat-injected])'); expect(allInjectedStyles1).toHaveLength(1); expect(allInjectedStyles1[0]).toBe(styleElement1); @@ -64,7 +64,7 @@ await renderWithFunction(() => useInjectStyles([styleElement1, styleElement2])); await host.snapshot(); - const allInjectedStyles2 = document.head.querySelectorAll('style:not([data-emotion])'); + const allInjectedStyles2 = document.head.querySelectorAll('style:not([data-emotion]):not([data-webchat-injected])'); expect(allInjectedStyles2).toHaveLength(2); expect(allInjectedStyles2[0]).toBe(styleElement1); @@ -73,7 +73,7 @@ await renderWithFunction(() => useInjectStyles([styleElement1, styleElement2, styleElement3])); await host.snapshot(); - const allInjectedStyles3 = document.head.querySelectorAll('style:not([data-emotion])'); + const allInjectedStyles3 = document.head.querySelectorAll('style:not([data-emotion]):not([data-webchat-injected])'); expect(allInjectedStyles3).toHaveLength(3); expect(allInjectedStyles3[0]).toBe(styleElement1); diff --git a/__tests__/html/withBuiltinDecorator.html b/__tests__/html/withBuiltinDecorator.html new file mode 100644 index 0000000000..fcd7d71f3e --- /dev/null +++ b/__tests__/html/withBuiltinDecorator.html @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + +
+ + + diff --git a/__tests__/html/withBuiltinDecorator.js b/__tests__/html/withBuiltinDecorator.js new file mode 100644 index 0000000000..2b9b9cba40 --- /dev/null +++ b/__tests__/html/withBuiltinDecorator.js @@ -0,0 +1,5 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +describe('built-in decorator', () => { + test('with decorators', () => runHTML('withBuiltinDecorator')); +}); diff --git a/package-lock.json b/package-lock.json index e21b4963ea..955b23f02d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "./packages/test/harness", "./packages/test/web-server", "./packages/core", + "./packages/styles", "./packages/support/cldr-data-downloader", "./packages/support/cldr-data", "./packages/api", @@ -6461,6 +6462,10 @@ "resolved": "packages/fluent-theme", "link": true }, + "node_modules/botframework-webchat-styles": { + "resolved": "packages/styles", + "link": true + }, "node_modules/boxen": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", @@ -23983,6 +23988,7 @@ "base64-js": "1.5.1", "botframework-webchat-api": "0.0.0-0", "botframework-webchat-core": "0.0.0-0", + "botframework-webchat-styles": "0.0.0-0", "classnames": "2.5.1", "compute-scroll-into-view": "1.0.20", "deep-freeze-strict": "1.1.1", @@ -25252,6 +25258,7 @@ "botframework-webchat-api": "0.0.0-0", "botframework-webchat-component": "0.0.0-0", "botframework-webchat-core": "0.0.0-0", + "botframework-webchat-styles": "0.0.0-0", "classnames": "2.5.1", "inject-meta-tag": "0.0.1", "math-random": "2.0.1", @@ -25304,6 +25311,33 @@ "webpack-cli": "^5.1.4" } }, + "packages/styles": { + "version": "0.0.0-0", + "license": "MIT", + "devDependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@tsconfig/strictest": "^2.0.5", + "@types/node": "^20.10.3", + "cross-env": "^7.0.3", + "type-fest": "^4.14.0", + "typescript": "^5.3.2" + }, + "peerDependencies": { + "react": ">= 16.8.6" + } + }, + "packages/styles/node_modules/type-fest": { + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz", + "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/support/cldr-data": { "name": "botframework-webchat-cldr-data", "version": "36.0.0-0", diff --git a/package.json b/package.json index cf87f80a98..f35e28bf13 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "./packages/test/harness", "./packages/test/web-server", "./packages/core", + "./packages/styles", "./packages/support/cldr-data-downloader", "./packages/support/cldr-data", "./packages/api", diff --git a/packages/api/src/StyleOptions.ts b/packages/api/src/StyleOptions.ts index b505801ab4..0466e1ef09 100644 --- a/packages/api/src/StyleOptions.ts +++ b/packages/api/src/StyleOptions.ts @@ -879,6 +879,35 @@ type StyleOptions = { * @default document.head */ stylesRoot?: Node; + + /** + * Border animation + */ + + /** + * Border animation 1st color + * + * CSS variable: `--webchat__animation--border-color-1` CSS variable to adjust the color + * + * New in 4.19.0. + */ + borderAnimationColor1?: string; + /** + * Border animation 2nd color + * + * CSS variable: `--webchat__animation--border-color-2` CSS variable to adjust the color + * + * New in 4.19.0. + */ + borderAnimationColor2?: string; + /** + * Border animation 3rd color + * + * CSS variable: `--webchat__animation--border-color-3` CSS variable to adjust the color + * + * New in 4.19.0. + */ + borderAnimationColor3?: string; }; // StrictStyleOptions is only used internally in Web Chat and for simplifying our code: diff --git a/packages/api/src/defaultStyleOptions.ts b/packages/api/src/defaultStyleOptions.ts index d49ee9b5cb..168209230a 100644 --- a/packages/api/src/defaultStyleOptions.ts +++ b/packages/api/src/defaultStyleOptions.ts @@ -294,7 +294,12 @@ const DEFAULT_OPTIONS: Required = { maxMessageLength: 2000, - stylesRoot: document.head + stylesRoot: document.head, + + // Border animation + borderAnimationColor1: '#203C91', + borderAnimationColor2: '#4DD3FF', + borderAnimationColor3: '#2B8DD8' }; export default DEFAULT_OPTIONS; diff --git a/packages/bundle/src/adaptiveCards/Attachment/AdaptiveCardHacks/private/closest.ts b/packages/bundle/src/adaptiveCards/Attachment/AdaptiveCardHacks/private/closest.ts index ca056c1ce3..fdbda5f5f5 100644 --- a/packages/bundle/src/adaptiveCards/Attachment/AdaptiveCardHacks/private/closest.ts +++ b/packages/bundle/src/adaptiveCards/Attachment/AdaptiveCardHacks/private/closest.ts @@ -9,7 +9,6 @@ export default function closest(element: HTMLElement, selector: string): HTMLEle while (current) { // "msMatchesSelector" is vendor-prefixed version of "matches". - // eslint-disable-next-line dot-notation if ((current.matches || (current['msMatchesSelector'] as (selector: string) => boolean)).call(current, selector)) { return current; } diff --git a/packages/bundle/src/adaptiveCards/Attachment/private/renderAdaptiveCard.ts b/packages/bundle/src/adaptiveCards/Attachment/private/renderAdaptiveCard.ts index 7c142e1625..a41cc32e71 100644 --- a/packages/bundle/src/adaptiveCards/Attachment/private/renderAdaptiveCard.ts +++ b/packages/bundle/src/adaptiveCards/Attachment/private/renderAdaptiveCard.ts @@ -36,7 +36,6 @@ export default function renderAdaptiveCard( // Because there could be timing difference between .parse and .render, we could be using wrong Markdown engine // "onProcessMarkdown" is a static function but we are trying to scope it to the current object instead. - // eslint-disable-next-line dot-notation adaptiveCard.constructor['onProcessMarkdown'] = (text: string, result: IMarkdownProcessingResult) => { if (renderMarkdownAsHTML) { result.outputHtml = renderMarkdownAsHTML(text); diff --git a/packages/bundle/src/index-es5.ts b/packages/bundle/src/index-es5.ts index 2f83d6bc82..b1ab935f8d 100644 --- a/packages/bundle/src/index-es5.ts +++ b/packages/bundle/src/index-es5.ts @@ -1,6 +1,3 @@ -/* eslint dot-notation: ["error", { "allowPattern": "^WebChat$" }] */ -// window['WebChat'] is required for TypeScript - // Importing polyfills required for IE11/ES5. import './polyfill'; diff --git a/packages/bundle/src/index-minimal.ts b/packages/bundle/src/index-minimal.ts index b8009fd34e..1a36fd213e 100644 --- a/packages/bundle/src/index-minimal.ts +++ b/packages/bundle/src/index-minimal.ts @@ -1,8 +1,6 @@ -/* eslint dot-notation: ["error", { "allowPattern": "^WebChat$" }] */ -// window['WebChat'] is required for TypeScript - import { StrictStyleOptions, StyleOptions } from 'botframework-webchat-api'; import * as decorator from 'botframework-webchat-api/decorator'; +import { WebChatDecorator } from 'botframework-webchat-component/decorator'; import { Constants, createStore, createStoreWithDevTools, createStoreWithOptions } from 'botframework-webchat-core'; import * as internal from 'botframework-webchat-component/internal'; @@ -79,7 +77,10 @@ window['WebChat'] = { createStore, createStoreWithOptions, createStyleSet, - decorator, + decorator: { + ...decorator, + WebChatDecorator + }, internal, hooks, ReactWebChat, diff --git a/packages/bundle/src/index.ts b/packages/bundle/src/index.ts index 40b9b468ab..7018624e6b 100644 --- a/packages/bundle/src/index.ts +++ b/packages/bundle/src/index.ts @@ -1,6 +1,3 @@ -/* eslint dot-notation: ["error", { "allowPattern": "^WebChat$" }] */ -// window['WebChat'] is required for TypeScript - export * from './index-minimal'; import FullComposer from './FullComposer'; diff --git a/packages/bundle/src/speech/createAudioContext.ts b/packages/bundle/src/speech/createAudioContext.ts index 4fa3ff32c9..972bdee443 100644 --- a/packages/bundle/src/speech/createAudioContext.ts +++ b/packages/bundle/src/speech/createAudioContext.ts @@ -4,10 +4,8 @@ export default function createAudioContext(): AudioContext { return new window.AudioContext(); // Required by TypeScript. - // eslint-disable-next-line dot-notation } else if (typeof window['webkitAudioContext'] !== 'undefined') { // This is for Safari as Web Audio API is still under vendor-prefixed. - // eslint-disable-next-line dot-notation return new window['webkitAudioContext'](); } diff --git a/packages/component/decorator.js b/packages/component/decorator.js new file mode 100644 index 0000000000..16dcf52fc7 --- /dev/null +++ b/packages/component/decorator.js @@ -0,0 +1,3 @@ +// This is required for Webpack 4 which does not support named exports. +// eslint-disable-next-line no-undef +module.exports = require('./lib/decorator/index'); diff --git a/packages/component/package.json b/packages/component/package.json index 0bd42da595..2089c9fa8f 100644 --- a/packages/component/package.json +++ b/packages/component/package.json @@ -24,6 +24,16 @@ "types": "./lib/internal.d.ts", "default": "./lib/internal.js" } + }, + "./decorator": { + "import": { + "types": "./dist/botframework-webchat-component.decorator.d.mts", + "default": "./dist/botframework-webchat-component.decorator.mjs" + }, + "require": { + "types": "./lib/decorator/index.d.ts", + "default": "./lib/decorator/index.js" + } } }, "publishConfig": { @@ -66,7 +76,8 @@ }, "localDependencies": { "botframework-webchat-api": "production", - "botframework-webchat-core": "production" + "botframework-webchat-core": "production", + "botframework-webchat-styles": "production" }, "pinDependencies": { "@babel/cli": [ @@ -132,6 +143,7 @@ "base64-js": "1.5.1", "botframework-webchat-api": "0.0.0-0", "botframework-webchat-core": "0.0.0-0", + "botframework-webchat-styles": "0.0.0-0", "classnames": "2.5.1", "compute-scroll-into-view": "1.0.20", "deep-freeze-strict": "1.1.1", diff --git a/packages/component/src/Composer.tsx b/packages/component/src/Composer.tsx index 4bc87521ce..86ea32900b 100644 --- a/packages/component/src/Composer.tsx +++ b/packages/component/src/Composer.tsx @@ -27,7 +27,6 @@ import { } from './hooks/internal/BypassSpeechSynthesisPonyfill'; import UITracker from './hooks/internal/UITracker'; import WebChatUIContext from './hooks/internal/WebChatUIContext'; -import useStyleSet from './hooks/useStyleSet'; import createDefaultActivityMiddleware from './Middleware/Activity/createCoreMiddleware'; import createDefaultActivityStatusMiddleware from './Middleware/ActivityStatus/createCoreMiddleware'; import createDefaultAttachmentForScreenReaderMiddleware from './Middleware/AttachmentForScreenReader/createCoreMiddleware'; @@ -53,6 +52,7 @@ import useInjectStyles from './hooks/internal/useInjectStyles'; import { LiveRegionTwinComposer } from './providers/LiveRegionTwin'; import { FocusSendBoxScope } from './hooks/sendBoxFocus'; import { ScrollRelativeTranscriptScope } from './hooks/transcriptScrollRelative'; +import useCustomPropertiesClassName from './Styles/useCustomPropertiesClassName'; const { useGetActivityByKey, useReferenceGrammarID, useStyleOptions } = hooks; @@ -79,8 +79,8 @@ const ROOT_STYLE = { }; const ComposerCoreUI = memo(({ children }: ComposerCoreUIProps) => { - const [{ cssCustomProperties }] = useStyleSet(); const [{ internalLiveRegionFadeAfter }] = useStyleOptions(); + const [customPropertiesClassName] = useCustomPropertiesClassName(); const rootClassName = useStyleToEmotionObject()(ROOT_STYLE) + ''; const dictationOnError = useCallback(err => { @@ -88,7 +88,7 @@ const ComposerCoreUI = memo(({ children }: ComposerCoreUIProps) => { }, []); return ( -
+
diff --git a/packages/component/src/Styles/CustomPropertyNames.ts b/packages/component/src/Styles/CustomPropertyNames.ts index 6662402438..6d0d734997 100644 --- a/packages/component/src/Styles/CustomPropertyNames.ts +++ b/packages/component/src/Styles/CustomPropertyNames.ts @@ -1,5 +1,8 @@ const CustomPropertyNames = Object.freeze({ // Make sure key names does not have JavaScript forbidden names. + BorderAnimationColor1: '--webchat__border-animation--color-1', + BorderAnimationColor2: '--webchat__border-animation--color-2', + BorderAnimationColor3: '--webchat__border-animation--color-3', ColorAccent: '--webchat__color--accent', ColorSubtle: '--webchat__color--subtle', ColorTimestamp: '--webchat__color--timestamp', diff --git a/packages/component/src/Styles/StyleSet/CSSCustomProperties.ts b/packages/component/src/Styles/StyleSet/CSSCustomProperties.ts deleted file mode 100644 index 6864211717..0000000000 --- a/packages/component/src/Styles/StyleSet/CSSCustomProperties.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { StrictStyleOptions } from 'botframework-webchat-api'; - -import CustomPropertyNames from '../CustomPropertyNames'; - -export default function createCSSCustomPropertiesStyle({ - accent, - bubbleImageMaxHeight, - bubbleImageMinHeight, - bubbleMaxWidth, - bubbleMinHeight, - fontSizeSmall, - markdownExternalLinkIconImage, - paddingRegular, - primaryFont, - subtle, - timestampColor -}: StrictStyleOptions) { - return { - '&.webchat__css-custom-properties': { - display: 'contents', - - // TODO: Should we register the CSS property for inheritance, type checking, and initial value? - // Registrations need to be done on global level, and duplicate registration will throw. - // https://developer.mozilla.org/en-US/docs/Web/CSS/@property - - // TODO: This is ongoing work. We are slowly adding CSS variables to ease calculations and stuff. - // - // We need to build a story to let web devs override these CSS variables. - // - // Candy points: - // - They should be able to override CSS variables for certain things (say, padding of popover) without affecting much. - // - // House rules: - // - We should put styling varibles here, e.g. paddingRegular - // - We MUST NOT put runtime variables here, e.g. sendTimeout - // - This is because we cannot programmatically know when the sendTimeout change - [CustomPropertyNames.ColorAccent]: accent, - [CustomPropertyNames.ColorSubtle]: subtle, - [CustomPropertyNames.ColorTimestamp]: timestampColor || subtle, // Maybe we should not need this if we allow web devs to override CSS variables for certain components. - [CustomPropertyNames.FontPrimary]: primaryFont, - [CustomPropertyNames.FontSizeSmall]: fontSizeSmall, - [CustomPropertyNames.IconURLExternalLink]: markdownExternalLinkIconImage, - [CustomPropertyNames.MaxWidthBubble]: bubbleMaxWidth + 'px', - [CustomPropertyNames.MinHeightBubble]: bubbleMinHeight + 'px', - [CustomPropertyNames.PaddingRegular]: paddingRegular + 'px', - [CustomPropertyNames.MaxHeightImageBubble]: - bubbleImageMaxHeight === Infinity ? undefined : bubbleImageMaxHeight + 'px', - [CustomPropertyNames.MinHeightImageBubble]: bubbleImageMinHeight + 'px' - } - }; -} diff --git a/packages/component/src/Styles/createStyleSet.ts b/packages/component/src/Styles/createStyleSet.ts index 4665a4526a..773a7cca3b 100644 --- a/packages/component/src/Styles/createStyleSet.ts +++ b/packages/component/src/Styles/createStyleSet.ts @@ -14,7 +14,6 @@ import createCarouselFilmStripAttachment from './StyleSet/CarouselFilmStripAttac import createCarouselFlipper from './StyleSet/CarouselFlipper'; import createCitationModalDialogStyle from './StyleSet/CitationModalDialog'; import createConnectivityNotification from './StyleSet/ConnectivityNotification'; -import createCSSCustomPropertiesStyle from './StyleSet/CSSCustomProperties'; import createDictationInterimsStyle from './StyleSet/DictationInterims'; import createErrorBoxStyle from './StyleSet/ErrorBox'; import createErrorNotificationStyle from './StyleSet/ErrorNotification'; @@ -105,7 +104,6 @@ export default function createStyleSet(styleOptions: StyleOptions) { // Following styles follows new house rules: // - Use CSS var instead of strictStyleOptions citationModalDialog: createCitationModalDialogStyle(), - cssCustomProperties: createCSSCustomPropertiesStyle(strictStyleOptions), linkDefinitions: createLinkDefinitionsStyle(), modalDialog: createModalDialogStyle(), renderMarkdown: createRenderMarkdownStyle(), diff --git a/packages/component/src/Styles/createStyles.ts b/packages/component/src/Styles/createStyles.ts new file mode 100644 index 0000000000..39afb9b924 --- /dev/null +++ b/packages/component/src/Styles/createStyles.ts @@ -0,0 +1,5 @@ +import { makeCreateStyles } from 'botframework-webchat-styles'; + +export const componentStyleContent = '@--COMPONENT-STYLES-CONTENT--@'; + +export default makeCreateStyles(componentStyleContent); diff --git a/packages/component/src/Styles/index.ts b/packages/component/src/Styles/index.ts new file mode 100644 index 0000000000..64b659b157 --- /dev/null +++ b/packages/component/src/Styles/index.ts @@ -0,0 +1,3 @@ +import createStyles from './createStyles'; + +export { createStyles }; diff --git a/packages/component/src/Styles/useCustomPropertiesClassName.ts b/packages/component/src/Styles/useCustomPropertiesClassName.ts new file mode 100644 index 0000000000..c5ebaacd88 --- /dev/null +++ b/packages/component/src/Styles/useCustomPropertiesClassName.ts @@ -0,0 +1,67 @@ +import { hooks } from 'botframework-webchat-api'; +import { makeCreateStyles } from 'botframework-webchat-styles'; +import random from 'math-random'; +import { useMemo } from 'react'; +import CustomPropertyNames from './CustomPropertyNames'; +import useInjectStyles from '../hooks/internal/useInjectStyles'; +import useNonce from '../hooks/internal/useNonce'; + +const { useStyleOptions } = hooks; + +const webchatCustomPropertiesClass = 'webchat__css-custom-properties'; + +export default function useCustomPropertiesClassName() { + const [styleOptions] = useStyleOptions(); + const nonce = useNonce(); + + const [styles, classNameState] = useMemo(() => { + const { + accent, + borderAnimationColor1, + borderAnimationColor2, + borderAnimationColor3, + bubbleImageMaxHeight, + bubbleImageMinHeight, + bubbleMaxWidth, + bubbleMinHeight, + fontSizeSmall, + markdownExternalLinkIconImage, + paddingRegular, + primaryFont, + subtle, + timestampColor + } = styleOptions; + + // eslint-disable-next-line no-magic-numbers + const randomClass = `wc-${Math.ceil(random() * Number.MAX_SAFE_INTEGER).toString(36)}` as const; + + const contents = ` +.${webchatCustomPropertiesClass}.${randomClass} { + display: contents; + ${CustomPropertyNames.BorderAnimationColor1}: ${borderAnimationColor1}; + ${CustomPropertyNames.BorderAnimationColor2}: ${borderAnimationColor2}; + ${CustomPropertyNames.BorderAnimationColor3}: ${borderAnimationColor3}; + ${CustomPropertyNames.ColorAccent}: ${accent}; + ${CustomPropertyNames.ColorSubtle}: ${subtle}; + ${CustomPropertyNames.ColorTimestamp}: ${timestampColor || subtle}; + ${CustomPropertyNames.FontPrimary}: ${primaryFont}; + ${CustomPropertyNames.FontSizeSmall}: ${fontSizeSmall}; + ${CustomPropertyNames.IconURLExternalLink}: ${markdownExternalLinkIconImage}; + ${CustomPropertyNames.MaxHeightImageBubble}: ${bubbleImageMaxHeight}px; + ${CustomPropertyNames.MaxWidthBubble}: ${bubbleMaxWidth}px; + ${CustomPropertyNames.MinHeightBubble}: ${bubbleMinHeight}px; + ${CustomPropertyNames.MinHeightImageBubble}: ${bubbleImageMinHeight}px; + ${CustomPropertyNames.PaddingRegular}: ${paddingRegular}px; +} +`; + const [style] = makeCreateStyles(contents)(); + + style.dataset.webchatInjected = 'component'; + + return [Object.freeze([style]), Object.freeze([`${webchatCustomPropertiesClass} ${randomClass}`] as const)]; + }, [styleOptions]); + + useInjectStyles(styles, nonce); + + return classNameState; +} diff --git a/packages/component/src/Utils/firstTabbableDescendant.js b/packages/component/src/Utils/firstTabbableDescendant.js index 52744ec83f..de392caebf 100644 --- a/packages/component/src/Utils/firstTabbableDescendant.js +++ b/packages/component/src/Utils/firstTabbableDescendant.js @@ -27,7 +27,6 @@ function orSelf(element) { } // "msMatchesSelector" is vendor-prefixed version of "matches". - // eslint-disable-next-line dot-notation if ((element.matches || element['msMatchesSelector']).call(element, SELECTOR)) { return element; } diff --git a/packages/component/src/decorator/index.ts b/packages/component/src/decorator/index.ts new file mode 100644 index 0000000000..382b48a18f --- /dev/null +++ b/packages/component/src/decorator/index.ts @@ -0,0 +1 @@ +export { default as WebChatDecorator } from './private/Decorator'; diff --git a/packages/fluent-theme/src/components/decorator/private/BorderFlair.module.css b/packages/component/src/decorator/private/BorderFlair.module.css similarity index 98% rename from packages/fluent-theme/src/components/decorator/private/BorderFlair.module.css rename to packages/component/src/decorator/private/BorderFlair.module.css index 30a496566e..c5f1a7f5f5 100644 --- a/packages/fluent-theme/src/components/decorator/private/BorderFlair.module.css +++ b/packages/component/src/decorator/private/BorderFlair.module.css @@ -86,7 +86,7 @@ } } -:global(.webchat-fluent) .border-flair { +:global(.webchat) .border-flair { /* Configurable variables */ --webchat-decorator-borderFlair-color1: var(--webchat-borderFlair-color1, var(--webchat__border-animation--color-1)); --webchat-decorator-borderFlair-color2: var(--webchat-borderFlair-color2, var(--webchat__border-animation--color-2)); @@ -123,7 +123,7 @@ position: absolute; } -:global(.webchat-fluent) .border-flair--complete { +:global(.webchat) .border-flair--complete { animation-play-state: paused; } @@ -535,7 +535,7 @@ } } - :global(.webchat-fluent) .border-flair { + :global(.webchat) .border-flair { --webchat-decorator-borderFlair-animated-angle: calc(0.75 + 2 * var(--webchat-decorator-borderFlair-animated)); --webchat-decorator-borderFlair-animated-color1: color-mix(in srgb, var(--webchat-decorator-borderFlair-color1) calc((1 - abs(var(--webchat-decorator-borderFlair-animated) * 3 - 1.5)) * 100%), diff --git a/packages/fluent-theme/src/components/decorator/private/BorderFlair.tsx b/packages/component/src/decorator/private/BorderFlair.tsx similarity index 93% rename from packages/fluent-theme/src/components/decorator/private/BorderFlair.tsx rename to packages/component/src/decorator/private/BorderFlair.tsx index 2a27301438..e8152a3086 100644 --- a/packages/fluent-theme/src/components/decorator/private/BorderFlair.tsx +++ b/packages/component/src/decorator/private/BorderFlair.tsx @@ -1,7 +1,7 @@ import React, { Fragment, memo, useCallback, useState, type ReactNode } from 'react'; import cx from 'classnames'; +import { useStyles } from 'botframework-webchat-styles/react'; -import { useStyles } from '../../../styles'; import styles from './BorderFlair.module.css'; function BorderFlair({ children }: Readonly<{ children?: ReactNode | undefined }>) { diff --git a/packages/fluent-theme/src/components/decorator/private/BorderLoader.module.css b/packages/component/src/decorator/private/BorderLoader.module.css similarity index 87% rename from packages/fluent-theme/src/components/decorator/private/BorderLoader.module.css rename to packages/component/src/decorator/private/BorderLoader.module.css index e3aa3c47e1..e15aa1a257 100644 --- a/packages/fluent-theme/src/components/decorator/private/BorderLoader.module.css +++ b/packages/component/src/decorator/private/BorderLoader.module.css @@ -10,21 +10,21 @@ } } -:global(.webchat-fluent) .border-loader { +:global(.webchat) .border-loader { border-radius: inherit; } -:global(.webchat-fluent) .border-loader__track { +:global(.webchat) .border-loader__track { --webchat-decorator-borderLoader-borderSize: var(--webchat-borderLoader-borderSize, 4px); - background-color: var(--webchat-colorNeutralBackground6); + background-color: var(--webchat-colorNeutralBackground6, #e6e6e6); height: var(--webchat-decorator-borderLoader-borderSize); position: relative; width: 100%; } -:global(.webchat-fluent) .border-loader__loader { +:global(.webchat) .border-loader__loader { --webchat-decorator-borderLoader-color1: var(--webchat-borderLoader-color1, var(--webchat__border-animation--color-1)); --webchat-decorator-borderLoader-color2: var(--webchat-borderLoader-color2, diff --git a/packages/fluent-theme/src/components/decorator/private/BorderLoader.tsx b/packages/component/src/decorator/private/BorderLoader.tsx similarity index 88% rename from packages/fluent-theme/src/components/decorator/private/BorderLoader.tsx rename to packages/component/src/decorator/private/BorderLoader.tsx index 77ef6011bd..7ef4012d70 100644 --- a/packages/fluent-theme/src/components/decorator/private/BorderLoader.tsx +++ b/packages/component/src/decorator/private/BorderLoader.tsx @@ -1,6 +1,6 @@ import React, { memo, type ReactNode } from 'react'; +import { useStyles } from 'botframework-webchat-styles/react'; -import { useStyles } from '../../../styles'; import styles from './BorderLoader.module.css'; function BorderLoader({ children }: Readonly<{ children?: ReactNode | undefined }>) { diff --git a/packages/fluent-theme/src/components/decorator/private/Decorator.tsx b/packages/component/src/decorator/private/Decorator.tsx similarity index 57% rename from packages/fluent-theme/src/components/decorator/private/Decorator.tsx rename to packages/component/src/decorator/private/Decorator.tsx index 66b299ec74..e681b24ef4 100644 --- a/packages/fluent-theme/src/components/decorator/private/Decorator.tsx +++ b/packages/component/src/decorator/private/Decorator.tsx @@ -1,8 +1,10 @@ import { DecoratorComposer, type DecoratorMiddleware } from 'botframework-webchat-api/decorator'; import React, { memo, type ReactNode } from 'react'; +import ThemeProvider from '../../providers/Theme/ThemeProvider'; import BorderFlair from './BorderFlair'; import BorderLoader from './BorderLoader'; +import createStyles from './createStyles'; const middleware: DecoratorMiddleware[] = [ init => @@ -13,8 +15,14 @@ const middleware: DecoratorMiddleware[] = [ (next => request => (request.livestreamingState === 'preparing' ? BorderLoader : next(request))) ]; -function FluentThemeDecorator({ children }: Readonly<{ readonly children?: ReactNode | undefined }>) { - return {children}; +const styles = createStyles(); + +function WebChatDecorator({ children }: Readonly<{ readonly children?: ReactNode | undefined }>) { + return ( + + {children} + + ); } -export default memo(FluentThemeDecorator); +export default memo(WebChatDecorator); diff --git a/packages/component/src/decorator/private/createStyles.ts b/packages/component/src/decorator/private/createStyles.ts new file mode 100644 index 0000000000..04f3f5fdb2 --- /dev/null +++ b/packages/component/src/decorator/private/createStyles.ts @@ -0,0 +1,5 @@ +import { makeCreateStyles } from 'botframework-webchat-styles'; + +export const decoratorStyleContent = '@--DECORATOR-STYLES-CONTENT--@'; + +export default makeCreateStyles(decoratorStyleContent); diff --git a/packages/component/src/env.d.ts b/packages/component/src/env.d.ts new file mode 100644 index 0000000000..30119490bb --- /dev/null +++ b/packages/component/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/component/src/hooks/internal/useObserveFocusVisible.ts b/packages/component/src/hooks/internal/useObserveFocusVisible.ts index 24f7f05837..62cd85431a 100644 --- a/packages/component/src/hooks/internal/useObserveFocusVisible.ts +++ b/packages/component/src/hooks/internal/useObserveFocusVisible.ts @@ -218,7 +218,6 @@ function useObserveFocusVisibleForModernBrowsers( if ( // "msMatchesSelector" is vendor-prefixed version of "matches". - // eslint-disable-next-line dot-notation (current.matches || (current['msMatchesSelector'] as (selector: string) => boolean)).call( current, ':focus-visible' diff --git a/packages/component/src/tsconfig.json b/packages/component/src/tsconfig.json index 155ef2b6cc..c5855241a1 100644 --- a/packages/component/src/tsconfig.json +++ b/packages/component/src/tsconfig.json @@ -14,5 +14,8 @@ "skipLibCheck": true, "sourceMap": true, "target": "ESNext" - } + }, + "extends": [ + "botframework-webchat-styles/tsconfig.json" + ] } diff --git a/packages/component/tsup.config.ts b/packages/component/tsup.config.ts index 9af3f4523e..4e4c8c8a68 100644 --- a/packages/component/tsup.config.ts +++ b/packages/component/tsup.config.ts @@ -1,10 +1,22 @@ import { defineConfig } from 'tsup'; import baseConfig from '../../tsup.base.config'; +import { componentStyleContent as componentStyleContentPlaceholder } from './src/Styles/createStyles'; +import { decoratorStyleContent as decoratorStyleContentPlaceholder } from './src/decorator/private/createStyles'; +import { injectCSSPlugin } from 'botframework-webchat-styles/build'; export default defineConfig({ ...baseConfig, + loader: { + ...baseConfig.loader, + '.css': 'local-css' + }, + esbuildPlugins: [ + injectCSSPlugin({ stylesPlaceholder: componentStyleContentPlaceholder }), + injectCSSPlugin({ stylesPlaceholder: decoratorStyleContentPlaceholder }) + ], entry: { 'botframework-webchat-component': './src/index.ts', - 'botframework-webchat-component.internal': './src/internal.ts' + 'botframework-webchat-component.internal': './src/internal.ts', + 'botframework-webchat-component.decorator': './src/decorator/index.ts' } }); diff --git a/packages/fluent-theme/.eslintrc.yml b/packages/fluent-theme/.eslintrc.yml index 1823986f3d..8307709bdc 100644 --- a/packages/fluent-theme/.eslintrc.yml +++ b/packages/fluent-theme/.eslintrc.yml @@ -9,4 +9,3 @@ env: rules: react/destructuring-assignment: off react/require-default-props: off - dot-notation: off diff --git a/packages/fluent-theme/package.json b/packages/fluent-theme/package.json index a24c1d9380..9bfbe0d92a 100644 --- a/packages/fluent-theme/package.json +++ b/packages/fluent-theme/package.json @@ -53,7 +53,8 @@ "localDependencies": { "botframework-webchat-api": "production", "botframework-webchat-component": "production", - "botframework-webchat-core": "production" + "botframework-webchat-core": "production", + "botframework-webchat-styles": "production" }, "pinDependencies": { "@types/react": [ @@ -73,6 +74,7 @@ "botframework-webchat-api": "0.0.0-0", "botframework-webchat-component": "0.0.0-0", "botframework-webchat-core": "0.0.0-0", + "botframework-webchat-styles": "0.0.0-0", "classnames": "2.5.1", "inject-meta-tag": "0.0.1", "math-random": "2.0.1", diff --git a/packages/fluent-theme/src/components/decorator/index.ts b/packages/fluent-theme/src/components/decorator/index.ts deleted file mode 100644 index 42c42ce3b0..0000000000 --- a/packages/fluent-theme/src/components/decorator/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as FluentThemeDecorator } from './private/Decorator'; diff --git a/packages/fluent-theme/src/env.d.ts b/packages/fluent-theme/src/env.d.ts index 0ca8f02237..30119490bb 100644 --- a/packages/fluent-theme/src/env.d.ts +++ b/packages/fluent-theme/src/env.d.ts @@ -1,7 +1 @@ -// CSS modules -type CSSModuleClasses = { readonly [key: string]: any }; - -declare module '*.module.css' { - const classes: CSSModuleClasses; - export default classes; -} +/// diff --git a/packages/fluent-theme/src/external.umd/botframework-webchat-component/decorator.ts b/packages/fluent-theme/src/external.umd/botframework-webchat-component/decorator.ts new file mode 100644 index 0000000000..1e3fdc3cb2 --- /dev/null +++ b/packages/fluent-theme/src/external.umd/botframework-webchat-component/decorator.ts @@ -0,0 +1 @@ +module.exports = (globalThis as any).WebChat.decorator; diff --git a/packages/fluent-theme/src/private/FluentThemeProvider.tsx b/packages/fluent-theme/src/private/FluentThemeProvider.tsx index 388eb442fc..7c1fcdfa6d 100644 --- a/packages/fluent-theme/src/private/FluentThemeProvider.tsx +++ b/packages/fluent-theme/src/private/FluentThemeProvider.tsx @@ -1,5 +1,6 @@ import type { ActivityMiddleware } from 'botframework-webchat-api'; import { Components } from 'botframework-webchat-component'; +import { WebChatDecorator } from 'botframework-webchat-component/decorator'; import React, { memo, type ReactNode } from 'react'; import { ActivityDecorator } from '../components/activity'; @@ -9,7 +10,6 @@ import { TelephoneKeypadProvider } from '../components/telephoneKeypad'; import { WebChatTheme } from '../components/theme'; import { createStyles } from '../styles'; import VariantComposer, { VariantList } from './VariantComposer'; -import { FluentThemeDecorator } from '../components/decorator'; import { isLinerMessageActivity, LinerMessageActivity } from '../components/linerActivity'; const { ThemeProvider } = Components; @@ -48,7 +48,7 @@ const FluentThemeProvider = ({ children, variant = 'fluent' }: Props) => ( - {children} + {children} diff --git a/packages/fluent-theme/src/styles/createStyles.ts b/packages/fluent-theme/src/styles/createStyles.ts index 2c9c6176a6..c12c5dedfc 100644 --- a/packages/fluent-theme/src/styles/createStyles.ts +++ b/packages/fluent-theme/src/styles/createStyles.ts @@ -1,11 +1,5 @@ -export const fluentStyleContent = '@--FLUENT-STYLES-CONTENT--@'; +import { makeCreateStyles } from 'botframework-webchat-styles'; -export default function createStyles() { - if (!globalThis.document) { - return []; - } +export const fluentStyleContent = '@--FLUENT-STYLES-CONTENT--@'; - const style = document.createElement('style'); - style.append(document.createTextNode(fluentStyleContent)); - return [style]; -} +export default makeCreateStyles(fluentStyleContent); diff --git a/packages/fluent-theme/src/styles/useStyles.ts b/packages/fluent-theme/src/styles/useStyles.ts index 216656910d..a2e6a089fb 100644 --- a/packages/fluent-theme/src/styles/useStyles.ts +++ b/packages/fluent-theme/src/styles/useStyles.ts @@ -1,19 +1,2 @@ -import { useMemo } from 'react'; - -function useStyles(styles: T): T { - // @ts-expect-error: entries/fromEntries don't allow to specify keys type - return useMemo( - () => - Object.freeze( - Object.fromEntries( - Object.entries(styles).map(([baseClassName, resultClassName]) => [ - baseClassName, - `${baseClassName} ${resultClassName}` - ]) - ) - ), - [styles] - ); -} - -export default useStyles; +// TODO: refactor after initial approval gained +export { useStyles as default } from 'botframework-webchat-styles/react'; diff --git a/packages/fluent-theme/src/tsconfig.json b/packages/fluent-theme/src/tsconfig.json index 95142ae36d..3a0f01fb5a 100644 --- a/packages/fluent-theme/src/tsconfig.json +++ b/packages/fluent-theme/src/tsconfig.json @@ -1,12 +1,14 @@ { - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "checkJs": false, - "jsx": "react", - "moduleResolution": "Bundler", - "skipLibCheck": true, - "target": "ESNext", - "plugins": [{ "name": "typescript-plugin-css-modules" }] - }, - "extends": "@tsconfig/strictest/tsconfig.json" + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "checkJs": false, + "jsx": "react", + "moduleResolution": "Bundler", + "skipLibCheck": true, + "target": "ESNext" + }, + "extends": [ + "@tsconfig/strictest/tsconfig.json", + "botframework-webchat-styles/tsconfig.json" + ] } diff --git a/packages/fluent-theme/tsup.config.ts b/packages/fluent-theme/tsup.config.ts index 7a20aa3f3b..39963d95dc 100644 --- a/packages/fluent-theme/tsup.config.ts +++ b/packages/fluent-theme/tsup.config.ts @@ -1,8 +1,9 @@ -import { join } from 'path'; +import { join } from 'node:path'; import { defineConfig } from 'tsup'; -import { fileURLToPath } from 'url'; +import { fileURLToPath } from 'node:url'; import baseConfig from '../../tsup.base.config'; import { fluentStyleContent as fluentStyleContentPlaceholder } from './src/styles/createStyles'; +import { injectCSSPlugin } from 'botframework-webchat-styles/build'; const umdResolvePlugin = { name: 'umd-resolve', @@ -26,19 +27,10 @@ const umdResolvePlugin = { build.onResolve({ filter: /^botframework-webchat-component\/internal$/u }, () => ({ path: join(fileURLToPath(import.meta.url), '../src/external.umd/botframework-webchat-component/internal.ts') })); - } -}; -const injectCSSPlugin = { - name: 'inject-css-plugin', - setup(build) { - build.onEnd(result => { - const js = result.outputFiles.find(f => f.path.match(/(\.js|\.mjs)$/u)); - const css = result.outputFiles.find(f => f.path.match(/(\.css)$/u)); - if (css && js?.text.includes(fluentStyleContentPlaceholder)) { - js.contents = Buffer.from(js.text.replace(`"${fluentStyleContentPlaceholder}"`, JSON.stringify(css.text))); - } - }); + build.onResolve({ filter: /^botframework-webchat-component\/decorator$/u }, () => ({ + path: join(fileURLToPath(import.meta.url), '../src/external.umd/botframework-webchat-component/decorator.ts') + })); } }; @@ -51,7 +43,7 @@ export default defineConfig([ ...baseConfig.loader, '.css': 'local-css' }, - esbuildPlugins: [...(baseConfig.esbuildPlugins || []), injectCSSPlugin], + esbuildPlugins: [...(baseConfig.esbuildPlugins || []), injectCSSPlugin({ stylesPlaceholder: fluentStyleContentPlaceholder })], format: ['cjs'] }, { @@ -61,7 +53,7 @@ export default defineConfig([ ...baseConfig.loader, '.css': 'local-css' }, - esbuildPlugins: [...(baseConfig.esbuildPlugins || []), injectCSSPlugin], + esbuildPlugins: [...(baseConfig.esbuildPlugins || []), injectCSSPlugin({ stylesPlaceholder: fluentStyleContentPlaceholder })], format: ['esm'] }, { @@ -71,7 +63,7 @@ export default defineConfig([ ...baseConfig.loader, '.css': 'local-css' }, - esbuildPlugins: [...(baseConfig.esbuildPlugins || []), injectCSSPlugin, umdResolvePlugin], + esbuildPlugins: [...(baseConfig.esbuildPlugins || []), injectCSSPlugin({ stylesPlaceholder: fluentStyleContentPlaceholder }), umdResolvePlugin], format: 'iife', outExtension() { return { js: '.js' }; @@ -84,7 +76,7 @@ export default defineConfig([ ...baseConfig.loader, '.css': 'local-css' }, - esbuildPlugins: [...(baseConfig.esbuildPlugins || []), injectCSSPlugin, umdResolvePlugin], + esbuildPlugins: [...(baseConfig.esbuildPlugins || []), injectCSSPlugin({ stylesPlaceholder: fluentStyleContentPlaceholder }), umdResolvePlugin], format: 'iife', minify: true, outExtension() { diff --git a/packages/styles/.eslintrc.yml b/packages/styles/.eslintrc.yml new file mode 100644 index 0000000000..1aa1350134 --- /dev/null +++ b/packages/styles/.eslintrc.yml @@ -0,0 +1,6 @@ +extends: + - ../../.eslintrc.production.yml + +# This package is compatible with web browser. +env: + browser: true diff --git a/packages/styles/.gitignore b/packages/styles/.gitignore new file mode 100644 index 0000000000..62b899b6ae --- /dev/null +++ b/packages/styles/.gitignore @@ -0,0 +1,4 @@ +/*.tgz +/dist/ +/lib/ +/node_modules/ diff --git a/packages/styles/README.md b/packages/styles/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/styles/build.js b/packages/styles/build.js new file mode 100644 index 0000000000..44fea9eec2 --- /dev/null +++ b/packages/styles/build.js @@ -0,0 +1,3 @@ +// This is required for Webpack 4 which does not support named exports. +// eslint-disable-next-line no-undef +module.exports = require('./dist/botframework-webchat-styles.build'); diff --git a/packages/styles/package.json b/packages/styles/package.json new file mode 100644 index 0000000000..7d8e79e8cb --- /dev/null +++ b/packages/styles/package.json @@ -0,0 +1,87 @@ +{ + "name": "botframework-webchat-styles", + "version": "0.0.0-0", + "description": "The botframework-webchat styles package", + "main": "./dist/botframework-webchat-styles.js", + "types": "./dist/botframework-webchat-styles.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/botframework-webchat-styles.d.mts", + "default": "./dist/botframework-webchat-styles.mjs" + }, + "require": { + "types": "./dist/botframework-webchat-styles.d.ts", + "default": "./dist/botframework-webchat-styles.js" + } + }, + "./build": { + "import": { + "types": "./dist/botframework-webchat-styles.build.d.mts", + "default": "./dist/botframework-webchat-styles.build.mjs" + }, + "require": { + "types": "./dist/botframework-webchat-styles.build.d.ts", + "default": "./dist/botframework-webchat-styles.build.js" + } + }, + "./react": { + "import": { + "types": "./dist/botframework-webchat-styles.react.d.mts", + "default": "./dist/botframework-webchat-styles.react.mjs" + }, + "require": { + "types": "./dist/botframework-webchat-styles.react.d.ts", + "default": "./dist/botframework-webchat-styles.react.js" + } + }, + "./tsconfig.json": "./src/ts-config/config.json", + "./env": { + "types": "./src/env.d.ts" + } + }, + "author": "Microsoft Corporation", + "license": "MIT", + "private": true, + "repository": { + "type": "git", + "url": "git+https://github.com/microsoft/BotFramework-WebChat.git" + }, + "bugs": { + "url": "https://github.com/microsoft/BotFramework-WebChat/issues" + }, + "files": [ + "./dist/**/*", + "./src/**/*", + "*.js" + ], + "homepage": "https://github.com/microsoft/BotFramework-WebChat/tree/main/packages/styles#readme", + "scripts": { + "build": "npm run build:tsup && npm run build:typescript", + "build:tsup": "tsup --config ./tsup.config.ts", + "build:typescript": "tsc --project src/tsconfig.json", + "bump": "npm run bump:prod && npm run bump:dev && (npm audit fix || exit 0)", + "bump:dev": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localDependencies // {} | keys) as $L | (.devDependencies // {}) | to_entries | map(select(.key as $K | $L | contains([$K]) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true", + "bump:prod": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localDependencies // {} | keys) as $L | (.dependencies // {}) | to_entries | map(select(.key as $K | $L | contains([$K]) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install --save-exact $PACKAGES_TO_BUMP || true", + "eslint": "npm run precommit", + "postversion": "cat package.json | jq '.version as $V | (.localDependencies // {} | with_entries(select(.value == \"production\") | { key: .key, value: $V })) as $L1 | (.localDependencies // {} | with_entries(select(.value == \"development\") | { key: .key, value: $V })) as $L2 | ((.dependencies // {}) + $L1 | to_entries | sort_by(.key) | from_entries) as $D1 | ((.devDependencies // {}) + $L2 | to_entries | sort_by(.key) | from_entries) as $D2 | . + { dependencies: $D1, devDependencies: $D2 }' > package-temp.json && mv package-temp.json package.json", + "precommit": "npm run precommit:eslint -- src && npm run precommit:typecheck", + "precommit:eslint": "../../node_modules/.bin/eslint --report-unused-disable-directives --max-warnings 0", + "precommit:typecheck": "tsc --project ./src --emitDeclarationOnly false --esModuleInterop true --noEmit --pretty false", + "preversion": "cat package.json | jq '(.localDependencies // {} | to_entries | map([if .value == \"production\" then \"dependencies\" else \"devDependencies\" end, .key])) as $P | delpaths($P)' > package-temp.json && mv package-temp.json package.json", + "start": "concurrently --kill-others --prefix-colors \"auto\" \"npm:start:*\"", + "start:tsup": "npm run build:tsup -- --watch", + "start:typescript": "npm run build:typescript -- --watch" + }, + "devDependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@tsconfig/strictest": "^2.0.5", + "@types/node": "^20.10.3", + "cross-env": "^7.0.3", + "type-fest": "^4.14.0", + "typescript": "^5.3.2" + }, + "peerDependencies": { + "react": ">= 16.8.6" + } +} diff --git a/packages/styles/react.js b/packages/styles/react.js new file mode 100644 index 0000000000..39ed10601e --- /dev/null +++ b/packages/styles/react.js @@ -0,0 +1,3 @@ +// This is required for Webpack 4 which does not support named exports. +// eslint-disable-next-line no-undef +module.exports = require('./dist/botframework-webchat-styles.react'); diff --git a/packages/styles/src/build/index.ts b/packages/styles/src/build/index.ts new file mode 100644 index 0000000000..02cb4c85ce --- /dev/null +++ b/packages/styles/src/build/index.ts @@ -0,0 +1 @@ +export { default as injectCSSPlugin, type InjectCSSPluginOptions } from './private/injectCSSPlugin'; diff --git a/packages/styles/src/build/private/injectCSSPlugin.ts b/packages/styles/src/build/private/injectCSSPlugin.ts new file mode 100644 index 0000000000..b6957f5ecd --- /dev/null +++ b/packages/styles/src/build/private/injectCSSPlugin.ts @@ -0,0 +1,76 @@ +import { decode, encode } from '@jridgewell/sourcemap-codec'; +import type { Plugin } from 'esbuild'; + +export interface InjectCSSPluginOptions { + stylesPlaceholder: string; +} + +function updateMappings(encoded: string, startIndex: number, offset: number) { + const mappings = decode(encoded); + + for (const mapping of mappings) { + for (const line of mapping) { + if (line[0] > startIndex) { + line[0] += offset; + } + } + } + + return encode(mappings); +} + +export default function injectCSSPlugin({ stylesPlaceholder }: InjectCSSPluginOptions): Plugin { + if (!stylesPlaceholder) { + throw new Error('inject-css-plugin: no placeholder for styles provided'); + } + + const stylesPlaceholderQuoted = JSON.stringify(stylesPlaceholder); + + return { + name: `inject-css-plugin(${stylesPlaceholder})`, + setup(build) { + build.onEnd(({ outputFiles = [] }) => { + for (const file of outputFiles) { + if (file.path.match(/(\.js|\.mjs)$/u)) { + const entryName = file.path.replace(/(\.js|\.mjs)$/u, ''); + const css = outputFiles.find(f => f.path.replace(/(\.css)$/u, '') === entryName); + + const jsText = file?.text; + if (css && jsText?.includes(stylesPlaceholderQuoted)) { + const cssText = JSON.stringify(css.text); + const index = jsText.indexOf(stylesPlaceholderQuoted); + const map = outputFiles.find(f => f.path.replace(/(\.map)$/u, '') === file.path); + + const updatedJsText = [ + jsText.slice(0, index), + cssText, + jsText.slice(index + stylesPlaceholderQuoted.length) + ].join(''); + + file.contents = Buffer.from(updatedJsText); + + // eslint-disable-next-line no-magic-numbers + if (updatedJsText.indexOf(stylesPlaceholder) !== -1) { + throw new Error( + `Duplicate placeholders are not supported.\nFound ${stylesPlaceholder} in ${file.path}.` + ); + } + + if (map) { + const parsed = JSON.parse(map.text); + + parsed.mappings = updateMappings( + parsed.mappings, + index, + cssText.length - stylesPlaceholderQuoted.length + ); + + map.contents = Buffer.from(JSON.stringify(parsed)); + } + } + } + } + }); + } + }; +} diff --git a/packages/styles/src/env.d.ts b/packages/styles/src/env.d.ts new file mode 100644 index 0000000000..0ca8f02237 --- /dev/null +++ b/packages/styles/src/env.d.ts @@ -0,0 +1,7 @@ +// CSS modules +type CSSModuleClasses = { readonly [key: string]: any }; + +declare module '*.module.css' { + const classes: CSSModuleClasses; + export default classes; +} diff --git a/packages/styles/src/index.ts b/packages/styles/src/index.ts new file mode 100644 index 0000000000..b2927a9375 --- /dev/null +++ b/packages/styles/src/index.ts @@ -0,0 +1 @@ +export { default as makeCreateStyles } from './private/makeCreateStyles'; diff --git a/packages/styles/src/private/makeCreateStyles.ts b/packages/styles/src/private/makeCreateStyles.ts new file mode 100644 index 0000000000..d6954270e0 --- /dev/null +++ b/packages/styles/src/private/makeCreateStyles.ts @@ -0,0 +1,17 @@ +function createStyle(content: string) { + const style = document.createElement('style'); + + style.append(document.createTextNode(content)); + + return style; +} + +export default function makeCreateStyles(...contents: string[]) { + return function createStyles() { + if (!globalThis.document) { + throw new Error('Unable to create styles: document is not defined'); + } + + return contents.map(content => createStyle(content)); + }; +} diff --git a/packages/styles/src/react/index.ts b/packages/styles/src/react/index.ts new file mode 100644 index 0000000000..629da3a671 --- /dev/null +++ b/packages/styles/src/react/index.ts @@ -0,0 +1 @@ +export { default as useStyles } from './private/useStyles'; diff --git a/packages/styles/src/react/private/useStyles.ts b/packages/styles/src/react/private/useStyles.ts new file mode 100644 index 0000000000..216656910d --- /dev/null +++ b/packages/styles/src/react/private/useStyles.ts @@ -0,0 +1,19 @@ +import { useMemo } from 'react'; + +function useStyles(styles: T): T { + // @ts-expect-error: entries/fromEntries don't allow to specify keys type + return useMemo( + () => + Object.freeze( + Object.fromEntries( + Object.entries(styles).map(([baseClassName, resultClassName]) => [ + baseClassName, + `${baseClassName} ${resultClassName}` + ]) + ) + ), + [styles] + ); +} + +export default useStyles; diff --git a/packages/styles/src/ts-config/config.json b/packages/styles/src/ts-config/config.json new file mode 100644 index 0000000000..e207c0ef70 --- /dev/null +++ b/packages/styles/src/ts-config/config.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "plugins": [{ "name": "typescript-plugin-css-modules" }] + } +} diff --git a/packages/styles/src/tsconfig.json b/packages/styles/src/tsconfig.json new file mode 100644 index 0000000000..a1c645b8b5 --- /dev/null +++ b/packages/styles/src/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "checkJs": false, + "jsx": "react", + "moduleResolution": "Bundler", + "noEmit": true, + "skipLibCheck": true, + "target": "ESNext" + }, + "extends": "@tsconfig/strictest/tsconfig.json" +} diff --git a/packages/styles/tsup.config.ts b/packages/styles/tsup.config.ts new file mode 100644 index 0000000000..a0796ae4c3 --- /dev/null +++ b/packages/styles/tsup.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'tsup'; +import baseConfig from '../../tsup.base.config'; + +export default defineConfig({ + ...baseConfig, + entry: { + 'botframework-webchat-styles': './src/index.ts', + 'botframework-webchat-styles.build': './src/build/index.ts', + 'botframework-webchat-styles.react': './src/react/index.ts' + }, + format: ['esm', 'cjs'] +});