From 74c19af79cb6b9c015ab3a454a3e69d453f1a217 Mon Sep 17 00:00:00 2001 From: Nicholas Rice Date: Tue, 28 Jul 2020 16:18:00 -0700 Subject: [PATCH] feat: adding directional stylesheet behavior (#3559) * adding directional stylesheet behavior * fixing documentation * Update packages/web-components/fast-components/docs/design/localization.md Co-authored-by: Jane Chu <7559015+janechu@users.noreply.github.com> * align imports and doc links Co-authored-by: nicholasrice Co-authored-by: Jane Chu <7559015+janechu@users.noreply.github.com> --- .../docs/design/localization.md | 53 +++++++++ .../fast-foundation/docs/api-report.md | 11 ++ .../design-system-provider.ts | 21 +++- .../src/utilities/style/direction.ts | 111 ++++++++++++++++++ .../src/utilities/style/index.ts | 1 + sites/website/sidebars.js | 1 + 6 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 packages/web-components/fast-components/docs/design/localization.md create mode 100644 packages/web-components/fast-foundation/src/utilities/style/direction.ts diff --git a/packages/web-components/fast-components/docs/design/localization.md b/packages/web-components/fast-components/docs/design/localization.md new file mode 100644 index 00000000000..64d9c61dc55 --- /dev/null +++ b/packages/web-components/fast-components/docs/design/localization.md @@ -0,0 +1,53 @@ +--- +id: localization +title: Localization +custom_edit_url: https://github.com/microsoft/fast/edit/master/packages/web-components/fast-components/docs/design/localization.md +--- + +## Document Direction +Many CSS layout properties like [flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox) and [CSS grid](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout) automatically handle reflow depending on the [document's primary direction](https://www.w3.org/International/questions/qa-html-dir). There are also CSS [logical properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties/Basic_concepts) that can be used as well to apply localized margins, paddings, borders and positioning. Unfortunately, browser support for these properties is limited and there are still styling cases not covered by these properties (directional glyphs, transforms, etc). That is why FAST provides several mechanisms to apply direction-based styles. + +### DesignSystemProvider +The [`FASTDesignSystemProvider`](/docs/api/fast-components.fastdesignsystemprovider/) exposes a `direction` property. This should be set to the primary document direction and defaults to `ltr`. This value will be used to inform the stylesheet behaviors described below. + +### `inlineStartBehavior` and `inlineEndBehavior` +[inlineStartBehavior](/docs/api/fast-components.inlinestartbehavior/) and [inlineEndBehavior](/docs/api/fast-components.inlineendbehavior/) can be used to apply localized [float](https://developer.mozilla.org/en-US/docs/Web/CSS/float) properties. These are drop-in replacements for the browsers `inline-start` and `inline-end` float values and should be used when the native values are not supported. If your browser-matrix supports `inline-start` and `inline-end` float values, please use the native values. + +**Example: Using `inlineStartBehavior` and `inlineEndBehavior`** +```ts +import { css } from "@microsoft/fast-element"; +import { inlineStartBehavior } from "@microsoft/fast-components"; + +const styles = css` + :host { + float: ${inlineStartBehavior.var} + } +`.withBehaviors(inlineStartBehavior) +``` + +### DirectionalStyleSheetBehavior +[`DirectionalStyleSheetBehavior`](/docs/api/fast-foundation.directionalstylesheetbehavior/) can be used to apply arbitrary LTR and RTL stylesheets, depending on the nearest [`FASTDesignSystemProvider`s direction](/docs/api/fast-components.fastdesignsystemprovider.direction/) property. + +**Example: Using `DirectionalStyleSheetBehavior`** +```ts +import { css } from "@microsoft/fast-element"; +import { DirectionalStyleSheetBehavior } from "@microsoft/fast-components"; + +const ltr = css` + :host { + left: 20px; + } +`; + +const rtl = css` + :host { + right: 20px; + } +`; + +const styles = css` + .host { + position: relative + } +`.withBehaviors(new DirectionalStyleSheetBehavior(ltr, rtl)) +``` diff --git a/packages/web-components/fast-foundation/docs/api-report.md b/packages/web-components/fast-foundation/docs/api-report.md index f0b6083485c..cc7959f4fbb 100644 --- a/packages/web-components/fast-foundation/docs/api-report.md +++ b/packages/web-components/fast-foundation/docs/api-report.md @@ -259,7 +259,9 @@ export class DesignSystemProvider extends FASTElement implements CSSCustomProper designSystemProperties: { [propertyName: string]: Required>; }; + // @deprecated disconnectedCSSCustomPropertyRegistry: CSSCustomPropertyDefinition[]; + disconnectedRegistry: Array<(provider: DesignSystemProvider) => void> | void; evaluate(definition: CSSCustomPropertyDefinition): string; static findProvider(el: HTMLElement & Partial): DesignSystemProvider | null; static isDesignSystemProvider(el: HTMLElement | DesignSystemProvider): el is DesignSystemProvider; @@ -299,6 +301,15 @@ export class Dialog extends FASTElement { // @public export const DialogTemplate: import("@microsoft/fast-element").ViewTemplate; +// @public +export class DirectionalStyleSheetBehavior implements Behavior { + constructor(ltr: ElementStyles | null, rtl: ElementStyles | null); + // @internal (undocumented) + bind(source: typeof FASTElement & HTMLElement): void; + // @internal (undocumented) + unbind(source: typeof FASTElement & HTMLElement): void; +} + // @public export const disabledCursor = "not-allowed"; diff --git a/packages/web-components/fast-foundation/src/design-system-provider/design-system-provider.ts b/packages/web-components/fast-foundation/src/design-system-provider/design-system-provider.ts index d3575a1a186..4d5efcc7d05 100644 --- a/packages/web-components/fast-foundation/src/design-system-provider/design-system-provider.ts +++ b/packages/web-components/fast-foundation/src/design-system-provider/design-system-provider.ts @@ -11,7 +11,7 @@ import { CSSCustomPropertyDefinition, CSSCustomPropertyTarget, } from "../custom-properties/index"; -import { composedParent } from "../utilities/index"; +import { composedParent } from "../utilities/composed-parent"; import { DecoratorDesignSystemPropertyConfiguration } from "./design-system-property"; const supportsAdoptedStylesheets = "adoptedStyleSheets" in window.ShadowRoot.prototype; @@ -245,9 +245,19 @@ export class DesignSystemProvider extends FASTElement * are defined before this DesignSystemProvider. * * @public + * @deprecated - use disconnectedRegistry */ public disconnectedCSSCustomPropertyRegistry: CSSCustomPropertyDefinition[]; + /** + * Allows arbitrary registration to the provider before the constructor runs. + * When the constructor runs, all registration functions in the disconnectedRegistry + * will be invoked with the provider instance. + * + * @public + */ + public disconnectedRegistry: Array<(provider: DesignSystemProvider) => void> | void; + /** * The target of CSSCustomPropertyDefinitions registered * with the provider. This will be #1 when adoptedStyleSheets are supported @@ -367,6 +377,14 @@ export class DesignSystemProvider extends FASTElement delete this.disconnectedCSSCustomPropertyRegistry; } + + if (Array.isArray(this.disconnectedRegistry)) { + for (let i = 0; i < this.disconnectedRegistry.length; i++) { + this.disconnectedRegistry[i](this); + } + + delete this.disconnectedRegistry; + } } /** @@ -450,6 +468,7 @@ export class DesignSystemProvider extends FASTElement definition.value({ ...this.designSystem }) : definition.value; } + /** * Synchronize the provider's design system with the local * overrides. Any value defined on the instance will take priority diff --git a/packages/web-components/fast-foundation/src/utilities/style/direction.ts b/packages/web-components/fast-foundation/src/utilities/style/direction.ts new file mode 100644 index 00000000000..41890bc5e35 --- /dev/null +++ b/packages/web-components/fast-foundation/src/utilities/style/direction.ts @@ -0,0 +1,111 @@ +import { + ElementStyles, + Behavior, + FASTElement, + Subscriber, + Observable, +} from "@microsoft/fast-element"; +import { DesignSystemProvider } from "../../design-system-provider"; +import { Direction } from "@microsoft/fast-web-utilities"; + +/** + * Behavior to conditionally apply LTR and RTL stylesheets. To determine which to apply, + * the behavior will use the nearest DesignSystemProvider's 'direction' design system value. + * + * @public + * @example + * ```ts + * import { css } from "@microsoft/fast-element"; + * import { DirectionalStyleSheetBehavior } from "@microsoft/fast-foundation"; + * + * css` + * // ... + * `.withBehaviors(new DirectionalStyleSheetBehavior( + * css`:host { content: "ltr"}`), + * css`:host { content: "rtl"}`), + * ) + * ``` + */ +export class DirectionalStyleSheetBehavior implements Behavior { + private ltr: ElementStyles | null; + private rtl: ElementStyles | null; + private cache: WeakMap< + HTMLElement, + [DesignSystemProvider, DirectionalStyleSheetBehaviorSubscription] + > = new WeakMap(); + + constructor(ltr: ElementStyles | null, rtl: ElementStyles | null) { + this.ltr = ltr; + this.rtl = rtl; + } + + /** + * @internal + */ + public bind(source: typeof FASTElement & HTMLElement) { + const provider = DesignSystemProvider.findProvider(source); + + if (provider !== null) { + if (provider.$fastController && provider.$fastController.isConnected) { + this.attach(source, provider); + } else { + if (!Array.isArray(provider.disconnectedRegistry)) { + provider.disconnectedRegistry = []; + } + + provider.disconnectedRegistry.push(this.attach.bind(this, source)); + } + } + } + + /** + * @internal + */ + public unbind(source: typeof FASTElement & HTMLElement) { + const cache = this.cache.get(source); + + if (cache) { + Observable.getNotifier(cache[0].designSystem).unsubscribe(cache[1]); + } + } + + private attach( + source: typeof FASTElement & HTMLElement, + provider: DesignSystemProvider + ) { + const subscriber = new DirectionalStyleSheetBehaviorSubscription( + this.ltr, + this.rtl, + source + ); + Observable.getNotifier(provider.designSystem).subscribe(subscriber, "direction"); + subscriber.attach(provider.designSystem["direction"]); + + this.cache.set(source, [provider, subscriber]); + } +} + +/** + * Subscription for {@link DirectionalStyleSheetBehavior} + */ +class DirectionalStyleSheetBehaviorSubscription implements Subscriber { + private attached: ElementStyles | null = null; + + constructor( + private ltr: ElementStyles | null, + private rtl: ElementStyles | null, + private source: HTMLElement + ) {} + + public handleChange(source: any) { + this.attach(source.direction); + } + + public attach(direction: Direction) { + if (this.attached !== this[direction] && this.source?.shadowRoot) { + this.attached?.removeStylesFrom(this.source.shadowRoot); + this[direction]?.addStylesTo(this.source.shadowRoot); + this.attached = this[direction]; + } + } +} diff --git a/packages/web-components/fast-foundation/src/utilities/style/index.ts b/packages/web-components/fast-foundation/src/utilities/style/index.ts index 9f41ad5f5a5..292d1f85f75 100644 --- a/packages/web-components/fast-foundation/src/utilities/style/index.ts +++ b/packages/web-components/fast-foundation/src/utilities/style/index.ts @@ -1,3 +1,4 @@ export * from "./disabled"; export * from "./display"; export * from "./focus"; +export * from "./direction"; diff --git a/sites/website/sidebars.js b/sites/website/sidebars.js index dfb9807cee4..7358bb65077 100644 --- a/sites/website/sidebars.js +++ b/sites/website/sidebars.js @@ -44,6 +44,7 @@ module.exports = { "design/design-system", "design/type-ramp", "design/color", + "design/localization", "design/match-media-stylesheets", ], },