diff --git a/.prettierignore b/.prettierignore index 095b5614af383..030b7cab28754 100644 --- a/.prettierignore +++ b/.prettierignore @@ -35,7 +35,6 @@ packages/fluentui/docs/src/behaviorMenu.json packages/fluentui/docs/src/exampleMenus packages/fluentui/docs/src/exampleSources packages/fluentui/ability-attributes/src/schema.ts -packages/web-components/src/**/*.styles.ts packages/web-components/**/*.spec.ts packages/web-components/src/default-palette.ts diff --git a/change/@fluentui-web-components-06188225-69da-4cf6-918c-932a9ceab1b3.json b/change/@fluentui-web-components-06188225-69da-4cf6-918c-932a9ceab1b3.json new file mode 100644 index 0000000000000..4e872c1571b2d --- /dev/null +++ b/change/@fluentui-web-components-06188225-69da-4cf6-918c-932a9ceab1b3.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "add text as a new component", + "packageName": "@fluentui/web-components", + "email": "chhol@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/web-components/.storybook/main.js b/packages/web-components/.storybook/main.cjs similarity index 100% rename from packages/web-components/.storybook/main.js rename to packages/web-components/.storybook/main.cjs diff --git a/packages/web-components/docs/api-report.md b/packages/web-components/docs/api-report.md index 89e2eb718dbf1..c027d600e8202 100644 --- a/packages/web-components/docs/api-report.md +++ b/packages/web-components/docs/api-report.md @@ -4,8 +4,86 @@ ```ts -// @public (undocumented) -export const empty = ""; +import { ElementStyles } from '@microsoft/fast-element'; +import { ElementViewTemplate } from '@microsoft/fast-element'; +import { FASTElement } from '@microsoft/fast-element'; +import { FASTElementDefinition } from '@microsoft/fast-element'; +import { ValuesOf } from '@microsoft/fast-foundation'; + +// @public +class Text_2 extends FASTElement { + align: TextAlign; + block: boolean; + font: TextFont; + italic: boolean; + nowrap: boolean; + size: TextSize; + strikethrough: boolean; + truncate: boolean; + underline: boolean; + weight: TextWeight; +} +export { Text_2 as Text } + +// @public +export const TextAlign: { + readonly start: "start"; + readonly end: "end"; + readonly center: "center"; + readonly justify: "justify"; +}; + +// @public +export type TextAlign = ValuesOf; + +// @public +export const TextDefinition: FASTElementDefinition; + +// @public +export const TextFont: { + readonly base: "base"; + readonly numeric: "numeric"; + readonly monospace: "monospace"; +}; + +// @public +export type TextFont = ValuesOf; + +// @public +export const TextSize: { + readonly _100: "100"; + readonly _200: "200"; + readonly _300: "300"; + readonly _400: "400"; + readonly _500: "500"; + readonly _600: "600"; + readonly _700: "700"; + readonly _800: "800"; + readonly _900: "900"; + readonly _1000: "1000"; +}; + +// @public +export type TextSize = ValuesOf; + +// @public +export const TextStyles: ElementStyles; + +// Warning: (ae-internal-missing-underscore) The name "TextTemplate" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) +export const TextTemplate: ElementViewTemplate; + +// @public +export const TextWeight: { + readonly medium: "medium"; + readonly regular: "regular"; + readonly semibold: "semibold"; + readonly bold: "bold"; +}; + +// @public +export type TextWeight = ValuesOf; // (No @packageDocumentation comment for this package) diff --git a/packages/web-components/package.json b/packages/web-components/package.json index c9ed5bf4e8df6..3bed54f6654c0 100644 --- a/packages/web-components/package.json +++ b/packages/web-components/package.json @@ -19,6 +19,16 @@ "main": "dist/esm/index.js", "types": "dist/web-components.d.ts", "unpkg": "dist/web-components.min.js", + "exports": { + ".": { + "types": "./dist/dts/index.d.ts", + "default": "./dist/esm/index.js" + }, + "./text": { + "types": "./dist/esm/text/define.d.ts", + "default": "./dist/esm/text/define.js" + } + }, "scripts": { "tsc": "tsc", "api-extractor": "api-extractor", @@ -77,8 +87,8 @@ "typescript": "4.7.4" }, "dependencies": { - "@microsoft/fast-element": "^2.0.0-beta.17", - "@microsoft/fast-foundation": "^3.0.0-alpha.21", + "@microsoft/fast-element": "^2.0.0-beta.20", + "@microsoft/fast-foundation": "^3.0.0-alpha.24", "@microsoft/fast-web-utilities": "^6.0.0", "@fluentui/tokens": "1.0.0-alpha.2", "tslib": "^2.1.0" diff --git a/packages/web-components/src/__test__/helpers.ts b/packages/web-components/src/__test__/helpers.ts new file mode 100644 index 0000000000000..5bdf603f031f9 --- /dev/null +++ b/packages/web-components/src/__test__/helpers.ts @@ -0,0 +1,56 @@ +import type { FASTElement, ViewTemplate } from '@microsoft/fast-element'; +import type { AnnotatedStoryFn, Args, ComponentAnnotations, StoryAnnotations } from '@storybook/csf'; + +/** + * A helper that returns a function to bind a Storybook story to a ViewTemplate. + * + * @param template - The ViewTemplate to render + * @returns - a function to bind a Storybook story + */ +export function renderComponent( + template: ViewTemplate, +): (args: TArgs) => Element | DocumentFragment | null { + return function (args) { + const storyFragment = new DocumentFragment(); + template.render(args, storyFragment); + if (storyFragment.childElementCount === 1) { + return storyFragment.firstElementChild; + } + return storyFragment; + }; +} + +/** + * A helper that returns a function to bind a Storybook story to a ViewTemplate. + */ +export type FASTFramework = { + component: typeof FASTElement; + storyResult: FASTElement | Element | DocumentFragment; +}; + +/** + * Metadata to configure the stories for a component. + */ +export type Meta = ComponentAnnotations>; + +/** + * Story function that represents a CSFv3 component example. + */ +export declare type StoryObj = StoryAnnotations; + +/** + * Story function that represents a CSFv2 component example. + */ +export declare type StoryFn = AnnotatedStoryFn; + +/** + * Story function that represents a CSFv2 component example. + * + * NOTE that in Storybook 7.0, this type will be renamed to `StoryFn` and replaced by the current `StoryObj` type. + */ +export declare type Story = StoryFn>; + +/** + * Combined Storybook story args. + */ +export type StoryArgs = Partial> & Args; diff --git a/packages/web-components/src/fluent-design-system.ts b/packages/web-components/src/fluent-design-system.ts new file mode 100644 index 0000000000000..293d22781974e --- /dev/null +++ b/packages/web-components/src/fluent-design-system.ts @@ -0,0 +1,5 @@ +export const FluentDesignSystem = Object.freeze({ + prefix: 'fluent', + shadowRootMode: 'open', + registry: customElements, +}); diff --git a/packages/web-components/src/index-rollup.ts b/packages/web-components/src/index-rollup.ts index ea465c2a34a4e..ee01cc9bd3233 100644 --- a/packages/web-components/src/index-rollup.ts +++ b/packages/web-components/src/index-rollup.ts @@ -1 +1 @@ -export * from './index'; +export * from './index.js'; diff --git a/packages/web-components/src/index.ts b/packages/web-components/src/index.ts index 7d394c507ef22..dd622fd56e566 100644 --- a/packages/web-components/src/index.ts +++ b/packages/web-components/src/index.ts @@ -1 +1 @@ -export const empty = ''; +export * from './text/index.js'; diff --git a/packages/web-components/src/text/define.ts b/packages/web-components/src/text/define.ts new file mode 100644 index 0000000000000..5d11371ac5fca --- /dev/null +++ b/packages/web-components/src/text/define.ts @@ -0,0 +1,4 @@ +import { FluentDesignSystem } from '../fluent-design-system.js'; +import { definition } from './text.definition.js'; + +definition.define(FluentDesignSystem.registry); diff --git a/packages/web-components/src/text/index.ts b/packages/web-components/src/text/index.ts new file mode 100644 index 0000000000000..663cf3ebbf645 --- /dev/null +++ b/packages/web-components/src/text/index.ts @@ -0,0 +1,5 @@ +export * from './text.js'; +export * from './text.options.js'; +export { template as TextTemplate } from './text.template.js'; +export { styles as TextStyles } from './text.styles.js'; +export { definition as TextDefinition } from './text.definition.js'; diff --git a/packages/web-components/src/text/text.definition.ts b/packages/web-components/src/text/text.definition.ts new file mode 100644 index 0000000000000..02bb15599612d --- /dev/null +++ b/packages/web-components/src/text/text.definition.ts @@ -0,0 +1,18 @@ +import { FluentDesignSystem } from '../fluent-design-system.js'; +import { Text } from './text.js'; +import { styles } from './text.styles.js'; +import { template } from './text.template.js'; + +/** + * The Fluent Text Element. + * + * + * @public + * @remarks + * HTML Element: \ + */ +export const definition = Text.compose({ + name: `${FluentDesignSystem.prefix}-text`, + template, + styles, +}); diff --git a/packages/web-components/src/text/text.options.ts b/packages/web-components/src/text/text.options.ts new file mode 100644 index 0000000000000..0c775c78b2ddd --- /dev/null +++ b/packages/web-components/src/text/text.options.ts @@ -0,0 +1,75 @@ +import { ValuesOf } from '@microsoft/fast-foundation'; + +/** + * TextSize constants + * @public + */ +export const TextSize = { + _100: '100', + _200: '200', + _300: '300', + _400: '400', + _500: '500', + _600: '600', + _700: '700', + _800: '800', + _900: '900', + _1000: '1000', +} as const; + +/** + * The type for TextSize + * The font size and line height based on the theme tokens + * @public + */ +export type TextSize = ValuesOf; + +/** + * TextFont Constants + * @public + */ +export const TextFont = { + base: 'base', + numeric: 'numeric', + monospace: 'monospace', +} as const; + +/** + * Applies font family to the content + * @public + */ +export type TextFont = ValuesOf; + +/** + * TextWeight Constants + * @public + */ +export const TextWeight = { + medium: 'medium', + regular: 'regular', + semibold: 'semibold', + bold: 'bold', +} as const; + +/** + * Applies font weight to the content + * @public + */ +export type TextWeight = ValuesOf; + +/** + * TextAlign Constants + * @public + */ +export const TextAlign = { + start: 'start', + end: 'end', + center: 'center', + justify: 'justify', +} as const; + +/** + * Aligns the content + * @public + */ +export type TextAlign = ValuesOf; diff --git a/packages/web-components/src/text/text.stories.ts b/packages/web-components/src/text/text.stories.ts new file mode 100644 index 0000000000000..511c274062484 --- /dev/null +++ b/packages/web-components/src/text/text.stories.ts @@ -0,0 +1,120 @@ +import { html } from '@microsoft/fast-element'; +import type { Args, Meta } from '@storybook/html'; +import { renderComponent } from '../__test__/helpers.js'; +import type { Text as FluentText } from './text.js'; +import './define.js'; +import { TextAlign, TextFont, TextSize, TextWeight } from './text.options.js'; + +type TextStoryArgs = Args & FluentText; +type TextStoryMeta = Meta; + +/** + * Used to generate slotted content for stories + * @param as - the element to generate + * @param content - the content for the element + * @returns ViewTemplate + */ +const generateSemanticElementTemplate = (as: string, content) => { + switch (as) { + case 'h1': + return html`

${content}

`; + case 'h2': + return html`

${content}

`; + case 'h3': + return html`

${content}

`; + case 'h4': + return html`

${content}

`; + case 'h5': + return html`
${content}
`; + case 'h6': + return html`
${content}
`; + case 'p': + return html`

${content}

`; + case 'pre': + return html`
${content}
`; + case 'span': + default: + return html`${content}`; + } +}; + +const storyTemplate = html` + x.align} + font=${x => x.font} + size=${x => x.size} + weight=${x => x.weight} + ?nowrap=${x => x.nowrap} + ?truncate=${x => x.truncate} + ?italic=${x => x.italic} + ?underline=${x => x.underline} + ?strikethrough=${x => x.strikethrough} + ?block=${x => x.block} + >${x => generateSemanticElementTemplate(x.as, x.content)} +`; + +export default { + title: 'Components/Text', + args: { + content: 'Text', + nowrap: false, + truncate: false, + italic: false, + underline: false, + strikethrough: false, + block: false, + }, + argTypes: { + as: { + options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'pre', 'span'], + control: { + type: 'select', + }, + }, + size: { + options: Object.values(TextSize), + control: { + type: 'select', + }, + }, + weight: { + options: Object.keys(TextWeight), + control: { + type: 'select', + }, + }, + align: { + options: Object.keys(TextAlign), + control: { + type: 'select', + }, + }, + font: { + options: Object.keys(TextFont), + control: { + type: 'select', + }, + }, + nowrap: { + control: 'boolean', + }, + truncate: { + control: 'boolean', + }, + italic: { + control: 'boolean', + }, + underline: { + control: 'boolean', + }, + strikethrough: { + control: 'boolean', + }, + block: { + control: 'boolean', + }, + }, +} as TextStoryMeta; + +export const Text = renderComponent(storyTemplate).bind({}); diff --git a/packages/web-components/src/text/text.styles.ts b/packages/web-components/src/text/text.styles.ts new file mode 100644 index 0000000000000..cfe208b822005 --- /dev/null +++ b/packages/web-components/src/text/text.styles.ts @@ -0,0 +1,133 @@ +import { css } from '@microsoft/fast-element'; +import { display } from '@microsoft/fast-foundation'; +import { + fontFamilyBase, + fontFamilyMonospace, + fontFamilyNumeric, + fontSizeBase100, + fontSizeBase200, + fontSizeBase300, + fontSizeBase400, + fontSizeBase500, + fontSizeBase600, + fontSizeHero1000, + fontSizeHero700, + fontSizeHero800, + fontSizeHero900, + fontWeightBold, + fontWeightMedium, + fontWeightRegular, + fontWeightSemibold, + lineHeightBase100, + lineHeightBase200, + lineHeightBase300, + lineHeightBase400, + lineHeightBase500, + lineHeightBase600, + lineHeightHero1000, + lineHeightHero700, + lineHeightHero800, + lineHeightHero900, +} from '../theme/design-tokens.js'; + +/** Text styles + * @public + */ +export const styles = css` + ${display('inline')} + + ::slotted(*) { + font-family: ${fontFamilyBase}; + font-size: ${fontSizeBase300}; + line-height: ${lineHeightBase300}; + font-weight: ${fontWeightRegular}; + text-align: start; + display: inline; + white-space: normal; + overflow: visible; + text-overflow: clip; + margin: 0; + } + :host([nowrap]) ::slotted(*) { + white-space: nowrap; + overflow: hidden; + } + :host([truncate]) ::slotted(*) { + text-overflow: ellipsis; + } + :host([block]) ::slotted(*) { + display: block; + } + :host([italic]) ::slotted(*) { + font-style: italic; + } + :host([underline]) ::slotted(*) { + text-decoration-line: underline; + } + :host([strikethrough]) ::slotted(*) { + text-decoration-line: line-through; + } + :host([underline][strikethrough]) ::slotted(*) { + text-decoration-line: line-through underline; + } + :host([size='100']) ::slotted(*) { + font-size: ${fontSizeBase100}; + line-height: ${lineHeightBase100}; + } + :host([size='200']) ::slotted(*) { + font-size: ${fontSizeBase200}; + line-height: ${lineHeightBase200}; + } + :host([size='400']) ::slotted(*) { + font-size: ${fontSizeBase400}; + line-height: ${lineHeightBase400}; + } + :host([size='500']) ::slotted(*) { + font-size: ${fontSizeBase500}; + line-height: ${lineHeightBase500}; + } + :host([size='600']) ::slotted(*) { + font-size: ${fontSizeBase600}; + line-height: ${lineHeightBase600}; + } + :host([size='700']) ::slotted(*) { + font-size: ${fontSizeHero700}; + line-height: ${lineHeightHero700}; + } + :host([size='800']) ::slotted(*) { + font-size: ${fontSizeHero800}; + line-height: ${lineHeightHero800}; + } + :host([size='900']) ::slotted(*) { + font-size: ${fontSizeHero900}; + line-height: ${lineHeightHero900}; + } + :host([size='1000']) ::slotted(*) { + font-size: ${fontSizeHero1000}; + line-height: ${lineHeightHero1000}; + } + :host([font='monospace']) ::slotted(*) { + font-family: ${fontFamilyMonospace}; + } + :host([font='numeric']) ::slotted(*) { + font-family: ${fontFamilyNumeric}; + } + :host([weight='medium']) ::slotted(*) { + font-weight: ${fontWeightMedium}; + } + :host([weight='semibold']) ::slotted(*) { + font-weight: ${fontWeightSemibold}; + } + :host([weight='bold']) ::slotted(*) { + font-weight: ${fontWeightBold}; + } + :host([align='center']) ::slotted(*) { + text-align: center; + } + :host([align='end']) ::slotted(*) { + text-align: end; + } + :host([align='justify']) ::slotted(*) { + text-align: justify; + } +`; diff --git a/packages/web-components/src/text/text.template.ts b/packages/web-components/src/text/text.template.ts new file mode 100644 index 0000000000000..1498579181854 --- /dev/null +++ b/packages/web-components/src/text/text.template.ts @@ -0,0 +1,7 @@ +import { ElementViewTemplate, html } from '@microsoft/fast-element'; +import type { Text } from './text.js'; + +/** + * @internal + */ +export const template: ElementViewTemplate = html``; diff --git a/packages/web-components/src/text/text.ts b/packages/web-components/src/text/text.ts new file mode 100644 index 0000000000000..a666fb0950822 --- /dev/null +++ b/packages/web-components/src/text/text.ts @@ -0,0 +1,111 @@ +import { attr, FASTElement } from '@microsoft/fast-element'; +import type { TextAlign, TextFont, TextSize, TextWeight } from './text.options.js'; + +/** + * The base class used for constructing a fluent-text custom element + * @public + */ +export class Text extends FASTElement { + /** + * The text will not wrap + * NOTE: In Fluent UI React v9 this is "wrap" + * Boolean attributes which default to true in HTML can't be switched off in the DOM + * + * @public + * @remarks + * HTML Attribute: nowrap + */ + @attr({ mode: 'boolean' }) + nowrap: boolean = false; + + /** + * The text truncates + * + * @public + * @remarks + * HTML Attribute: truncate + */ + @attr({ mode: 'boolean' }) + truncate: boolean = false; + + /** + * The text style is italic + * + * @public + * @remarks + * HTML Attribute: italic + */ + @attr({ mode: 'boolean' }) + italic: boolean = false; + + /** + * The text style is underline + * + * @public + * @remarks + * HTML Attribute: underline + */ + @attr({ mode: 'boolean' }) + underline: boolean = false; + + /** + * The text style is strikethrough + * + * @public + * @remarks + * HTML Attribute: strikethrough + */ + @attr({ mode: 'boolean' }) + strikethrough: boolean = false; + + /** + * An text can take up the width of its container. + * + * @public + * @remarks + * HTML Attribute: block + */ + @attr({ mode: 'boolean' }) + block: boolean = false; + + /** + * THe Text size + * + * @public + * @remarks + * HTML Attribute: size + * + */ + @attr + size: TextSize; + + /** + * THe Text font + * + * @public + * @remarks + * HTML Attribute: font + */ + @attr + font: TextFont; + + /** + * THe Text weight + * + * @public + * @remarks + * HTML Attribute: weight + */ + @attr + weight: TextWeight; + + /** + * THe Text align + * + * @public + * @remarks + * HTML Attribute: align + */ + @attr + align: TextAlign; +} diff --git a/packages/web-components/src/theme/index.ts b/packages/web-components/src/theme/index.ts index 805ad62b80b66..28968493cd475 100644 --- a/packages/web-components/src/theme/index.ts +++ b/packages/web-components/src/theme/index.ts @@ -1,2 +1,2 @@ -export * from './design-tokens'; -export { setTheme } from './set-theme'; +export * from './design-tokens.js'; +export { setTheme } from './set-theme.js'; diff --git a/packages/web-components/src/theme/set-theme.ts b/packages/web-components/src/theme/set-theme.ts index 7c86f95ab2ac5..ea0aa8cd11065 100644 --- a/packages/web-components/src/theme/set-theme.ts +++ b/packages/web-components/src/theme/set-theme.ts @@ -1,5 +1,5 @@ import type { Theme } from '@fluentui/tokens'; -import * as tokens from './design-tokens'; +import * as tokens from './design-tokens.js'; const tokenNames = Object.keys(tokens); diff --git a/packages/web-components/src/theme/theme.stories.ts b/packages/web-components/src/theme/theme.stories.ts index 183987d06c461..63c78cb670fcd 100644 --- a/packages/web-components/src/theme/theme.stories.ts +++ b/packages/web-components/src/theme/theme.stories.ts @@ -1,5 +1,5 @@ import { DesignToken } from '@microsoft/fast-foundation'; -import * as tokens from '../theme/design-tokens'; +import * as tokens from '../theme/design-tokens.js'; DesignToken.registerDefaultStyleTarget(); diff --git a/packages/web-components/tsconfig.json b/packages/web-components/tsconfig.json index 1e252751d2e55..ec8a5a0595591 100644 --- a/packages/web-components/tsconfig.json +++ b/packages/web-components/tsconfig.json @@ -4,7 +4,7 @@ "esModuleInterop": true, "experimentalDecorators": true, "resolveJsonModule": true, - "moduleResolution": "node", + "moduleResolution": "Node16", "declarationDir": "dist/dts", "outDir": "dist/esm", "strictNullChecks": true, diff --git a/yarn.lock b/yarn.lock index d58feef3ce2cc..18ca8e3d3f1a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2674,18 +2674,18 @@ resolved "https://registry.yarnpkg.com/@microsoft/fast-element/-/fast-element-1.13.0.tgz#d390ff13697064a48dc6ad6bb332a5f5489f73f8" integrity sha512-iFhzKbbD0cFRo9cEzLS3Tdo9BYuatdxmCEKCpZs1Cro/93zNMpZ/Y9/Z7SknmW6fhDZbpBvtO8lLh9TFEcNVAQ== -"@microsoft/fast-element@^2.0.0-beta.17": - version "2.0.0-beta.17" - resolved "https://registry.yarnpkg.com/@microsoft/fast-element/-/fast-element-2.0.0-beta.17.tgz#46f9b1feaf57e0cb841d31a80574537257b2239f" - integrity sha512-R9NLnH0rh/btfBIdDicHGblw4uk4U7TNTnfpZ25W5IC52cWg7DLr0ky5GujH7wXge8F4ze/Z8anABzhC9eHYrA== +"@microsoft/fast-element@^2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@microsoft/fast-element/-/fast-element-2.0.0-beta.20.tgz#a8b145386fc2442c3dc60f2495f54b12cab85ef5" + integrity sha512-7Ehsq1FzLUQA90rQH14Bx6C0UAtx/13Ihm+0M7zQk46vL/PVvP0t/supn5UnjtI9d/zDrM5jxaFcxYjTfW8aIA== -"@microsoft/fast-foundation@^3.0.0-alpha.21": - version "3.0.0-alpha.21" - resolved "https://registry.yarnpkg.com/@microsoft/fast-foundation/-/fast-foundation-3.0.0-alpha.21.tgz#b867b3483d9e2ed496b301e6a6c4017adfa333f4" - integrity sha512-hKY2aI7OI54SN+h2ayJVh4CLD8dcbCuaOWihdiUTBnwNVgl/xANazO3q+Cs2P/5gHHOy2lIgJOZvsK9ilBsCQQ== +"@microsoft/fast-foundation@^3.0.0-alpha.24": + version "3.0.0-alpha.24" + resolved "https://registry.yarnpkg.com/@microsoft/fast-foundation/-/fast-foundation-3.0.0-alpha.24.tgz#3b750f1fbee9f7f9ca771053c7c941ff2d69fb6c" + integrity sha512-Ln33vUNm5t//rK0UAF/GM6njJr36M/ure0NUwbifAPqYmV8l9FgnnI+IY7kWBFhn+kZLERBh5sSz2TWGgMF9lg== dependencies: "@floating-ui/dom" "^1.0.3" - "@microsoft/fast-element" "^2.0.0-beta.17" + "@microsoft/fast-element" "^2.0.0-beta.20" "@microsoft/fast-web-utilities" "^6.0.0" tabbable "^5.2.0" tslib "^2.4.0"