From b81f48eea1ec1800f479b61046109dbd99d98126 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 18 May 2023 22:27:52 +0000 Subject: [PATCH 001/198] update mergeStyles to support contructible stylesheets First step toward supporting Shadow DOM. --- packages/merge-styles/src/Stylesheet.ts | 62 +++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/packages/merge-styles/src/Stylesheet.ts b/packages/merge-styles/src/Stylesheet.ts index 3be6223b3d311..6c48b59014a7b 100644 --- a/packages/merge-styles/src/Stylesheet.ts +++ b/packages/merge-styles/src/Stylesheet.ts @@ -15,6 +15,11 @@ export const InjectionMode = { * Appends rules using appendChild. */ appendChild: 2 as 2, + + /** + * Inserts rules into constructible stylesheets. + */ + constructibleStylesheet: 3 as 3, }; export type InjectionMode = (typeof InjectionMode)[keyof typeof InjectionMode]; @@ -94,6 +99,8 @@ const STYLESHEET_SETTING = '__stylesheet__'; */ const REUSE_STYLE_NODE = typeof navigator !== 'undefined' && /rv:11.0/.test(navigator.userAgent); +const SUPPORTS_CONSTRUCTIBLE_STYLESHEETS = 'CSSStyleSheet' in window; + let _global: (Window | {}) & { [STYLESHEET_SETTING]?: Stylesheet; FabricConfig?: { @@ -124,6 +131,9 @@ let _stylesheet: Stylesheet | undefined; export class Stylesheet { private _lastStyleElement?: HTMLStyleElement; private _styleElement?: HTMLStyleElement; + + private _constructibleSheet?: CSSStyleSheet; + private _rules: string[] = []; private _preservedRules: string[] = []; private _config: IStyleSheetConfig; @@ -151,15 +161,21 @@ export class Stylesheet { } constructor(config?: IStyleSheetConfig, serializedStylesheet?: ISerializedStylesheet) { + // If there is no document we won't have an element to inject into. + // const defaultInjectionMode = typeof document === 'undefined' ? InjectionMode.none : InjectionMode.insertNode; + const defaultInjectionMode = InjectionMode.constructibleStylesheet; this._config = { - // If there is no document we won't have an element to inject into. - injectionMode: typeof document === 'undefined' ? InjectionMode.none : InjectionMode.insertNode, + injectionMode: defaultInjectionMode, defaultPrefix: 'css', namespace: undefined, cspSettings: undefined, ...config, }; + if (!SUPPORTS_CONSTRUCTIBLE_STYLESHEETS && this._config.injectionMode === InjectionMode.constructibleStylesheet) { + this._config.injectionMode = defaultInjectionMode; + } + this._classNameToArgs = serializedStylesheet?.classNameToArgs ?? this._classNameToArgs; this._counter = serializedStylesheet?.counter ?? this._counter; this._keyToClassName = this._config.classNameCache ?? serializedStylesheet?.keyToClassName ?? this._keyToClassName; @@ -284,7 +300,12 @@ export class Stylesheet { */ public insertRule(rule: string, preserve?: boolean): void { const { injectionMode } = this._config; - const element = injectionMode !== InjectionMode.none ? this._getStyleElement() : undefined; + const element = + injectionMode === InjectionMode.constructibleStylesheet + ? this._getConstructibleStylesheet() + : injectionMode !== InjectionMode.none + ? this._getStyleElement() + : undefined; if (preserve) { this._preservedRules.push(rule); @@ -293,7 +314,7 @@ export class Stylesheet { if (element) { switch (injectionMode) { case InjectionMode.insertNode: - const { sheet } = element!; + const { sheet } = element! as HTMLStyleElement; try { (sheet as CSSStyleSheet).insertRule(rule, (sheet as CSSStyleSheet).cssRules.length); @@ -305,7 +326,17 @@ export class Stylesheet { break; case InjectionMode.appendChild: - element.appendChild(document.createTextNode(rule)); + (element as HTMLStyleElement).appendChild(document.createTextNode(rule)); + break; + + case InjectionMode.constructibleStylesheet: + try { + (element as CSSStyleSheet).insertRule(rule, (element as CSSStyleSheet).cssRules.length); + } catch (e) { + // The browser will throw exceptions on unsupported rules (such as a moz prefix in webkit.) + // We need to swallow the exceptions for this scenario, otherwise we'd need to filter + // which could be slower and bulkier. + } break; } } else { @@ -394,6 +425,27 @@ export class Stylesheet { return styleElement; } + private _getConstructibleStylesheet(): CSSStyleSheet { + if (!this._constructibleSheet) { + this._constructibleSheet = this._createConstructibleStylesheet(); + + // Reset the style element on the next frame. + window.requestAnimationFrame(() => { + this._styleElement = undefined; + }); + } + + return this._constructibleSheet; + } + + private _createConstructibleStylesheet(): CSSStyleSheet { + const sheet = new CSSStyleSheet(); + + document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]; + + return sheet; + } + private _findPlaceholderStyleTag(): Element | null { const head: HTMLHeadElement = document.head; if (head) { From 42e74d8378e6d88f7991cfc759eb85e2e695bb4e Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Fri, 19 May 2023 20:39:51 +0000 Subject: [PATCH 002/198] constructible sheets --- packages/merge-styles/etc/merge-styles.api.md | 1 + packages/merge-styles/src/Stylesheet.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packages/merge-styles/etc/merge-styles.api.md b/packages/merge-styles/etc/merge-styles.api.md index bd0e748d69dfc..0fd88c1a59fbf 100644 --- a/packages/merge-styles/etc/merge-styles.api.md +++ b/packages/merge-styles/etc/merge-styles.api.md @@ -75,6 +75,7 @@ export const InjectionMode: { none: 0; insertNode: 1; appendChild: 2; + constructibleStylesheet: 3; }; // @public (undocumented) diff --git a/packages/merge-styles/src/Stylesheet.ts b/packages/merge-styles/src/Stylesheet.ts index 6c48b59014a7b..ae678f2e3c3cd 100644 --- a/packages/merge-styles/src/Stylesheet.ts +++ b/packages/merge-styles/src/Stylesheet.ts @@ -441,6 +441,8 @@ export class Stylesheet { private _createConstructibleStylesheet(): CSSStyleSheet { const sheet = new CSSStyleSheet(); + // eslint-disable-next-line + // @ts-ignore this exists document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]; return sheet; From 63f0233fceddbdaacc4dfc159a930f56118a0b6a Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 30 May 2023 22:48:09 +0000 Subject: [PATCH 003/198] working example, still rough edges --- packages/merge-styles/etc/merge-styles.api.md | 52 ++++-- packages/merge-styles/src/IStyleFunction.ts | 1 + packages/merge-styles/src/IStyleOptions.ts | 1 + packages/merge-styles/src/IStyleSet.ts | 17 +- packages/merge-styles/src/Stylesheet.ts | 112 ++++++++++-- .../merge-styles/src/extractStyleParts.ts | 7 +- packages/merge-styles/src/index.ts | 2 + packages/merge-styles/src/mergeStyleSets.ts | 20 ++- packages/merge-styles/src/mergeStyles.ts | 3 +- packages/merge-styles/src/styleToClassName.ts | 16 +- .../react/Button/Button.Default.Example.tsx | 57 +++++- .../Button/BaseButton.classNames.ts | 164 ++++++++++-------- .../src/components/Button/BaseButton.tsx | 1 + packages/utilities/etc/utilities.api.md | 8 + packages/utilities/src/classNamesFunction.ts | 3 + .../src/customizations/customizable.tsx | 55 +++--- packages/utilities/src/index.ts | 5 + .../MergeStylesContext/MergeStylesContext.tsx | 100 +++++++++++ packages/utilities/src/styled.tsx | 8 + yarn.lock | 155 ++++++++++++++++- 20 files changed, 637 insertions(+), 150 deletions(-) create mode 100644 packages/utilities/src/shadowDom/MergeStylesContext/MergeStylesContext.tsx diff --git a/packages/merge-styles/etc/merge-styles.api.md b/packages/merge-styles/etc/merge-styles.api.md index 0fd88c1a59fbf..a04428d9bec98 100644 --- a/packages/merge-styles/etc/merge-styles.api.md +++ b/packages/merge-styles/etc/merge-styles.api.md @@ -33,17 +33,39 @@ export type DeepPartial = { [P in keyof T]?: T[P] extends (infer U)[] ? DeepPartial[] : T[P] extends object ? DeepPartial : T[P]; }; +// @public (undocumented) +export class EventMap { + constructor(); + // (undocumented) + get(key: K): V | undefined; + // (undocumented) + has(key: K): boolean; + // Warning: (ae-forgotten-export) The symbol "EventHandler" needs to be exported by the entry point index.d.ts + // + // (undocumented) + on(type: string, callback: EventHandler): void; + // (undocumented) + raise(type: string, data: { + key: K; + sheet: V; + }): void; + // (undocumented) + set(key: K, value: V): void; +} + // @public export function fontFace(font: IFontFace): void; +// Warning: (ae-forgotten-export) The symbol "IStylesheetKey" needs to be exported by the entry point index.d.ts +// // @public export type IConcatenatedStyleSet> = { - [P in keyof Omit_2]: IStyle; + [P in keyof Omit_2]: IStyle; } & { subComponentStyles?: { [P in keyof TStyleSet['subComponentStyles']]: IStyleFunction; }; -}; +} & IStylesheetKey; // @public export interface ICSPSettings { @@ -75,7 +97,7 @@ export const InjectionMode: { none: 0; insertNode: 1; appendChild: 2; - constructibleStylesheet: 3; + unstable_constructibleStylesheet: 3; }; // @public (undocumented) @@ -83,12 +105,12 @@ export type InjectionMode = (typeof InjectionMode)[keyof typeof InjectionMode]; // @public export type IProcessedStyleSet> = { - [P in keyof Omit_2]: string; + [P in keyof Omit_2]: string; } & { subComponentStyles: { [P in keyof TStyleSet['subComponentStyles']]: __MapToFunctionType; }; -}; +} & IStylesheetKey; // @public export interface IRawFontStyle { @@ -426,7 +448,7 @@ export interface IStyleBaseArray extends Array { } // @public -export type IStyleFunction> = (props: TStylesProps) => DeepPartial; +export type IStyleFunction> = (props: TStylesProps, __stylesheetKey__?: string) => DeepPartial; // @public export type IStyleFunctionOrObject> = IStyleFunction | DeepPartial; @@ -435,12 +457,12 @@ export type IStyleFunctionOrObject = { [key: string]: any; }> = { - [P in keyof Omit_2]: IStyle; + [P in keyof Omit_2]: IStyle; } & { subComponentStyles?: { [P in keyof TStyleSet['subComponentStyles']]: IStyleFunctionOrObject; }; -}; +} & IStylesheetKey; // @public export interface IStyleSheetConfig { @@ -462,20 +484,20 @@ export function keyframes(timeline: IKeyframes): string; // Warning: (ae-forgotten-export) The symbol "IStyleOptions" needs to be exported by the entry point index.d.ts // // @public -export function mergeCss(args: (IStyle | IStyleBaseArray | false | null | undefined) | (IStyle | IStyleBaseArray | false | null | undefined)[], options?: IStyleOptions): string; +export function mergeCss(args: (IStyle | IStyleBaseArray | false | null | undefined) | (IStyle | IStyleBaseArray | false | null | undefined)[], options?: IStyleOptions, stylesheetKey?: string): string; // @public -export function mergeCssSets(styleSets: [TStyleSet | false | null | undefined], options?: IStyleOptions): IProcessedStyleSet; +export function mergeCssSets(styleSets: [TStyleSet | false | null | undefined], options?: IStyleOptions, stylesheetKey?: string): IProcessedStyleSet; // @public -export function mergeCssSets(styleSets: [TStyleSet1 | false | null | undefined, TStyleSet2 | false | null | undefined], options?: IStyleOptions): IProcessedStyleSet; +export function mergeCssSets(styleSets: [TStyleSet1 | false | null | undefined, TStyleSet2 | false | null | undefined], options?: IStyleOptions, stylesheetKey?: string): IProcessedStyleSet; // @public export function mergeCssSets(styleSets: [ TStyleSet1 | false | null | undefined, TStyleSet2 | false | null | undefined, TStyleSet3 | false | null | undefined -], options?: IStyleOptions): IProcessedStyleSet; +], options?: IStyleOptions, stylesheetKey?: string): IProcessedStyleSet; // @public export function mergeCssSets(styleSets: [ @@ -483,10 +505,10 @@ TStyleSet1 | false | null | undefined, TStyleSet2 | false | null | undefined, TStyleSet3 | false | null | undefined, TStyleSet4 | false | null | undefined -], options?: IStyleOptions): IProcessedStyleSet & ObjectOnly & ObjectOnly & ObjectOnly>; +], options?: IStyleOptions, stylesheetKey?: string): IProcessedStyleSet & ObjectOnly & ObjectOnly & ObjectOnly>; // @public -export function mergeCssSets(styleSet: [TStyleSet | false | null | undefined], options?: IStyleOptions): IProcessedStyleSet; +export function mergeCssSets(styleSet: [TStyleSet | false | null | undefined], options?: IStyleOptions, stylesheetKey?: string): IProcessedStyleSet; // @public export function mergeStyles(...args: (IStyle | IStyleBaseArray | false | null | undefined)[]): string; @@ -528,7 +550,7 @@ export class Stylesheet { getClassNameCache(): { [key: string]: string; }; - static getInstance(): Stylesheet; + static getInstance(stylesheetKey?: string): Stylesheet; getRules(includePreservedRules?: boolean): string; insertedRulesFromClassName(className: string): string[] | undefined; insertRule(rule: string, preserve?: boolean): void; diff --git a/packages/merge-styles/src/IStyleFunction.ts b/packages/merge-styles/src/IStyleFunction.ts index 8c2d9e8609c87..73d70761815b1 100644 --- a/packages/merge-styles/src/IStyleFunction.ts +++ b/packages/merge-styles/src/IStyleFunction.ts @@ -7,6 +7,7 @@ import { DeepPartial } from './DeepPartial'; */ export type IStyleFunction> = ( props: TStylesProps, + __stylesheetKey__?: string, ) => DeepPartial; /** diff --git a/packages/merge-styles/src/IStyleOptions.ts b/packages/merge-styles/src/IStyleOptions.ts index cb68444e487cf..250da313b4954 100644 --- a/packages/merge-styles/src/IStyleOptions.ts +++ b/packages/merge-styles/src/IStyleOptions.ts @@ -1,4 +1,5 @@ export interface IStyleOptions { rtl?: boolean; specificityMultiplier?: number; + stylesheetKey?: string; } diff --git a/packages/merge-styles/src/IStyleSet.ts b/packages/merge-styles/src/IStyleSet.ts index 64aa0d9e6a81d..bf95a614e31bf 100644 --- a/packages/merge-styles/src/IStyleSet.ts +++ b/packages/merge-styles/src/IStyleSet.ts @@ -30,20 +30,20 @@ export type __MapToFunctionType = Extract extends never */ export type IStyleSet = { [key: string]: any }> = { // eslint-disable-next-line deprecation/deprecation - [P in keyof Omit]: IStyle; + [P in keyof Omit]: IStyle; } & { subComponentStyles?: { [P in keyof TStyleSet['subComponentStyles']]: IStyleFunctionOrObject }; -}; +} & IStylesheetKey; /** * A concatenated style set differs from `IStyleSet` in that subComponentStyles will always be a style function. */ export type IConcatenatedStyleSet> = { // eslint-disable-next-line deprecation/deprecation - [P in keyof Omit]: IStyle; + [P in keyof Omit]: IStyle; } & { subComponentStyles?: { [P in keyof TStyleSet['subComponentStyles']]: IStyleFunction }; -}; +} & IStylesheetKey; /** * A processed style set is one which the set of styles associated with each area has been converted @@ -51,11 +51,18 @@ export type IConcatenatedStyleSet> = { */ export type IProcessedStyleSet> = { // eslint-disable-next-line deprecation/deprecation - [P in keyof Omit]: string; + [P in keyof Omit]: string; } & { subComponentStyles: { [P in keyof TStyleSet['subComponentStyles']]: __MapToFunctionType< TStyleSet['subComponentStyles'] extends infer J ? (P extends keyof J ? J[P] : never) : never >; }; +} & IStylesheetKey; + +/** + * NOTE: This API is unstable and subject to breaking change or removal at any time. + */ +export type IStylesheetKey = { + __stylesheetKey__?: string; }; diff --git a/packages/merge-styles/src/Stylesheet.ts b/packages/merge-styles/src/Stylesheet.ts index ae678f2e3c3cd..2ed5be7e0013f 100644 --- a/packages/merge-styles/src/Stylesheet.ts +++ b/packages/merge-styles/src/Stylesheet.ts @@ -18,8 +18,11 @@ export const InjectionMode = { /** * Inserts rules into constructible stylesheets. + * NOTE: This API is unstable and subject to change or removal without notice. + * Depend on it at your own risk. */ - constructibleStylesheet: 3 as 3, + // eslint-disable-next-line @typescript-eslint/naming-convention + unstable_constructibleStylesheet: 3 as 3, }; export type InjectionMode = (typeof InjectionMode)[keyof typeof InjectionMode]; @@ -93,6 +96,9 @@ export interface ISerializedStylesheet { } const STYLESHEET_SETTING = '__stylesheet__'; + +const ADOPTED_STYLESHEETS = '__mergeStylesAdoptedStyleSheets__'; + /** * MSIE 11 doesn't cascade styles based on DOM ordering, but rather on the order that each style node * is created. As such, to maintain consistent priority, IE11 should reuse a single style node. @@ -101,12 +107,71 @@ const REUSE_STYLE_NODE = typeof navigator !== 'undefined' && /rv:11.0/.test(navi const SUPPORTS_CONSTRUCTIBLE_STYLESHEETS = 'CSSStyleSheet' in window; +// export type EventMap = Map & { +// raise: (type: string, data: { key: K; sheet: V }) => void; +// on: (type: string, callback: (data: { key: K; sheet: V }) => void) => void; +// off: (type: string) => void; +// } + +export type EventArgs = { key: string; sheet: Stylesheet }; +export type EventHandler = (args: EventArgs) => void; + +export class EventMap { + private _events: Map; + private _self: Map; + + constructor() { + this._self = new Map(); + this._events = new Map(); + } + + public get(key: K) { + return this._self.get(key); + } + + public set(key: K, value: V) { + this._self.set(key, value); + } + + public has(key: K) { + return this._self.has(key); + } + + public raise(type: string, data: { key: K; sheet: V }) { + const handlers = this._events.get(type); + if (!handlers) { + return; + } + + for (const handler of handlers) { + // eslint-disable-next-line + // @ts-ignore + handler?.(data as EventArgs); + } + } + + public on(type: string, callback: EventHandler) { + const handlers = this._events.get(type); + if (!handlers) { + this._events.set(type, [callback]); + } else { + handlers.push(callback); + } + } +} + +export type AdoptableStylesheet = { + fluentSheet: Stylesheet; + adoptedSheet: CSSStyleSheet; +}; + let _global: (Window | {}) & { [STYLESHEET_SETTING]?: Stylesheet; FabricConfig?: { mergeStyles?: IStyleSheetConfig; serializedStylesheet?: ISerializedStylesheet; }; + [ADOPTED_STYLESHEETS]?: EventMap; } = {}; // Grab window. @@ -133,6 +198,7 @@ export class Stylesheet { private _styleElement?: HTMLStyleElement; private _constructibleSheet?: CSSStyleSheet; + private _stylesheetKey?: string; private _rules: string[] = []; private _preservedRules: string[] = []; @@ -146,24 +212,38 @@ export class Stylesheet { /** * Gets the singleton instance. */ - public static getInstance(): Stylesheet { - _stylesheet = _global[STYLESHEET_SETTING] as Stylesheet; + public static getInstance(stylesheetKey?: string): Stylesheet { + if (stylesheetKey) { + _stylesheet = _global[ADOPTED_STYLESHEETS]?.get(stylesheetKey); + } else { + _stylesheet = _global[STYLESHEET_SETTING] as Stylesheet; + } if (!_stylesheet || (_stylesheet._lastStyleElement && _stylesheet._lastStyleElement.ownerDocument !== document)) { const fabricConfig = _global?.FabricConfig || {}; - const stylesheet = new Stylesheet(fabricConfig.mergeStyles, fabricConfig.serializedStylesheet); + const stylesheet = new Stylesheet(fabricConfig.mergeStyles, fabricConfig.serializedStylesheet, stylesheetKey); _stylesheet = stylesheet; - _global[STYLESHEET_SETTING] = stylesheet; + if (stylesheetKey) { + if (!_global[ADOPTED_STYLESHEETS]) { + _global[ADOPTED_STYLESHEETS] = new EventMap(); + } + _global[ADOPTED_STYLESHEETS]!.set(stylesheetKey, stylesheet); + const css = _stylesheet._getConstructibleStylesheet(); + css.__yo__ = stylesheetKey; + _global[ADOPTED_STYLESHEETS]!.raise('add-sheet', { key: stylesheetKey, sheet: stylesheet }); + } else { + _global[STYLESHEET_SETTING] = stylesheet; + } } return _stylesheet; } - constructor(config?: IStyleSheetConfig, serializedStylesheet?: ISerializedStylesheet) { + constructor(config?: IStyleSheetConfig, serializedStylesheet?: ISerializedStylesheet, stylesheetKey?: string) { // If there is no document we won't have an element to inject into. // const defaultInjectionMode = typeof document === 'undefined' ? InjectionMode.none : InjectionMode.insertNode; - const defaultInjectionMode = InjectionMode.constructibleStylesheet; + const defaultInjectionMode = InjectionMode.unstable_constructibleStylesheet; this._config = { injectionMode: defaultInjectionMode, defaultPrefix: 'css', @@ -172,7 +252,10 @@ export class Stylesheet { ...config, }; - if (!SUPPORTS_CONSTRUCTIBLE_STYLESHEETS && this._config.injectionMode === InjectionMode.constructibleStylesheet) { + if ( + !SUPPORTS_CONSTRUCTIBLE_STYLESHEETS && + this._config.injectionMode === InjectionMode.unstable_constructibleStylesheet + ) { this._config.injectionMode = defaultInjectionMode; } @@ -181,6 +264,12 @@ export class Stylesheet { this._keyToClassName = this._config.classNameCache ?? serializedStylesheet?.keyToClassName ?? this._keyToClassName; this._preservedRules = serializedStylesheet?.preservedRules ?? this._preservedRules; this._rules = serializedStylesheet?.rules ?? this._rules; + + this._stylesheetKey = stylesheetKey; + } + + public getAdoptableStyleSheet(): CSSStyleSheet | undefined { + return this._constructibleSheet; } /** @@ -301,7 +390,7 @@ export class Stylesheet { public insertRule(rule: string, preserve?: boolean): void { const { injectionMode } = this._config; const element = - injectionMode === InjectionMode.constructibleStylesheet + injectionMode === InjectionMode.unstable_constructibleStylesheet ? this._getConstructibleStylesheet() : injectionMode !== InjectionMode.none ? this._getStyleElement() @@ -329,7 +418,7 @@ export class Stylesheet { (element as HTMLStyleElement).appendChild(document.createTextNode(rule)); break; - case InjectionMode.constructibleStylesheet: + case InjectionMode.unstable_constructibleStylesheet: try { (element as CSSStyleSheet).insertRule(rule, (element as CSSStyleSheet).cssRules.length); } catch (e) { @@ -429,6 +518,7 @@ export class Stylesheet { if (!this._constructibleSheet) { this._constructibleSheet = this._createConstructibleStylesheet(); + // _global[ADOPTED_STYLESHEETS]!.raise('add-sheet', { key: this._stylesheetKey!, sheet: this }); // Reset the style element on the next frame. window.requestAnimationFrame(() => { this._styleElement = undefined; @@ -443,7 +533,7 @@ export class Stylesheet { // eslint-disable-next-line // @ts-ignore this exists - document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]; + // document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]; return sheet; } diff --git a/packages/merge-styles/src/extractStyleParts.ts b/packages/merge-styles/src/extractStyleParts.ts index 78e559b46e510..563d306aca490 100644 --- a/packages/merge-styles/src/extractStyleParts.ts +++ b/packages/merge-styles/src/extractStyleParts.ts @@ -5,13 +5,16 @@ import { Stylesheet } from './Stylesheet'; * Separates the classes and style objects. Any classes that are pre-registered * args are auto expanded into objects. */ -export function extractStyleParts(...args: (IStyle | IStyle[] | false | null | undefined)[]): { +export function extractStyleParts( + stylesheetKey: string = '__global__', + ...args: (IStyle | IStyle[] | false | null | undefined)[] +): { classes: string[]; objects: IStyleBaseArray; } { const classes: string[] = []; const objects: {}[] = []; - const stylesheet = Stylesheet.getInstance(); + const stylesheet = Stylesheet.getInstance(stylesheetKey); function _processArgs(argsList: (IStyle | IStyle[])[]): void { for (const arg of argsList) { diff --git a/packages/merge-styles/src/index.ts b/packages/merge-styles/src/index.ts index 3f2d819d21d81..1ee51eb5d555b 100644 --- a/packages/merge-styles/src/index.ts +++ b/packages/merge-styles/src/index.ts @@ -39,4 +39,6 @@ export { setRTL } from './StyleOptionsState'; export type { ObjectOnly } from './ObjectOnly'; +export { EventMap } from './Stylesheet'; + import './version'; diff --git a/packages/merge-styles/src/mergeStyleSets.ts b/packages/merge-styles/src/mergeStyleSets.ts index a66f7226277c5..53e13a3869e34 100644 --- a/packages/merge-styles/src/mergeStyleSets.ts +++ b/packages/merge-styles/src/mergeStyleSets.ts @@ -88,7 +88,13 @@ export function mergeStyleSets(...styleSets: Array): IProcessedStyleSet { - return mergeCssSets(styleSets as any, getStyleOptions()); + const last = styleSets[styleSets.length - 1]; + const { stylesheetKey } = last; + if (stylesheetKey) { + styleSets.pop(); + } + + return mergeCssSets(styleSets as any, getStyleOptions(), stylesheetKey); } /** @@ -103,6 +109,7 @@ export function mergeStyleSets(...styleSets: Array( styleSets: [TStyleSet | false | null | undefined], options?: IStyleOptions, + stylesheetKey?: string, ): IProcessedStyleSet; /** @@ -117,6 +124,7 @@ export function mergeCssSets( export function mergeCssSets( styleSets: [TStyleSet1 | false | null | undefined, TStyleSet2 | false | null | undefined], options?: IStyleOptions, + stylesheetKey?: string, ): IProcessedStyleSet; /** @@ -135,6 +143,7 @@ export function mergeCssSets( TStyleSet3 | false | null | undefined, ], options?: IStyleOptions, + stylesheetKey?: string, ): IProcessedStyleSet; /** @@ -154,6 +163,7 @@ export function mergeCssSets( TStyleSet4 | false | null | undefined, ], options?: IStyleOptions, + stylesheetKey?: string, ): IProcessedStyleSet< ObjectOnly & ObjectOnly & ObjectOnly & ObjectOnly >; @@ -170,6 +180,7 @@ export function mergeCssSets( export function mergeCssSets( styleSet: [TStyleSet | false | null | undefined], options?: IStyleOptions, + stylesheetKey?: string, ): IProcessedStyleSet; /** @@ -184,6 +195,7 @@ export function mergeCssSets( export function mergeCssSets( styleSets: Array, options?: IStyleOptions, + stylesheetKey?: string, ): IProcessedStyleSet { const classNameSet: IProcessedStyleSet = { subComponentStyles: {} }; @@ -206,10 +218,10 @@ export function mergeCssSets( const styles: IStyle = (concatenatedStyleSet as any)[styleSetArea]; - const { classes, objects } = extractStyleParts(styles); + const { classes, objects } = extractStyleParts(stylesheetKey, styles); if (objects?.length) { - const registration = styleToRegistration(options || {}, { displayName: styleSetArea }, objects); + const registration = styleToRegistration(stylesheetKey, options || {}, { displayName: styleSetArea }, objects); if (registration) { registrations.push(registration); @@ -225,7 +237,7 @@ export function mergeCssSets( for (const registration of registrations) { if (registration) { - applyRegistration(registration, options?.specificityMultiplier); + applyRegistration(registration, options?.specificityMultiplier, stylesheetKey); } } diff --git a/packages/merge-styles/src/mergeStyles.ts b/packages/merge-styles/src/mergeStyles.ts index 1bedc734117f3..ff87090fed529 100644 --- a/packages/merge-styles/src/mergeStyles.ts +++ b/packages/merge-styles/src/mergeStyles.ts @@ -22,9 +22,10 @@ export function mergeStyles(...args: (IStyle | IStyleBaseArray | false | null | export function mergeCss( args: (IStyle | IStyleBaseArray | false | null | undefined) | (IStyle | IStyleBaseArray | false | null | undefined)[], options?: IStyleOptions, + stylesheetKey?: string, ): string { const styleArgs = args instanceof Array ? args : [args]; - const { classes, objects } = extractStyleParts(styleArgs); + const { classes, objects } = extractStyleParts(stylesheetKey, styleArgs); if (objects.length) { classes.push(styleToClassName(options || {}, objects)); diff --git a/packages/merge-styles/src/styleToClassName.ts b/packages/merge-styles/src/styleToClassName.ts index b94b9b1d0766c..39f9fd7195350 100644 --- a/packages/merge-styles/src/styleToClassName.ts +++ b/packages/merge-styles/src/styleToClassName.ts @@ -243,12 +243,16 @@ export interface IRegistration { rulesToInsert: string[]; } -export function styleToRegistration(options: IStyleOptions, ...args: IStyle[]): IRegistration | undefined { +export function styleToRegistration( + stylesheetKey: string, + options: IStyleOptions, + ...args: IStyle[] +): IRegistration | undefined { const rules: IRuleSet = extractRules(args); const key = getKeyForRules(options, rules); if (key) { - const stylesheet = Stylesheet.getInstance(); + const stylesheet = Stylesheet.getInstance(stylesheetKey ?? '__global__'); const registration: Partial = { className: stylesheet.classNameFromKey(key), key, @@ -277,8 +281,12 @@ export function styleToRegistration(options: IStyleOptions, ...args: IStyle[]): * @param specificityMultiplier Number of times classname selector is repeated in the css rule. * This is to increase css specificity in case it's needed. Default to 1. */ -export function applyRegistration(registration: IRegistration, specificityMultiplier: number = 1): void { - const stylesheet = Stylesheet.getInstance(); +export function applyRegistration( + registration: IRegistration, + specificityMultiplier: number = 1, + stylesheetKey: string = '__global__', +): void { + const stylesheet = Stylesheet.getInstance(stylesheetKey); const { className, key, args, rulesToInsert } = registration; if (rulesToInsert) { diff --git a/packages/react-examples/src/react/Button/Button.Default.Example.tsx b/packages/react-examples/src/react/Button/Button.Default.Example.tsx index 3f9fd8b2fd100..38376b5f967c8 100644 --- a/packages/react-examples/src/react/Button/Button.Default.Example.tsx +++ b/packages/react-examples/src/react/Button/Button.Default.Example.tsx @@ -1,24 +1,71 @@ import * as React from 'react'; -import { Stack, IStackTokens } from '@fluentui/react'; +import { Stack, IStackTokens, SpinButton } from '@fluentui/react'; import { DefaultButton, PrimaryButton } from '@fluentui/react/lib/Button'; +import { MergeStylesProvider_unstable, useAdoptedStylesheet_unstable } from '@fluentui/utilities'; +// import { createProxy as _createProxy, default as _root } from 'react-shadow'; +// eslint-disable-next-line +import root from 'react-shadow'; export interface IButtonExampleProps { // These are set based on the toggles shown above the examples (not needed in real code) disabled?: boolean; checked?: boolean; } +// type CreateProxyRenderFn = ({ children }: { children: React.ReactNode; root: ShadowRoot }) => React.ReactNode; +// type CreateProxyFn = (target: unknown, id: string, render: CreateProxyRenderFn) => typeof _root; + +// const createProxy: CreateProxyFn = _createProxy; + +// const FluentWrapper: React.FC<{ children: React.ReactNode; root: ShadowRoot }> = ({ children, root }) => { +// // I think we'll need to implement something here to allow mergeStyles +// // to add styles to the shadowRoot. + +// return <>{children}; +// }; + +// export const root = createProxy({}, 'fluentui-v8', ({ children, root }) => ( +// {children} +// )); // Example formatting const stackTokens: IStackTokens = { childrenGap: 40 }; +const Hmmm = props => { + useAdoptedStylesheet_unstable('Hmmm'); + return
; +}; + export const ButtonDefaultExample: React.FunctionComponent = props => { const { disabled, checked } = props; + const [shadowRootEl, setShadowRootEl] = React.useState(null); + + const setter = val => { + setShadowRootEl(val); + }; return ( - - - - + + + + + + Hmmm + + + + ); }; diff --git a/packages/react/src/components/Button/BaseButton.classNames.ts b/packages/react/src/components/Button/BaseButton.classNames.ts index 4a05c847e144e..42d036558b262 100644 --- a/packages/react/src/components/Button/BaseButton.classNames.ts +++ b/packages/react/src/components/Button/BaseButton.classNames.ts @@ -39,90 +39,100 @@ export const getBaseButtonClassNames = memoizeFunction( checked: boolean, expanded: boolean, isSplit: boolean | undefined, + stylesheetKey?: string, ): IButtonClassNames => { const classNames = getGlobalClassNames(ButtonGlobalClassNames, theme || {}); const isExpanded = expanded && !isSplit; - return mergeStyleSets({ - root: [ - classNames.msButton, - styles.root, - variantClassName, - checked && ['is-checked', styles.rootChecked], - isExpanded && [ - 'is-expanded', - styles.rootExpanded, - { - selectors: { - [`:hover .${classNames.msButtonIcon}`]: styles.iconExpandedHovered, - // menuIcon falls back to rootExpandedHovered to support original behavior - [`:hover .${classNames.msButtonMenuIcon}`]: styles.menuIconExpandedHovered || styles.rootExpandedHovered, - ':hover': styles.rootExpandedHovered, + return mergeStyleSets( + { + root: [ + classNames.msButton, + styles.root, + variantClassName, + checked && ['is-checked', styles.rootChecked], + isExpanded && [ + 'is-expanded', + styles.rootExpanded, + { + selectors: { + [`:hover .${classNames.msButtonIcon}`]: styles.iconExpandedHovered, + // menuIcon falls back to rootExpandedHovered to support original behavior + [`:hover .${classNames.msButtonMenuIcon}`]: + styles.menuIconExpandedHovered || styles.rootExpandedHovered, + ':hover': styles.rootExpandedHovered, + }, }, - }, - ], - hasMenu && [ButtonGlobalClassNames.msButtonHasMenu, styles.rootHasMenu], - disabled && ['is-disabled', styles.rootDisabled], - !disabled && - !isExpanded && - !checked && { - selectors: { - ':hover': styles.rootHovered, - [`:hover .${classNames.msButtonLabel}`]: styles.labelHovered, - [`:hover .${classNames.msButtonIcon}`]: styles.iconHovered, - [`:hover .${classNames.msButtonDescription}`]: styles.descriptionHovered, - [`:hover .${classNames.msButtonMenuIcon}`]: styles.menuIconHovered, - ':focus': styles.rootFocused, - ':active': styles.rootPressed, - [`:active .${classNames.msButtonIcon}`]: styles.iconPressed, - [`:active .${classNames.msButtonDescription}`]: styles.descriptionPressed, - [`:active .${classNames.msButtonMenuIcon}`]: styles.menuIconPressed, + ], + hasMenu && [ButtonGlobalClassNames.msButtonHasMenu, styles.rootHasMenu], + disabled && ['is-disabled', styles.rootDisabled], + !disabled && + !isExpanded && + !checked && { + selectors: { + ':hover': styles.rootHovered, + [`:hover .${classNames.msButtonLabel}`]: styles.labelHovered, + [`:hover .${classNames.msButtonIcon}`]: styles.iconHovered, + [`:hover .${classNames.msButtonDescription}`]: styles.descriptionHovered, + [`:hover .${classNames.msButtonMenuIcon}`]: styles.menuIconHovered, + ':focus': styles.rootFocused, + ':active': styles.rootPressed, + [`:active .${classNames.msButtonIcon}`]: styles.iconPressed, + [`:active .${classNames.msButtonDescription}`]: styles.descriptionPressed, + [`:active .${classNames.msButtonMenuIcon}`]: styles.menuIconPressed, + }, }, - }, - disabled && checked && [styles.rootCheckedDisabled], - !disabled && - checked && { - selectors: { - ':hover': styles.rootCheckedHovered, - ':active': styles.rootCheckedPressed, + disabled && checked && [styles.rootCheckedDisabled], + !disabled && + checked && { + selectors: { + ':hover': styles.rootCheckedHovered, + ':active': styles.rootCheckedPressed, + }, }, - }, - className, - ], - flexContainer: [classNames.msButtonFlexContainer, styles.flexContainer], - textContainer: [classNames.msButtonTextContainer, styles.textContainer], - icon: [ - classNames.msButtonIcon, - iconClassName, - styles.icon, - isExpanded && styles.iconExpanded, - checked && styles.iconChecked, - disabled && styles.iconDisabled, - ], - label: [classNames.msButtonLabel, styles.label, checked && styles.labelChecked, disabled && styles.labelDisabled], - menuIcon: [ - classNames.msButtonMenuIcon, - menuIconClassName, - styles.menuIcon, - checked && styles.menuIconChecked, - disabled && !isSplit && styles.menuIconDisabled, - !disabled && - !isExpanded && - !checked && { - selectors: { - ':hover': styles.menuIconHovered, - ':active': styles.menuIconPressed, + className, + ], + flexContainer: [classNames.msButtonFlexContainer, styles.flexContainer], + textContainer: [classNames.msButtonTextContainer, styles.textContainer], + icon: [ + classNames.msButtonIcon, + iconClassName, + styles.icon, + isExpanded && styles.iconExpanded, + checked && styles.iconChecked, + disabled && styles.iconDisabled, + ], + label: [ + classNames.msButtonLabel, + styles.label, + checked && styles.labelChecked, + disabled && styles.labelDisabled, + ], + menuIcon: [ + classNames.msButtonMenuIcon, + menuIconClassName, + styles.menuIcon, + checked && styles.menuIconChecked, + disabled && !isSplit && styles.menuIconDisabled, + !disabled && + !isExpanded && + !checked && { + selectors: { + ':hover': styles.menuIconHovered, + ':active': styles.menuIconPressed, + }, }, - }, - isExpanded && ['is-expanded', styles.menuIconExpanded], - ], - description: [ - classNames.msButtonDescription, - styles.description, - checked && styles.descriptionChecked, - disabled && styles.descriptionDisabled, - ], - screenReaderText: [classNames.msButtonScreenReaderText, styles.screenReaderText], - }); + isExpanded && ['is-expanded', styles.menuIconExpanded], + ], + description: [ + classNames.msButtonDescription, + styles.description, + checked && styles.descriptionChecked, + disabled && styles.descriptionDisabled, + ], + screenReaderText: [classNames.msButtonScreenReaderText, styles.screenReaderText], + }, + { stylesheetKey }, + ); }, ); diff --git a/packages/react/src/components/Button/BaseButton.tsx b/packages/react/src/components/Button/BaseButton.tsx index 94d0917462d5d..a0cdebc577f0b 100644 --- a/packages/react/src/components/Button/BaseButton.tsx +++ b/packages/react/src/components/Button/BaseButton.tsx @@ -172,6 +172,7 @@ export class BaseButton extends React.Component; + // @public export function modalize(target: HTMLElement): () => void; @@ -1263,6 +1268,9 @@ export const trProperties: Record; // @public export function unhoistMethods(source: any, methodNames: string[]): void; +// @public +export const useAdoptedStylesheet_unstable: (stylesheetKey: string) => void; + // @public export function useCustomizationSettings(properties: string[], scopeName?: string): ISettings; diff --git a/packages/utilities/src/classNamesFunction.ts b/packages/utilities/src/classNamesFunction.ts index b02104aa13c8b..5157299577038 100644 --- a/packages/utilities/src/classNamesFunction.ts +++ b/packages/utilities/src/classNamesFunction.ts @@ -117,6 +117,9 @@ export function classNamesFunction, ], { rtl: !!rtl, specificityMultiplier: options.useStaticStyles ? DEFAULT_SPECIFICITY_MULTIPLIER : undefined }, + // eslint-disable-next-line + // @ts-ignore + styleFunctionOrObject.__stylesheetKey__, ); } diff --git a/packages/utilities/src/customizations/customizable.tsx b/packages/utilities/src/customizations/customizable.tsx index c4e178ca53d16..6aeb025dc1b94 100644 --- a/packages/utilities/src/customizations/customizable.tsx +++ b/packages/utilities/src/customizations/customizable.tsx @@ -4,6 +4,7 @@ import { hoistStatics } from '../hoistStatics'; import { CustomizerContext } from './CustomizerContext'; import { concatStyleSets } from '@fluentui/merge-styles'; import type { ICustomizerContext } from './CustomizerContext'; +import { MergeStylesContextConsumer } from '../shadowDom/MergeStylesContext/MergeStylesContext'; export function customizable( scope: string, @@ -35,36 +36,40 @@ export function customizable( public render(): JSX.Element { return ( - - {(context: ICustomizerContext) => { - const defaultProps = Customizations.getSettings(fields, scope, context.customizations); + + + {(context: ICustomizerContext) => { + const defaultProps = Customizations.getSettings(fields, scope, context.customizations); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const componentProps = this.props as any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const componentProps = this.props as any; - // If defaultProps.styles is a function, evaluate it before calling concatStyleSets - if (defaultProps.styles && typeof defaultProps.styles === 'function') { - defaultProps.styles = defaultProps.styles({ ...defaultProps, ...componentProps }); - } - - // If concatStyles is true and custom styles have been defined compute those styles - if (concatStyles && defaultProps.styles) { - if ( - this._styleCache.default !== defaultProps.styles || - this._styleCache.component !== componentProps.styles - ) { - const mergedStyles = concatStyleSets(defaultProps.styles, componentProps.styles); - this._styleCache.default = defaultProps.styles; - this._styleCache.component = componentProps.styles; - this._styleCache.merged = mergedStyles; + // If defaultProps.styles is a function, evaluate it before calling concatStyleSets + if (defaultProps.styles && typeof defaultProps.styles === 'function') { + defaultProps.styles = defaultProps.styles({ ...defaultProps, ...componentProps }); } - return ; - } + // If concatStyles is true and custom styles have been defined compute those styles + if (concatStyles && defaultProps.styles) { + if ( + this._styleCache.default !== defaultProps.styles || + this._styleCache.component !== componentProps.styles + ) { + const mergedStyles = concatStyleSets(defaultProps.styles, componentProps.styles); + this._styleCache.default = defaultProps.styles; + this._styleCache.component = componentProps.styles; + this._styleCache.merged = mergedStyles; + this._styleCache.merged.__stylesheetKey__ = scope; + } + + return ; + } - return ; - }} - + const styles = { ...defaultProps.styles, ...componentProps.styles, __stylesheetKey__: scope }; + return ; + }} + + ); } diff --git a/packages/utilities/src/index.ts b/packages/utilities/src/index.ts index 44d60c3e7c0a0..03a561be76423 100644 --- a/packages/utilities/src/index.ts +++ b/packages/utilities/src/index.ts @@ -235,3 +235,8 @@ import './version'; // eslint-disable-next-line deprecation/deprecation export type { IStyleFunctionOrObject, Omit } from '@fluentui/merge-styles'; + +export { + MergeStylesProvider_unstable, + useAdoptedStylesheet_unstable, +} from './shadowDom/MergeStylesContext/MergeStylesContext'; diff --git a/packages/utilities/src/shadowDom/MergeStylesContext/MergeStylesContext.tsx b/packages/utilities/src/shadowDom/MergeStylesContext/MergeStylesContext.tsx new file mode 100644 index 0000000000000..d0b81228f2f58 --- /dev/null +++ b/packages/utilities/src/shadowDom/MergeStylesContext/MergeStylesContext.tsx @@ -0,0 +1,100 @@ +import * as React from 'react'; +import { EventMap } from '@fluentui/merge-styles'; + +declare global { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface Window { + __mergeStylesAdoptedStyleSheets__?: Map; + } +} + +/** + * NOTE: This API is unstable and subject to breaking change or removal without notice. + */ +export type MergeStylesContextValue = { + stylesheets: Map; + shadowRoot?: ShadowRoot | null; +}; + +const MergeStylesContext = React.createContext({ + stylesheets: new Map(), +}); + +/** + * NOTE: This API is unstable and subject to breaking change or removal without notice. + */ +export type MergeStylesProviderProps = { + shadowRoot?: ShadowRoot | null; +}; + +/** + * NOTE: This API is unstable and subject to breaking change or removal without notice. + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export const MergeStylesProvider_unstable: React.FC = ({ shadowRoot, ...props }) => { + const ctx = useMergeStylesContext_unstable(); + + const value = React.useMemo(() => { + return { + stylesheets: ctx.stylesheets, + shadowRoot, + }; + }, [ctx, shadowRoot]); + + return ; +}; + +export type MergeStylesContextConsumerProps = { + stylesheetKey?: string; +}; + +export const MergeStylesContextConsumer: React.FC = ({ stylesheetKey, children }) => { + useAdoptedStylesheet_unstable(stylesheetKey ?? '__global__'); + + return <>{children}; +}; + +/** + * NOTE: This API is unstable and subject to breaking change or removal without notice. + */ +export const useMergeStylesContext_unstable = () => { + return React.useContext(MergeStylesContext); +}; + +// type AddSheetCallback = ({ key: string, sheet: CSSStyleSheet }) => void; + +/** + * NOTE: This API is unstable and subject to breaking change or removal without notice. + */ +export const useAdoptedStylesheet_unstable = (stylesheetKey: string): void => { + const ctx = useMergeStylesContext_unstable(); + + if (ctx.shadowRoot && !ctx.stylesheets.has(stylesheetKey)) { + const stylesheet = window.__mergeStylesAdoptedStyleSheets__?.get(stylesheetKey); + if (stylesheet) { + ctx.stylesheets.set(stylesheetKey, stylesheet); + // eslint-disable-next-line + // @ts-ignore types not working for some reason + ctx.shadowRoot.adoptedStyleSheets = [...ctx.shadowRoot.adoptedStyleSheets, stylesheet]; + } else { + if (!window.__mergeStylesAdoptedStyleSheets__) { + // eslint-disable-next-line + // @ts-ignore + window.__mergeStylesAdoptedStyleSheets__ = new EventMap(); + } + // eslint-disable-next-line + // @ts-ignore + window.__mergeStylesAdoptedStyleSheets__.on('add-sheet', ({ key, sheet }) => { + if (!ctx.stylesheets.has(key)) { + ctx.stylesheets.set(key, sheet); + // eslint-disable-next-line + // @ts-ignore types not working for some reason + const adoptee = sheet.getAdoptableStyleSheet(); + if (adoptee) { + ctx.shadowRoot.adoptedStyleSheets = [...ctx.shadowRoot.adoptedStyleSheets, adoptee]; + } + } + }); + } + } +}; diff --git a/packages/utilities/src/styled.tsx b/packages/utilities/src/styled.tsx index 0b65e10ed6cfa..93b713ba8ee7a 100644 --- a/packages/utilities/src/styled.tsx +++ b/packages/utilities/src/styled.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { concatStyleSetsWithProps } from '@fluentui/merge-styles'; +import { useAdoptedStylesheet_unstable } from './shadowDom/MergeStylesContext/MergeStylesContext'; import { useCustomizationSettings } from './customizations/useCustomizationSettings'; import type { IStyleSet, IStyleFunctionOrObject } from '@fluentui/merge-styles'; @@ -88,6 +89,8 @@ export function styled< const { scope, fields = DefaultFields } = customizable; + const stylesheetKey = scope || '__global__'; + const Wrapped = React.forwardRef((props: TComponentProps, forwardedRef: React.Ref) => { const styles = React.useRef>(); @@ -117,8 +120,13 @@ export function styled< !customizedStyles && !propStyles; styles.current = concatenatedStyles as StyleFunction; + // eslint-disable-next-line + // @ts-ignore + styles.current.__stylesheetKey__ = stylesheetKey; } + useAdoptedStylesheet_unstable(stylesheetKey); + return ; }); // Function.prototype.name is an ES6 feature, so the cast to any is required until we're diff --git a/yarn.lock b/yarn.lock index 7d8bf8fec08ac..10ba4fbbcdad5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6742,6 +6742,11 @@ resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.2.tgz#b695ff674e8216efa632a3d36ad51ae9843380c0" integrity sha512-+R0juSseERyoPvnBQ/cZih6bpF7IpCXlWbHRoCRzYzqpz6gWHOgf8o4MOEf6KBVuOyqU+gCNLkCWVIJAro8XyQ== +"@xobotyi/scrollbar-width@^1.9.5": + version "1.9.5" + resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" + integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ== + "@xstate/react@^1.5.1": version "1.6.3" resolved "https://registry.yarnpkg.com/@xstate/react/-/react-1.6.3.tgz#706f3beb7bc5879a78088985c8fd43b9dab7f725" @@ -10237,6 +10242,13 @@ css-in-js-utils@^3.0.0: dependencies: hyphenate-style-name "^1.0.2" +css-in-js-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz#640ae6a33646d401fc720c54fc61c42cd76ae2bb" + integrity sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A== + dependencies: + hyphenate-style-name "^1.0.3" + css-loader@5.0.1, css-loader@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.0.1.tgz#9e4de0d6636a6266a585bd0900b422c85539d25f" @@ -10304,6 +10316,14 @@ css-to-react-native@^3.0.0: css-color-keywords "^1.0.0" postcss-value-parser "^4.0.2" +css-tree@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + css-what@2.1: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" @@ -12790,6 +12810,11 @@ fast-loops@^1.0.0, fast-loops@^1.0.1: resolved "https://registry.yarnpkg.com/fast-loops/-/fast-loops-1.1.2.tgz#2ee75ba943a08a9b1ffdf9b49f133322c99bda68" integrity sha512-ql8BgnHFryLogmmzR5O3uobe+3Zzaq6h6MWn/VtAyL9OXb51r5PSTbCm9f56fvEvMWWGjbdkr4xyhT0/vLJkKw== +fast-loops@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/fast-loops/-/fast-loops-1.1.3.tgz#ce96adb86d07e7bf9b4822ab9c6fac9964981f75" + integrity sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g== + fast-png@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/fast-png/-/fast-png-6.1.0.tgz#c0abd3015346e16752acbe4ea74f1f0170b55b9e" @@ -12799,11 +12824,21 @@ fast-png@^6.1.0: iobuffer "^5.0.4" pako "^2.0.4" +fast-shallow-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz#d4dcaf6472440dcefa6f88b98e3251e27f25628b" + integrity sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw== + fastest-levenshtein@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== +fastest-stable-stringify@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz#3757a6774f6ec8de40c4e86ec28ea02417214c76" + integrity sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q== + fastq@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2" @@ -15002,6 +15037,11 @@ hyphenate-style-name@^1.0.2: resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ== +hyphenate-style-name@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" + integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== + iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -15231,6 +15271,14 @@ inline-style-parser@0.1.1: resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== +inline-style-prefixer@^6.0.0: + version "6.0.4" + resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-6.0.4.tgz#4290ed453ab0e4441583284ad86e41ad88384f44" + integrity sha512-FwXmZC2zbeeS7NzGjJ6pAiqRhXR0ugUShSNb6GApMl6da0/XGc4MOJsoWAywia52EEWbXNSy0pzkwz/+Y+swSg== + dependencies: + css-in-js-utils "^3.1.0" + fast-loops "^1.1.3" + inquirer@^7.0.0: version "7.3.3" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" @@ -16644,6 +16692,11 @@ jpeg-js@^0.4.4: resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg== +js-cookie@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== + js-message@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/js-message/-/js-message-1.0.5.tgz#2300d24b1af08e89dd095bc1a4c9c9cfcb892d15" @@ -18318,6 +18371,11 @@ mdast-util-to-string@^1.0.0: resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + mdurl@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" @@ -18939,6 +18997,20 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== +nano-css@^5.3.1: + version "5.3.5" + resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.3.5.tgz#3075ea29ffdeb0c7cb6d25edb21d8f7fa8e8fe8e" + integrity sha512-vSB9X12bbNu4ALBu7nigJgRViZ6ja3OU7CeuiV1zMIbXOdmkLahgtPmh3GBOlDxbKY0CitqlPdOReGlBLSp+yg== + dependencies: + css-tree "^1.1.2" + csstype "^3.0.6" + fastest-stable-stringify "^2.0.2" + inline-style-prefixer "^6.0.0" + rtl-css-js "^1.14.0" + sourcemap-codec "^1.4.8" + stacktrace-js "^2.0.2" + stylis "^4.0.6" + nanoid@^3.1.23, nanoid@^3.3.1: version "3.3.6" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" @@ -21766,6 +21838,31 @@ react-transition-group@^4.3.0, react-transition-group@^4.4.1: loose-envify "^1.4.0" prop-types "^15.6.2" +react-universal-interface@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b" + integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw== + +react-use@^17.0.0: + version "17.4.0" + resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.4.0.tgz#cefef258b0a6c534a5c8021c2528ac6e1a4cdc6d" + integrity sha512-TgbNTCA33Wl7xzIJegn1HndB4qTS9u03QUwyNycUnXaweZkE4Kq2SB+Yoxx8qbshkZGYBDvUXbXWRUmQDcZZ/Q== + dependencies: + "@types/js-cookie" "^2.2.6" + "@xobotyi/scrollbar-width" "^1.9.5" + copy-to-clipboard "^3.3.1" + fast-deep-equal "^3.1.3" + fast-shallow-equal "^1.0.0" + js-cookie "^2.2.1" + nano-css "^5.3.1" + react-universal-interface "^0.6.2" + resize-observer-polyfill "^1.5.1" + screenfull "^5.1.0" + set-harmonic-interval "^1.0.1" + throttle-debounce "^3.0.1" + ts-easing "^0.2.0" + tslib "^2.1.0" + react-virtualized@^9.21.1: version "9.21.2" resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.2.tgz#02e6df65c1e020c8dbf574ec4ce971652afca84e" @@ -22434,6 +22531,11 @@ reselect@^4.0.0: resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.7.tgz#56480d9ff3d3188970ee2b76527bd94a95567a42" integrity sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A== +resize-observer-polyfill@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== + resolve-alpn@^1.0.0, resolve-alpn@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" @@ -22730,6 +22832,13 @@ rtl-css-js@^1.1.3, rtl-css-js@^1.16.1: dependencies: "@babel/runtime" "^7.1.2" +rtl-css-js@^1.14.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.16.1.tgz#4b48b4354b0ff917a30488d95100fbf7219a3e80" + integrity sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg== + dependencies: + "@babel/runtime" "^7.1.2" + run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -23124,6 +23233,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-harmonic-interval@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249" + integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g== + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -23476,6 +23590,11 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= +source-map@0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA== + source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.6, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -23498,7 +23617,7 @@ source-map@~0.2.0: dependencies: amdefine ">=0.0.4" -sourcemap-codec@^1.4.4: +sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8: version "1.4.8" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== @@ -23634,6 +23753,13 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== +stack-generator@^2.0.5: + version "2.0.10" + resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.10.tgz#8ae171e985ed62287d4f1ed55a1633b3fb53bb4d" + integrity sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ== + dependencies: + stackframe "^1.3.4" + stack-trace@0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" @@ -23651,6 +23777,28 @@ stackframe@^1.1.1: resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA== +stackframe@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" + integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== + +stacktrace-gps@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz#0c40b24a9b119b20da4525c398795338966a2fb0" + integrity sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ== + dependencies: + source-map "0.5.6" + stackframe "^1.3.4" + +stacktrace-js@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b" + integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg== + dependencies: + error-stack-parser "^2.0.6" + stack-generator "^2.0.5" + stacktrace-gps "^3.0.4" + state-toggle@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" @@ -24858,6 +25006,11 @@ ts-dedent@^2.0.0: resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5" integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== +ts-easing@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec" + integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ== + ts-invariant@^0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.10.3.tgz#3e048ff96e91459ffca01304dbc7f61c1f642f6c" From 811035af2b231e866059735519b67e707a27dba2 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 8 Aug 2023 04:55:04 +0000 Subject: [PATCH 004/198] refactor mergestyles shadow dom/constructable style sheets implementation Adds: - MergeStylesRootContext to act as a "global" context for tracking style sheets. This should better enable multi-window scenarios and widgets. - MergeStylesShadowRootContext to act as a proxy for the shadow root. --- .../tsconfig.json | 2 +- packages/merge-styles/etc/merge-styles.api.md | 11 +- packages/merge-styles/src/Stylesheet.ts | 43 ++++- packages/merge-styles/src/mergeStyleSets.ts | 22 ++- packages/merge-styles/src/mergeStyles.ts | 4 +- .../merge-styles/src/styleToClassName.test.ts | 75 +++++--- packages/merge-styles/src/styleToClassName.ts | 8 +- .../react/Button/Button.Default.Example.tsx | 60 ++++--- packages/react/etc/react.api.md | 2 + .../Button/BaseButton.classNames.ts | 163 +++++++++--------- .../src/components/Button/Button.types.ts | 2 + packages/utilities/etc/utilities.api.md | 10 +- .../src/customizations/customizable.tsx | 6 +- packages/utilities/src/index.ts | 11 +- .../MergeStylesContext/MergeStylesContext.tsx | 100 ----------- .../src/shadowDom/MergeStylesRootContext.tsx | 113 ++++++++++++ .../MergeStylesShadowRootContext.tsx | 81 +++++++++ packages/utilities/src/styled.tsx | 2 +- yarn.lock | 155 +---------------- 19 files changed, 461 insertions(+), 409 deletions(-) delete mode 100644 packages/utilities/src/shadowDom/MergeStylesContext/MergeStylesContext.tsx create mode 100644 packages/utilities/src/shadowDom/MergeStylesRootContext.tsx create mode 100644 packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx diff --git a/packages/jest-serializer-merge-styles/tsconfig.json b/packages/jest-serializer-merge-styles/tsconfig.json index ea104793c121e..9d96846319c43 100644 --- a/packages/jest-serializer-merge-styles/tsconfig.json +++ b/packages/jest-serializer-merge-styles/tsconfig.json @@ -3,7 +3,7 @@ "target": "es5", "outDir": "lib", "module": "commonjs", - "lib": ["es2017"], + "lib": ["es2017", "dom"], "jsx": "react", "declaration": true, "sourceMap": true, diff --git a/packages/merge-styles/etc/merge-styles.api.md b/packages/merge-styles/etc/merge-styles.api.md index a04428d9bec98..a14050a28c42a 100644 --- a/packages/merge-styles/etc/merge-styles.api.md +++ b/packages/merge-styles/etc/merge-styles.api.md @@ -37,9 +37,13 @@ export type DeepPartial = { export class EventMap { constructor(); // (undocumented) + forEach(callback: (value: V, key: K, map: Map) => void): void; + // (undocumented) get(key: K): V | undefined; // (undocumented) has(key: K): boolean; + // (undocumented) + off(type: string, callback: EventHandler): void; // Warning: (ae-forgotten-export) The symbol "EventHandler" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -528,6 +532,9 @@ export function mergeStyleSets(s // @public export function mergeStyleSets(...styleSets: Array): IProcessedStyleSet; +// @public (undocumented) +export function mergeStyleSets(stylesheetKey: string, ...styleSets: Array): IProcessedStyleSet; + // @public (undocumented) export type ObjectOnly = TArg extends {} ? TArg : {}; @@ -542,10 +549,12 @@ export function setRTL(isRTL: boolean): void; // @public export class Stylesheet { - constructor(config?: IStyleSheetConfig, serializedStylesheet?: ISerializedStylesheet); + constructor(config?: IStyleSheetConfig, serializedStylesheet?: ISerializedStylesheet, stylesheetKey?: string); argsFromClassName(className: string): IStyle[] | undefined; cacheClassName(className: string, key: string, args: IStyle[], rules: string[]): void; classNameFromKey(key: string): string | undefined; + // (undocumented) + getAdoptableStyleSheet(): CSSStyleSheet | undefined; getClassName(displayName?: string): string; getClassNameCache(): { [key: string]: string; diff --git a/packages/merge-styles/src/Stylesheet.ts b/packages/merge-styles/src/Stylesheet.ts index 2ed5be7e0013f..86372ece2a923 100644 --- a/packages/merge-styles/src/Stylesheet.ts +++ b/packages/merge-styles/src/Stylesheet.ts @@ -137,6 +137,10 @@ export class EventMap { return this._self.has(key); } + public forEach(callback: (value: V, key: K, map: Map) => void) { + this._self.forEach(callback); + } + public raise(type: string, data: { key: K; sheet: V }) { const handlers = this._events.get(type); if (!handlers) { @@ -158,6 +162,16 @@ export class EventMap { handlers.push(callback); } } + + public off(type: string, callback: EventHandler) { + const handlers = this._events.get(type); + if (handlers) { + const index = handlers.indexOf(callback); + if (index >= 0) { + handlers.splice(index, 1); + } + } + } } export type AdoptableStylesheet = { @@ -186,6 +200,8 @@ try { let _stylesheet: Stylesheet | undefined; +let constructableStyleSheetCounter = 0; + /** * Represents the state of styles registered in the page. Abstracts * the surface for adding styles to the stylesheet, exposes helpers @@ -203,7 +219,7 @@ export class Stylesheet { private _rules: string[] = []; private _preservedRules: string[] = []; private _config: IStyleSheetConfig; - private _counter = 0; + private _styleCounter = 0; private _keyToClassName: { [key: string]: string } = {}; private _onInsertRuleCallbacks: Function[] = []; private _onResetCallbacks: Function[] = []; @@ -230,8 +246,10 @@ export class Stylesheet { } _global[ADOPTED_STYLESHEETS]!.set(stylesheetKey, stylesheet); const css = _stylesheet._getConstructibleStylesheet(); - css.__yo__ = stylesheetKey; - _global[ADOPTED_STYLESHEETS]!.raise('add-sheet', { key: stylesheetKey, sheet: stylesheet }); + // css.__yo__ = stylesheetKey; + requestAnimationFrame(() => { + _global[ADOPTED_STYLESHEETS]!.raise('add-sheet', { key: stylesheetKey, sheet: stylesheet }); + }); } else { _global[STYLESHEET_SETTING] = stylesheet; } @@ -260,7 +278,9 @@ export class Stylesheet { } this._classNameToArgs = serializedStylesheet?.classNameToArgs ?? this._classNameToArgs; - this._counter = serializedStylesheet?.counter ?? this._counter; + if (this._config.injectionMode !== InjectionMode.unstable_constructibleStylesheet) { + this._styleCounter = serializedStylesheet?.counter ?? this._styleCounter; + } this._keyToClassName = this._config.classNameCache ?? serializedStylesheet?.keyToClassName ?? this._keyToClassName; this._preservedRules = serializedStylesheet?.preservedRules ?? this._preservedRules; this._rules = serializedStylesheet?.rules ?? this._rules; @@ -333,6 +353,7 @@ export class Stylesheet { const { namespace } = this._config; const prefix = displayName || this._config.defaultPrefix; + // return `${namespace ? namespace + '-' : ''}${prefix}-${this._counter++}`; return `${namespace ? namespace + '-' : ''}${prefix}-${this._counter++}`; } @@ -467,6 +488,20 @@ export class Stylesheet { this._keyToClassName = {}; } + private get _counter(): number { + return this._config.injectionMode === InjectionMode.unstable_constructibleStylesheet + ? constructableStyleSheetCounter + : this._styleCounter; + } + + private set _counter(value: number) { + if (this._config.injectionMode === InjectionMode.unstable_constructibleStylesheet) { + constructableStyleSheetCounter = value; + } else { + this._styleCounter = value; + } + } + private _getStyleElement(): HTMLStyleElement | undefined { if (!this._styleElement && typeof document !== 'undefined') { this._styleElement = this._createStyleElement(); diff --git a/packages/merge-styles/src/mergeStyleSets.ts b/packages/merge-styles/src/mergeStyleSets.ts index 53e13a3869e34..1b23598b324d6 100644 --- a/packages/merge-styles/src/mergeStyleSets.ts +++ b/packages/merge-styles/src/mergeStyleSets.ts @@ -79,6 +79,11 @@ export function mergeStyleSets( */ export function mergeStyleSets(...styleSets: Array): IProcessedStyleSet; +export function mergeStyleSets( + stylesheetKey: string, + ...styleSets: Array +): IProcessedStyleSet; + /** * Takes in one or more style set objects, each consisting of a set of areas, * each which will produce a class name. Using this is analogous to calling @@ -88,10 +93,17 @@ export function mergeStyleSets(...styleSets: Array): IProcessedStyleSet { - const last = styleSets[styleSets.length - 1]; - const { stylesheetKey } = last; - if (stylesheetKey) { - styleSets.pop(); + // const last = styleSets[styleSets.length - 1]; + // const { stylesheetKey } = last; + // if (stylesheetKey) { + // styleSets.pop(); + // } + + let stylesheetKey = undefined; + let sets = styleSets; + if (typeof styleSets[0] === 'string') { + stylesheetKey = styleSets[0]; + sets = styleSets.slice(1); } return mergeCssSets(styleSets as any, getStyleOptions(), stylesheetKey); @@ -195,7 +207,7 @@ export function mergeCssSets( export function mergeCssSets( styleSets: Array, options?: IStyleOptions, - stylesheetKey?: string, + stylesheetKey: string = '__global__', ): IProcessedStyleSet { const classNameSet: IProcessedStyleSet = { subComponentStyles: {} }; diff --git a/packages/merge-styles/src/mergeStyles.ts b/packages/merge-styles/src/mergeStyles.ts index ff87090fed529..c6c39e28e97d5 100644 --- a/packages/merge-styles/src/mergeStyles.ts +++ b/packages/merge-styles/src/mergeStyles.ts @@ -22,13 +22,13 @@ export function mergeStyles(...args: (IStyle | IStyleBaseArray | false | null | export function mergeCss( args: (IStyle | IStyleBaseArray | false | null | undefined) | (IStyle | IStyleBaseArray | false | null | undefined)[], options?: IStyleOptions, - stylesheetKey?: string, + stylesheetKey: string = '__global__', ): string { const styleArgs = args instanceof Array ? args : [args]; const { classes, objects } = extractStyleParts(stylesheetKey, styleArgs); if (objects.length) { - classes.push(styleToClassName(options || {}, objects)); + classes.push(styleToClassName(stylesheetKey, options || {}, objects)); } return classes.join(' '); diff --git a/packages/merge-styles/src/styleToClassName.test.ts b/packages/merge-styles/src/styleToClassName.test.ts index d0e8e33eb845f..6b0cdbb428e06 100644 --- a/packages/merge-styles/src/styleToClassName.test.ts +++ b/packages/merge-styles/src/styleToClassName.test.ts @@ -12,17 +12,17 @@ describe('styleToClassName', () => { }); it('can register classes and avoid re-registering', () => { - let className = styleToClassName({}, { background: 'red' }); + let className = styleToClassName('__globalTest__', {}, { background: 'red' }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.css-0{background:red;}'); - className = styleToClassName({}, { background: 'red' }); + className = styleToClassName('__globalTest__', {}, { background: 'red' }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.css-0{background:red;}'); - className = styleToClassName({}, { background: 'green' }); + className = styleToClassName('__globalTest__', {}, { background: 'green' }); expect(className).toEqual('css-1'); expect(_stylesheet.getRules()).toEqual('.css-0{background:red;}.css-1{background:green;}'); @@ -30,6 +30,7 @@ describe('styleToClassName', () => { it('can have child selectors', () => { styleToClassName( + '__globalTest__', {}, { selectors: { @@ -43,6 +44,7 @@ describe('styleToClassName', () => { it('can have child selectors without the selectors wrapper', () => { styleToClassName( + '__globalTest__', {}, { '.foo': { background: 'red' }, @@ -54,6 +56,7 @@ describe('styleToClassName', () => { it('can have child selectors with comma', () => { styleToClassName( + '__globalTest__', {}, { selectors: { @@ -67,6 +70,7 @@ describe('styleToClassName', () => { it('can have child selectors with comma without the selectors wrapper', () => { styleToClassName( + '__globalTest__', {}, { '.foo, .bar': { background: 'red' }, @@ -78,6 +82,7 @@ describe('styleToClassName', () => { it('can have child selectors with comma with pseudo selectors', () => { styleToClassName( + '__globalTest__', {}, { selectors: { @@ -91,6 +96,7 @@ describe('styleToClassName', () => { it('can have child selectors with comma with pseudo selectors', () => { styleToClassName( + '__globalTest__', {}, { ':hover, :active': { background: 'red' }, @@ -102,6 +108,7 @@ describe('styleToClassName', () => { it('can have child selectors with comma with @media query', () => { styleToClassName( + '__globalTest__', {}, { selectors: { @@ -119,6 +126,7 @@ describe('styleToClassName', () => { it('can have child selectors with comma with @media query', () => { styleToClassName( + '__globalTest__', {}, { '@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none)': { @@ -134,6 +142,7 @@ describe('styleToClassName', () => { it('can have same element class selectors', () => { styleToClassName( + '__globalTest__', {}, { selectors: { @@ -147,6 +156,7 @@ describe('styleToClassName', () => { it('can have same element class selectors without the selectors wrapper', () => { styleToClassName( + '__globalTest__', {}, { '&.foo': [{ background: 'red' }], @@ -158,6 +168,7 @@ describe('styleToClassName', () => { it('can register pseudo selectors', () => { const className = styleToClassName( + '__globalTest__', {}, { selectors: { @@ -172,6 +183,7 @@ describe('styleToClassName', () => { it('can register pseudo selectors without the selectors wrapper', () => { const className = styleToClassName( + '__globalTest__', {}, { ':hover': { background: 'red' }, @@ -184,6 +196,7 @@ describe('styleToClassName', () => { it('can register parent and sibling selectors', () => { const className = styleToClassName( + '__globalTest__', {}, { selectors: { @@ -199,6 +212,7 @@ describe('styleToClassName', () => { it('can merge rules', () => { let className = styleToClassName( + '__globalTest__', {}, null, false, @@ -210,53 +224,56 @@ describe('styleToClassName', () => { expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.css-0{background-color:green;color:white;}'); - className = styleToClassName({}, { backgroundColor: 'green', color: 'white' }); + className = styleToClassName('__globalTest__', {}, { backgroundColor: 'green', color: 'white' }); expect(className).toEqual('css-0'); }); it('returns blank string with no input', () => { - expect(styleToClassName({})).toEqual(''); + expect(styleToClassName('__globalTest__', {})).toEqual(''); }); it('does not emit a rule which has an undefined value', () => { - expect(styleToClassName({}, { fontFamily: undefined })).toEqual(''); + expect(styleToClassName('__globalTest__', {}, { fontFamily: undefined })).toEqual(''); expect(_stylesheet.getRules()).toEqual(''); }); it('returns the same class name for a rule that only has a displayName', () => { - expect(styleToClassName({}, { displayName: 'foo' })).toEqual('foo-0'); - expect(styleToClassName({}, { displayName: 'foo' })).toEqual('foo-0'); + expect(styleToClassName('__globalTest__', {}, { displayName: 'foo' })).toEqual('foo-0'); + expect(styleToClassName('__globalTest__', {}, { displayName: 'foo' })).toEqual('foo-0'); expect(_stylesheet.getRules()).toEqual(''); }); it('can preserve displayName in names', () => { - expect(styleToClassName({}, { displayName: 'DisplayName', background: 'red' })).toEqual('DisplayName-0'); + expect(styleToClassName('__globalTest__', {}, { displayName: 'DisplayName', background: 'red' })).toEqual( + 'DisplayName-0', + ); expect(_stylesheet.getRules()).toEqual('.DisplayName-0{background:red;}'); }); it('can flip rtl and add units', () => { - styleToClassName({ rtl: true }, { left: 40 }); + styleToClassName('__globalTest__', { rtl: true }, { left: 40 }); expect(_stylesheet.getRules()).toEqual('.css-0{right:40px;}'); }); it('can prefix webkit specific things', () => { - styleToClassName({}, { WebkitFontSmoothing: 'none' }); + styleToClassName('__globalTest__', {}, { WebkitFontSmoothing: 'none' }); expect(_stylesheet.getRules()).toEqual('.css-0{-webkit-font-smoothing:none;}'); }); // TODO: It may not be valid to pass a previously registered rule into styleToClassName // since mergeStyles/mergeStyleSets should probably do this in the resolution code. it('can expand previously defined rules', () => { - const className = styleToClassName({}, { background: 'red' }); - const newClassName = styleToClassName({}, className, { color: 'white' }); + const className = styleToClassName('__globalTest__', {}, { background: 'red' }); + const newClassName = styleToClassName('__globalTest__', {}, className, { color: 'white' }); expect(newClassName).toEqual('css-1'); expect(_stylesheet.getRules()).toEqual('.css-0{background:red;}.css-1{background:red;color:white;}'); }); it('can expand previously defined rules in selectors', () => { - const className = styleToClassName({}, { background: 'red' }); + const className = styleToClassName('__globalTest__', {}, { background: 'red' }); const newClassName = styleToClassName( + '__globalTest__', {}, { selectors: { @@ -271,6 +288,7 @@ describe('styleToClassName', () => { it('can register global selectors', () => { const className = styleToClassName( + '__globalTest__', {}, { selectors: { @@ -285,6 +303,7 @@ describe('styleToClassName', () => { it('can register global selectors for a parent', () => { const className = styleToClassName( + '__globalTest__', {}, { selectors: { @@ -299,6 +318,7 @@ describe('styleToClassName', () => { it('can register global selectors hover parent for a selector', () => { const className = styleToClassName( + '__globalTest__', {}, { selectors: { @@ -313,6 +333,7 @@ describe('styleToClassName', () => { it('can register multiple selectors within a global wrapper', () => { const className = styleToClassName( + '__globalTest__', {}, { selectors: { @@ -327,6 +348,7 @@ describe('styleToClassName', () => { it('can register multiple selectors wrapped within a global wrappers', () => { const className = styleToClassName( + '__globalTest__', {}, { selectors: { @@ -341,6 +363,7 @@ describe('styleToClassName', () => { it('can process a ":global(.class3, button)" selector', () => { const className = styleToClassName( + '__globalTest__', {}, { selectors: { @@ -355,6 +378,7 @@ describe('styleToClassName', () => { it('can process a ":global(.class3 button)" selector', () => { const className = styleToClassName( + '__globalTest__', {}, { selectors: { @@ -369,6 +393,7 @@ describe('styleToClassName', () => { it('can process a "button:focus, :global(.class1, .class2, .class3)" selector', () => { const className = styleToClassName( + '__globalTest__', {}, { selectors: { @@ -385,6 +410,7 @@ describe('styleToClassName', () => { it('can process a complex multiple global selector', () => { const className = styleToClassName( + '__globalTest__', {}, { selectors: { @@ -401,12 +427,13 @@ describe('styleToClassName', () => { }); it('can expand an array of rules', () => { - styleToClassName({}, [{ background: 'red' }, { background: 'white' }]); + styleToClassName('__globalTest__', {}, [{ background: 'red' }, { background: 'white' }]); expect(_stylesheet.getRules()).toEqual('.css-0{background:white;}'); }); it('can expand increased specificity rules', () => { styleToClassName( + '__globalTest__', {}, { selectors: { @@ -422,6 +449,7 @@ describe('styleToClassName', () => { it('can apply media queries', () => { styleToClassName( + '__globalTest__', {}, { background: 'blue', @@ -451,6 +479,7 @@ describe('styleToClassName', () => { it('can apply @support queries', () => { styleToClassName( + '__globalTest__', {}, { selectors: { @@ -466,6 +495,7 @@ describe('styleToClassName', () => { it('ignores undefined property values', () => { styleToClassName( + '__globalTest__', {}, { background: 'red', @@ -484,14 +514,14 @@ describe('styleToClassName with specificityMultiplier', () => { }); it('can repeat classname', () => { - const className = styleToClassName(options, { background: 'red' }); + const className = styleToClassName('__globalTest__', options, { background: 'red' }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.css-0.css-0{background:red;}'); }); it('can repeat classname when have child selectors', () => { - styleToClassName(options, { + styleToClassName('__globalTest__', options, { selectors: { '.foo': { background: 'red' }, }, @@ -501,7 +531,7 @@ describe('styleToClassName with specificityMultiplier', () => { }); it('can repeat classname when have child selectors with comma', () => { - styleToClassName(options, { + styleToClassName('__globalTest__', options, { selectors: { '.foo, .bar': { background: 'red' }, }, @@ -511,7 +541,7 @@ describe('styleToClassName with specificityMultiplier', () => { }); it('can repeat classname when have child selectors with comma with pseudo selectors', () => { - styleToClassName(options, { + styleToClassName('__globalTest__', options, { selectors: { ':hover, :active': { background: 'red' }, }, @@ -521,7 +551,7 @@ describe('styleToClassName with specificityMultiplier', () => { }); it('can repeat classname when have child selectors with comma with @media query', () => { - styleToClassName(options, { + styleToClassName('__globalTest__', options, { selectors: { '@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none)': { background: 'red', @@ -535,7 +565,7 @@ describe('styleToClassName with specificityMultiplier', () => { }); it('do not repeat classname when have global selector', () => { - const className = styleToClassName(options, { + const className = styleToClassName('__globalTest__', options, { selectors: { ':global(.class1)': { background: 'red' }, }, @@ -547,6 +577,7 @@ describe('styleToClassName with specificityMultiplier', () => { it('handles numeric 0 in props with shorthand syntax (margin, padding)', () => { styleToClassName( + '__globalTest__', {}, { margin: 0, @@ -560,6 +591,7 @@ describe('styleToClassName with specificityMultiplier', () => { it('handles calc(...) in props with shorthand syntax (margin, padding)', () => { styleToClassName( + '__globalTest__', {}, { padding: 'calc(24px / 2) 0', @@ -583,6 +615,7 @@ describe('styleToClassName with specificityMultiplier', () => { it('handles !important in props with shorthand syntax (margin, padding)', () => { styleToClassName( + '__globalTest__', {}, { padding: '42px !important', diff --git a/packages/merge-styles/src/styleToClassName.ts b/packages/merge-styles/src/styleToClassName.ts index 39f9fd7195350..66345ecc9f060 100644 --- a/packages/merge-styles/src/styleToClassName.ts +++ b/packages/merge-styles/src/styleToClassName.ts @@ -244,7 +244,7 @@ export interface IRegistration { } export function styleToRegistration( - stylesheetKey: string, + stylesheetKey: string = '__global__', options: IStyleOptions, ...args: IStyle[] ): IRegistration | undefined { @@ -252,7 +252,7 @@ export function styleToRegistration( const key = getKeyForRules(options, rules); if (key) { - const stylesheet = Stylesheet.getInstance(stylesheetKey ?? '__global__'); + const stylesheet = Stylesheet.getInstance(stylesheetKey); const registration: Partial = { className: stylesheet.classNameFromKey(key), key, @@ -306,8 +306,8 @@ export function applyRegistration( } } -export function styleToClassName(options: IStyleOptions, ...args: IStyle[]): string { - const registration = styleToRegistration(options, ...args); +export function styleToClassName(stylesheetKey: string, options: IStyleOptions, ...args: IStyle[]): string { + const registration = styleToRegistration(stylesheetKey, options, ...args); if (registration) { applyRegistration(registration, options.specificityMultiplier); diff --git a/packages/react-examples/src/react/Button/Button.Default.Example.tsx b/packages/react-examples/src/react/Button/Button.Default.Example.tsx index 38376b5f967c8..c24d80a6e3148 100644 --- a/packages/react-examples/src/react/Button/Button.Default.Example.tsx +++ b/packages/react-examples/src/react/Button/Button.Default.Example.tsx @@ -1,7 +1,11 @@ import * as React from 'react'; import { Stack, IStackTokens, SpinButton } from '@fluentui/react'; import { DefaultButton, PrimaryButton } from '@fluentui/react/lib/Button'; -import { MergeStylesProvider_unstable, useAdoptedStylesheet_unstable } from '@fluentui/utilities'; +import { + MergeStylesRootProvider_unstable, + MergeStylesShadowRootProvider_unstable, + useAdoptedStylesheet_unstable, +} from '@fluentui/utilities'; // import { createProxy as _createProxy, default as _root } from 'react-shadow'; // eslint-disable-next-line import root from 'react-shadow'; @@ -40,32 +44,42 @@ export const ButtonDefaultExample: React.FunctionComponent const [shadowRootEl, setShadowRootEl] = React.useState(null); const setter = val => { + console.log('setter'); setShadowRootEl(val); }; return ( - - - - - - Hmmm - - - - + + + + + + + + Hmmm + + + + + ); }; diff --git a/packages/react/etc/react.api.md b/packages/react/etc/react.api.md index 705811e613ba9..adc6d91f674eb 100644 --- a/packages/react/etc/react.api.md +++ b/packages/react/etc/react.api.md @@ -2720,6 +2720,8 @@ export interface IButtonProps extends React_2.AllHTMLAttributes; +export const MergeStylesRootProvider_unstable: React_2.FC; + +// Warning: (ae-forgotten-export) The symbol "MergeStylesShadowRootProviderProps" needs to be exported by the entry point index.d.ts +// +// @public +export const MergeStylesShadowRootProvider_unstable: React_2.FC; // @public export function modalize(target: HTMLElement): () => void; diff --git a/packages/utilities/src/customizations/customizable.tsx b/packages/utilities/src/customizations/customizable.tsx index 6aeb025dc1b94..47f62edd819d6 100644 --- a/packages/utilities/src/customizations/customizable.tsx +++ b/packages/utilities/src/customizations/customizable.tsx @@ -4,7 +4,7 @@ import { hoistStatics } from '../hoistStatics'; import { CustomizerContext } from './CustomizerContext'; import { concatStyleSets } from '@fluentui/merge-styles'; import type { ICustomizerContext } from './CustomizerContext'; -import { MergeStylesContextConsumer } from '../shadowDom/MergeStylesContext/MergeStylesContext'; +import { MergeStylesShadowRootConsumer } from '../shadowDom/MergeStylesShadowRootContext'; export function customizable( scope: string, @@ -36,7 +36,7 @@ export function customizable( public render(): JSX.Element { return ( - + {(context: ICustomizerContext) => { const defaultProps = Customizations.getSettings(fields, scope, context.customizations); @@ -69,7 +69,7 @@ export function customizable( return ; }} - + ); } diff --git a/packages/utilities/src/index.ts b/packages/utilities/src/index.ts index 03a561be76423..02749300b5c67 100644 --- a/packages/utilities/src/index.ts +++ b/packages/utilities/src/index.ts @@ -236,7 +236,14 @@ import './version'; // eslint-disable-next-line deprecation/deprecation export type { IStyleFunctionOrObject, Omit } from '@fluentui/merge-styles'; +// export { +// MergeStylesProvider_unstable, +// useAdoptedStylesheet_unstable, +// } from './shadowDom/MergeStylesContext/MergeStylesContext'; + export { - MergeStylesProvider_unstable, + MergeStylesShadowRootProvider_unstable, useAdoptedStylesheet_unstable, -} from './shadowDom/MergeStylesContext/MergeStylesContext'; +} from './shadowDom/MergeStylesShadowRootContext'; + +export { MergeStylesRootProvider_unstable } from './shadowDom/MergeStylesRootContext'; diff --git a/packages/utilities/src/shadowDom/MergeStylesContext/MergeStylesContext.tsx b/packages/utilities/src/shadowDom/MergeStylesContext/MergeStylesContext.tsx deleted file mode 100644 index d0b81228f2f58..0000000000000 --- a/packages/utilities/src/shadowDom/MergeStylesContext/MergeStylesContext.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import * as React from 'react'; -import { EventMap } from '@fluentui/merge-styles'; - -declare global { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface Window { - __mergeStylesAdoptedStyleSheets__?: Map; - } -} - -/** - * NOTE: This API is unstable and subject to breaking change or removal without notice. - */ -export type MergeStylesContextValue = { - stylesheets: Map; - shadowRoot?: ShadowRoot | null; -}; - -const MergeStylesContext = React.createContext({ - stylesheets: new Map(), -}); - -/** - * NOTE: This API is unstable and subject to breaking change or removal without notice. - */ -export type MergeStylesProviderProps = { - shadowRoot?: ShadowRoot | null; -}; - -/** - * NOTE: This API is unstable and subject to breaking change or removal without notice. - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export const MergeStylesProvider_unstable: React.FC = ({ shadowRoot, ...props }) => { - const ctx = useMergeStylesContext_unstable(); - - const value = React.useMemo(() => { - return { - stylesheets: ctx.stylesheets, - shadowRoot, - }; - }, [ctx, shadowRoot]); - - return ; -}; - -export type MergeStylesContextConsumerProps = { - stylesheetKey?: string; -}; - -export const MergeStylesContextConsumer: React.FC = ({ stylesheetKey, children }) => { - useAdoptedStylesheet_unstable(stylesheetKey ?? '__global__'); - - return <>{children}; -}; - -/** - * NOTE: This API is unstable and subject to breaking change or removal without notice. - */ -export const useMergeStylesContext_unstable = () => { - return React.useContext(MergeStylesContext); -}; - -// type AddSheetCallback = ({ key: string, sheet: CSSStyleSheet }) => void; - -/** - * NOTE: This API is unstable and subject to breaking change or removal without notice. - */ -export const useAdoptedStylesheet_unstable = (stylesheetKey: string): void => { - const ctx = useMergeStylesContext_unstable(); - - if (ctx.shadowRoot && !ctx.stylesheets.has(stylesheetKey)) { - const stylesheet = window.__mergeStylesAdoptedStyleSheets__?.get(stylesheetKey); - if (stylesheet) { - ctx.stylesheets.set(stylesheetKey, stylesheet); - // eslint-disable-next-line - // @ts-ignore types not working for some reason - ctx.shadowRoot.adoptedStyleSheets = [...ctx.shadowRoot.adoptedStyleSheets, stylesheet]; - } else { - if (!window.__mergeStylesAdoptedStyleSheets__) { - // eslint-disable-next-line - // @ts-ignore - window.__mergeStylesAdoptedStyleSheets__ = new EventMap(); - } - // eslint-disable-next-line - // @ts-ignore - window.__mergeStylesAdoptedStyleSheets__.on('add-sheet', ({ key, sheet }) => { - if (!ctx.stylesheets.has(key)) { - ctx.stylesheets.set(key, sheet); - // eslint-disable-next-line - // @ts-ignore types not working for some reason - const adoptee = sheet.getAdoptableStyleSheet(); - if (adoptee) { - ctx.shadowRoot.adoptedStyleSheets = [...ctx.shadowRoot.adoptedStyleSheets, adoptee]; - } - } - }); - } - } -}; diff --git a/packages/utilities/src/shadowDom/MergeStylesRootContext.tsx b/packages/utilities/src/shadowDom/MergeStylesRootContext.tsx new file mode 100644 index 0000000000000..f5b6ee4045e27 --- /dev/null +++ b/packages/utilities/src/shadowDom/MergeStylesRootContext.tsx @@ -0,0 +1,113 @@ +import * as React from 'react'; +import { EventMap, Stylesheet } from '@fluentui/merge-styles'; +import { getWindow } from '../dom'; + +declare global { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface Window { + __mergeStylesAdoptedStyleSheets__?: EventMap; + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + interface DocumentOrShadowRoot { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/adoptedStyleSheets) */ + adoptedStyleSheets: CSSStyleSheet[]; + } +} + +/** + * NOTE: This API is unstable and subject to breaking change or removal without notice. + */ +export type MergeStylesRootContextValue = { + stylesheets: Map; +}; + +const MergeStylesRootContext = React.createContext({ + stylesheets: new Map(), +}); + +/** + * NOTE: This API is unstable and subject to breaking change or removal without notice. + */ +export type MergeStylesRootProviderProps = { + stylesheets?: Map; + window?: Window; +}; + +/** + * NOTE: This API is unstable and subject to breaking change or removal without notice. + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export const MergeStylesRootProvider_unstable: React.FC = ({ + stylesheets: userSheets, + window: userWindow, + ...props +}) => { + const win = userWindow ?? getWindow(); + const [stylesheets, setStylesheets] = React.useState>(() => userSheets ?? new Map()); + + const sheetHandler = React.useCallback(({ key, sheet }) => { + setStylesheets(prev => { + const next = new Map(prev); + next.set(key, sheet); + return next; + }); + }, []); + + // Udapte stylesheets based on user style sheet changes + React.useEffect(() => { + setStylesheets(userSheets ?? new Map()); + }, [userSheets]); + + // Wire up listener for adopted stylesheets + React.useEffect(() => { + if (!win) { + return; + } + + if (!win.__mergeStylesAdoptedStyleSheets__) { + win.__mergeStylesAdoptedStyleSheets__ = new EventMap(); + } + + win.__mergeStylesAdoptedStyleSheets__.on('add-sheet', sheetHandler); + + return () => { + win?.__mergeStylesAdoptedStyleSheets__ && win.__mergeStylesAdoptedStyleSheets__.off('add-sheet', sheetHandler); + }; + }, [win, sheetHandler]); + + // Read stylesheets from window on mount + React.useEffect(() => { + if (!win || !win.__mergeStylesAdoptedStyleSheets__) { + return; + } + + let changed = false; + const next = new Map(stylesheets); + win.__mergeStylesAdoptedStyleSheets__.forEach((sheet, key) => { + next.set(key, sheet); + changed = true; + }); + + if (changed) { + setStylesheets(next); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const value = React.useMemo(() => { + return { + stylesheets, + }; + }, [stylesheets]); + + return ; +}; + +/** + * NOTE: This API is unstable and subject to breaking change or removal without notice. + */ +export const useMergeStylesRootStylesheets_unstable = () => { + return React.useContext(MergeStylesRootContext).stylesheets; +}; diff --git a/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx b/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx new file mode 100644 index 0000000000000..3d2fd13a6122b --- /dev/null +++ b/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx @@ -0,0 +1,81 @@ +import * as React from 'react'; +import { useMergeStylesRootStylesheets_unstable } from './MergeStylesRootContext'; + +/** + * NOTE: This API is unstable and subject to breaking change or removal without notice. + */ +export type MergeStylesShadowRootContextValue = { + stylesheets: Map; + shadowRoot?: ShadowRoot | null; +}; + +const MergeStylesShadowRootContext = React.createContext({ + stylesheets: new Map(), +}); + +/** + * NOTE: This API is unstable and subject to breaking change or removal without notice. + */ +export type MergeStylesShadowRootProviderProps = { + shadowRoot?: ShadowRoot | null; +}; + +/** + * NOTE: This API is unstable and subject to breaking change or removal without notice. + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export const MergeStylesShadowRootProvider_unstable: React.FC = ({ + shadowRoot, + ...props +}) => { + const ctx = useMergeStylesShadowRootContext_unstable(); + + const value = React.useMemo(() => { + return { + stylesheets: ctx.stylesheets, + shadowRoot, + }; + }, [ctx, shadowRoot]); + + return ; +}; + +export type MergeStylesContextConsumerProps = { + stylesheetKey?: string; +}; + +export const MergeStylesShadowRootConsumer: React.FC = ({ + stylesheetKey, + children, +}) => { + // useAdoptedStylesheet_unstable('__global__'); + useAdoptedStylesheet_unstable(stylesheetKey ?? '__global__'); + + return <>{children}; +}; + +// const GlobalStyles: React.FC = props => { +// useAdoptedStylesheet_unstable('__global__'); +// return null; +// }; + +/** + * NOTE: This API is unstable and subject to breaking change or removal without notice. + */ +export const useAdoptedStylesheet_unstable = (stylesheetKey: string): void => { + const shadowCtx = useMergeStylesShadowRootContext_unstable(); + const rootMergeStyles = useMergeStylesRootStylesheets_unstable(); + + if (shadowCtx.shadowRoot && !shadowCtx.stylesheets.has(stylesheetKey)) { + const stylesheet = rootMergeStyles.get(stylesheetKey); + const adoptableStyleSheet = stylesheet?.getAdoptableStyleSheet(); + if (adoptableStyleSheet) { + shadowCtx.stylesheets.set(stylesheetKey, adoptableStyleSheet); + shadowCtx.shadowRoot.adoptedStyleSheets = [...shadowCtx.shadowRoot.adoptedStyleSheets, adoptableStyleSheet]; + } + } +}; + +export const useMergeStylesShadowRootContext_unstable = () => { + return React.useContext(MergeStylesShadowRootContext); +}; diff --git a/packages/utilities/src/styled.tsx b/packages/utilities/src/styled.tsx index 93b713ba8ee7a..fde7a5380842c 100644 --- a/packages/utilities/src/styled.tsx +++ b/packages/utilities/src/styled.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { concatStyleSetsWithProps } from '@fluentui/merge-styles'; -import { useAdoptedStylesheet_unstable } from './shadowDom/MergeStylesContext/MergeStylesContext'; +import { useAdoptedStylesheet_unstable } from './shadowDom/MergeStylesShadowRootContext'; import { useCustomizationSettings } from './customizations/useCustomizationSettings'; import type { IStyleSet, IStyleFunctionOrObject } from '@fluentui/merge-styles'; diff --git a/yarn.lock b/yarn.lock index 10ba4fbbcdad5..7d8bf8fec08ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6742,11 +6742,6 @@ resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.2.tgz#b695ff674e8216efa632a3d36ad51ae9843380c0" integrity sha512-+R0juSseERyoPvnBQ/cZih6bpF7IpCXlWbHRoCRzYzqpz6gWHOgf8o4MOEf6KBVuOyqU+gCNLkCWVIJAro8XyQ== -"@xobotyi/scrollbar-width@^1.9.5": - version "1.9.5" - resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" - integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ== - "@xstate/react@^1.5.1": version "1.6.3" resolved "https://registry.yarnpkg.com/@xstate/react/-/react-1.6.3.tgz#706f3beb7bc5879a78088985c8fd43b9dab7f725" @@ -10242,13 +10237,6 @@ css-in-js-utils@^3.0.0: dependencies: hyphenate-style-name "^1.0.2" -css-in-js-utils@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz#640ae6a33646d401fc720c54fc61c42cd76ae2bb" - integrity sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A== - dependencies: - hyphenate-style-name "^1.0.3" - css-loader@5.0.1, css-loader@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.0.1.tgz#9e4de0d6636a6266a585bd0900b422c85539d25f" @@ -10316,14 +10304,6 @@ css-to-react-native@^3.0.0: css-color-keywords "^1.0.0" postcss-value-parser "^4.0.2" -css-tree@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - css-what@2.1: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" @@ -12810,11 +12790,6 @@ fast-loops@^1.0.0, fast-loops@^1.0.1: resolved "https://registry.yarnpkg.com/fast-loops/-/fast-loops-1.1.2.tgz#2ee75ba943a08a9b1ffdf9b49f133322c99bda68" integrity sha512-ql8BgnHFryLogmmzR5O3uobe+3Zzaq6h6MWn/VtAyL9OXb51r5PSTbCm9f56fvEvMWWGjbdkr4xyhT0/vLJkKw== -fast-loops@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fast-loops/-/fast-loops-1.1.3.tgz#ce96adb86d07e7bf9b4822ab9c6fac9964981f75" - integrity sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g== - fast-png@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/fast-png/-/fast-png-6.1.0.tgz#c0abd3015346e16752acbe4ea74f1f0170b55b9e" @@ -12824,21 +12799,11 @@ fast-png@^6.1.0: iobuffer "^5.0.4" pako "^2.0.4" -fast-shallow-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz#d4dcaf6472440dcefa6f88b98e3251e27f25628b" - integrity sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw== - fastest-levenshtein@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== -fastest-stable-stringify@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz#3757a6774f6ec8de40c4e86ec28ea02417214c76" - integrity sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q== - fastq@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2" @@ -15037,11 +15002,6 @@ hyphenate-style-name@^1.0.2: resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ== -hyphenate-style-name@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" - integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== - iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -15271,14 +15231,6 @@ inline-style-parser@0.1.1: resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== -inline-style-prefixer@^6.0.0: - version "6.0.4" - resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-6.0.4.tgz#4290ed453ab0e4441583284ad86e41ad88384f44" - integrity sha512-FwXmZC2zbeeS7NzGjJ6pAiqRhXR0ugUShSNb6GApMl6da0/XGc4MOJsoWAywia52EEWbXNSy0pzkwz/+Y+swSg== - dependencies: - css-in-js-utils "^3.1.0" - fast-loops "^1.1.3" - inquirer@^7.0.0: version "7.3.3" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" @@ -16692,11 +16644,6 @@ jpeg-js@^0.4.4: resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg== -js-cookie@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" - integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== - js-message@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/js-message/-/js-message-1.0.5.tgz#2300d24b1af08e89dd095bc1a4c9c9cfcb892d15" @@ -18371,11 +18318,6 @@ mdast-util-to-string@^1.0.0: resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - mdurl@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" @@ -18997,20 +18939,6 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== -nano-css@^5.3.1: - version "5.3.5" - resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.3.5.tgz#3075ea29ffdeb0c7cb6d25edb21d8f7fa8e8fe8e" - integrity sha512-vSB9X12bbNu4ALBu7nigJgRViZ6ja3OU7CeuiV1zMIbXOdmkLahgtPmh3GBOlDxbKY0CitqlPdOReGlBLSp+yg== - dependencies: - css-tree "^1.1.2" - csstype "^3.0.6" - fastest-stable-stringify "^2.0.2" - inline-style-prefixer "^6.0.0" - rtl-css-js "^1.14.0" - sourcemap-codec "^1.4.8" - stacktrace-js "^2.0.2" - stylis "^4.0.6" - nanoid@^3.1.23, nanoid@^3.3.1: version "3.3.6" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" @@ -21838,31 +21766,6 @@ react-transition-group@^4.3.0, react-transition-group@^4.4.1: loose-envify "^1.4.0" prop-types "^15.6.2" -react-universal-interface@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b" - integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw== - -react-use@^17.0.0: - version "17.4.0" - resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.4.0.tgz#cefef258b0a6c534a5c8021c2528ac6e1a4cdc6d" - integrity sha512-TgbNTCA33Wl7xzIJegn1HndB4qTS9u03QUwyNycUnXaweZkE4Kq2SB+Yoxx8qbshkZGYBDvUXbXWRUmQDcZZ/Q== - dependencies: - "@types/js-cookie" "^2.2.6" - "@xobotyi/scrollbar-width" "^1.9.5" - copy-to-clipboard "^3.3.1" - fast-deep-equal "^3.1.3" - fast-shallow-equal "^1.0.0" - js-cookie "^2.2.1" - nano-css "^5.3.1" - react-universal-interface "^0.6.2" - resize-observer-polyfill "^1.5.1" - screenfull "^5.1.0" - set-harmonic-interval "^1.0.1" - throttle-debounce "^3.0.1" - ts-easing "^0.2.0" - tslib "^2.1.0" - react-virtualized@^9.21.1: version "9.21.2" resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.2.tgz#02e6df65c1e020c8dbf574ec4ce971652afca84e" @@ -22531,11 +22434,6 @@ reselect@^4.0.0: resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.7.tgz#56480d9ff3d3188970ee2b76527bd94a95567a42" integrity sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A== -resize-observer-polyfill@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" - integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== - resolve-alpn@^1.0.0, resolve-alpn@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" @@ -22832,13 +22730,6 @@ rtl-css-js@^1.1.3, rtl-css-js@^1.16.1: dependencies: "@babel/runtime" "^7.1.2" -rtl-css-js@^1.14.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.16.1.tgz#4b48b4354b0ff917a30488d95100fbf7219a3e80" - integrity sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg== - dependencies: - "@babel/runtime" "^7.1.2" - run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -23233,11 +23124,6 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -set-harmonic-interval@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249" - integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g== - set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -23590,11 +23476,6 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= -source-map@0.5.6: - version "0.5.6" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" - integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA== - source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.6, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -23617,7 +23498,7 @@ source-map@~0.2.0: dependencies: amdefine ">=0.0.4" -sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8: +sourcemap-codec@^1.4.4: version "1.4.8" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== @@ -23753,13 +23634,6 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -stack-generator@^2.0.5: - version "2.0.10" - resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.10.tgz#8ae171e985ed62287d4f1ed55a1633b3fb53bb4d" - integrity sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ== - dependencies: - stackframe "^1.3.4" - stack-trace@0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" @@ -23777,28 +23651,6 @@ stackframe@^1.1.1: resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA== -stackframe@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" - integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== - -stacktrace-gps@^3.0.4: - version "3.1.2" - resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz#0c40b24a9b119b20da4525c398795338966a2fb0" - integrity sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ== - dependencies: - source-map "0.5.6" - stackframe "^1.3.4" - -stacktrace-js@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b" - integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg== - dependencies: - error-stack-parser "^2.0.6" - stack-generator "^2.0.5" - stacktrace-gps "^3.0.4" - state-toggle@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" @@ -25006,11 +24858,6 @@ ts-dedent@^2.0.0: resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5" integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== -ts-easing@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec" - integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ== - ts-invariant@^0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.10.3.tgz#3e048ff96e91459ffca01304dbc7f61c1f642f6c" From fdb022774f7c89b58c4e605b4986586f5462e235 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 10 Aug 2023 19:42:56 +0000 Subject: [PATCH 005/198] support styling in both ligth and shadow dom --- packages/merge-styles/src/Stylesheet.ts | 158 +++++++++++++---- .../merge-styles/src/extractStyleParts.ts | 5 +- packages/merge-styles/src/mergeStyleSets.ts | 39 ++--- packages/merge-styles/src/mergeStyles.ts | 4 +- packages/merge-styles/src/styleToClassName.ts | 15 +- .../react/Button/Button.Default.Example.tsx | 90 ++++------ .../Button/BaseButton.classNames.ts | 164 ++++++++++-------- .../src/components/Button/BaseButton.tsx | 1 + .../src/components/Button/Button.types.ts | 2 + packages/utilities/src/classNamesFunction.ts | 5 + .../src/customizations/customizable.tsx | 61 ++++--- packages/utilities/src/index.ts | 5 - .../MergeStylesShadowRootContext.tsx | 66 +++++-- packages/utilities/src/styled.tsx | 19 +- 14 files changed, 387 insertions(+), 247 deletions(-) diff --git a/packages/merge-styles/src/Stylesheet.ts b/packages/merge-styles/src/Stylesheet.ts index 86372ece2a923..870b747be02df 100644 --- a/packages/merge-styles/src/Stylesheet.ts +++ b/packages/merge-styles/src/Stylesheet.ts @@ -1,4 +1,5 @@ import { IStyle } from './IStyle'; +import { ShadowConfig } from './mergeStyleSets'; export const InjectionMode = { /** @@ -23,6 +24,10 @@ export const InjectionMode = { */ // eslint-disable-next-line @typescript-eslint/naming-convention unstable_constructibleStylesheet: 3 as 3, + + insertNodeAndConstructableStylesheet: 4 as 4, + + appedChildAndConstructableStylesheet: 5 as 5, }; export type InjectionMode = (typeof InjectionMode)[keyof typeof InjectionMode]; @@ -228,8 +233,10 @@ export class Stylesheet { /** * Gets the singleton instance. */ - public static getInstance(stylesheetKey?: string): Stylesheet { - if (stylesheetKey) { + public static getInstance( + { stylesheetKey, inShadow }: ShadowConfig = { stylesheetKey: '__global__', inShadow: false }, + ): Stylesheet { + if (stylesheetKey && inShadow) { _stylesheet = _global[ADOPTED_STYLESHEETS]?.get(stylesheetKey); } else { _stylesheet = _global[STYLESHEET_SETTING] as Stylesheet; @@ -237,19 +244,28 @@ export class Stylesheet { if (!_stylesheet || (_stylesheet._lastStyleElement && _stylesheet._lastStyleElement.ownerDocument !== document)) { const fabricConfig = _global?.FabricConfig || {}; + if (inShadow) { + fabricConfig.mergeStyles = fabricConfig.mergeStyles || {}; + fabricConfig.mergeStyles.injectionMode = InjectionMode.unstable_constructibleStylesheet; + } const stylesheet = new Stylesheet(fabricConfig.mergeStyles, fabricConfig.serializedStylesheet, stylesheetKey); _stylesheet = stylesheet; if (stylesheetKey) { - if (!_global[ADOPTED_STYLESHEETS]) { - _global[ADOPTED_STYLESHEETS] = new EventMap(); + if (inShadow || stylesheetKey === '__global__') { + if (!_global[ADOPTED_STYLESHEETS]) { + _global[ADOPTED_STYLESHEETS] = new EventMap(); + } + _global[ADOPTED_STYLESHEETS]!.set(stylesheetKey, stylesheet); + const css = _stylesheet._getConstructibleStylesheet(); + requestAnimationFrame(() => { + _global[ADOPTED_STYLESHEETS]!.raise('add-sheet', { key: stylesheetKey, sheet: stylesheet }); + }); + } + + if (stylesheetKey === '__global__') { + _global[STYLESHEET_SETTING] = stylesheet; } - _global[ADOPTED_STYLESHEETS]!.set(stylesheetKey, stylesheet); - const css = _stylesheet._getConstructibleStylesheet(); - // css.__yo__ = stylesheetKey; - requestAnimationFrame(() => { - _global[ADOPTED_STYLESHEETS]!.raise('add-sheet', { key: stylesheetKey, sheet: stylesheet }); - }); } else { _global[STYLESHEET_SETTING] = stylesheet; } @@ -260,8 +276,10 @@ export class Stylesheet { constructor(config?: IStyleSheetConfig, serializedStylesheet?: ISerializedStylesheet, stylesheetKey?: string) { // If there is no document we won't have an element to inject into. - // const defaultInjectionMode = typeof document === 'undefined' ? InjectionMode.none : InjectionMode.insertNode; - const defaultInjectionMode = InjectionMode.unstable_constructibleStylesheet; + const defaultInjectionMode = typeof document === 'undefined' ? InjectionMode.none : InjectionMode.insertNode; + // const defaultInjectionMode = + // typeof stylesheetKey === 'string' ? InjectionMode.unstable_constructibleStylesheet : InjectionMode.insertNode; + // const defaultInjectionMode = InjectionMode.unstable_constructibleStylesheet; this._config = { injectionMode: defaultInjectionMode, defaultPrefix: 'css', @@ -270,6 +288,7 @@ export class Stylesheet { ...config, }; + // Need to add a adoptedStyleSheets polyfill if ( !SUPPORTS_CONSTRUCTIBLE_STYLESHEETS && this._config.injectionMode === InjectionMode.unstable_constructibleStylesheet @@ -277,7 +296,18 @@ export class Stylesheet { this._config.injectionMode = defaultInjectionMode; } + // When something is inserted globally, outside of a shadow context + // we proably still need to adopt it in the shadow context + if (stylesheetKey === '__global__') { + if (this._config.injectionMode === InjectionMode.insertNode) { + this._config.injectionMode = InjectionMode.insertNodeAndConstructableStylesheet; + } else if (this._config.injectionMode === InjectionMode.appedChildAndConstructableStylesheet) { + this._config.injectionMode = InjectionMode.appedChildAndConstructableStylesheet; + } + } + this._classNameToArgs = serializedStylesheet?.classNameToArgs ?? this._classNameToArgs; + // Come back to this if (this._config.injectionMode !== InjectionMode.unstable_constructibleStylesheet) { this._styleCounter = serializedStylesheet?.counter ?? this._styleCounter; } @@ -410,43 +440,73 @@ export class Stylesheet { */ public insertRule(rule: string, preserve?: boolean): void { const { injectionMode } = this._config; - const element = - injectionMode === InjectionMode.unstable_constructibleStylesheet - ? this._getConstructibleStylesheet() - : injectionMode !== InjectionMode.none - ? this._getStyleElement() - : undefined; + // const element = + // injectionMode === InjectionMode.unstable_constructibleStylesheet + // ? this._getConstructibleStylesheet() + // : injectionMode !== InjectionMode.none + // ? this._getStyleElement() + // : undefined; + + let element: HTMLStyleElement | undefined = undefined; + let constructableSheet: CSSStyleSheet | undefined = undefined; + + if (injectionMode === InjectionMode.insertNode || injectionMode === InjectionMode.appendChild) { + element = this._getStyleElement(); + } else if (injectionMode === InjectionMode.unstable_constructibleStylesheet) { + constructableSheet = this._getConstructibleStylesheet(); + } else if ( + injectionMode === InjectionMode.insertNodeAndConstructableStylesheet || + injectionMode === InjectionMode.appedChildAndConstructableStylesheet + ) { + element = this._getStyleElement(); + constructableSheet = this._getConstructibleStylesheet(); + } if (preserve) { this._preservedRules.push(rule); } - if (element) { + if (element || constructableSheet) { switch (injectionMode) { case InjectionMode.insertNode: - const { sheet } = element! as HTMLStyleElement; - - try { - (sheet as CSSStyleSheet).insertRule(rule, (sheet as CSSStyleSheet).cssRules.length); - } catch (e) { - // The browser will throw exceptions on unsupported rules (such as a moz prefix in webkit.) - // We need to swallow the exceptions for this scenario, otherwise we'd need to filter - // which could be slower and bulkier. - } + this._insertNode(element!, rule); + // const { sheet } = element! as HTMLStyleElement; + + // try { + // (sheet as CSSStyleSheet).insertRule(rule, (sheet as CSSStyleSheet).cssRules.length); + // } catch (e) { + // // The browser will throw exceptions on unsupported rules (such as a moz prefix in webkit.) + // // We need to swallow the exceptions for this scenario, otherwise we'd need to filter + // // which could be slower and bulkier. + // } + break; + + case InjectionMode.insertNodeAndConstructableStylesheet: + this._insertNode(element!, rule); + this._insertRuleIntoSheet(constructableSheet!, rule); break; case InjectionMode.appendChild: (element as HTMLStyleElement).appendChild(document.createTextNode(rule)); break; + case InjectionMode.appedChildAndConstructableStylesheet: + (element as HTMLStyleElement).appendChild(document.createTextNode(rule)); + this._insertRuleIntoSheet(constructableSheet!, rule); + break; + case InjectionMode.unstable_constructibleStylesheet: - try { - (element as CSSStyleSheet).insertRule(rule, (element as CSSStyleSheet).cssRules.length); - } catch (e) { - // The browser will throw exceptions on unsupported rules (such as a moz prefix in webkit.) - // We need to swallow the exceptions for this scenario, otherwise we'd need to filter - // which could be slower and bulkier. - } + this._insertRuleIntoSheet(constructableSheet!, rule); + // try { + // (constructableSheet as CSSStyleSheet).insertRule( + // rule, + // (constructableSheet as CSSStyleSheet).cssRules.length, + // ); + // } catch (e) { + // // The browser will throw exceptions on unsupported rules (such as a moz prefix in webkit.) + // // We need to swallow the exceptions for this scenario, otherwise we'd need to filter + // // which could be slower and bulkier. + // } break; } } else { @@ -502,6 +562,28 @@ export class Stylesheet { } } + private _insertNode(element: HTMLStyleElement, rule: string): void { + const { sheet } = element! as HTMLStyleElement; + + try { + (sheet as CSSStyleSheet).insertRule(rule, (sheet as CSSStyleSheet).cssRules.length); + } catch (e) { + // The browser will throw exceptions on unsupported rules (such as a moz prefix in webkit.) + // We need to swallow the exceptions for this scenario, otherwise we'd need to filter + // which could be slower and bulkier. + } + } + + private _insertRuleIntoSheet(sheet: CSSStyleSheet, rule: string): void { + try { + sheet.insertRule(rule, sheet.cssRules.length); + } catch (e) { + // The browser will throw exceptions on unsupported rules (such as a moz prefix in webkit.) + // We need to swallow the exceptions for this scenario, otherwise we'd need to filter + // which could be slower and bulkier. + } + } + private _getStyleElement(): HTMLStyleElement | undefined { if (!this._styleElement && typeof document !== 'undefined') { this._styleElement = this._createStyleElement(); @@ -555,9 +637,9 @@ export class Stylesheet { // _global[ADOPTED_STYLESHEETS]!.raise('add-sheet', { key: this._stylesheetKey!, sheet: this }); // Reset the style element on the next frame. - window.requestAnimationFrame(() => { - this._styleElement = undefined; - }); + // window.requestAnimationFrame(() => { + // this._styleElement = undefined; + // }); } return this._constructibleSheet; diff --git a/packages/merge-styles/src/extractStyleParts.ts b/packages/merge-styles/src/extractStyleParts.ts index 563d306aca490..ab5618a432dbd 100644 --- a/packages/merge-styles/src/extractStyleParts.ts +++ b/packages/merge-styles/src/extractStyleParts.ts @@ -1,12 +1,13 @@ import { IStyle, IStyleBaseArray } from './IStyle'; import { Stylesheet } from './Stylesheet'; +import { ShadowConfig } from './mergeStyleSets'; /** * Separates the classes and style objects. Any classes that are pre-registered * args are auto expanded into objects. */ export function extractStyleParts( - stylesheetKey: string = '__global__', + shadowConfig?: ShadowConfig, ...args: (IStyle | IStyle[] | false | null | undefined)[] ): { classes: string[]; @@ -14,7 +15,7 @@ export function extractStyleParts( } { const classes: string[] = []; const objects: {}[] = []; - const stylesheet = Stylesheet.getInstance(stylesheetKey); + const stylesheet = Stylesheet.getInstance(shadowConfig); function _processArgs(argsList: (IStyle | IStyle[])[]): void { for (const arg of argsList) { diff --git a/packages/merge-styles/src/mergeStyleSets.ts b/packages/merge-styles/src/mergeStyleSets.ts index 1b23598b324d6..2d2dbabe5e160 100644 --- a/packages/merge-styles/src/mergeStyleSets.ts +++ b/packages/merge-styles/src/mergeStyleSets.ts @@ -7,6 +7,11 @@ import { getStyleOptions } from './StyleOptionsState'; import { applyRegistration, styleToRegistration } from './styleToClassName'; import { ObjectOnly } from './ObjectOnly'; +export type ShadowConfig = { + stylesheetKey: string; + inShadow: boolean; +}; + /** * Takes in one or more style set objects, each consisting of a set of areas, * each which will produce a class name. Using this is analogous to calling @@ -80,7 +85,7 @@ export function mergeStyleSets( export function mergeStyleSets(...styleSets: Array): IProcessedStyleSet; export function mergeStyleSets( - stylesheetKey: string, + shadowConfig: ShadowConfig, ...styleSets: Array ): IProcessedStyleSet; @@ -93,20 +98,14 @@ export function mergeStyleSets( * @param styleSets - One or more style sets to be merged. */ export function mergeStyleSets(...styleSets: Array): IProcessedStyleSet { - // const last = styleSets[styleSets.length - 1]; - // const { stylesheetKey } = last; - // if (stylesheetKey) { - // styleSets.pop(); - // } - - let stylesheetKey = undefined; + let shadowConfig = undefined; let sets = styleSets; - if (typeof styleSets[0] === 'string') { - stylesheetKey = styleSets[0]; + if (typeof styleSets[0].stylesheetKey === 'string') { + shadowConfig = styleSets[0]; sets = styleSets.slice(1); } - return mergeCssSets(styleSets as any, getStyleOptions(), stylesheetKey); + return mergeCssSets(styleSets as any, getStyleOptions(), shadowConfig); } /** @@ -121,7 +120,7 @@ export function mergeStyleSets(...styleSets: Array( styleSets: [TStyleSet | false | null | undefined], options?: IStyleOptions, - stylesheetKey?: string, + shadowConfig?: ShadowConfig, ): IProcessedStyleSet; /** @@ -136,7 +135,7 @@ export function mergeCssSets( export function mergeCssSets( styleSets: [TStyleSet1 | false | null | undefined, TStyleSet2 | false | null | undefined], options?: IStyleOptions, - stylesheetKey?: string, + shadowConfig?: ShadowConfig, ): IProcessedStyleSet; /** @@ -155,7 +154,7 @@ export function mergeCssSets( TStyleSet3 | false | null | undefined, ], options?: IStyleOptions, - stylesheetKey?: string, + shadowConfig?: ShadowConfig, ): IProcessedStyleSet; /** @@ -175,7 +174,7 @@ export function mergeCssSets( TStyleSet4 | false | null | undefined, ], options?: IStyleOptions, - stylesheetKey?: string, + shadowConfig?: ShadowConfig, ): IProcessedStyleSet< ObjectOnly & ObjectOnly & ObjectOnly & ObjectOnly >; @@ -192,7 +191,7 @@ export function mergeCssSets( export function mergeCssSets( styleSet: [TStyleSet | false | null | undefined], options?: IStyleOptions, - stylesheetKey?: string, + shadowConfig?: ShadowConfig, ): IProcessedStyleSet; /** @@ -207,7 +206,7 @@ export function mergeCssSets( export function mergeCssSets( styleSets: Array, options?: IStyleOptions, - stylesheetKey: string = '__global__', + shadowConfig?: ShadowConfig, ): IProcessedStyleSet { const classNameSet: IProcessedStyleSet = { subComponentStyles: {} }; @@ -230,10 +229,10 @@ export function mergeCssSets( const styles: IStyle = (concatenatedStyleSet as any)[styleSetArea]; - const { classes, objects } = extractStyleParts(stylesheetKey, styles); + const { classes, objects } = extractStyleParts(shadowConfig, styles); if (objects?.length) { - const registration = styleToRegistration(stylesheetKey, options || {}, { displayName: styleSetArea }, objects); + const registration = styleToRegistration(options || {}, shadowConfig, { displayName: styleSetArea }, objects); if (registration) { registrations.push(registration); @@ -249,7 +248,7 @@ export function mergeCssSets( for (const registration of registrations) { if (registration) { - applyRegistration(registration, options?.specificityMultiplier, stylesheetKey); + applyRegistration(registration, options?.specificityMultiplier, shadowConfig); } } diff --git a/packages/merge-styles/src/mergeStyles.ts b/packages/merge-styles/src/mergeStyles.ts index c6c39e28e97d5..aa4816f4f1f89 100644 --- a/packages/merge-styles/src/mergeStyles.ts +++ b/packages/merge-styles/src/mergeStyles.ts @@ -22,13 +22,13 @@ export function mergeStyles(...args: (IStyle | IStyleBaseArray | false | null | export function mergeCss( args: (IStyle | IStyleBaseArray | false | null | undefined) | (IStyle | IStyleBaseArray | false | null | undefined)[], options?: IStyleOptions, - stylesheetKey: string = '__global__', + stylesheetKey?: string, ): string { const styleArgs = args instanceof Array ? args : [args]; const { classes, objects } = extractStyleParts(stylesheetKey, styleArgs); if (objects.length) { - classes.push(styleToClassName(stylesheetKey, options || {}, objects)); + classes.push(styleToClassName(options || {}, stylesheetKey, objects)); } return classes.join(' '); diff --git a/packages/merge-styles/src/styleToClassName.ts b/packages/merge-styles/src/styleToClassName.ts index 66345ecc9f060..f080fac8c18cf 100644 --- a/packages/merge-styles/src/styleToClassName.ts +++ b/packages/merge-styles/src/styleToClassName.ts @@ -8,6 +8,7 @@ import { provideUnits } from './transforms/provideUnits'; import { rtlifyRules } from './transforms/rtlifyRules'; import { IStyleOptions } from './IStyleOptions'; import { tokenizeWithParentheses } from './tokenizeWithParentheses'; +import { ShadowConfig } from './mergeStyleSets'; const DISPLAY_NAME = 'displayName'; @@ -244,15 +245,15 @@ export interface IRegistration { } export function styleToRegistration( - stylesheetKey: string = '__global__', options: IStyleOptions, + shadowConfig?: ShadowConfig, ...args: IStyle[] ): IRegistration | undefined { const rules: IRuleSet = extractRules(args); const key = getKeyForRules(options, rules); if (key) { - const stylesheet = Stylesheet.getInstance(stylesheetKey); + const stylesheet = Stylesheet.getInstance(shadowConfig); const registration: Partial = { className: stylesheet.classNameFromKey(key), key, @@ -284,9 +285,9 @@ export function styleToRegistration( export function applyRegistration( registration: IRegistration, specificityMultiplier: number = 1, - stylesheetKey: string = '__global__', + shadowConfig?: ShadowConfig, ): void { - const stylesheet = Stylesheet.getInstance(stylesheetKey); + const stylesheet = Stylesheet.getInstance(shadowConfig); const { className, key, args, rulesToInsert } = registration; if (rulesToInsert) { @@ -306,10 +307,10 @@ export function applyRegistration( } } -export function styleToClassName(stylesheetKey: string, options: IStyleOptions, ...args: IStyle[]): string { - const registration = styleToRegistration(stylesheetKey, options, ...args); +export function styleToClassName(options: IStyleOptions, shadowConfig?: ShadowConfig, ...args: IStyle[]): string { + const registration = styleToRegistration(options, shadowConfig, ...args); if (registration) { - applyRegistration(registration, options.specificityMultiplier); + applyRegistration(registration, options.specificityMultiplier, shadowConfig); return registration.className; } diff --git a/packages/react-examples/src/react/Button/Button.Default.Example.tsx b/packages/react-examples/src/react/Button/Button.Default.Example.tsx index c24d80a6e3148..774db62c42e91 100644 --- a/packages/react-examples/src/react/Button/Button.Default.Example.tsx +++ b/packages/react-examples/src/react/Button/Button.Default.Example.tsx @@ -6,7 +6,6 @@ import { MergeStylesShadowRootProvider_unstable, useAdoptedStylesheet_unstable, } from '@fluentui/utilities'; -// import { createProxy as _createProxy, default as _root } from 'react-shadow'; // eslint-disable-next-line import root from 'react-shadow'; @@ -15,21 +14,6 @@ export interface IButtonExampleProps { disabled?: boolean; checked?: boolean; } -// type CreateProxyRenderFn = ({ children }: { children: React.ReactNode; root: ShadowRoot }) => React.ReactNode; -// type CreateProxyFn = (target: unknown, id: string, render: CreateProxyRenderFn) => typeof _root; - -// const createProxy: CreateProxyFn = _createProxy; - -// const FluentWrapper: React.FC<{ children: React.ReactNode; root: ShadowRoot }> = ({ children, root }) => { -// // I think we'll need to implement something here to allow mergeStyles -// // to add styles to the shadowRoot. - -// return <>{children}; -// }; - -// export const root = createProxy({}, 'fluentui-v8', ({ children, root }) => ( -// {children} -// )); // Example formatting const stackTokens: IStackTokens = { childrenGap: 40 }; @@ -40,49 +24,49 @@ const Hmmm = props => { }; export const ButtonDefaultExample: React.FunctionComponent = props => { - const { disabled, checked } = props; const [shadowRootEl, setShadowRootEl] = React.useState(null); const setter = val => { - console.log('setter'); setShadowRootEl(val); }; + const [disabled, setDisabled] = React.useState(false); + const onClick = e => { + setDisabled(!disabled); + }; + return ( - - - - - - - - Hmmm - - - - - + <> + + + + + {/* eslint-disable-next-line */} + + {/* + */} + + + + + + + {/* eslint-disable-next-line */} + + + + ); }; - -function _alertClicked(): void { - alert('Clicked'); -} diff --git a/packages/react/src/components/Button/BaseButton.classNames.ts b/packages/react/src/components/Button/BaseButton.classNames.ts index 9c3afc5d365ac..83a5ea6b4f884 100644 --- a/packages/react/src/components/Button/BaseButton.classNames.ts +++ b/packages/react/src/components/Button/BaseButton.classNames.ts @@ -40,90 +40,100 @@ export const getBaseButtonClassNames = memoizeFunction( expanded: boolean, isSplit: boolean | undefined, stylesheetKey?: string, + inShadow?: boolean, ): IButtonClassNames => { const classNames = getGlobalClassNames(ButtonGlobalClassNames, theme || {}); const isExpanded = expanded && !isSplit; - return mergeStyleSets(stylesheetKey, { - root: [ - classNames.msButton, - styles.root, - variantClassName, - checked && ['is-checked', styles.rootChecked], - isExpanded && [ - 'is-expanded', - styles.rootExpanded, - { - selectors: { - [`:hover .${classNames.msButtonIcon}`]: styles.iconExpandedHovered, - // menuIcon falls back to rootExpandedHovered to support original behavior - [`:hover .${classNames.msButtonMenuIcon}`]: styles.menuIconExpandedHovered || styles.rootExpandedHovered, - ':hover': styles.rootExpandedHovered, + return mergeStyleSets( + { stylesheetKey, inShadow }, + { + root: [ + classNames.msButton, + styles.root, + variantClassName, + checked && ['is-checked', styles.rootChecked], + isExpanded && [ + 'is-expanded', + styles.rootExpanded, + { + selectors: { + [`:hover .${classNames.msButtonIcon}`]: styles.iconExpandedHovered, + // menuIcon falls back to rootExpandedHovered to support original behavior + [`:hover .${classNames.msButtonMenuIcon}`]: + styles.menuIconExpandedHovered || styles.rootExpandedHovered, + ':hover': styles.rootExpandedHovered, + }, }, - }, - ], - hasMenu && [ButtonGlobalClassNames.msButtonHasMenu, styles.rootHasMenu], - disabled && ['is-disabled', styles.rootDisabled], - !disabled && - !isExpanded && - !checked && { - selectors: { - ':hover': styles.rootHovered, - [`:hover .${classNames.msButtonLabel}`]: styles.labelHovered, - [`:hover .${classNames.msButtonIcon}`]: styles.iconHovered, - [`:hover .${classNames.msButtonDescription}`]: styles.descriptionHovered, - [`:hover .${classNames.msButtonMenuIcon}`]: styles.menuIconHovered, - ':focus': styles.rootFocused, - ':active': styles.rootPressed, - [`:active .${classNames.msButtonIcon}`]: styles.iconPressed, - [`:active .${classNames.msButtonDescription}`]: styles.descriptionPressed, - [`:active .${classNames.msButtonMenuIcon}`]: styles.menuIconPressed, + ], + hasMenu && [ButtonGlobalClassNames.msButtonHasMenu, styles.rootHasMenu], + disabled && ['is-disabled', styles.rootDisabled], + !disabled && + !isExpanded && + !checked && { + selectors: { + ':hover': styles.rootHovered, + [`:hover .${classNames.msButtonLabel}`]: styles.labelHovered, + [`:hover .${classNames.msButtonIcon}`]: styles.iconHovered, + [`:hover .${classNames.msButtonDescription}`]: styles.descriptionHovered, + [`:hover .${classNames.msButtonMenuIcon}`]: styles.menuIconHovered, + ':focus': styles.rootFocused, + ':active': styles.rootPressed, + [`:active .${classNames.msButtonIcon}`]: styles.iconPressed, + [`:active .${classNames.msButtonDescription}`]: styles.descriptionPressed, + [`:active .${classNames.msButtonMenuIcon}`]: styles.menuIconPressed, + }, }, - }, - disabled && checked && [styles.rootCheckedDisabled], - !disabled && - checked && { - selectors: { - ':hover': styles.rootCheckedHovered, - ':active': styles.rootCheckedPressed, + disabled && checked && [styles.rootCheckedDisabled], + !disabled && + checked && { + selectors: { + ':hover': styles.rootCheckedHovered, + ':active': styles.rootCheckedPressed, + }, }, - }, - className, - ], - flexContainer: [classNames.msButtonFlexContainer, styles.flexContainer], - textContainer: [classNames.msButtonTextContainer, styles.textContainer], - icon: [ - classNames.msButtonIcon, - iconClassName, - styles.icon, - isExpanded && styles.iconExpanded, - checked && styles.iconChecked, - disabled && styles.iconDisabled, - ], - label: [classNames.msButtonLabel, styles.label, checked && styles.labelChecked, disabled && styles.labelDisabled], - menuIcon: [ - classNames.msButtonMenuIcon, - menuIconClassName, - styles.menuIcon, - checked && styles.menuIconChecked, - disabled && !isSplit && styles.menuIconDisabled, - !disabled && - !isExpanded && - !checked && { - selectors: { - ':hover': styles.menuIconHovered, - ':active': styles.menuIconPressed, + className, + ], + flexContainer: [classNames.msButtonFlexContainer, styles.flexContainer], + textContainer: [classNames.msButtonTextContainer, styles.textContainer], + icon: [ + classNames.msButtonIcon, + iconClassName, + styles.icon, + isExpanded && styles.iconExpanded, + checked && styles.iconChecked, + disabled && styles.iconDisabled, + ], + label: [ + classNames.msButtonLabel, + styles.label, + checked && styles.labelChecked, + disabled && styles.labelDisabled, + ], + menuIcon: [ + classNames.msButtonMenuIcon, + menuIconClassName, + styles.menuIcon, + checked && styles.menuIconChecked, + disabled && !isSplit && styles.menuIconDisabled, + !disabled && + !isExpanded && + !checked && { + selectors: { + ':hover': styles.menuIconHovered, + ':active': styles.menuIconPressed, + }, }, - }, - isExpanded && ['is-expanded', styles.menuIconExpanded], - ], - description: [ - classNames.msButtonDescription, - styles.description, - checked && styles.descriptionChecked, - disabled && styles.descriptionDisabled, - ], - screenReaderText: [classNames.msButtonScreenReaderText, styles.screenReaderText], - }); + isExpanded && ['is-expanded', styles.menuIconExpanded], + ], + description: [ + classNames.msButtonDescription, + styles.description, + checked && styles.descriptionChecked, + disabled && styles.descriptionDisabled, + ], + screenReaderText: [classNames.msButtonScreenReaderText, styles.screenReaderText], + }, + ); }, ); diff --git a/packages/react/src/components/Button/BaseButton.tsx b/packages/react/src/components/Button/BaseButton.tsx index a0cdebc577f0b..2d475d3f4ee4f 100644 --- a/packages/react/src/components/Button/BaseButton.tsx +++ b/packages/react/src/components/Button/BaseButton.tsx @@ -173,6 +173,7 @@ export class BaseButton extends React.Component | undefined, styleProps?: TStyleProps, + stylesheetKey?: string, ) => IProcessedStyleSet { // We build a trie where each node is a Map. The map entry key represents an argument // value, and the entry value is another node (Map). Each node has a `__retval__` @@ -75,6 +76,7 @@ export function classNamesFunction | undefined, styleProps: TStyleProps = {} as TStyleProps, + stylesheetKey?: string, ): IProcessedStyleSet => { // If useStaticStyles is true, styleFunctionOrObject returns slot to classname mappings. // If there is also no style overrides, we can skip merge styles completely and @@ -120,6 +122,9 @@ export function classNamesFunction - - {(context: ICustomizerContext) => { - const defaultProps = Customizations.getSettings(fields, scope, context.customizations); + {(inShadow: boolean) => { + return ( + + {(context: ICustomizerContext) => { + const defaultProps = Customizations.getSettings(fields, scope, context.customizations); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const componentProps = this.props as any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const componentProps = this.props as any; - // If defaultProps.styles is a function, evaluate it before calling concatStyleSets - if (defaultProps.styles && typeof defaultProps.styles === 'function') { - defaultProps.styles = defaultProps.styles({ ...defaultProps, ...componentProps }); - } + // If defaultProps.styles is a function, evaluate it before calling concatStyleSets + if (defaultProps.styles && typeof defaultProps.styles === 'function') { + defaultProps.styles = defaultProps.styles({ ...defaultProps, ...componentProps }); + } - // If concatStyles is true and custom styles have been defined compute those styles - if (concatStyles && defaultProps.styles) { - if ( - this._styleCache.default !== defaultProps.styles || - this._styleCache.component !== componentProps.styles - ) { - const mergedStyles = concatStyleSets(defaultProps.styles, componentProps.styles); - this._styleCache.default = defaultProps.styles; - this._styleCache.component = componentProps.styles; - this._styleCache.merged = mergedStyles; - this._styleCache.merged.__stylesheetKey__ = scope; - } + // If concatStyles is true and custom styles have been defined compute those styles + if (concatStyles && defaultProps.styles) { + if ( + this._styleCache.default !== defaultProps.styles || + this._styleCache.component !== componentProps.styles + ) { + const mergedStyles = concatStyleSets(defaultProps.styles, componentProps.styles); + this._styleCache.default = defaultProps.styles; + this._styleCache.component = componentProps.styles; + this._styleCache.merged = mergedStyles; + this._styleCache.merged.__stylesheetKey__ = scope; + this._styleCache.merged.__inShadow__ = inShadow; + } - return ; - } + return ( + + ); + } - const styles = { ...defaultProps.styles, ...componentProps.styles, __stylesheetKey__: scope }; - return ; - }} - + const styles = { ...defaultProps.styles, ...componentProps.styles, __stylesheetKey__: scope }; + return ; + }} + + ); + }} ); } diff --git a/packages/utilities/src/index.ts b/packages/utilities/src/index.ts index 02749300b5c67..92c0268e4c742 100644 --- a/packages/utilities/src/index.ts +++ b/packages/utilities/src/index.ts @@ -236,11 +236,6 @@ import './version'; // eslint-disable-next-line deprecation/deprecation export type { IStyleFunctionOrObject, Omit } from '@fluentui/merge-styles'; -// export { -// MergeStylesProvider_unstable, -// useAdoptedStylesheet_unstable, -// } from './shadowDom/MergeStylesContext/MergeStylesContext'; - export { MergeStylesShadowRootProvider_unstable, useAdoptedStylesheet_unstable, diff --git a/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx b/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx index 3d2fd13a6122b..29fe0178283a4 100644 --- a/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx +++ b/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { useMergeStylesRootStylesheets_unstable } from './MergeStylesRootContext'; +import { getDocument, getWindow } from '../dom'; /** * NOTE: This API is unstable and subject to breaking change or removal without notice. @@ -9,9 +10,11 @@ export type MergeStylesShadowRootContextValue = { shadowRoot?: ShadowRoot | null; }; -const MergeStylesShadowRootContext = React.createContext({ - stylesheets: new Map(), -}); +// const MergeStylesShadowRootContext = React.createContext({ +// stylesheets: new Map(), +// }); + +const MergeStylesShadowRootContext = React.createContext(undefined); /** * NOTE: This API is unstable and subject to breaking change or removal without notice. @@ -28,43 +31,72 @@ export const MergeStylesShadowRootProvider_unstable: React.FC { - const ctx = useMergeStylesShadowRootContext_unstable(); - const value = React.useMemo(() => { return { - stylesheets: ctx.stylesheets, + stylesheets: new Map(), shadowRoot, }; - }, [ctx, shadowRoot]); + }, [shadowRoot]); - return ; + return ( + + /* + {props.children} + */ + ); }; export type MergeStylesContextConsumerProps = { - stylesheetKey?: string; + stylesheetKey: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + children: (inShadow: boolean) => React.ReactElement; }; export const MergeStylesShadowRootConsumer: React.FC = ({ stylesheetKey, children, }) => { - // useAdoptedStylesheet_unstable('__global__'); - useAdoptedStylesheet_unstable(stylesheetKey ?? '__global__'); + useAdoptedStylesheet_unstable('__global__'); + // useAdoptedStylesheet_unstable('IconButton'); + // useAdoptedStylesheet_unstable('Fabric'); + useAdoptedStylesheet_unstable(stylesheetKey); + + const inShadow = useHasMergeStylesShadowRootContext(); - return <>{children}; + return children(inShadow); + + // return <>{children}; }; // const GlobalStyles: React.FC = props => { -// useAdoptedStylesheet_unstable('__global__'); +// // useAdoptedStylesheet_unstable('@fluentui/style-utilities', true); +// // useAdoptedStylesheet_unstable('__global__', true); // return null; // }; /** * NOTE: This API is unstable and subject to breaking change or removal without notice. */ -export const useAdoptedStylesheet_unstable = (stylesheetKey: string): void => { +export const useAdoptedStylesheet_unstable = ( + stylesheetKey: string, + adopteGlobally: boolean = false, +): string | undefined => { const shadowCtx = useMergeStylesShadowRootContext_unstable(); const rootMergeStyles = useMergeStylesRootStylesheets_unstable(); + // console.log('useAdoptedStylesheets', stylesheetKey); + + if (!shadowCtx) { + return undefined; + } + + if (adopteGlobally) { + const doc = getDocument(); + const win = getWindow(); + const stylesheet = win?.__mergeStylesAdoptedStyleSheets__?.get(stylesheetKey)?.getAdoptableStyleSheet(); + if (doc && stylesheet && !doc.adoptedStyleSheets.includes(stylesheet)) { + doc.adoptedStyleSheets = [...doc.adoptedStyleSheets, stylesheet]; + } + } if (shadowCtx.shadowRoot && !shadowCtx.stylesheets.has(stylesheetKey)) { const stylesheet = rootMergeStyles.get(stylesheetKey); @@ -74,6 +106,12 @@ export const useAdoptedStylesheet_unstable = (stylesheetKey: string): void => { shadowCtx.shadowRoot.adoptedStyleSheets = [...shadowCtx.shadowRoot.adoptedStyleSheets, adoptableStyleSheet]; } } + + return stylesheetKey; +}; + +export const useHasMergeStylesShadowRootContext = () => { + return !!useMergeStylesRootStylesheets_unstable(); }; export const useMergeStylesShadowRootContext_unstable = () => { diff --git a/packages/utilities/src/styled.tsx b/packages/utilities/src/styled.tsx index fde7a5380842c..6f448fd633633 100644 --- a/packages/utilities/src/styled.tsx +++ b/packages/utilities/src/styled.tsx @@ -1,6 +1,9 @@ import * as React from 'react'; import { concatStyleSetsWithProps } from '@fluentui/merge-styles'; -import { useAdoptedStylesheet_unstable } from './shadowDom/MergeStylesShadowRootContext'; +import { + useAdoptedStylesheet_unstable, + useHasMergeStylesShadowRootContext, +} from './shadowDom/MergeStylesShadowRootContext'; import { useCustomizationSettings } from './customizations/useCustomizationSettings'; import type { IStyleSet, IStyleFunctionOrObject } from '@fluentui/merge-styles'; @@ -89,7 +92,7 @@ export function styled< const { scope, fields = DefaultFields } = customizable; - const stylesheetKey = scope || '__global__'; + const stylesheetKey = scope; // || '__global__'; const Wrapped = React.forwardRef((props: TComponentProps, forwardedRef: React.Ref) => { const styles = React.useRef>(); @@ -98,6 +101,8 @@ export function styled< const { styles: customizedStyles, dir, ...rest } = settings; const additionalProps = getProps ? getProps(props) : undefined; + const inShadow = useHasMergeStylesShadowRootContext(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const cache = (styles.current && (styles.current as any).__cachedInputs__) || []; const propStyles = props.styles; @@ -123,10 +128,20 @@ export function styled< // eslint-disable-next-line // @ts-ignore styles.current.__stylesheetKey__ = stylesheetKey; + // eslint-disable-next-line + // @ts-ignore + styles.current.__inShadow__ = inShadow; } useAdoptedStylesheet_unstable(stylesheetKey); + // const inShadow = useAdoptedStylesheet_unstable(stylesheetKey); + // if (styles.current) { + // // eslint-disable-next-line + // // @ts-ignore + // styles.current.__stylesheetKey__ = inShadow ? stylesheetKey : undefined; + // } + return ; }); // Function.prototype.name is an ES6 feature, so the cast to any is required until we're From d2dc96a656e07694ff89a5ee9bcf0a4a69381495 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 10 Aug 2023 20:19:57 +0000 Subject: [PATCH 006/198] add exports to @fluentui/react --- .../react/Button/Button.Default.Example.tsx | 31 +++++-------------- packages/react/src/Utilities.ts | 3 ++ 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/packages/react-examples/src/react/Button/Button.Default.Example.tsx b/packages/react-examples/src/react/Button/Button.Default.Example.tsx index 774db62c42e91..493437382816e 100644 --- a/packages/react-examples/src/react/Button/Button.Default.Example.tsx +++ b/packages/react-examples/src/react/Button/Button.Default.Example.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Stack, IStackTokens, SpinButton } from '@fluentui/react'; +import { Stack, IStackTokens, SpinButton, Text } from '@fluentui/react'; import { DefaultButton, PrimaryButton } from '@fluentui/react/lib/Button'; import { MergeStylesRootProvider_unstable, @@ -16,12 +16,7 @@ export interface IButtonExampleProps { } // Example formatting -const stackTokens: IStackTokens = { childrenGap: 40 }; - -const Hmmm = props => { - useAdoptedStylesheet_unstable('Hmmm'); - return
; -}; +const stackTokens: IStackTokens = { childrenGap: 10 }; export const ButtonDefaultExample: React.FunctionComponent = props => { const [shadowRootEl, setShadowRootEl] = React.useState(null); @@ -40,31 +35,21 @@ export const ButtonDefaultExample: React.FunctionComponent - + + Shadow DOM {/* eslint-disable-next-line */} - {/* - */} + - + + Light DOM {/* eslint-disable-next-line */} + diff --git a/packages/react/src/Utilities.ts b/packages/react/src/Utilities.ts index dba190f377c82..0f07f1739fdfc 100644 --- a/packages/react/src/Utilities.ts +++ b/packages/react/src/Utilities.ts @@ -130,6 +130,9 @@ export { mergeCustomizations, mergeScopedSettings, mergeSettings, + MergeStylesShadowRootProvider_unstable, + useAdoptedStylesheet_unstable, + MergeStylesRootProvider_unstable, modalize, nullRender, olProperties, From fbb87f68a65687df3ee6fb2927140ce07868ba62 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 10 Aug 2023 21:02:19 +0000 Subject: [PATCH 007/198] fix tests --- .../src/extractStyleParts.test.ts | 11 +- packages/merge-styles/src/mergeStyleSets.ts | 3 +- .../merge-styles/src/styleToClassName.test.ts | 413 +++++++----------- 3 files changed, 159 insertions(+), 268 deletions(-) diff --git a/packages/merge-styles/src/extractStyleParts.test.ts b/packages/merge-styles/src/extractStyleParts.test.ts index dc0dc011fdcef..072a8e1d761ba 100644 --- a/packages/merge-styles/src/extractStyleParts.test.ts +++ b/packages/merge-styles/src/extractStyleParts.test.ts @@ -1,18 +1,25 @@ import { extractStyleParts } from './extractStyleParts'; import { mergeCss } from './mergeStyles'; +import { ShadowConfig } from './mergeStyleSets'; import { Stylesheet, InjectionMode } from './Stylesheet'; const _stylesheet: Stylesheet = Stylesheet.getInstance(); _stylesheet.setConfig({ injectionMode: InjectionMode.none }); +let shadowConfig: ShadowConfig; describe('extractStyleParts', () => { beforeEach(() => { _stylesheet.reset(); + shadowConfig = { stylesheetKey: '__globalTest__', inShadow: false }; }); it('can extract classes and objects', () => { - const { classes, objects } = extractStyleParts('a', 'b', ['c', 'd'], { left: 1 }, ['e', { left: 2 }, { left: 3 }]); + const { classes, objects } = extractStyleParts(shadowConfig, 'a', 'b', ['c', 'd'], { left: 1 }, [ + 'e', + { left: 2 }, + { left: 3 }, + ]); expect(classes).toEqual(['a', 'b', 'c', 'd', 'e']); expect(objects).toEqual([{ left: 1 }, { left: 2 }, { left: 3 }]); @@ -20,7 +27,7 @@ describe('extractStyleParts', () => { it('can expand previously registered rules', () => { const className = mergeCss({ left: 1 }); - const { classes, objects } = extractStyleParts(className, { left: 2 }); + const { classes, objects } = extractStyleParts(shadowConfig, className, { left: 2 }); expect(classes).toEqual([]); expect(objects).toEqual([{ left: 1 }, { left: 2 }]); diff --git a/packages/merge-styles/src/mergeStyleSets.ts b/packages/merge-styles/src/mergeStyleSets.ts index 2d2dbabe5e160..8144ef5faad46 100644 --- a/packages/merge-styles/src/mergeStyleSets.ts +++ b/packages/merge-styles/src/mergeStyleSets.ts @@ -100,7 +100,8 @@ export function mergeStyleSets( export function mergeStyleSets(...styleSets: Array): IProcessedStyleSet { let shadowConfig = undefined; let sets = styleSets; - if (typeof styleSets[0].stylesheetKey === 'string') { + const first = styleSets[0]; + if (first && typeof first.stylesheetKey === 'string') { shadowConfig = styleSets[0]; sets = styleSets.slice(1); } diff --git a/packages/merge-styles/src/styleToClassName.test.ts b/packages/merge-styles/src/styleToClassName.test.ts index 6b0cdbb428e06..fd975981bd22d 100644 --- a/packages/merge-styles/src/styleToClassName.test.ts +++ b/packages/merge-styles/src/styleToClassName.test.ts @@ -1,123 +1,98 @@ import { InjectionMode, Stylesheet } from './Stylesheet'; import { styleToClassName } from './styleToClassName'; import { IStyleOptions } from './IStyleOptions'; +import { ShadowConfig } from './mergeStyleSets'; const _stylesheet: Stylesheet = Stylesheet.getInstance(); _stylesheet.setConfig({ injectionMode: InjectionMode.none }); +let shadowConfig: ShadowConfig; describe('styleToClassName', () => { beforeEach(() => { _stylesheet.reset(); + shadowConfig = { stylesheetKey: '__globalTest__', inShadow: false }; }); it('can register classes and avoid re-registering', () => { - let className = styleToClassName('__globalTest__', {}, { background: 'red' }); + let className = styleToClassName({}, shadowConfig, { background: 'red' }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.css-0{background:red;}'); - className = styleToClassName('__globalTest__', {}, { background: 'red' }); + className = styleToClassName({}, shadowConfig, { background: 'red' }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.css-0{background:red;}'); - className = styleToClassName('__globalTest__', {}, { background: 'green' }); + className = styleToClassName({}, shadowConfig, { background: 'green' }); expect(className).toEqual('css-1'); expect(_stylesheet.getRules()).toEqual('.css-0{background:red;}.css-1{background:green;}'); }); it('can have child selectors', () => { - styleToClassName( - '__globalTest__', - {}, - { - selectors: { - '.foo': { background: 'red' }, - }, + styleToClassName({}, shadowConfig, { + selectors: { + '.foo': { background: 'red' }, }, - ); + }); expect(_stylesheet.getRules()).toEqual('.css-0 .foo{background:red;}'); }); it('can have child selectors without the selectors wrapper', () => { - styleToClassName( - '__globalTest__', - {}, - { - '.foo': { background: 'red' }, - }, - ); + styleToClassName({}, shadowConfig, { + '.foo': { background: 'red' }, + }); expect(_stylesheet.getRules()).toEqual('.css-0 .foo{background:red;}'); }); it('can have child selectors with comma', () => { - styleToClassName( - '__globalTest__', - {}, - { - selectors: { - '.foo, .bar': { background: 'red' }, - }, + styleToClassName({}, shadowConfig, { + selectors: { + '.foo, .bar': { background: 'red' }, }, - ); + }); expect(_stylesheet.getRules()).toEqual('.css-0 .foo{background:red;}.css-0 .bar{background:red;}'); }); it('can have child selectors with comma without the selectors wrapper', () => { - styleToClassName( - '__globalTest__', - {}, - { - '.foo, .bar': { background: 'red' }, - }, - ); + styleToClassName({}, shadowConfig, { + '.foo, .bar': { background: 'red' }, + }); expect(_stylesheet.getRules()).toEqual('.css-0 .foo{background:red;}.css-0 .bar{background:red;}'); }); it('can have child selectors with comma with pseudo selectors', () => { - styleToClassName( - '__globalTest__', - {}, - { - selectors: { - ':hover, :active': { background: 'red' }, - }, + styleToClassName({}, shadowConfig, { + selectors: { + ':hover, :active': { background: 'red' }, }, - ); + }); expect(_stylesheet.getRules()).toEqual('.css-0:hover{background:red;}.css-0:active{background:red;}'); }); it('can have child selectors with comma with pseudo selectors', () => { - styleToClassName( - '__globalTest__', - {}, - { - ':hover, :active': { background: 'red' }, - }, - ); + styleToClassName({}, shadowConfig, { + ':hover, :active': { background: 'red' }, + }); expect(_stylesheet.getRules()).toEqual('.css-0:hover{background:red;}.css-0:active{background:red;}'); }); it('can have child selectors with comma with @media query', () => { - styleToClassName( - '__globalTest__', - {}, - { - selectors: { - '@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none)': { - background: 'red', - }, + styleToClassName({}, shadowConfig, { + selectors: { + '@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none)': { + background: 'red', }, }, - ); + }); expect(_stylesheet.getRules()).toEqual( '@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none){.css-0{background:red;}}', @@ -125,15 +100,11 @@ describe('styleToClassName', () => { }); it('can have child selectors with comma with @media query', () => { - styleToClassName( - '__globalTest__', - {}, - { - '@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none)': { - background: 'red', - }, + styleToClassName({}, shadowConfig, { + '@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none)': { + background: 'red', }, - ); + }); expect(_stylesheet.getRules()).toEqual( '@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none){.css-0{background:red;}}', @@ -141,70 +112,50 @@ describe('styleToClassName', () => { }); it('can have same element class selectors', () => { - styleToClassName( - '__globalTest__', - {}, - { - selectors: { - '&.foo': [{ background: 'red' }], - }, + styleToClassName({}, shadowConfig, { + selectors: { + '&.foo': [{ background: 'red' }], }, - ); + }); expect(_stylesheet.getRules()).toEqual('.css-0.foo{background:red;}'); }); it('can have same element class selectors without the selectors wrapper', () => { - styleToClassName( - '__globalTest__', - {}, - { - '&.foo': [{ background: 'red' }], - }, - ); + styleToClassName({}, shadowConfig, { + '&.foo': [{ background: 'red' }], + }); expect(_stylesheet.getRules()).toEqual('.css-0.foo{background:red;}'); }); it('can register pseudo selectors', () => { - const className = styleToClassName( - '__globalTest__', - {}, - { - selectors: { - ':hover': { background: 'red' }, - }, + const className = styleToClassName({}, shadowConfig, { + selectors: { + ':hover': { background: 'red' }, }, - ); + }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.css-0:hover{background:red;}'); }); it('can register pseudo selectors without the selectors wrapper', () => { - const className = styleToClassName( - '__globalTest__', - {}, - { - ':hover': { background: 'red' }, - }, - ); + const className = styleToClassName({}, shadowConfig, { + ':hover': { background: 'red' }, + }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.css-0:hover{background:red;}'); }); it('can register parent and sibling selectors', () => { - const className = styleToClassName( - '__globalTest__', - {}, - { - selectors: { - '& .child': { background: 'red' }, - '.parent &': { background: 'green' }, - }, + const className = styleToClassName({}, shadowConfig, { + selectors: { + '& .child': { background: 'red' }, + '.parent &': { background: 'green' }, }, - ); + }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.css-0 .child{background:red;}.parent .css-0{background:green;}'); @@ -212,8 +163,8 @@ describe('styleToClassName', () => { it('can merge rules', () => { let className = styleToClassName( - '__globalTest__', {}, + shadowConfig, null, false, undefined, @@ -224,183 +175,147 @@ describe('styleToClassName', () => { expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.css-0{background-color:green;color:white;}'); - className = styleToClassName('__globalTest__', {}, { backgroundColor: 'green', color: 'white' }); + className = styleToClassName({}, shadowConfig, { backgroundColor: 'green', color: 'white' }); expect(className).toEqual('css-0'); }); it('returns blank string with no input', () => { - expect(styleToClassName('__globalTest__', {})).toEqual(''); + expect(styleToClassName({}, shadowConfig)).toEqual(''); }); it('does not emit a rule which has an undefined value', () => { - expect(styleToClassName('__globalTest__', {}, { fontFamily: undefined })).toEqual(''); + expect(styleToClassName({}, shadowConfig, { fontFamily: undefined })).toEqual(''); expect(_stylesheet.getRules()).toEqual(''); }); it('returns the same class name for a rule that only has a displayName', () => { - expect(styleToClassName('__globalTest__', {}, { displayName: 'foo' })).toEqual('foo-0'); - expect(styleToClassName('__globalTest__', {}, { displayName: 'foo' })).toEqual('foo-0'); + expect(styleToClassName({}, shadowConfig, { displayName: 'foo' })).toEqual('foo-0'); + expect(styleToClassName({}, shadowConfig, { displayName: 'foo' })).toEqual('foo-0'); expect(_stylesheet.getRules()).toEqual(''); }); it('can preserve displayName in names', () => { - expect(styleToClassName('__globalTest__', {}, { displayName: 'DisplayName', background: 'red' })).toEqual( + expect(styleToClassName({}, shadowConfig, { displayName: 'DisplayName', background: 'red' })).toEqual( 'DisplayName-0', ); expect(_stylesheet.getRules()).toEqual('.DisplayName-0{background:red;}'); }); it('can flip rtl and add units', () => { - styleToClassName('__globalTest__', { rtl: true }, { left: 40 }); + styleToClassName({ rtl: true }, shadowConfig, { left: 40 }); expect(_stylesheet.getRules()).toEqual('.css-0{right:40px;}'); }); it('can prefix webkit specific things', () => { - styleToClassName('__globalTest__', {}, { WebkitFontSmoothing: 'none' }); + styleToClassName({}, shadowConfig, { WebkitFontSmoothing: 'none' }); expect(_stylesheet.getRules()).toEqual('.css-0{-webkit-font-smoothing:none;}'); }); // TODO: It may not be valid to pass a previously registered rule into styleToClassName // since mergeStyles/mergeStyleSets should probably do this in the resolution code. it('can expand previously defined rules', () => { - const className = styleToClassName('__globalTest__', {}, { background: 'red' }); - const newClassName = styleToClassName('__globalTest__', {}, className, { color: 'white' }); + const className = styleToClassName({}, shadowConfig, { background: 'red' }); + const newClassName = styleToClassName({}, shadowConfig, className, { color: 'white' }); expect(newClassName).toEqual('css-1'); expect(_stylesheet.getRules()).toEqual('.css-0{background:red;}.css-1{background:red;color:white;}'); }); it('can expand previously defined rules in selectors', () => { - const className = styleToClassName('__globalTest__', {}, { background: 'red' }); - const newClassName = styleToClassName( - '__globalTest__', - {}, - { - selectors: { - '& > *': className, - }, + const className = styleToClassName({}, shadowConfig, { background: 'red' }); + const newClassName = styleToClassName({}, shadowConfig, { + selectors: { + '& > *': className, }, - ); + }); expect(newClassName).toEqual('css-1'); expect(_stylesheet.getRules()).toEqual('.css-0{background:red;}.css-1 > *{background:red;}'); }); it('can register global selectors', () => { - const className = styleToClassName( - '__globalTest__', - {}, - { - selectors: { - ':global(button)': { background: 'red' }, - }, + const className = styleToClassName({}, shadowConfig, { + selectors: { + ':global(button)': { background: 'red' }, }, - ); + }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('button{background:red;}'); }); it('can register global selectors for a parent', () => { - const className = styleToClassName( - '__globalTest__', - {}, - { - selectors: { - '& :global(button)': { background: 'red' }, - }, + const className = styleToClassName({}, shadowConfig, { + selectors: { + '& :global(button)': { background: 'red' }, }, - ); + }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.css-0 button{background:red;}'); }); it('can register global selectors hover parent for a selector', () => { - const className = styleToClassName( - '__globalTest__', - {}, - { - selectors: { - ':global(.ms-button):hover &': { background: 'red' }, - }, + const className = styleToClassName({}, shadowConfig, { + selectors: { + ':global(.ms-button):hover &': { background: 'red' }, }, - ); + }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.ms-button:hover .css-0{background:red;}'); }); it('can register multiple selectors within a global wrapper', () => { - const className = styleToClassName( - '__globalTest__', - {}, - { - selectors: { - ':global(.class1, .class2, .class3)': { top: 140 }, - }, + const className = styleToClassName({}, shadowConfig, { + selectors: { + ':global(.class1, .class2, .class3)': { top: 140 }, }, - ); + }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.class1{top:140px;}.class2{top:140px;}.class3{top:140px;}'); }); it('can register multiple selectors wrapped within a global wrappers', () => { - const className = styleToClassName( - '__globalTest__', - {}, - { - selectors: { - ':global(.class1), :global(.class2), :global(.class3)': { top: 140 }, - }, + const className = styleToClassName({}, shadowConfig, { + selectors: { + ':global(.class1), :global(.class2), :global(.class3)': { top: 140 }, }, - ); + }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.class1{top:140px;}.class2{top:140px;}.class3{top:140px;}'); }); it('can process a ":global(.class3, button)" selector', () => { - const className = styleToClassName( - '__globalTest__', - {}, - { - selectors: { - ':global(.class3, button)': { top: 140 }, - }, + const className = styleToClassName({}, shadowConfig, { + selectors: { + ':global(.class3, button)': { top: 140 }, }, - ); + }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.class3{top:140px;}button{top:140px;}'); }); it('can process a ":global(.class3 button)" selector', () => { - const className = styleToClassName( - '__globalTest__', - {}, - { - selectors: { - ':global(.class3 button)': { top: 140 }, - }, + const className = styleToClassName({}, shadowConfig, { + selectors: { + ':global(.class3 button)': { top: 140 }, }, - ); + }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.class3 button{top:140px;}'); }); it('can process a "button:focus, :global(.class1, .class2, .class3)" selector', () => { - const className = styleToClassName( - '__globalTest__', - {}, - { - selectors: { - 'button:focus, :global(.class1, .class2, .class3)': { top: 140 }, - }, + const className = styleToClassName({}, shadowConfig, { + selectors: { + 'button:focus, :global(.class1, .class2, .class3)': { top: 140 }, }, - ); + }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual( @@ -409,15 +324,11 @@ describe('styleToClassName', () => { }); it('can process a complex multiple global selector', () => { - const className = styleToClassName( - '__globalTest__', - {}, - { - selectors: { - ':global(.css20, .css50, #myId) button:hover :global(.class1, .class2, .class3)': { top: 140 }, - }, + const className = styleToClassName({}, shadowConfig, { + selectors: { + ':global(.css20, .css50, #myId) button:hover :global(.class1, .class2, .class3)': { top: 140 }, }, - ); + }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual( @@ -427,44 +338,36 @@ describe('styleToClassName', () => { }); it('can expand an array of rules', () => { - styleToClassName('__globalTest__', {}, [{ background: 'red' }, { background: 'white' }]); + styleToClassName({}, shadowConfig, [{ background: 'red' }, { background: 'white' }]); expect(_stylesheet.getRules()).toEqual('.css-0{background:white;}'); }); it('can expand increased specificity rules', () => { - styleToClassName( - '__globalTest__', - {}, - { - selectors: { - '&&&': { - background: 'red', - }, + styleToClassName({}, shadowConfig, { + selectors: { + '&&&': { + background: 'red', }, }, - ); + }); expect(_stylesheet.getRules()).toEqual('.css-0.css-0.css-0{background:red;}'); }); it('can apply media queries', () => { - styleToClassName( - '__globalTest__', - {}, - { - background: 'blue', - selectors: { - '@media(min-width: 300px)': { - background: 'red', - selectors: { - ':hover': { - background: 'green', - }, + styleToClassName({}, shadowConfig, { + background: 'blue', + selectors: { + '@media(min-width: 300px)': { + background: 'red', + selectors: { + ':hover': { + background: 'green', }, }, }, }, - ); + }); expect(_stylesheet.getRules()).toEqual( '.css-0{background:blue;}' + @@ -478,30 +381,22 @@ describe('styleToClassName', () => { }); it('can apply @support queries', () => { - styleToClassName( - '__globalTest__', - {}, - { - selectors: { - '@supports(display: grid)': { - display: 'grid', - }, + styleToClassName({}, shadowConfig, { + selectors: { + '@supports(display: grid)': { + display: 'grid', }, }, - ); + }); expect(_stylesheet.getRules()).toEqual('@supports(display: grid){' + '.css-0{display:grid;}' + '}'); }); it('ignores undefined property values', () => { - styleToClassName( - '__globalTest__', - {}, - { - background: 'red', - color: undefined, - }, - ); + styleToClassName({}, shadowConfig, { + background: 'red', + color: undefined, + }); expect(_stylesheet.getRules()).toEqual('.css-0{background:red;}'); }); @@ -514,14 +409,14 @@ describe('styleToClassName with specificityMultiplier', () => { }); it('can repeat classname', () => { - const className = styleToClassName('__globalTest__', options, { background: 'red' }); + const className = styleToClassName(options, shadowConfig, { background: 'red' }); expect(className).toEqual('css-0'); expect(_stylesheet.getRules()).toEqual('.css-0.css-0{background:red;}'); }); it('can repeat classname when have child selectors', () => { - styleToClassName('__globalTest__', options, { + styleToClassName(options, shadowConfig, { selectors: { '.foo': { background: 'red' }, }, @@ -531,7 +426,7 @@ describe('styleToClassName with specificityMultiplier', () => { }); it('can repeat classname when have child selectors with comma', () => { - styleToClassName('__globalTest__', options, { + styleToClassName(options, shadowConfig, { selectors: { '.foo, .bar': { background: 'red' }, }, @@ -541,7 +436,7 @@ describe('styleToClassName with specificityMultiplier', () => { }); it('can repeat classname when have child selectors with comma with pseudo selectors', () => { - styleToClassName('__globalTest__', options, { + styleToClassName(options, shadowConfig, { selectors: { ':hover, :active': { background: 'red' }, }, @@ -551,7 +446,7 @@ describe('styleToClassName with specificityMultiplier', () => { }); it('can repeat classname when have child selectors with comma with @media query', () => { - styleToClassName('__globalTest__', options, { + styleToClassName(options, shadowConfig, { selectors: { '@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none)': { background: 'red', @@ -565,7 +460,7 @@ describe('styleToClassName with specificityMultiplier', () => { }); it('do not repeat classname when have global selector', () => { - const className = styleToClassName('__globalTest__', options, { + const className = styleToClassName(options, shadowConfig, { selectors: { ':global(.class1)': { background: 'red' }, }, @@ -576,13 +471,9 @@ describe('styleToClassName with specificityMultiplier', () => { }); it('handles numeric 0 in props with shorthand syntax (margin, padding)', () => { - styleToClassName( - '__globalTest__', - {}, - { - margin: 0, - }, - ); + styleToClassName({}, shadowConfig, { + margin: 0, + }); expect(_stylesheet.getRules()).toEqual( '.css-0{margin-top:0px;margin-right:0px;margin-bottom:0px;margin-left:0px;}', @@ -590,14 +481,10 @@ describe('styleToClassName with specificityMultiplier', () => { }); it('handles calc(...) in props with shorthand syntax (margin, padding)', () => { - styleToClassName( - '__globalTest__', - {}, - { - padding: 'calc(24px / 2) 0', - margin: '0 2px calc(2 * (var(--a) + var(--b))) ', - }, - ); + styleToClassName({}, shadowConfig, { + padding: 'calc(24px / 2) 0', + margin: '0 2px calc(2 * (var(--a) + var(--b))) ', + }); expect(_stylesheet.getRules()).toEqual( '.css-0{' + @@ -614,14 +501,10 @@ describe('styleToClassName with specificityMultiplier', () => { }); it('handles !important in props with shorthand syntax (margin, padding)', () => { - styleToClassName( - '__globalTest__', - {}, - { - padding: '42px !important', - margin: ' 0 2px calc(2 * (var(--a) + var(--b))) !important ', - }, - ); + styleToClassName({}, shadowConfig, { + padding: '42px !important', + margin: ' 0 2px calc(2 * (var(--a) + var(--b))) !important ', + }); expect(_stylesheet.getRules()).toEqual( '.css-0{' + From 4fde94436bb39025c7009a9818c9d5bf959bd120 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 10 Aug 2023 21:18:55 +0000 Subject: [PATCH 008/198] fix build --- packages/merge-styles/etc/merge-styles.api.md | 19 +++++++++++-------- packages/merge-styles/src/mergeStyleSets.ts | 6 +++--- packages/merge-styles/src/mergeStyles.ts | 7 ++++--- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/merge-styles/etc/merge-styles.api.md b/packages/merge-styles/etc/merge-styles.api.md index a14050a28c42a..fa30d27261e6c 100644 --- a/packages/merge-styles/etc/merge-styles.api.md +++ b/packages/merge-styles/etc/merge-styles.api.md @@ -102,6 +102,8 @@ export const InjectionMode: { insertNode: 1; appendChild: 2; unstable_constructibleStylesheet: 3; + insertNodeAndConstructableStylesheet: 4; + appedChildAndConstructableStylesheet: 5; }; // @public (undocumented) @@ -486,22 +488,23 @@ export interface IStyleSheetConfig { export function keyframes(timeline: IKeyframes): string; // Warning: (ae-forgotten-export) The symbol "IStyleOptions" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "ShadowConfig" needs to be exported by the entry point index.d.ts // // @public -export function mergeCss(args: (IStyle | IStyleBaseArray | false | null | undefined) | (IStyle | IStyleBaseArray | false | null | undefined)[], options?: IStyleOptions, stylesheetKey?: string): string; +export function mergeCss(args: (IStyle | IStyleBaseArray | false | null | undefined) | (IStyle | IStyleBaseArray | false | null | undefined)[], options?: IStyleOptions, shadowConfig?: ShadowConfig): string; // @public -export function mergeCssSets(styleSets: [TStyleSet | false | null | undefined], options?: IStyleOptions, stylesheetKey?: string): IProcessedStyleSet; +export function mergeCssSets(styleSets: [TStyleSet | false | null | undefined], options?: IStyleOptions, shadowConfig?: ShadowConfig): IProcessedStyleSet; // @public -export function mergeCssSets(styleSets: [TStyleSet1 | false | null | undefined, TStyleSet2 | false | null | undefined], options?: IStyleOptions, stylesheetKey?: string): IProcessedStyleSet; +export function mergeCssSets(styleSets: [TStyleSet1 | false | null | undefined, TStyleSet2 | false | null | undefined], options?: IStyleOptions, shadowConfig?: ShadowConfig): IProcessedStyleSet; // @public export function mergeCssSets(styleSets: [ TStyleSet1 | false | null | undefined, TStyleSet2 | false | null | undefined, TStyleSet3 | false | null | undefined -], options?: IStyleOptions, stylesheetKey?: string): IProcessedStyleSet; +], options?: IStyleOptions, shadowConfig?: ShadowConfig): IProcessedStyleSet; // @public export function mergeCssSets(styleSets: [ @@ -509,10 +512,10 @@ TStyleSet1 | false | null | undefined, TStyleSet2 | false | null | undefined, TStyleSet3 | false | null | undefined, TStyleSet4 | false | null | undefined -], options?: IStyleOptions, stylesheetKey?: string): IProcessedStyleSet & ObjectOnly & ObjectOnly & ObjectOnly>; +], options?: IStyleOptions, shadowConfig?: ShadowConfig): IProcessedStyleSet & ObjectOnly & ObjectOnly & ObjectOnly>; // @public -export function mergeCssSets(styleSet: [TStyleSet | false | null | undefined], options?: IStyleOptions, stylesheetKey?: string): IProcessedStyleSet; +export function mergeCssSets(styleSet: [TStyleSet | false | null | undefined], options?: IStyleOptions, shadowConfig?: ShadowConfig): IProcessedStyleSet; // @public export function mergeStyles(...args: (IStyle | IStyleBaseArray | false | null | undefined)[]): string; @@ -533,7 +536,7 @@ export function mergeStyleSets(s export function mergeStyleSets(...styleSets: Array): IProcessedStyleSet; // @public (undocumented) -export function mergeStyleSets(stylesheetKey: string, ...styleSets: Array): IProcessedStyleSet; +export function mergeStyleSets(shadowConfig: ShadowConfig, ...styleSets: Array): IProcessedStyleSet; // @public (undocumented) export type ObjectOnly = TArg extends {} ? TArg : {}; @@ -559,7 +562,7 @@ export class Stylesheet { getClassNameCache(): { [key: string]: string; }; - static getInstance(stylesheetKey?: string): Stylesheet; + static getInstance({ stylesheetKey, inShadow }?: ShadowConfig): Stylesheet; getRules(includePreservedRules?: boolean): string; insertedRulesFromClassName(className: string): string[] | undefined; insertRule(rule: string, preserve?: boolean): void; diff --git a/packages/merge-styles/src/mergeStyleSets.ts b/packages/merge-styles/src/mergeStyleSets.ts index 8144ef5faad46..69f198ff8bf6d 100644 --- a/packages/merge-styles/src/mergeStyleSets.ts +++ b/packages/merge-styles/src/mergeStyleSets.ts @@ -98,11 +98,11 @@ export function mergeStyleSets( * @param styleSets - One or more style sets to be merged. */ export function mergeStyleSets(...styleSets: Array): IProcessedStyleSet { - let shadowConfig = undefined; + let shadowConfig: ShadowConfig | undefined = undefined; let sets = styleSets; const first = styleSets[0]; - if (first && typeof first.stylesheetKey === 'string') { - shadowConfig = styleSets[0]; + if (first && first.hasOwnProperty('stylesheetKey')) { + shadowConfig = styleSets[0] as ShadowConfig; sets = styleSets.slice(1); } diff --git a/packages/merge-styles/src/mergeStyles.ts b/packages/merge-styles/src/mergeStyles.ts index aa4816f4f1f89..f4f9bf37a7e21 100644 --- a/packages/merge-styles/src/mergeStyles.ts +++ b/packages/merge-styles/src/mergeStyles.ts @@ -1,6 +1,7 @@ import { extractStyleParts } from './extractStyleParts'; import { IStyle, IStyleBaseArray } from './IStyle'; import { IStyleOptions } from './IStyleOptions'; +import { ShadowConfig } from './mergeStyleSets'; import { getStyleOptions } from './StyleOptionsState'; import { styleToClassName } from './styleToClassName'; @@ -22,13 +23,13 @@ export function mergeStyles(...args: (IStyle | IStyleBaseArray | false | null | export function mergeCss( args: (IStyle | IStyleBaseArray | false | null | undefined) | (IStyle | IStyleBaseArray | false | null | undefined)[], options?: IStyleOptions, - stylesheetKey?: string, + shadowConfig?: ShadowConfig, ): string { const styleArgs = args instanceof Array ? args : [args]; - const { classes, objects } = extractStyleParts(stylesheetKey, styleArgs); + const { classes, objects } = extractStyleParts(shadowConfig, styleArgs); if (objects.length) { - classes.push(styleToClassName(options || {}, stylesheetKey, objects)); + classes.push(styleToClassName(options || {}, shadowConfig, objects)); } return classes.join(' '); From bcc181cb2a1feafa79cce303a2c964ace3b3ec60 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 10 Aug 2023 21:31:28 +0000 Subject: [PATCH 009/198] update api snapshot --- packages/utilities/etc/utilities.api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/utilities/etc/utilities.api.md b/packages/utilities/etc/utilities.api.md index c63cfeebe6919..38545f213665b 100644 --- a/packages/utilities/etc/utilities.api.md +++ b/packages/utilities/etc/utilities.api.md @@ -130,7 +130,7 @@ export function calculatePrecision(value: number | string): number; export function canUseDOM(): boolean; // @public -export function classNamesFunction>(options?: IClassNamesFunctionOptions): (getStyles: IStyleFunctionOrObject | undefined, styleProps?: TStyleProps) => IProcessedStyleSet; +export function classNamesFunction>(options?: IClassNamesFunctionOptions): (getStyles: IStyleFunctionOrObject | undefined, styleProps?: TStyleProps, stylesheetKey?: string) => IProcessedStyleSet; // @public (undocumented) export const colGroupProperties: Record; @@ -1275,7 +1275,7 @@ export const trProperties: Record; export function unhoistMethods(source: any, methodNames: string[]): void; // @public -export const useAdoptedStylesheet_unstable: (stylesheetKey: string) => void; +export const useAdoptedStylesheet_unstable: (stylesheetKey: string, adopteGlobally?: boolean) => string | undefined; // @public export function useCustomizationSettings(properties: string[], scopeName?: string): ISettings; From 254c523dc3b5f12c814d5a3ca4aa8b46d6de644b Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 10 Aug 2023 23:55:58 +0000 Subject: [PATCH 010/198] refactor shadow dom configuration passing --- packages/merge-styles/etc/merge-styles.api.md | 6 +- packages/merge-styles/src/IStyleSet.ts | 6 +- packages/react/etc/react.api.md | 5 +- .../Button/BaseButton.classNames.ts | 167 +++++++++--------- .../src/components/Button/BaseButton.tsx | 4 +- .../src/components/Button/Button.types.ts | 7 +- packages/utilities/etc/utilities.api.md | 5 +- packages/utilities/src/classNamesFunction.ts | 12 +- .../src/customizations/customizable.tsx | 36 +++- .../MergeStylesShadowRootContext.tsx | 11 +- packages/utilities/src/styled.tsx | 35 ++-- 11 files changed, 160 insertions(+), 134 deletions(-) diff --git a/packages/merge-styles/etc/merge-styles.api.md b/packages/merge-styles/etc/merge-styles.api.md index fa30d27261e6c..8ffc7ae11eaef 100644 --- a/packages/merge-styles/etc/merge-styles.api.md +++ b/packages/merge-styles/etc/merge-styles.api.md @@ -64,7 +64,7 @@ export function fontFace(font: IFontFace): void; // // @public export type IConcatenatedStyleSet> = { - [P in keyof Omit_2]: IStyle; + [P in keyof Omit_2]: IStyle; } & { subComponentStyles?: { [P in keyof TStyleSet['subComponentStyles']]: IStyleFunction; @@ -111,7 +111,7 @@ export type InjectionMode = (typeof InjectionMode)[keyof typeof InjectionMode]; // @public export type IProcessedStyleSet> = { - [P in keyof Omit_2]: string; + [P in keyof Omit_2]: string; } & { subComponentStyles: { [P in keyof TStyleSet['subComponentStyles']]: __MapToFunctionType; @@ -463,7 +463,7 @@ export type IStyleFunctionOrObject = { [key: string]: any; }> = { - [P in keyof Omit_2]: IStyle; + [P in keyof Omit_2]: IStyle; } & { subComponentStyles?: { [P in keyof TStyleSet['subComponentStyles']]: IStyleFunctionOrObject; diff --git a/packages/merge-styles/src/IStyleSet.ts b/packages/merge-styles/src/IStyleSet.ts index bf95a614e31bf..bcd85ae299e05 100644 --- a/packages/merge-styles/src/IStyleSet.ts +++ b/packages/merge-styles/src/IStyleSet.ts @@ -30,7 +30,7 @@ export type __MapToFunctionType = Extract extends never */ export type IStyleSet = { [key: string]: any }> = { // eslint-disable-next-line deprecation/deprecation - [P in keyof Omit]: IStyle; + [P in keyof Omit]: IStyle; } & { subComponentStyles?: { [P in keyof TStyleSet['subComponentStyles']]: IStyleFunctionOrObject }; } & IStylesheetKey; @@ -40,7 +40,7 @@ export type IStyleSet = { [key: string]: */ export type IConcatenatedStyleSet> = { // eslint-disable-next-line deprecation/deprecation - [P in keyof Omit]: IStyle; + [P in keyof Omit]: IStyle; } & { subComponentStyles?: { [P in keyof TStyleSet['subComponentStyles']]: IStyleFunction }; } & IStylesheetKey; @@ -51,7 +51,7 @@ export type IConcatenatedStyleSet> = { */ export type IProcessedStyleSet> = { // eslint-disable-next-line deprecation/deprecation - [P in keyof Omit]: string; + [P in keyof Omit]: string; } & { subComponentStyles: { [P in keyof TStyleSet['subComponentStyles']]: __MapToFunctionType< diff --git a/packages/react/etc/react.api.md b/packages/react/etc/react.api.md index adc6d91f674eb..dbc3bb467fa6c 100644 --- a/packages/react/etc/react.api.md +++ b/packages/react/etc/react.api.md @@ -354,6 +354,7 @@ import { Settings } from '@fluentui/utilities'; import { SettingsFunction } from '@fluentui/utilities'; import { setVirtualParent } from '@fluentui/utilities'; import { setWarningCallback } from '@fluentui/utilities'; +import { ShadowConfig } from '@fluentui/merge-styles/lib/mergeStyleSets'; import { shallowCompare } from '@fluentui/utilities'; import { SharedColors } from '@fluentui/theme'; import { shouldWrapFocus } from '@fluentui/utilities'; @@ -2706,6 +2707,8 @@ export interface IButtonProps extends React_2.AllHTMLAttributes | React_2.AnchorHTMLAttributes; secondaryText?: string; + // (undocumented) + shadowDom?: ShadowConfig; split?: boolean; splitButtonAriaLabel?: string; splitButtonMenuProps?: IButtonProps; @@ -2720,8 +2723,6 @@ export interface IButtonProps extends React_2.AllHTMLAttributes { const classNames = getGlobalClassNames(ButtonGlobalClassNames, theme || {}); const isExpanded = expanded && !isSplit; - return mergeStyleSets( - { stylesheetKey, inShadow }, - { - root: [ - classNames.msButton, - styles.root, - variantClassName, - checked && ['is-checked', styles.rootChecked], - isExpanded && [ - 'is-expanded', - styles.rootExpanded, - { - selectors: { - [`:hover .${classNames.msButtonIcon}`]: styles.iconExpandedHovered, - // menuIcon falls back to rootExpandedHovered to support original behavior - [`:hover .${classNames.msButtonMenuIcon}`]: - styles.menuIconExpandedHovered || styles.rootExpandedHovered, - ':hover': styles.rootExpandedHovered, - }, + return mergeStyleSets(shadowDom, { + root: [ + classNames.msButton, + styles.root, + variantClassName, + checked && ['is-checked', styles.rootChecked], + isExpanded && [ + 'is-expanded', + styles.rootExpanded, + { + selectors: { + [`:hover .${classNames.msButtonIcon}`]: styles.iconExpandedHovered, + // menuIcon falls back to rootExpandedHovered to support original behavior + [`:hover .${classNames.msButtonMenuIcon}`]: styles.menuIconExpandedHovered || styles.rootExpandedHovered, + ':hover': styles.rootExpandedHovered, }, - ], - hasMenu && [ButtonGlobalClassNames.msButtonHasMenu, styles.rootHasMenu], - disabled && ['is-disabled', styles.rootDisabled], - !disabled && - !isExpanded && - !checked && { - selectors: { - ':hover': styles.rootHovered, - [`:hover .${classNames.msButtonLabel}`]: styles.labelHovered, - [`:hover .${classNames.msButtonIcon}`]: styles.iconHovered, - [`:hover .${classNames.msButtonDescription}`]: styles.descriptionHovered, - [`:hover .${classNames.msButtonMenuIcon}`]: styles.menuIconHovered, - ':focus': styles.rootFocused, - ':active': styles.rootPressed, - [`:active .${classNames.msButtonIcon}`]: styles.iconPressed, - [`:active .${classNames.msButtonDescription}`]: styles.descriptionPressed, - [`:active .${classNames.msButtonMenuIcon}`]: styles.menuIconPressed, - }, + }, + ], + hasMenu && [ButtonGlobalClassNames.msButtonHasMenu, styles.rootHasMenu], + disabled && ['is-disabled', styles.rootDisabled], + !disabled && + !isExpanded && + !checked && { + selectors: { + ':hover': styles.rootHovered, + [`:hover .${classNames.msButtonLabel}`]: styles.labelHovered, + [`:hover .${classNames.msButtonIcon}`]: styles.iconHovered, + [`:hover .${classNames.msButtonDescription}`]: styles.descriptionHovered, + [`:hover .${classNames.msButtonMenuIcon}`]: styles.menuIconHovered, + ':focus': styles.rootFocused, + ':active': styles.rootPressed, + [`:active .${classNames.msButtonIcon}`]: styles.iconPressed, + [`:active .${classNames.msButtonDescription}`]: styles.descriptionPressed, + [`:active .${classNames.msButtonMenuIcon}`]: styles.menuIconPressed, }, - disabled && checked && [styles.rootCheckedDisabled], - !disabled && - checked && { - selectors: { - ':hover': styles.rootCheckedHovered, - ':active': styles.rootCheckedPressed, - }, + }, + disabled && checked && [styles.rootCheckedDisabled], + !disabled && + checked && { + selectors: { + ':hover': styles.rootCheckedHovered, + ':active': styles.rootCheckedPressed, }, - className, - ], - flexContainer: [classNames.msButtonFlexContainer, styles.flexContainer], - textContainer: [classNames.msButtonTextContainer, styles.textContainer], - icon: [ - classNames.msButtonIcon, - iconClassName, - styles.icon, - isExpanded && styles.iconExpanded, - checked && styles.iconChecked, - disabled && styles.iconDisabled, - ], - label: [ - classNames.msButtonLabel, - styles.label, - checked && styles.labelChecked, - disabled && styles.labelDisabled, - ], - menuIcon: [ - classNames.msButtonMenuIcon, - menuIconClassName, - styles.menuIcon, - checked && styles.menuIconChecked, - disabled && !isSplit && styles.menuIconDisabled, - !disabled && - !isExpanded && - !checked && { - selectors: { - ':hover': styles.menuIconHovered, - ':active': styles.menuIconPressed, - }, + }, + className, + ], + flexContainer: [classNames.msButtonFlexContainer, styles.flexContainer], + textContainer: [classNames.msButtonTextContainer, styles.textContainer], + icon: [ + classNames.msButtonIcon, + iconClassName, + styles.icon, + isExpanded && styles.iconExpanded, + checked && styles.iconChecked, + disabled && styles.iconDisabled, + ], + label: [classNames.msButtonLabel, styles.label, checked && styles.labelChecked, disabled && styles.labelDisabled], + menuIcon: [ + classNames.msButtonMenuIcon, + menuIconClassName, + styles.menuIcon, + checked && styles.menuIconChecked, + disabled && !isSplit && styles.menuIconDisabled, + !disabled && + !isExpanded && + !checked && { + selectors: { + ':hover': styles.menuIconHovered, + ':active': styles.menuIconPressed, }, - isExpanded && ['is-expanded', styles.menuIconExpanded], - ], - description: [ - classNames.msButtonDescription, - styles.description, - checked && styles.descriptionChecked, - disabled && styles.descriptionDisabled, - ], - screenReaderText: [classNames.msButtonScreenReaderText, styles.screenReaderText], - }, - ); + }, + isExpanded && ['is-expanded', styles.menuIconExpanded], + ], + description: [ + classNames.msButtonDescription, + styles.description, + checked && styles.descriptionChecked, + disabled && styles.descriptionDisabled, + ], + screenReaderText: [classNames.msButtonScreenReaderText, styles.screenReaderText], + }); }, ); diff --git a/packages/react/src/components/Button/BaseButton.tsx b/packages/react/src/components/Button/BaseButton.tsx index 2d475d3f4ee4f..186855075aad4 100644 --- a/packages/react/src/components/Button/BaseButton.tsx +++ b/packages/react/src/components/Button/BaseButton.tsx @@ -139,6 +139,7 @@ export class BaseButton extends React.Component>(options?: IClassNamesFunctionOptions): (getStyles: IStyleFunctionOrObject | undefined, styleProps?: TStyleProps, stylesheetKey?: string) => IProcessedStyleSet; +export function classNamesFunction>(options?: IClassNamesFunctionOptions): (getStyles: IStyleFunctionOrObject | undefined, styleProps?: TStyleProps, shadowDom?: ShadowConfig) => IProcessedStyleSet; // @public (undocumented) export const colGroupProperties: Record; @@ -1275,7 +1276,7 @@ export const trProperties: Record; export function unhoistMethods(source: any, methodNames: string[]): void; // @public -export const useAdoptedStylesheet_unstable: (stylesheetKey: string, adopteGlobally?: boolean) => string | undefined; +export const useAdoptedStylesheet_unstable: (stylesheetKey: string, adopteGlobally?: boolean) => boolean; // @public export function useCustomizationSettings(properties: string[], scopeName?: string): ISettings; diff --git a/packages/utilities/src/classNamesFunction.ts b/packages/utilities/src/classNamesFunction.ts index 10d663d1c4f81..048aae3af2041 100644 --- a/packages/utilities/src/classNamesFunction.ts +++ b/packages/utilities/src/classNamesFunction.ts @@ -3,6 +3,7 @@ import { getRTL } from './rtl'; import { getWindow } from './dom'; import type { IStyleSet, IProcessedStyleSet, IStyleFunctionOrObject } from '@fluentui/merge-styles'; import type { StyleFunction } from './styled'; +import { ShadowConfig } from '@fluentui/merge-styles/lib/mergeStyleSets'; const MAX_CACHE_COUNT = 50; const DEFAULT_SPECIFICITY_MULTIPLIER = 5; @@ -59,7 +60,7 @@ export function classNamesFunction | undefined, styleProps?: TStyleProps, - stylesheetKey?: string, + shadowDom?: ShadowConfig, ) => IProcessedStyleSet { // We build a trie where each node is a Map. The map entry key represents an argument // value, and the entry value is another node (Map). Each node has a `__retval__` @@ -76,7 +77,7 @@ export function classNamesFunction | undefined, styleProps: TStyleProps = {} as TStyleProps, - stylesheetKey?: string, + shadowDom?: ShadowConfig, ): IProcessedStyleSet => { // If useStaticStyles is true, styleFunctionOrObject returns slot to classname mappings. // If there is also no style overrides, we can skip merge styles completely and @@ -119,12 +120,7 @@ export function classNamesFunction, ], { rtl: !!rtl, specificityMultiplier: options.useStaticStyles ? DEFAULT_SPECIFICITY_MULTIPLIER : undefined }, - // eslint-disable-next-line - // @ts-ignore - styleFunctionOrObject.__stylesheetKey__, - // eslint-disable-next-line - // @ts-ignore - styleFunctionOrObject.__inShadow__, + shadowDom, ); } diff --git a/packages/utilities/src/customizations/customizable.tsx b/packages/utilities/src/customizations/customizable.tsx index 32021f01f1596..71ec2b3f672cf 100644 --- a/packages/utilities/src/customizations/customizable.tsx +++ b/packages/utilities/src/customizations/customizable.tsx @@ -5,6 +5,7 @@ import { CustomizerContext } from './CustomizerContext'; import { concatStyleSets } from '@fluentui/merge-styles'; import type { ICustomizerContext } from './CustomizerContext'; import { MergeStylesShadowRootConsumer } from '../shadowDom/MergeStylesShadowRootContext'; +import { ShadowConfig } from '@fluentui/merge-styles/lib/mergeStyleSets'; export function customizable( scope: string, @@ -20,6 +21,8 @@ export function customizable( // eslint-disable-next-line @typescript-eslint/no-explicit-any private _styleCache: { default?: any; component?: any; merged?: any } = {}; + private _shadowDom: ShadowConfig | undefined; + constructor(props: P) { super(props); @@ -43,6 +46,17 @@ export function customizable( {(context: ICustomizerContext) => { const defaultProps = Customizations.getSettings(fields, scope, context.customizations); + if ( + !this._shadowDom || + this._shadowDom.stylesheetKey !== scope || + this._shadowDom.inShadow !== inShadow + ) { + this._shadowDom = { + stylesheetKey: scope, + inShadow, + }; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any const componentProps = this.props as any; @@ -61,17 +75,29 @@ export function customizable( this._styleCache.default = defaultProps.styles; this._styleCache.component = componentProps.styles; this._styleCache.merged = mergedStyles; - this._styleCache.merged.__stylesheetKey__ = scope; - this._styleCache.merged.__inShadow__ = inShadow; + // this._styleCache.merged.__stylesheetKey__ = scope; + // this._styleCache.merged.__inShadow__ = inShadow; } return ( - + ); } - const styles = { ...defaultProps.styles, ...componentProps.styles, __stylesheetKey__: scope }; - return ; + const styles = { ...defaultProps.styles, ...componentProps.styles }; + return ( + + ); }} ); diff --git a/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx b/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx index 29fe0178283a4..ffe0156cfe2a1 100644 --- a/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx +++ b/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx @@ -77,16 +77,13 @@ export const MergeStylesShadowRootConsumer: React.FC { +export const useAdoptedStylesheet_unstable = (stylesheetKey: string, adopteGlobally: boolean = false): boolean => { const shadowCtx = useMergeStylesShadowRootContext_unstable(); const rootMergeStyles = useMergeStylesRootStylesheets_unstable(); // console.log('useAdoptedStylesheets', stylesheetKey); if (!shadowCtx) { - return undefined; + return false; } if (adopteGlobally) { @@ -107,11 +104,11 @@ export const useAdoptedStylesheet_unstable = ( } } - return stylesheetKey; + return true; }; export const useHasMergeStylesShadowRootContext = () => { - return !!useMergeStylesRootStylesheets_unstable(); + return !!useMergeStylesShadowRootContext_unstable(); }; export const useMergeStylesShadowRootContext_unstable = () => { diff --git a/packages/utilities/src/styled.tsx b/packages/utilities/src/styled.tsx index 6f448fd633633..62948c3e3c704 100644 --- a/packages/utilities/src/styled.tsx +++ b/packages/utilities/src/styled.tsx @@ -6,6 +6,7 @@ import { } from './shadowDom/MergeStylesShadowRootContext'; import { useCustomizationSettings } from './customizations/useCustomizationSettings'; import type { IStyleSet, IStyleFunctionOrObject } from '@fluentui/merge-styles'; +import { ShadowConfig } from '@fluentui/merge-styles/lib/mergeStyleSets'; export interface IPropsWithStyles> { styles?: IStyleFunctionOrObject; @@ -92,8 +93,6 @@ export function styled< const { scope, fields = DefaultFields } = customizable; - const stylesheetKey = scope; // || '__global__'; - const Wrapped = React.forwardRef((props: TComponentProps, forwardedRef: React.Ref) => { const styles = React.useRef>(); @@ -102,6 +101,13 @@ export function styled< const additionalProps = getProps ? getProps(props) : undefined; const inShadow = useHasMergeStylesShadowRootContext(); + const shadowDom = React.useRef({ stylesheetKey: scope, inShadow }); + if (shadowDom.current.stylesheetKey !== scope || shadowDom.current.inShadow !== inShadow) { + shadowDom.current = { + stylesheetKey: scope, + inShadow, + }; + } // eslint-disable-next-line @typescript-eslint/no-explicit-any const cache = (styles.current && (styles.current as any).__cachedInputs__) || []; @@ -125,15 +131,15 @@ export function styled< !customizedStyles && !propStyles; styles.current = concatenatedStyles as StyleFunction; - // eslint-disable-next-line - // @ts-ignore - styles.current.__stylesheetKey__ = stylesheetKey; - // eslint-disable-next-line - // @ts-ignore - styles.current.__inShadow__ = inShadow; + // // eslint-disable-next-line + // // @ts-ignore + // styles.current.__stylesheetKey__ = stylesheetKey; + // // eslint-disable-next-line + // // @ts-ignore + // styles.current.__inShadow__ = inShadow; } - useAdoptedStylesheet_unstable(stylesheetKey); + useAdoptedStylesheet_unstable(scope); // const inShadow = useAdoptedStylesheet_unstable(stylesheetKey); // if (styles.current) { @@ -142,7 +148,16 @@ export function styled< // styles.current.__stylesheetKey__ = inShadow ? stylesheetKey : undefined; // } - return ; + return ( + + ); }); // Function.prototype.name is an ES6 feature, so the cast to any is required until we're // able to drop IE 11 support and compile with ES6 libs From ed70d2a805fb29369ffecc809d1e530f573a199b Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Fri, 11 Aug 2023 00:46:47 +0000 Subject: [PATCH 011/198] add shadow dom exports --- packages/react/src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 5f3d49abe0686..db78e907935dc 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1340,6 +1340,9 @@ export { mergeCustomizations, mergeScopedSettings, mergeSettings, + MergeStylesShadowRootProvider_unstable, + useAdoptedStylesheet_unstable, + MergeStylesRootProvider_unstable, modalize, nullRender, olProperties, From dc0af67e2132ecbd002c9269241e86798e41a24a Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Fri, 11 Aug 2023 01:40:57 +0000 Subject: [PATCH 012/198] plumb shadow dom config through --- packages/merge-styles/src/styleToClassName.ts | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/merge-styles/src/styleToClassName.ts b/packages/merge-styles/src/styleToClassName.ts index f080fac8c18cf..76c52fe3c72b6 100644 --- a/packages/merge-styles/src/styleToClassName.ts +++ b/packages/merge-styles/src/styleToClassName.ts @@ -85,24 +85,35 @@ function expandSelector(newSelector: string, currentSelector: string): string { return newSelector; } -function extractSelector(currentSelector: string, rules: IRuleSet = { __order: [] }, selector: string, value: IStyle) { +function extractSelector( + currentSelector: string, + rules: IRuleSet = { __order: [] }, + selector: string, + value: IStyle, + shadowConfig?: ShadowConfig, +) { if (selector.indexOf('@') === 0) { selector = selector + '{' + currentSelector; - extractRules([value], rules, selector); + extractRules([value], rules, selector, shadowConfig); } else if (selector.indexOf(',') > -1) { expandCommaSeparatedGlobals(selector) .split(',') .map((s: string) => s.trim()) .forEach((separatedSelector: string) => - extractRules([value], rules, expandSelector(separatedSelector, currentSelector)), + extractRules([value], rules, expandSelector(separatedSelector, currentSelector), shadowConfig), ); } else { - extractRules([value], rules, expandSelector(selector, currentSelector)); + extractRules([value], rules, expandSelector(selector, currentSelector), shadowConfig); } } -function extractRules(args: IStyle[], rules: IRuleSet = { __order: [] }, currentSelector: string = '&'): IRuleSet { - const stylesheet = Stylesheet.getInstance(); +function extractRules( + args: IStyle[], + rules: IRuleSet = { __order: [] }, + currentSelector: string = '&', + shadowConfig?: ShadowConfig, +): IRuleSet { + const stylesheet = Stylesheet.getInstance(shadowConfig); let currentRules: IDictionary | undefined = rules[currentSelector] as IDictionary; if (!currentRules) { @@ -117,11 +128,11 @@ function extractRules(args: IStyle[], rules: IRuleSet = { __order: [] }, current const expandedRules = stylesheet.argsFromClassName(arg); if (expandedRules) { - extractRules(expandedRules, rules, currentSelector); + extractRules(expandedRules, rules, currentSelector, shadowConfig); } // Else if the arg is an array, we need to recurse in. } else if (Array.isArray(arg)) { - extractRules(arg, rules, currentSelector); + extractRules(arg, rules, currentSelector, shadowConfig); } else { for (const prop in arg as any) { if ((arg as any).hasOwnProperty(prop)) { @@ -133,13 +144,13 @@ function extractRules(args: IStyle[], rules: IRuleSet = { __order: [] }, current for (const newSelector in selectors) { if (selectors.hasOwnProperty(newSelector)) { - extractSelector(currentSelector, rules, newSelector, selectors[newSelector]); + extractSelector(currentSelector, rules, newSelector, selectors[newSelector], shadowConfig); } } } else if (typeof propValue === 'object') { // prop is a selector. if (propValue !== null) { - extractSelector(currentSelector, rules, prop, propValue); + extractSelector(currentSelector, rules, prop, propValue, shadowConfig); } } else { if (propValue !== undefined) { @@ -249,7 +260,7 @@ export function styleToRegistration( shadowConfig?: ShadowConfig, ...args: IStyle[] ): IRegistration | undefined { - const rules: IRuleSet = extractRules(args); + const rules: IRuleSet = extractRules(args, undefined, undefined, shadowConfig); const key = getKeyForRules(options, rules); if (key) { From 6bcf9fb9cf240555582e77474a2c971dfb5999cf Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Fri, 11 Aug 2023 01:41:21 +0000 Subject: [PATCH 013/198] update api snapshot --- packages/react/etc/react.api.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/react/etc/react.api.md b/packages/react/etc/react.api.md index dbc3bb467fa6c..881bcb524cf10 100644 --- a/packages/react/etc/react.api.md +++ b/packages/react/etc/react.api.md @@ -286,6 +286,8 @@ import { mergeScopedSettings } from '@fluentui/utilities'; import { mergeSettings } from '@fluentui/utilities'; import { mergeStyles } from '@fluentui/style-utilities'; import { mergeStyleSets } from '@fluentui/style-utilities'; +import { MergeStylesRootProvider_unstable } from '@fluentui/utilities'; +import { MergeStylesShadowRootProvider_unstable } from '@fluentui/utilities'; import { mergeThemes } from '@fluentui/theme'; import { modalize } from '@fluentui/utilities'; import { MonthOfYear } from '@fluentui/date-time-utilities'; @@ -373,6 +375,7 @@ import { toMatrix } from '@fluentui/utilities'; import { trProperties } from '@fluentui/utilities'; import { unhoistMethods } from '@fluentui/utilities'; import { unregisterIcons } from '@fluentui/style-utilities'; +import { useAdoptedStylesheet_unstable } from '@fluentui/utilities'; import { useCustomizationSettings } from '@fluentui/utilities'; import { useDocument } from '@fluentui/react-window-provider'; import { useFocusRects } from '@fluentui/utilities'; @@ -9943,6 +9946,10 @@ export { mergeStyles } export { mergeStyleSets } +export { MergeStylesRootProvider_unstable } + +export { MergeStylesShadowRootProvider_unstable } + export { mergeThemes } // @public (undocumented) @@ -11354,6 +11361,8 @@ export function updateSV(color: IColor, s: number, v: number): IColor; // @public export function updateT(color: IColor, t: number): IColor; +export { useAdoptedStylesheet_unstable } + export { useCustomizationSettings } export { useDocument } From b641f711223d4aa0804cbc98dd160e65ba3ff404 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Fri, 11 Aug 2023 02:57:09 +0000 Subject: [PATCH 014/198] publish more stuff from CI --- .codesandbox/ci.json | 7 +- .../react/Button/Button.Default.Example.tsx | 80 +++++++++++++------ 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index 2cd7dc2ea27a1..b1613a202efde 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -1,6 +1,11 @@ { "buildCommand": "build:codesandbox", - "packages": ["packages/react", "packages/react-components/react-components"], + "packages": [ + "packages/react", + "packages/react-components/react-components", + "packages/merge-styles", + "packages/utilities" + ], "sandboxes": ["x5u3t", "spnyu"], "node": "16" } diff --git a/packages/react-examples/src/react/Button/Button.Default.Example.tsx b/packages/react-examples/src/react/Button/Button.Default.Example.tsx index 493437382816e..a48d6271fa409 100644 --- a/packages/react-examples/src/react/Button/Button.Default.Example.tsx +++ b/packages/react-examples/src/react/Button/Button.Default.Example.tsx @@ -1,11 +1,17 @@ import * as React from 'react'; -import { Stack, IStackTokens, SpinButton, Text } from '@fluentui/react'; -import { DefaultButton, PrimaryButton } from '@fluentui/react/lib/Button'; import { + DefaultButton, + PrimaryButton, + Stack, + IStackTokens, + SpinButton, + Text, + FontIcon, + Icon, MergeStylesRootProvider_unstable, MergeStylesShadowRootProvider_unstable, - useAdoptedStylesheet_unstable, -} from '@fluentui/utilities'; +} from '@fluentui/react'; +import { CompassNWIcon, DictionaryIcon, TrainSolidIcon } from '@fluentui/react-icons-mdl2'; // eslint-disable-next-line import root from 'react-shadow'; @@ -18,40 +24,66 @@ export interface IButtonExampleProps { // Example formatting const stackTokens: IStackTokens = { childrenGap: 10 }; -export const ButtonDefaultExample: React.FunctionComponent = props => { - const [shadowRootEl, setShadowRootEl] = React.useState(null); +type TestCompProps = { + inShadow: boolean; +}; - const setter = val => { - setShadowRootEl(val); - }; +const TestComp: React.FC = ({ inShadow }) => { + const label = inShadow ? 'Shadow DOM' : 'Light DOM'; const [disabled, setDisabled] = React.useState(false); const onClick = e => { setDisabled(!disabled); }; + return ( + + {label} + {/* eslint-disable-next-line */} + + + + + FontIcons + + + + + + + + Icons + + + + + + + + SVG Icons + + + + + + + + ); +}; + +export const ButtonDefaultExample: React.FunctionComponent = props => { + const [shadowRootEl, setShadowRootEl] = React.useState(null); + return ( <> - + - - Shadow DOM - {/* eslint-disable-next-line */} - - - - + - - Light DOM - {/* eslint-disable-next-line */} - - - - + ); }; From 4e7b379d2c03b5019451e1378ee5006d11bf5359 Mon Sep 17 00:00:00 2001 From: Brendon Wai Date: Mon, 14 Aug 2023 14:44:02 -0700 Subject: [PATCH 015/198] support focus rect in shadow dom --- .../MergeStylesShadowRootContext.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx b/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx index ffe0156cfe2a1..7d1e33edb4e6d 100644 --- a/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx +++ b/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { useMergeStylesRootStylesheets_unstable } from './MergeStylesRootContext'; import { getDocument, getWindow } from '../dom'; +import { FocusRectsProvider } from '../FocusRectsProvider'; /** * NOTE: This API is unstable and subject to breaking change or removal without notice. @@ -27,22 +28,25 @@ export type MergeStylesShadowRootProviderProps = { * NOTE: This API is unstable and subject to breaking change or removal without notice. */ // eslint-disable-next-line @typescript-eslint/naming-convention -export const MergeStylesShadowRootProvider_unstable: React.FC = ({ - shadowRoot, - ...props -}) => { +export const MergeStylesShadowRootProvider_unstable: React.FC< + React.PropsWithChildren +> = ({ shadowRoot, ...props }) => { const value = React.useMemo(() => { return { stylesheets: new Map(), shadowRoot, }; }, [shadowRoot]); + const focusProviderRef = React.useRef(null); return ( - - /* - {props.children} - */ + + +
+ {props.children} +
+
+
); }; From e92d7701000d95277ba7d00c66cc030dde69e1c9 Mon Sep 17 00:00:00 2001 From: Brendon Wai Date: Mon, 14 Aug 2023 16:46:12 -0700 Subject: [PATCH 016/198] Address comments and add change file --- ...tui-utilities-27116d94-1a5f-4443-9287-25d1d27f63ca.json | 7 +++++++ .../src/shadowDom/MergeStylesShadowRootContext.tsx | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 change/@fluentui-utilities-27116d94-1a5f-4443-9287-25d1d27f63ca.json diff --git a/change/@fluentui-utilities-27116d94-1a5f-4443-9287-25d1d27f63ca.json b/change/@fluentui-utilities-27116d94-1a5f-4443-9287-25d1d27f63ca.json new file mode 100644 index 0000000000000..36e8196592dcd --- /dev/null +++ b/change/@fluentui-utilities-27116d94-1a5f-4443-9287-25d1d27f63ca.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Fix an issue where focus rect styles are not visible for components within shadow doms", + "packageName": "@fluentui/utilities", + "email": "brwai@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx b/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx index 7d1e33edb4e6d..f4110ea5fd8d8 100644 --- a/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx +++ b/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx @@ -42,7 +42,7 @@ export const MergeStylesShadowRootProvider_unstable: React.FC< return ( -
+
{props.children}
From 870fe426dd3662ab272c1ba3eea29b643c6ebd76 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 30 Aug 2023 15:09:19 -0700 Subject: [PATCH 017/198] mergeStyles: initial shadow DOM and constructable stylesheets implementation (#28906) * feat: update mergeStyles to support shadow DOM NOTE: this is stil a work in progress. - Refactors Stylesheet.ts to support shadow DOM - Adds React context and hooks for opting in to shadow DOM - Adds an API for projecting styles to child windows. * refactor and docs - Refactors stylesheet shadow DOM implementation a bit. - Adds a new docs page. * refactor to clean up types - Fixes various Typescript errors - Adds APIs to better encapsulate shadow DOM features * change files * add pipeline trigger * update test * fix typescript error * improve 'ShadowConfig' check * export ShadowConfig type * add constant for '__global__' * add default ShadowConfig * remove commented out line * better change file messages * remove IStyleSheetKey type * clean up * fix import path * add null checks before inserting styles * remove commented out code * refactor IStyleOptions to optionally include shadowConfig Adds an optional `shadowConfig` object to IStyleOptions. This allows us to pass the shadow DOM configuration object through to all the places we need it without modifying APIs to add a parameter for "shadowConfig". Aligns naming. Previously we had a type `ShadowConfig` and mostly referred to objects as "shadowConfig" but there were some instances of "shadowDom". Everything is now referred to by "shadowConfig/ShadowConfig". * update styleToClassName tests * update customizable tests * update component types for shadow DOM - remove "shadowDom" prop - update syles types to include "__shadowConfig__" key * remove unused variable * fix types in shadow dom example * add guard to calling requestAnimationFrame in Stylesheet This fix is for ssr-tests. --- azure-pipelines.yml | 1 + ...-8ad53e9f-3456-4924-b50d-48ffd979b2ce.json | 7 + ...-284eeed7-0d8c-4ebf-a44c-1e34467aca9b.json | 7 + ...-b73398fa-9e0a-4d05-97a4-7a4820a876c1.json | 7 + ...-3b3cdc9c-0e4f-4c68-aee6-9040e041186c.json | 7 + .../tsconfig.json | 2 +- packages/merge-styles/etc/merge-styles.api.md | 48 ++- packages/merge-styles/src/EventMap.ts | 58 ++++ packages/merge-styles/src/IStyleOptions.ts | 4 + packages/merge-styles/src/IStyleSet.ts | 9 +- packages/merge-styles/src/Stylesheet.ts | 286 ++++++++++++++++-- .../src/extractStyleParts.test.ts | 11 +- .../merge-styles/src/extractStyleParts.ts | 8 +- packages/merge-styles/src/index.ts | 3 + packages/merge-styles/src/mergeStyleSets.ts | 28 +- packages/merge-styles/src/mergeStyles.ts | 6 +- .../merge-styles/src/shadowConfig.test.ts | 83 +++++ packages/merge-styles/src/shadowConfig.ts | 32 ++ packages/merge-styles/src/styleToClassName.ts | 46 ++- .../ShadowDOM/ShadowDOM.Default.Example.tsx | 146 +++++++++ .../src/react/ShadowDOM/ShadowDOM.doc.tsx | 30 ++ .../ShadowDOM/docs/ShadowDOMBestPractices.md | 0 .../react/ShadowDOM/docs/ShadowDOMOverview.md | 1 + .../PersonaCoin/PersonaCoin.test.tsx | 6 +- packages/react/etc/react.api.md | 16 + packages/react/src/Utilities.ts | 3 + .../Button/BaseButton.classNames.ts | 2 +- .../src/components/Button/Button.types.ts | 3 + .../components/SpinButton/SpinButton.types.ts | 3 + .../components/TextField/TextField.types.ts | 3 + packages/react/src/index.ts | 3 + packages/utilities/etc/utilities.api.md | 16 + packages/utilities/src/classNamesFunction.ts | 6 +- .../src/customizations/customizable.test.tsx | 33 +- .../src/customizations/customizable.tsx | 95 ++++-- packages/utilities/src/index.ts | 7 + .../src/shadowDom/MergeStylesRootContext.tsx | 104 +++++++ .../MergeStylesShadowRootContext.tsx | 106 +++++++ packages/utilities/src/styled.tsx | 32 +- 39 files changed, 1163 insertions(+), 105 deletions(-) create mode 100644 change/@fluentui-jest-serializer-merge-styles-8ad53e9f-3456-4924-b50d-48ffd979b2ce.json create mode 100644 change/@fluentui-merge-styles-284eeed7-0d8c-4ebf-a44c-1e34467aca9b.json create mode 100644 change/@fluentui-react-b73398fa-9e0a-4d05-97a4-7a4820a876c1.json create mode 100644 change/@fluentui-utilities-3b3cdc9c-0e4f-4c68-aee6-9040e041186c.json create mode 100644 packages/merge-styles/src/EventMap.ts create mode 100644 packages/merge-styles/src/shadowConfig.test.ts create mode 100644 packages/merge-styles/src/shadowConfig.ts create mode 100644 packages/react-examples/src/react/ShadowDOM/ShadowDOM.Default.Example.tsx create mode 100644 packages/react-examples/src/react/ShadowDOM/ShadowDOM.doc.tsx create mode 100644 packages/react-examples/src/react/ShadowDOM/docs/ShadowDOMBestPractices.md create mode 100644 packages/react-examples/src/react/ShadowDOM/docs/ShadowDOMOverview.md create mode 100644 packages/utilities/src/shadowDom/MergeStylesRootContext.tsx create mode 100644 packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8f90f7e4737c1..c5d92274c5c9a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,5 +1,6 @@ pr: - master + - shadow-dom # Remove before merging to master # There's a separate pipeline for CI which also uses this file, but with a trigger override in the UI # https://dev.azure.com/uifabric/fabricpublic/_apps/hub/ms.vss-ciworkflow.build-ci-hub?_a=edit-build-definition&id=164&view=Tab_Triggers diff --git a/change/@fluentui-jest-serializer-merge-styles-8ad53e9f-3456-4924-b50d-48ffd979b2ce.json b/change/@fluentui-jest-serializer-merge-styles-8ad53e9f-3456-4924-b50d-48ffd979b2ce.json new file mode 100644 index 0000000000000..4248b6568e18b --- /dev/null +++ b/change/@fluentui-jest-serializer-merge-styles-8ad53e9f-3456-4924-b50d-48ffd979b2ce.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: add typescript types", + "packageName": "@fluentui/jest-serializer-merge-styles", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-merge-styles-284eeed7-0d8c-4ebf-a44c-1e34467aca9b.json b/change/@fluentui-merge-styles-284eeed7-0d8c-4ebf-a44c-1e34467aca9b.json new file mode 100644 index 0000000000000..abaaa5d8e4c87 --- /dev/null +++ b/change/@fluentui-merge-styles-284eeed7-0d8c-4ebf-a44c-1e34467aca9b.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "feat: add support for shadow dom and constructable stylesheets", + "packageName": "@fluentui/merge-styles", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-b73398fa-9e0a-4d05-97a4-7a4820a876c1.json b/change/@fluentui-react-b73398fa-9e0a-4d05-97a4-7a4820a876c1.json new file mode 100644 index 0000000000000..8127128a9fa87 --- /dev/null +++ b/change/@fluentui-react-b73398fa-9e0a-4d05-97a4-7a4820a876c1.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "feat: update some components to optionally support shadow dom", + "packageName": "@fluentui/react", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-utilities-3b3cdc9c-0e4f-4c68-aee6-9040e041186c.json b/change/@fluentui-utilities-3b3cdc9c-0e4f-4c68-aee6-9040e041186c.json new file mode 100644 index 0000000000000..efdf957b68fb9 --- /dev/null +++ b/change/@fluentui-utilities-3b3cdc9c-0e4f-4c68-aee6-9040e041186c.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "feat: add support for shadow dom and constructable stylesheets", + "packageName": "@fluentui/utilities", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/jest-serializer-merge-styles/tsconfig.json b/packages/jest-serializer-merge-styles/tsconfig.json index ea104793c121e..9d96846319c43 100644 --- a/packages/jest-serializer-merge-styles/tsconfig.json +++ b/packages/jest-serializer-merge-styles/tsconfig.json @@ -3,7 +3,7 @@ "target": "es5", "outDir": "lib", "module": "commonjs", - "lib": ["es2017"], + "lib": ["es2017", "dom"], "jsx": "react", "declaration": true, "sourceMap": true, diff --git a/packages/merge-styles/etc/merge-styles.api.md b/packages/merge-styles/etc/merge-styles.api.md index bd0e748d69dfc..ae54c24d9fe1b 100644 --- a/packages/merge-styles/etc/merge-styles.api.md +++ b/packages/merge-styles/etc/merge-styles.api.md @@ -36,6 +36,11 @@ export type DeepPartial = { // @public export function fontFace(font: IFontFace): void; +// @public (undocumented) +export const GLOBAL_STYLESHEET_KEY = "__global__"; + +// Warning: (ae-forgotten-export) The symbol "IShadowConfig" needs to be exported by the entry point index.d.ts +// // @public export type IConcatenatedStyleSet> = { [P in keyof Omit_2]: IStyle; @@ -43,7 +48,7 @@ export type IConcatenatedStyleSet> = { subComponentStyles?: { [P in keyof TStyleSet['subComponentStyles']]: IStyleFunction; }; -}; +} & IShadowConfig; // @public export interface ICSPSettings { @@ -75,6 +80,9 @@ export const InjectionMode: { none: 0; insertNode: 1; appendChild: 2; + constructableStylesheet: 3; + insertNodeAndConstructableStylesheet: 4; + appedChildAndConstructableStylesheet: 5; }; // @public (undocumented) @@ -87,7 +95,7 @@ export type IProcessedStyleSet> = { subComponentStyles: { [P in keyof TStyleSet['subComponentStyles']]: __MapToFunctionType; }; -}; +} & IShadowConfig; // @public export interface IRawFontStyle { @@ -439,7 +447,7 @@ export type IStyleSet = { subComponentStyles?: { [P in keyof TStyleSet['subComponentStyles']]: IStyleFunctionOrObject; }; -}; +} & IShadowConfig; // @public export interface IStyleSheetConfig { @@ -503,7 +511,10 @@ export function mergeStyleSets(styleSet1: TS export function mergeStyleSets(styleSet1: TStyleSet1 | false | null | undefined, styleSet2: TStyleSet2 | false | null | undefined, styleSet3: TStyleSet3 | false | null | undefined, styleSet4: TStyleSet4 | false | null | undefined): IProcessedStyleSet & ObjectOnly & ObjectOnly & ObjectOnly>; // @public -export function mergeStyleSets(...styleSets: Array): IProcessedStyleSet; +export function mergeStyleSets(...styleSets: Array): IProcessedStyleSet; + +// @public (undocumented) +export function mergeStyleSets(shadowConfig: ShadowConfig, ...styleSets: Array): IProcessedStyleSet; // @public (undocumented) export type ObjectOnly = TArg extends {} ? TArg : {}; @@ -517,32 +528,55 @@ export { Omit_2 as Omit } // @public export function setRTL(isRTL: boolean): void; +// @public (undocumented) +export type ShadowConfig = { + stylesheetKey: string; + inShadow: boolean; + window?: Window; +}; + // @public export class Stylesheet { - constructor(config?: IStyleSheetConfig, serializedStylesheet?: ISerializedStylesheet); + constructor(config?: IStyleSheetConfig, serializedStylesheet?: ISerializedStylesheet, stylesheetKey?: string); argsFromClassName(className: string): IStyle[] | undefined; cacheClassName(className: string, key: string, args: IStyle[], rules: string[]): void; classNameFromKey(key: string): string | undefined; + // (undocumented) + get counter(): number; + // (undocumented) + static forEachAdoptedStyleSheet(callback: (value: Stylesheet, key: string, map: Map) => void, srcWindow?: Window): void; + // (undocumented) + getAdoptableStyleSheet(): CSSStyleSheet | undefined; getClassName(displayName?: string): string; getClassNameCache(): { [key: string]: string; }; - static getInstance(): Stylesheet; + static getInstance(shadowConfig?: ShadowConfig): Stylesheet; getRules(includePreservedRules?: boolean): string; insertedRulesFromClassName(className: string): string[] | undefined; insertRule(rule: string, preserve?: boolean): void; + // (undocumented) + static offAddConstructableStyleSheet(callback: EventHandler, targetWindow?: Window): void; + // Warning: (ae-forgotten-export) The symbol "EventHandler" needs to be exported by the entry point index.d.ts + // + // (undocumented) + static onAddConstructableStyleSheet(callback: EventHandler, targetWindow?: Window): void; onInsertRule(callback: Function): Function; onReset(callback: Function): Function; + // (undocumented) + static projectStylesToWindow(targetWindow: Window, srcWindow?: Window): void; reset(): void; // (undocumented) resetKeys(): void; serialize(): string; + // (undocumented) + setAdoptableStyleSheet(sheet: CSSStyleSheet): void; setConfig(config?: IStyleSheetConfig): void; } // Warnings were encountered during analysis: // -// lib/IStyleSet.d.ts:53:5 - (ae-forgotten-export) The symbol "__MapToFunctionType" needs to be exported by the entry point index.d.ts +// lib/IStyleSet.d.ts:54:5 - (ae-forgotten-export) The symbol "__MapToFunctionType" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/merge-styles/src/EventMap.ts b/packages/merge-styles/src/EventMap.ts new file mode 100644 index 0000000000000..e1337f51fbd72 --- /dev/null +++ b/packages/merge-styles/src/EventMap.ts @@ -0,0 +1,58 @@ +export type EventArgs = { key: string; sheet: T }; +export type EventHandler = (args: EventArgs) => void; + +export class EventMap { + private _events: Map[]>; + private _self: Map; + + constructor() { + this._self = new Map(); + this._events = new Map[]>(); + } + + public get(key: K) { + return this._self.get(key); + } + + public set(key: K, value: V) { + this._self.set(key, value); + } + + public has(key: K) { + return this._self.has(key); + } + + public forEach(callback: (value: V, key: K, map: Map) => void) { + this._self.forEach(callback); + } + + public raise(type: string, data: EventArgs) { + const handlers = this._events.get(type); + if (!handlers) { + return; + } + + for (const handler of handlers) { + handler?.(data); + } + } + + public on(type: string, callback: EventHandler) { + const handlers = this._events.get(type); + if (!handlers) { + this._events.set(type, [callback]); + } else { + handlers.push(callback); + } + } + + public off(type: string, callback: EventHandler) { + const handlers = this._events.get(type); + if (handlers) { + const index = handlers.indexOf(callback); + if (index >= 0) { + handlers.splice(index, 1); + } + } + } +} diff --git a/packages/merge-styles/src/IStyleOptions.ts b/packages/merge-styles/src/IStyleOptions.ts index cb68444e487cf..07f82f33d500f 100644 --- a/packages/merge-styles/src/IStyleOptions.ts +++ b/packages/merge-styles/src/IStyleOptions.ts @@ -1,4 +1,8 @@ +import { ShadowConfig } from './shadowConfig'; + export interface IStyleOptions { rtl?: boolean; specificityMultiplier?: number; + // stylesheetKey?: string; + shadowConfig?: ShadowConfig; } diff --git a/packages/merge-styles/src/IStyleSet.ts b/packages/merge-styles/src/IStyleSet.ts index 64aa0d9e6a81d..b02c22b98a6cd 100644 --- a/packages/merge-styles/src/IStyleSet.ts +++ b/packages/merge-styles/src/IStyleSet.ts @@ -1,5 +1,6 @@ import { IStyle } from './IStyle'; import { IStyleFunctionOrObject, IStyleFunction } from './IStyleFunction'; +import { ShadowConfig } from './shadowConfig'; /** * @deprecated Use `Exclude` provided by TypeScript instead. @@ -33,7 +34,7 @@ export type IStyleSet = { [key: string]: [P in keyof Omit]: IStyle; } & { subComponentStyles?: { [P in keyof TStyleSet['subComponentStyles']]: IStyleFunctionOrObject }; -}; +} & IShadowConfig; /** * A concatenated style set differs from `IStyleSet` in that subComponentStyles will always be a style function. @@ -43,7 +44,7 @@ export type IConcatenatedStyleSet> = { [P in keyof Omit]: IStyle; } & { subComponentStyles?: { [P in keyof TStyleSet['subComponentStyles']]: IStyleFunction }; -}; +} & IShadowConfig; /** * A processed style set is one which the set of styles associated with each area has been converted @@ -58,4 +59,8 @@ export type IProcessedStyleSet> = { TStyleSet['subComponentStyles'] extends infer J ? (P extends keyof J ? J[P] : never) : never >; }; +} & IShadowConfig; + +type IShadowConfig = { + __shadowConfig__?: ShadowConfig; }; diff --git a/packages/merge-styles/src/Stylesheet.ts b/packages/merge-styles/src/Stylesheet.ts index 3be6223b3d311..2a346f0b6bf18 100644 --- a/packages/merge-styles/src/Stylesheet.ts +++ b/packages/merge-styles/src/Stylesheet.ts @@ -1,4 +1,6 @@ import { IStyle } from './IStyle'; +import { DEFAULT_SHADOW_CONFIG, GLOBAL_STYLESHEET_KEY, ShadowConfig } from './shadowConfig'; +import { EventHandler, EventMap } from './EventMap'; export const InjectionMode = { /** @@ -15,6 +17,21 @@ export const InjectionMode = { * Appends rules using appendChild. */ appendChild: 2 as 2, + + /** + * Inserts rules into constructable stylesheets. + */ + constructableStylesheet: 3 as 3, + + /** + * Same as `insertNode` and `constructableStylesheet` + */ + insertNodeAndConstructableStylesheet: 4 as 4, + + /** + * Same as `appendChild` and `constructableStylesheet` + */ + appedChildAndConstructableStylesheet: 5 as 5, }; export type InjectionMode = (typeof InjectionMode)[keyof typeof InjectionMode]; @@ -88,18 +105,29 @@ export interface ISerializedStylesheet { } const STYLESHEET_SETTING = '__stylesheet__'; + +const ADOPTED_STYLESHEETS = '__mergeStylesAdoptedStyleSheets__'; + /** * MSIE 11 doesn't cascade styles based on DOM ordering, but rather on the order that each style node * is created. As such, to maintain consistent priority, IE11 should reuse a single style node. */ const REUSE_STYLE_NODE = typeof navigator !== 'undefined' && /rv:11.0/.test(navigator.userAgent); +const SUPPORTS_CONSTRUCTIBLE_STYLESHEETS = typeof window !== 'undefined' && 'CSSStyleSheet' in window; + +export type AdoptableStylesheet = { + fluentSheet: Stylesheet; + adoptedSheet: CSSStyleSheet; +}; + let _global: (Window | {}) & { [STYLESHEET_SETTING]?: Stylesheet; FabricConfig?: { mergeStyles?: IStyleSheetConfig; serializedStylesheet?: ISerializedStylesheet; }; + [ADOPTED_STYLESHEETS]?: EventMap; } = {}; // Grab window. @@ -114,6 +142,8 @@ try { let _stylesheet: Stylesheet | undefined; +let constructableStyleSheetCounter = 0; + /** * Represents the state of styles registered in the page. Abstracts * the surface for adding styles to the stylesheet, exposes helpers @@ -124,10 +154,14 @@ let _stylesheet: Stylesheet | undefined; export class Stylesheet { private _lastStyleElement?: HTMLStyleElement; private _styleElement?: HTMLStyleElement; + + private _constructibleSheet?: CSSStyleSheet; + private _stylesheetKey?: string; + private _rules: string[] = []; private _preservedRules: string[] = []; private _config: IStyleSheetConfig; - private _counter = 0; + private _styleCounter = 0; private _keyToClassName: { [key: string]: string } = {}; private _onInsertRuleCallbacks: Function[] = []; private _onResetCallbacks: Function[] = []; @@ -136,35 +170,158 @@ export class Stylesheet { /** * Gets the singleton instance. */ - public static getInstance(): Stylesheet { - _stylesheet = _global[STYLESHEET_SETTING] as Stylesheet; + public static getInstance(shadowConfig?: ShadowConfig): Stylesheet { + const { stylesheetKey, inShadow, window: win } = shadowConfig ?? DEFAULT_SHADOW_CONFIG; + const global = (win ?? _global) as typeof _global; + + if (stylesheetKey && inShadow) { + _stylesheet = global[ADOPTED_STYLESHEETS]?.get(stylesheetKey); + } else { + _stylesheet = global[STYLESHEET_SETTING] as Stylesheet; + } if (!_stylesheet || (_stylesheet._lastStyleElement && _stylesheet._lastStyleElement.ownerDocument !== document)) { - const fabricConfig = _global?.FabricConfig || {}; + const fabricConfig = global?.FabricConfig || {}; + if (inShadow) { + fabricConfig.mergeStyles = fabricConfig.mergeStyles || {}; + fabricConfig.mergeStyles.injectionMode = InjectionMode.constructableStylesheet; + } - const stylesheet = new Stylesheet(fabricConfig.mergeStyles, fabricConfig.serializedStylesheet); + const stylesheet = new Stylesheet(fabricConfig.mergeStyles, fabricConfig.serializedStylesheet, stylesheetKey); _stylesheet = stylesheet; - _global[STYLESHEET_SETTING] = stylesheet; + if (stylesheetKey) { + if (inShadow || stylesheetKey === GLOBAL_STYLESHEET_KEY) { + if (!global[ADOPTED_STYLESHEETS]) { + global[ADOPTED_STYLESHEETS] = new EventMap(); + } + global[ADOPTED_STYLESHEETS]!.set(stylesheetKey, stylesheet); + (global as Window).requestAnimationFrame?.(() => { + global[ADOPTED_STYLESHEETS]!.raise('add-sheet', { key: stylesheetKey, sheet: stylesheet }); + }); + } + + if (stylesheetKey === GLOBAL_STYLESHEET_KEY) { + global[STYLESHEET_SETTING] = stylesheet; + } + } else { + global[STYLESHEET_SETTING] = stylesheet; + } } return _stylesheet; } - constructor(config?: IStyleSheetConfig, serializedStylesheet?: ISerializedStylesheet) { + public static projectStylesToWindow(targetWindow: Window, srcWindow?: Window): void { + const global = (srcWindow ?? _global) as typeof _global; + const clone = new EventMap(); + + // TODO: add support for