From 0dc7573b95f7af7849716a81eafbd709f34cb99b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B5=D0=B2=20=D0=A2?= =?UTF-8?q?=D0=B8=D0=BC=D1=83=D1=80=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5?= =?UTF-8?q?=D0=B5=D0=B2=D0=B8=D1=87?= Date: Tue, 24 Dec 2024 11:51:28 +0500 Subject: [PATCH 1/3] feat(ValidationContextWrapper): add virtualized wrapper --- packages/react-ui-validations/package.json | 4 +- .../src/ValidationContextWrapper.tsx | 63 ++++-- .../src/ValidationWrapperVirtualized.tsx | 54 +++++ .../ValidationWrapperVirtualizedInternal.tsx | 194 ++++++++++++++++++ .../src/Validations/ValidationReader.ts | 37 ++++ packages/react-ui-validations/src/index.tsx | 4 + .../stories/VirtualizedTable.stories.tsx | 159 ++++++++++++++ yarn.lock | 86 +++++--- 8 files changed, 555 insertions(+), 46 deletions(-) create mode 100644 packages/react-ui-validations/src/ValidationWrapperVirtualized.tsx create mode 100644 packages/react-ui-validations/src/ValidationWrapperVirtualizedInternal.tsx create mode 100644 packages/react-ui-validations/stories/VirtualizedTable.stories.tsx diff --git a/packages/react-ui-validations/package.json b/packages/react-ui-validations/package.json index bf46ac8809e..e1d8ddee458 100644 --- a/packages/react-ui-validations/package.json +++ b/packages/react-ui-validations/package.json @@ -75,10 +75,10 @@ "@skbkontur/storybook-addon-live-examples": "0.0.8", "@storybook/addon-a11y": "7.6.18", "@storybook/addon-controls": "7.6.18", + "@storybook/addon-docs": "7.6.18", "@storybook/addon-essentials": "7.6.18", "@storybook/addon-interactions": "^7.6.18", "@storybook/addon-links": "7.6.18", - "@storybook/addon-docs": "7.6.18", "@storybook/blocks": "7.6.18", "@storybook/react": "7.6.18", "@storybook/react-webpack5": "7.6.18", @@ -91,6 +91,7 @@ "@types/react-dom": "18.3.0", "@types/react-helmet": "^6.1.11", "@types/react-syntax-highlighter": "^15.5.13", + "@types/react-window": "1.8.8", "@types/styled-components": "^5.1.34", "@types/warning": "^3.0.0", "babel-loader": "^9.1.3", @@ -118,6 +119,7 @@ "react-hot-loader": "^4.13.1", "react-router-dom": "^6.23.1", "react-syntax-highlighter": "15.5.0", + "react-window": "1.8.10", "rimraf": "^5.0.7", "rollup": "^4.18.0", "rollup-plugin-typescript2": "^0.36.0", diff --git a/packages/react-ui-validations/src/ValidationContextWrapper.tsx b/packages/react-ui-validations/src/ValidationContextWrapper.tsx index 18e7c3ea95f..af76c2a85cd 100644 --- a/packages/react-ui-validations/src/ValidationContextWrapper.tsx +++ b/packages/react-ui-validations/src/ValidationContextWrapper.tsx @@ -4,6 +4,7 @@ import { ValidationWrapperInternal } from './ValidationWrapperInternal'; import type { ScrollOffset, ValidateArgumentType } from './ValidationContainer'; import { isNullable } from './utils/isNullable'; import { FocusMode } from './FocusMode'; +import { ValidationWrapperVirtualizedInternal } from './ValidationWrapperVirtualizedInternal'; export interface ValidationContextSettings { scrollOffset: ScrollOffset; @@ -20,16 +21,23 @@ export interface ValidationContextWrapperProps { export interface ValidationContextType { register: (wrapper: ValidationWrapperInternal) => void; + registerVirtual: (reader: ValidationWrapperVirtualizedInternal) => void; unregister: (wrapper: ValidationWrapperInternal) => void; + unregisterVirtual: (reader: ValidationWrapperVirtualizedInternal) => void; instanceProcessBlur: (wrapper: ValidationWrapperInternal) => void; - onValidationUpdated: (wrapper: ValidationWrapperInternal, isValid: boolean) => void; + onValidationUpdated: ( + wrapper: ValidationWrapperInternal | ValidationWrapperVirtualizedInternal, + isValid: boolean, + ) => void; getSettings: () => ValidationContextSettings; isAnyWrapperInChangingMode: () => boolean; } export const ValidationContext = React.createContext({ register: () => undefined, + registerVirtual: () => undefined, unregister: () => undefined, + unregisterVirtual: () => undefined, instanceProcessBlur: () => undefined, onValidationUpdated: () => undefined, getSettings: () => ({ @@ -43,6 +51,7 @@ ValidationContext.displayName = 'ValidationContext'; export class ValidationContextWrapper extends React.Component { public childWrappers: ValidationWrapperInternal[] = []; + public virtualWrappers: ValidationWrapperVirtualizedInternal[] = []; public getSettings(): ValidationContextSettings { let scrollOffset: ScrollOffset = {}; @@ -67,27 +76,46 @@ export class ValidationContextWrapper extends React.Component x !== instance && !x.isIndependent())) { wrapper.processBlur(); } } - public onValidationUpdated(wrapper: ValidationWrapperInternal, isValid?: boolean) { + public onValidationUpdated( + wrapper: ValidationWrapperInternal | ValidationWrapperVirtualizedInternal, + isValid?: boolean, + ) { const { onValidationUpdated } = this.props; if (onValidationUpdated) { - const isValidResult = !this.childWrappers.find((x) => { - if (x === wrapper) { - return !isValid; - } - return x.hasError(); - }); - onValidationUpdated(isValidResult); + const isValidResult = + !this.childWrappers.find((x) => { + if (x === wrapper) { + return !isValid; + } + return x.hasError(); + }) || + this.virtualWrappers.find((x) => { + if (x === wrapper) { + return !isValid; + } + return x.hasError(); + }); + onValidationUpdated(!!isValidResult); } } @@ -95,6 +123,14 @@ export class ValidationContextWrapper extends React.Component x.isChanging); } + public onVirtualValidationRemoved() { + const { onValidationUpdated } = this.props; + if (onValidationUpdated) { + const isValidResult = !this.virtualWrappers.find((x) => x.hasError()); + onValidationUpdated(isValidResult); + } + } + public onValidationRemoved() { const { onValidationUpdated } = this.props; if (onValidationUpdated) { @@ -103,8 +139,8 @@ export class ValidationContextWrapper extends React.Component ({ + public getChildWrappersSortedByPosition(): Array { + const wrappersWithPosition = [...this.childWrappers, ...this.virtualWrappers].map((x) => ({ target: x, position: x.getControlPosition(), })); @@ -132,7 +168,10 @@ export class ValidationContextWrapper extends React.Component { const focusMode = ValidationContextWrapper.getFocusMode(withoutFocusOrValidationSettings); - await Promise.all(this.childWrappers.map((x) => x.processSubmit())); + await Promise.all([ + ...this.childWrappers.map((x) => x.processSubmit()), + ...this.virtualWrappers.map((x) => x.processSubmit()), + ]); const childrenWrappersSortedByPosition = this.getChildWrappersSortedByPosition(); const firstError = childrenWrappersSortedByPosition.find((x) => x.hasError()); diff --git a/packages/react-ui-validations/src/ValidationWrapperVirtualized.tsx b/packages/react-ui-validations/src/ValidationWrapperVirtualized.tsx new file mode 100644 index 00000000000..8c740b31873 --- /dev/null +++ b/packages/react-ui-validations/src/ValidationWrapperVirtualized.tsx @@ -0,0 +1,54 @@ +import React from 'react'; + +import { Nullable } from '../typings/Types'; + +import { Validation } from './ValidationWrapperInternal'; +import { + ValidationBehaviour, + ValidationLevel, + ValidationWrapperVirtualizedInternal, + ValidationWrapperVirtualizedInternalProps, +} from './ValidationWrapperVirtualizedInternal'; +import { ValidationReader } from './Validations'; + +export interface ValidationWrapperVirtualizedProps extends Pick { + children?: React.ReactElement; + validationInfos: ValidationReader; + onValidation?: (index: number | null, validation: Nullable) => void; + level?: ValidationLevel; + behaviour?: ValidationBehaviour; + independent?: boolean; + scrollToElement?: (index: number) => void; +} + +export class ValidationWrapperVirtualized extends React.Component { + public static __KONTUR_REACT_UI__ = 'ValidationWrapperVirtualized'; + public static displayName = 'ValidationWrapperVirtualized'; + + public render() { + const { + children, + onValidation, + behaviour, + level, + independent, + scrollToElement, + validationInfos, + 'data-tid': datTid, + } = this.props; + + return ( + + {children} + + ); + } +} diff --git a/packages/react-ui-validations/src/ValidationWrapperVirtualizedInternal.tsx b/packages/react-ui-validations/src/ValidationWrapperVirtualizedInternal.tsx new file mode 100644 index 00000000000..eefa6f026b6 --- /dev/null +++ b/packages/react-ui-validations/src/ValidationWrapperVirtualizedInternal.tsx @@ -0,0 +1,194 @@ +import React, { ReactInstance } from 'react'; +import warning from 'warning'; + +import { Nullable } from '../typings/Types'; + +import { getRootNode } from './utils/getRootNode'; +import { isBrowser } from './utils/utils'; +import { smoothScrollIntoView } from './smoothScrollIntoView'; +import { getLevel, getVisibleValidation, isEqual } from './ValidationHelper'; +import { ValidationContext, ValidationContextType } from './ValidationContextWrapper'; +import { Validation } from './ValidationWrapperInternal'; +import { ValidationReader } from './Validations'; + +if (isBrowser && typeof HTMLElement === 'undefined') { + const w = window as any; + w.HTMLElement = w.Element; +} + +export type ValidationBehaviour = 'immediate' | 'lostfocus' | 'submit'; + +export type ValidationLevel = 'error' | 'warning'; + +export interface ValidationWrapperVirtualizedInternalProps { + children?: React.ReactElement; + onValidation?: (index: number | null, validation: Nullable) => void; + validationInfos: ValidationReader; + scrollToElement?: (index: number) => void; + level?: ValidationLevel; + behaviour?: ValidationBehaviour; + independent?: boolean; + 'data-tid'?: string; +} + +interface ValidationWrapperVirtualizedInternalState { + validation: Nullable; +} + +interface Point { + x: number; + y: number; +} + +export class ValidationWrapperVirtualizedInternal extends React.Component< + ValidationWrapperVirtualizedInternalProps, + ValidationWrapperVirtualizedInternalState +> { + public state: ValidationWrapperVirtualizedInternalState = { + validation: null, + }; + + private child: any; // todo type + private rootNode: Nullable; + + public static contextType = ValidationContext; + public context: ValidationContextType = this.context; + public validationIndex: number | null = null; + private validationInfoTemplate: Validation = { + independent: this.props.independent || false, + message: '', + behaviour: this.props.behaviour || 'submit', + level: this.props.level || 'error', + }; + + public componentDidMount() { + warning( + this.context, + 'ValidationWrapper should appears as child of ValidationContainer.\n' + + 'https://tech.skbkontur.ru/react-ui-validations/#/getting-started', + ); + if (this.context) { + this.context.registerVirtual(this); + } + + this.validationIndex = this.props.validationInfos.getFirstNodeWithValidation(); + this.applyValidation(this.validationIndex ? this.validationInfoTemplate : null); + } + + public componentWillUnmount() { + this.context.unregisterVirtual(this); + } + + public componentDidUpdate() { + this.validationIndex = this.props.validationInfos.getFirstNodeWithValidation(); + this.applyValidation(this.validationIndex ? this.validationInfoTemplate : null); + } + + public async focus(): Promise { + const htmlElement = this.getRootNode(); + if (htmlElement instanceof HTMLElement) { + const { disableSmoothScroll, scrollOffset } = this.context.getSettings(); + if (this.props.scrollToElement && this.validationIndex) { + this.props.scrollToElement(this.validationIndex); + } + if (!disableSmoothScroll) { + await smoothScrollIntoView(htmlElement, scrollOffset); + } + if (this.child && typeof this.child.focus === 'function') { + this.child.focus(); + } + } + } + + public render() { + const { children, 'data-tid': dataTid } = this.props; + + return children ? ( + React.cloneElement(children, { + ref: this.customRef, + 'data-tid': dataTid, + }) + ) : ( + + ); + } + + private customRef = (instance: Nullable) => { + const { children } = this.props; + + this.setRootNode(instance); + const child = children as any; // todo type or maybe React.Children.only + if (child && child.ref) { + if (typeof child.ref === 'function') { + child.ref(instance); + } + if (Object.prototype.hasOwnProperty.call(child.ref, 'current')) { + child.ref.current = instance; + } + } + this.child = instance; + }; + + private setRootNode = (element: Nullable) => { + this.rootNode = getRootNode(element); + }; + + public getRootNode = () => { + return this.rootNode; + }; + + public getControlPosition(): Nullable { + const htmlElement = this.getRootNode(); + if (htmlElement instanceof HTMLElement) { + const rect = htmlElement.getBoundingClientRect(); + return { x: rect.top, y: rect.left }; + } + return null; + } + + public async processSubmit(): Promise { + return this.setValidation(this.validationIndex ? this.validationInfoTemplate : null); + } + + public hasError(): boolean { + return getLevel(this.state.validation) === 'error'; + } + + public hasWarning(): boolean { + return getLevel(this.state.validation) === 'warning'; + } + + private applyValidation(actual: Nullable) { + const visible = this.getVisibleValidation(actual); + this.setValidation(visible); + } + + private setValidation(validation: Nullable): Promise { + const current = this.state.validation; + + if (current === validation) { + return Promise.resolve(); + } + + return new Promise((resolve) => { + this.setState({ validation }, () => { + if (Boolean(current) !== Boolean(validation)) { + if (this.props.onValidation) { + this.props.onValidation(this.validationIndex, validation); + } + this.context.onValidationUpdated(this, !validation); + } + resolve(); + }); + }); + } + + private getVisibleValidation(actual: Nullable): Nullable { + const visible = this.state.validation; + if (isEqual(visible, actual)) { + return visible; + } + const changing = this.context.isAnyWrapperInChangingMode(); + return getVisibleValidation(visible, actual, changing); + } +} diff --git a/packages/react-ui-validations/src/Validations/ValidationReader.ts b/packages/react-ui-validations/src/Validations/ValidationReader.ts index 0d160eccc4f..e4b057847f0 100644 --- a/packages/react-ui-validations/src/Validations/ValidationReader.ts +++ b/packages/react-ui-validations/src/Validations/ValidationReader.ts @@ -28,6 +28,43 @@ export class ValidationReader { return this.node ? this.node.validation : null; } + private findValueInNestedObject(obj: Nullable>, valuesToFind: string[]) { + let foundKey = null; + if (!obj) { + return null; + } + + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + const currentValue = obj[key]; + + if (valuesToFind.includes(currentValue)) { + return key; + } + + if (typeof currentValue === 'object') { + foundKey = this.findValueInNestedObject(currentValue, valuesToFind); + + if (foundKey !== null) { + return key; + } + } + } + } + + return null; + } + + public getFirstNodeWithValidation(): number | null { + if (!this.node) { + return null; + } + if (this.node.validation) { + return 0; + } + return Number(this.findValueInNestedObject(this.node.children, ['invalid', 'warning'])); + } + private getReaderInternal(path: string[]): ValidationReader { const node = this.getNodeInternal(path); return new ValidationReader(node, this.tokens); diff --git a/packages/react-ui-validations/src/index.tsx b/packages/react-ui-validations/src/index.tsx index 60961cbe779..33f03e0b961 100644 --- a/packages/react-ui-validations/src/index.tsx +++ b/packages/react-ui-validations/src/index.tsx @@ -16,7 +16,9 @@ import type { ValidationBehaviour, } from './ValidationWrapperInternal'; import { ValidationWrapper } from './ValidationWrapper'; +import { ValidationWrapperVirtualized } from './ValidationWrapperVirtualized'; import type { ValidationInfo, ValidationWrapperProps } from './ValidationWrapper'; +import type { ValidationWrapperVirtualizedProps } from './ValidationWrapperVirtualized'; import type { ValidationContextType, ValidationContextWrapperProps } from './ValidationContextWrapper'; import { ValidationContext, ValidationContextWrapper, ValidationContextSettings } from './ValidationContextWrapper'; import { @@ -32,6 +34,7 @@ export type { ValidationContextType, ValidationContextWrapperProps, ValidationWrapperProps, + ValidationWrapperVirtualizedProps, ValidationTooltipProps, ScrollOffset, ValidationLevel, @@ -54,6 +57,7 @@ export { ValidationContextWrapper, ValidationWrapper as ValidationWrapperV1, ValidationWrapper, + ValidationWrapperVirtualized, ValidationTooltip, tooltip, text, diff --git a/packages/react-ui-validations/stories/VirtualizedTable.stories.tsx b/packages/react-ui-validations/stories/VirtualizedTable.stories.tsx new file mode 100644 index 00000000000..87e7fb20230 --- /dev/null +++ b/packages/react-ui-validations/stories/VirtualizedTable.stories.tsx @@ -0,0 +1,159 @@ +import React from 'react'; +import { Meta } from '@storybook/react'; +import { Button, Token } from '@skbkontur/react-ui'; +import { FixedSizeList as List } from 'react-window'; + +import { + createValidator, + ValidationContainer, + ValidationReader, + ValidationWrapper, + ValidationWrapperVirtualized, +} from '../src'; + +interface Props { + data: Data; + index: number; + validationReader: ValidationReader; + style?: React.CSSProperties; +} + +const SimpleElement = ({ data: value, validationReader, style }: Props) => { + return ( +
+
строка {value.value.value}
+
+ {value.title} + x.value.value).get()}> + {value.value.subtitle} + +
+
+ ); +}; + +interface WindowProps { + rowHeight: number; + gap?: number; + data: Data[]; + validationInfos: ValidationReader; +} + +const Window: React.FC = ({ rowHeight, data, validationInfos }) => { + const containerRef = React.useRef(null); + const [firstInvalidRow, setFirstInvalidRow] = React.useState(null); + + return ( +
+ {firstInvalidRow ? `first invalid row is ${firstInvalidRow}` : null} + setFirstInvalidRow(index)} + scrollToElement={(index) => { + containerRef.current?.scrollToItem(index, 'center'); + }} + behaviour="submit" + > + { + return ( + + ); + }} + /> + +
+ ); +}; + +const validator = createValidator<{ template: Data; array: Data[]; secondTemplate: Data }>((b) => { + b.prop( + (x) => x.array, + (b) => { + b.array( + (x) => x, + (b) => { + b.prop( + (x) => x.value, + (b) => { + b.prop( + (x) => x.subtitle, + (b) => { + b.invalid(() => true, 'invlid sub'); + }, + ); + b.prop( + (x) => x.value, + (b) => { + b.invalid((x) => x > 40, 'invalid', 'immediate'); + }, + ); + }, + ); + }, + ); + }, + ); + b.prop( + (x) => x.template, + (b) => { + b.invalid(() => true, 'kek'); + }, + ); +}); + +interface Data { + title: string; + value: { + subtitle: string; + value: number; + }; +} + +export const Default = () => { + const containerRef = React.useRef(null); + const data: Data[] = new Array(100).fill(0).map((_, i) => ({ + title: `title ${i}`, + value: { subtitle: `subtitle ${i}`, value: i }, + })); + const values = { + template: { title: 'titke', value: { subtitle: 'sub', value: 90 } }, + array: data, + secondTemplate: { title: 'titke', value: { subtitle: 'sub', value: 90 } }, + }; + const createValidator = validator(values); + const [isValid, setIsValid] = React.useState(true); + const handleValidate = async () => { + const result = await containerRef.current?.validate(); + setIsValid(!!result); + }; + + return ( + +
+ x.array)} /> + + isValid {isValid.toString()} + x.secondTemplate).get()}> + заглушка + +
+
+ ); +}; + +export default { + title: 'Virtualized', +} as Meta; diff --git a/yarn.lock b/yarn.lock index 1dd56d7e918..458eb930886 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6805,6 +6805,13 @@ dependencies: "@types/react" "*" +"@types/react-window@1.8.8": + version "1.8.8" + resolved "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz#c20645414d142364fbe735818e1c1e0a145696e3" + integrity sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q== + dependencies: + "@types/react" "*" + "@types/react@*": version "18.2.75" resolved "https://registry.npmjs.org/@types/react/-/react-18.2.75.tgz#45d18f384939306d35312def1bf532eb38a68562" @@ -8364,7 +8371,7 @@ better-opn@^3.0.2: big-integer@^1.6.44: version "1.6.52" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" + resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== big.js@^5.2.2: @@ -8611,7 +8618,7 @@ byte-size@^7.0.0: bytes@3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= bytes@3.1.2: @@ -9391,7 +9398,7 @@ constants-browserify@^1.0.0: content-disposition@0.5.2: version "0.5.2" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= content-disposition@0.5.4, content-disposition@~0.5.2: @@ -9526,7 +9533,7 @@ convert-source-map@^1.5.0: convert-source-map@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== cookie-signature@1.0.6: @@ -10058,7 +10065,7 @@ decamelize-keys@^1.1.0: decamelize@^1.1.0, decamelize@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= decamelize@^4.0.0: @@ -10620,7 +10627,7 @@ duplexer@^0.1.1, duplexer@^0.1.2: duplexify@^3.5.0, duplexify@^3.6.0: version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + resolved "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== dependencies: end-of-stream "^1.0.0" @@ -10675,7 +10682,7 @@ emittery@^0.13.1: emoji-regex@^7.0.1: version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== emoji-regex@^8.0.0: @@ -10685,7 +10692,7 @@ emoji-regex@^8.0.0: emoji-regex@^9.2.2: version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== emojis-list@^3.0.0: @@ -11034,7 +11041,7 @@ esbuild-plugin-alias@^0.2.1: esbuild-register@^3.5.0: version "3.5.0" - resolved "https://registry.yarnpkg.com/esbuild-register/-/esbuild-register-3.5.0.tgz#449613fb29ab94325c722f560f800dd946dc8ea8" + resolved "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.5.0.tgz#449613fb29ab94325c722f560f800dd946dc8ea8" integrity sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A== dependencies: debug "^4.3.4" @@ -11771,7 +11778,7 @@ filenamify@^4.3.0: filesize@^8.0.6: version "8.0.7" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" + resolved "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== fill-range@^4.0.0: @@ -12080,7 +12087,7 @@ form-data@~2.3.2: format@^0.2.0: version "0.2.2" - resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + resolved "https://registry.npmjs.org/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs= forwarded@0.2.0: @@ -12788,7 +12795,7 @@ has-flag@^3.0.0: has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: @@ -12822,7 +12829,7 @@ has-unicode@^2.0.0, has-unicode@^2.0.1: has-value@^0.3.1: version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + resolved "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= dependencies: get-value "^2.0.3" @@ -13015,7 +13022,7 @@ htmlparser2@^6.0.0: htmlparser2@^6.1.0: version "6.1.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== dependencies: domelementtype "^2.0.1" @@ -15629,7 +15636,7 @@ mdast-util-to-markdown@^0.6.0: mdast-util-to-string@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" + resolved "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== mdast-util-to-string@^2.0.0: @@ -15664,6 +15671,11 @@ memfs@^4.6.0: sonic-forest "^1.0.0" tslib "^2.0.0" +"memoize-one@>=3.1.1 <6": + version "5.2.1" + resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + memoizerific@^1.11.3: version "1.11.3" resolved "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" @@ -15681,7 +15693,7 @@ memory-fs@^0.4.1: memorystream@^0.3.1: version "0.3.1" - resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" + resolved "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= meow@^12.0.1: @@ -15738,7 +15750,7 @@ methods@~1.1.2: micromark@~2.11.0: version "2.11.4" - resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.11.4.tgz#d13436138eea826383e822449c9a5c50ee44665a" + resolved "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz#d13436138eea826383e822449c9a5c50ee44665a" integrity sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA== dependencies: debug "^4.0.0" @@ -15870,7 +15882,7 @@ minimatch@5.0.1: minimatch@^5.0.1: version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" @@ -16555,7 +16567,7 @@ nwsapi@^2.2.2: nypm@^0.3.8: version "0.3.8" - resolved "https://registry.yarnpkg.com/nypm/-/nypm-0.3.8.tgz#a16b078b161be5885351e72cf0b97326973722bf" + resolved "https://registry.npmjs.org/nypm/-/nypm-0.3.8.tgz#a16b078b161be5885351e72cf0b97326973722bf" integrity sha512-IGWlC6So2xv6V4cIDmoV0SwwWx7zLG086gyqkyumteH2fIgCAM4nDVFB2iDRszDvmdSVW9xb1N+2KjQ6C7d4og== dependencies: citty "^0.1.6" @@ -16698,7 +16710,7 @@ object.pick@^1.3.0: object.values@^1.1.0, object.values@^1.1.1: version "1.1.3" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.3.tgz#eaa8b1e17589f02f698db093f7c62ee1699742ee" + resolved "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz#eaa8b1e17589f02f698db093f7c62ee1699742ee" integrity sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw== dependencies: call-bind "^1.0.2" @@ -17253,7 +17265,7 @@ path-key@^3.0.0, path-key@^3.1.0: path-key@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + resolved "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== path-parse@^1.0.6, path-parse@^1.0.7: @@ -17811,7 +17823,7 @@ puppeteer-core@22.9.0: puppeteer-core@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-2.1.1.tgz#e9b3fbc1237b4f66e25999832229e9db3e0b90ed" + resolved "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-2.1.1.tgz#e9b3fbc1237b4f66e25999832229e9db3e0b90ed" integrity sha512-n13AWriBMPYxnpbb6bnaY5YoY6rGj8vPLrz6CZF3o0qJNEwlcfJVxBzYZ0NJsQ21UbdJoijPCDrM++SUVEz7+w== dependencies: "@types/mime-types" "^2.1.0" @@ -17942,7 +17954,7 @@ ramda@0.29.0: randexp@0.4.6: version "0.4.6" - resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" + resolved "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ== dependencies: discontinuous-range "1.0.0" @@ -18443,6 +18455,14 @@ react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" +react-window@1.8.10: + version "1.8.10" + resolved "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz#9e6b08548316814b443f7002b1cf8fd3a1bdde03" + integrity sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + react@18.3.1, react@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" @@ -18530,7 +18550,7 @@ read-pkg@^3.0.0: read-pkg@^5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== dependencies: "@types/normalize-package-data" "^2.4.0" @@ -19191,7 +19211,7 @@ rxjs@^6.6.0: rxjs@^7.5.5, rxjs@^7.8.1: version "7.8.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== dependencies: tslib "^2.1.0" @@ -19342,7 +19362,7 @@ selfsigned@^2.1.1, selfsigned@^2.4.1: "semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== semver@7.3.5: @@ -19407,7 +19427,7 @@ serialize-javascript@6.0.0: serialize-javascript@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== dependencies: randombytes "^2.1.0" @@ -19478,7 +19498,7 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: set-function-length@^1.2.1: version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== dependencies: define-data-property "^1.1.4" @@ -19551,7 +19571,7 @@ shebang-command@^2.0.0: shebang-regex@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= shebang-regex@^3.0.0: @@ -20825,7 +20845,7 @@ to-object-path@^0.3.0: to-regex-range@^2.1.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= dependencies: is-number "^3.0.0" @@ -21037,7 +21057,7 @@ type-fest@^0.18.0: type-fest@^0.20.2: version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== type-fest@^0.4.1: @@ -21323,7 +21343,7 @@ unist-util-visit-parents@^5.1.1: unist-util-visit@^2.0.0: version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" + resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== dependencies: "@types/unist" "^2.0.0" @@ -22079,7 +22099,7 @@ which@^1.2.14, which@^1.2.9, which@^1.3.1: which@^2.0.1, which@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" From 105917fb1ff34282f365d0a59d0ca94dd1360da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B5=D0=B2=20=D0=A2?= =?UTF-8?q?=D0=B8=D0=BC=D1=83=D1=80=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5?= =?UTF-8?q?=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 25 Dec 2024 13:54:36 +0500 Subject: [PATCH 2/3] docs(VirtualizedList): add docs --- .../Default/chromeDefault.png | 4 +- .../.storybook/preview.tsx | 3 +- .../docs/Pages_NEW/Api.mdx | 41 +++++++ .../VirtualizedList.docs.stories.tsx | 116 ++++++++++++++++++ .../VirtualizedList/VirtualizedList.mdx | 13 ++ .../src/ValidationContextWrapper.tsx | 18 +-- ...tualized.tsx => ValidationListWrapper.tsx} | 18 +-- ....tsx => ValidationListWrapperInternal.tsx} | 14 +-- .../src/Validations/ValidationReader.ts | 5 +- packages/react-ui-validations/src/index.tsx | 8 +- .../stories/VirtualizedTable.stories.tsx | 9 +- 11 files changed, 211 insertions(+), 38 deletions(-) create mode 100644 packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.docs.stories.tsx create mode 100644 packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.mdx rename packages/react-ui-validations/src/{ValidationWrapperVirtualized.tsx => ValidationListWrapper.tsx} (63%) rename packages/react-ui-validations/src/{ValidationWrapperVirtualizedInternal.tsx => ValidationListWrapperInternal.tsx} (92%) diff --git a/packages/react-ui-validations/.creevey/images/ValidationContainer/Default/chromeDefault.png b/packages/react-ui-validations/.creevey/images/ValidationContainer/Default/chromeDefault.png index 96b113f8980..56e3aba7db8 100644 --- a/packages/react-ui-validations/.creevey/images/ValidationContainer/Default/chromeDefault.png +++ b/packages/react-ui-validations/.creevey/images/ValidationContainer/Default/chromeDefault.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:95dc696f146cfd2e3d16d2f2963d8782ab53dd6a7029c3896b7a87324b759f2d -size 4432 +oid sha256:15e8e5c41fd7ec98292916d9ea3dbbbabb6caf6b8cfc04b676c96ad690a624db +size 1149 diff --git a/packages/react-ui-validations/.storybook/preview.tsx b/packages/react-ui-validations/.storybook/preview.tsx index 3ae668b4f80..d5e63bb3658 100644 --- a/packages/react-ui-validations/.storybook/preview.tsx +++ b/packages/react-ui-validations/.storybook/preview.tsx @@ -9,7 +9,7 @@ import styled from 'styled-components'; import { HandThumbDownIcon } from '@skbkontur/icons/icons/HandThumbDownIcon'; import { HandThumbUpIcon } from '@skbkontur/icons/icons/HandThumbUpIcon'; import ThumbUpIcon from '@skbkontur/react-icons/ThumbUp'; - +import { FixedSizeList as List } from 'react-window'; import * as Validations from '../src/index'; import * as ReactUI from '../../react-ui/index'; import * as ControlsWithValidations from '../docs/Pages_NEW/Concepts/InlineValidations/ControlsWithValidations'; @@ -100,6 +100,7 @@ addons.setConfig({ HandThumbUpIcon, ThumbUpIcon, SpaceFiller, + List, }, decorators: [ThemeDecorator, FeatureFlagsDecorator], } as LiveConfig, diff --git a/packages/react-ui-validations/docs/Pages_NEW/Api.mdx b/packages/react-ui-validations/docs/Pages_NEW/Api.mdx index f3bd2dc5d75..54ae7f99f15 100644 --- a/packages/react-ui-validations/docs/Pages_NEW/Api.mdx +++ b/packages/react-ui-validations/docs/Pages_NEW/Api.mdx @@ -155,3 +155,44 @@ type RenderErrorMessage = ### `data-tid?: string` Позволяет задать кастомный `data-tid`, для доступа к содержимому ошибок. + +## ValidationListWrapper + +Обертка для виртуализированного списка, осуществляет поиск валидаций в "неотрендеренных" контролах. + +### `children: React.Node` + +Дочерний компонент должен быть ровно один. ValidationListWrapper контролирует его поведение путём передачи +prop-ов ref и data-tid. Для работы с компонентом используется +[React.cloneElement()](https://facebook.github.io/react/docs/react-api.html#cloneelement); + +### `validationInfos: ?ValidationReader` + +Результат возвращаемый из функции createValidator передает валидации в ValidationListWrapper. Если на любом уровне, вложенных в объект значений есть невалидное +- список считается невалидным. + + +### `onValidation?: (index: number | null, validation: Nullable) => void` + +Колбек для получения индекса невалидной строки и сообщения о невалидности + + +### `level?: ValidationLevel` + +См. [Документацию](https://ui.gitlab-pages.kontur.host/storybook-documentation/?path=/docs/react_ui_validations_displaying-validation-level--docs) + +### `behaviour?: ValidationBehaviour` + +См. [Документацию](https://ui.gitlab-pages.kontur.host/storybook-documentation/?path=/docs/react_ui_validations_displaying-validation-type--docs) + +### `independent?: boolean` + +См. [Документацию](https://ui.gitlab-pages.kontur.host/storybook-documentation/?path=/docs/react_ui_validations_validator-independent--docs) + +### `scrollToElement?: (index: number) => void` + +Колбек, принимающий индекс невалидного элемента для возможности прокрутки к невалидному неотрендеренному элементу + +### `data-tid?: string` + +Позволяет задать кастомный `data-tid`, для доступа к содержимому ошибок. diff --git a/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.docs.stories.tsx b/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.docs.stories.tsx new file mode 100644 index 00000000000..33a9693d6e2 --- /dev/null +++ b/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.docs.stories.tsx @@ -0,0 +1,116 @@ +import { Meta, Story } from '@skbkontur/react-ui/typings/stories'; +import React from 'react'; +import { Button } from '@skbkontur/react-ui/components/Button'; +import { FixedSizeList as List } from 'react-window'; +import { Token } from '@skbkontur/react-ui'; + +import { createValidator, ValidationContainer, ValidationListWrapper, ValidationWrapper } from '../../../../src'; + +export default { + title: 'Examples/Virtualized List example', + parameters: { creevey: { skip: true } }, +} as Meta; + + +interface Data { + title: string; + value: { + subtitle: string; + value: number; + }; +} + +export const VirtualizedListExample: Story = () => { + const containerRef = React.useRef(null); + const listRef = React.useRef(null); + const [firstInvalidRow, setFirstInvalidRow] = React.useState(null); + + const data: Data[] = new Array(100).fill(0).map((_, i) => ({ + title: `title ${i}`, + value: { subtitle: `subtitle ${i}`, value: i }, + })); + const values = { + array: data, + }; + + const validator = createValidator<{ array: Data[]; }>((b) => { + b.prop( + (x) => x.array, + (b) => { + b.array( + (x) => x, + (b) => { + b.prop( + (x) => x.value, + (b) => { + b.prop( + (x) => x.subtitle, + (b) => { + b.invalid(() => true, 'invlid sub'); + }, + ); + b.prop( + (x) => x.value, + (b) => { + b.invalid((x) => x > 40, 'invalid', 'immediate'); + }, + ); + }, + ); + }, + ); + }, + ); + }); + + + const validationRules = validator(values); + const [isValid, setIsValid] = React.useState(true); + const handleValidate = async () => { + const result = await containerRef.current?.validate(); + setIsValid(!!result); + }; + + return ( + +
+
+ {firstInvalidRow ? `first invalid row is ${firstInvalidRow}` : null} + x.array)} + onValidation={(index) => setFirstInvalidRow(index)} + scrollToElement={(index) => { + listRef.current?.scrollToItem(index, 'center'); + }} + behaviour="submit" + > + { + return ( +
+
строка {data[index].value.value}
+
+ {data[index].title} + x.array).getNodeByIndex(index).getNode((x) => x.value.value).get()}> + {data[index].value.subtitle} + +
+
+ ); + }} + /> +
+
+ + isValid {isValid.toString()} +
+
+ ); +}; diff --git a/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.mdx b/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.mdx new file mode 100644 index 00000000000..71ede197b4c --- /dev/null +++ b/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.mdx @@ -0,0 +1,13 @@ +import { Story } from '@storybook/blocks'; +import * as DocsStories from './VirtualizedList.docs.stories.tsx'; +import { Meta } from '../../../../.storybook/Meta'; + + + +# Валидация массива + +В примере показана валидация с виртуализированным списком + +### Пример + + diff --git a/packages/react-ui-validations/src/ValidationContextWrapper.tsx b/packages/react-ui-validations/src/ValidationContextWrapper.tsx index af76c2a85cd..75146a89a06 100644 --- a/packages/react-ui-validations/src/ValidationContextWrapper.tsx +++ b/packages/react-ui-validations/src/ValidationContextWrapper.tsx @@ -4,7 +4,7 @@ import { ValidationWrapperInternal } from './ValidationWrapperInternal'; import type { ScrollOffset, ValidateArgumentType } from './ValidationContainer'; import { isNullable } from './utils/isNullable'; import { FocusMode } from './FocusMode'; -import { ValidationWrapperVirtualizedInternal } from './ValidationWrapperVirtualizedInternal'; +import { ValidationListWrapperInternal } from './ValidationListWrapperInternal'; export interface ValidationContextSettings { scrollOffset: ScrollOffset; @@ -21,12 +21,12 @@ export interface ValidationContextWrapperProps { export interface ValidationContextType { register: (wrapper: ValidationWrapperInternal) => void; - registerVirtual: (reader: ValidationWrapperVirtualizedInternal) => void; + registerVirtual: (reader: ValidationListWrapperInternal) => void; unregister: (wrapper: ValidationWrapperInternal) => void; - unregisterVirtual: (reader: ValidationWrapperVirtualizedInternal) => void; + unregisterVirtual: (reader: ValidationListWrapperInternal) => void; instanceProcessBlur: (wrapper: ValidationWrapperInternal) => void; onValidationUpdated: ( - wrapper: ValidationWrapperInternal | ValidationWrapperVirtualizedInternal, + wrapper: ValidationWrapperInternal | ValidationListWrapperInternal, isValid: boolean, ) => void; getSettings: () => ValidationContextSettings; @@ -51,7 +51,7 @@ ValidationContext.displayName = 'ValidationContext'; export class ValidationContextWrapper extends React.Component { public childWrappers: ValidationWrapperInternal[] = []; - public virtualWrappers: ValidationWrapperVirtualizedInternal[] = []; + public virtualWrappers: ValidationListWrapperInternal[] = []; public getSettings(): ValidationContextSettings { let scrollOffset: ScrollOffset = {}; @@ -76,7 +76,7 @@ export class ValidationContextWrapper extends React.Component { + public getChildWrappersSortedByPosition(): Array { const wrappersWithPosition = [...this.childWrappers, ...this.virtualWrappers].map((x) => ({ target: x, position: x.getControlPosition(), diff --git a/packages/react-ui-validations/src/ValidationWrapperVirtualized.tsx b/packages/react-ui-validations/src/ValidationListWrapper.tsx similarity index 63% rename from packages/react-ui-validations/src/ValidationWrapperVirtualized.tsx rename to packages/react-ui-validations/src/ValidationListWrapper.tsx index 8c740b31873..8311c38f1b9 100644 --- a/packages/react-ui-validations/src/ValidationWrapperVirtualized.tsx +++ b/packages/react-ui-validations/src/ValidationListWrapper.tsx @@ -6,12 +6,12 @@ import { Validation } from './ValidationWrapperInternal'; import { ValidationBehaviour, ValidationLevel, - ValidationWrapperVirtualizedInternal, - ValidationWrapperVirtualizedInternalProps, -} from './ValidationWrapperVirtualizedInternal'; + ValidationListWrapperInternal, + ValidationListInternalProps, +} from './ValidationListWrapperInternal'; import { ValidationReader } from './Validations'; -export interface ValidationWrapperVirtualizedProps extends Pick { +export interface ValidationListProps extends Pick { children?: React.ReactElement; validationInfos: ValidationReader; onValidation?: (index: number | null, validation: Nullable) => void; @@ -21,9 +21,9 @@ export interface ValidationWrapperVirtualizedProps extends Pick void; } -export class ValidationWrapperVirtualized extends React.Component { - public static __KONTUR_REACT_UI__ = 'ValidationWrapperVirtualized'; - public static displayName = 'ValidationWrapperVirtualized'; +export class ValidationListWrapper extends React.Component { + public static __KONTUR_REACT_UI__ = 'ValidationListWrapper'; + public static displayName = 'ValidationListWrapper'; public render() { const { @@ -38,7 +38,7 @@ export class ValidationWrapperVirtualized extends React.Component {children} - + ); } } diff --git a/packages/react-ui-validations/src/ValidationWrapperVirtualizedInternal.tsx b/packages/react-ui-validations/src/ValidationListWrapperInternal.tsx similarity index 92% rename from packages/react-ui-validations/src/ValidationWrapperVirtualizedInternal.tsx rename to packages/react-ui-validations/src/ValidationListWrapperInternal.tsx index eefa6f026b6..309f1af828f 100644 --- a/packages/react-ui-validations/src/ValidationWrapperVirtualizedInternal.tsx +++ b/packages/react-ui-validations/src/ValidationListWrapperInternal.tsx @@ -20,7 +20,7 @@ export type ValidationBehaviour = 'immediate' | 'lostfocus' | 'submit'; export type ValidationLevel = 'error' | 'warning'; -export interface ValidationWrapperVirtualizedInternalProps { +export interface ValidationListInternalProps { children?: React.ReactElement; onValidation?: (index: number | null, validation: Nullable) => void; validationInfos: ValidationReader; @@ -31,7 +31,7 @@ export interface ValidationWrapperVirtualizedInternalProps { 'data-tid'?: string; } -interface ValidationWrapperVirtualizedInternalState { +interface ValidationListInternalState { validation: Nullable; } @@ -40,11 +40,11 @@ interface Point { y: number; } -export class ValidationWrapperVirtualizedInternal extends React.Component< - ValidationWrapperVirtualizedInternalProps, - ValidationWrapperVirtualizedInternalState +export class ValidationListWrapperInternal extends React.Component< + ValidationListInternalProps, + ValidationListInternalState > { - public state: ValidationWrapperVirtualizedInternalState = { + public state: ValidationListInternalState = { validation: null, }; @@ -64,7 +64,7 @@ export class ValidationWrapperVirtualizedInternal extends React.Component< public componentDidMount() { warning( this.context, - 'ValidationWrapper should appears as child of ValidationContainer.\n' + + 'ValidationListWrapper should appears as child of ValidationContainer.\n' + 'https://tech.skbkontur.ru/react-ui-validations/#/getting-started', ); if (this.context) { diff --git a/packages/react-ui-validations/src/Validations/ValidationReader.ts b/packages/react-ui-validations/src/Validations/ValidationReader.ts index e4b057847f0..badb2846102 100644 --- a/packages/react-ui-validations/src/Validations/ValidationReader.ts +++ b/packages/react-ui-validations/src/Validations/ValidationReader.ts @@ -55,14 +55,15 @@ export class ValidationReader { return null; } - public getFirstNodeWithValidation(): number | null { + public getFirstNodeWithValidation(level: "all" | "error" | "warning" = "all"): number | null { + const values = level === "all" ? ['invalid', 'warning'] : [level]; if (!this.node) { return null; } if (this.node.validation) { return 0; } - return Number(this.findValueInNestedObject(this.node.children, ['invalid', 'warning'])); + return Number(this.findValueInNestedObject(this.node.children, values)); } private getReaderInternal(path: string[]): ValidationReader { diff --git a/packages/react-ui-validations/src/index.tsx b/packages/react-ui-validations/src/index.tsx index 33f03e0b961..65365a8abb2 100644 --- a/packages/react-ui-validations/src/index.tsx +++ b/packages/react-ui-validations/src/index.tsx @@ -16,9 +16,9 @@ import type { ValidationBehaviour, } from './ValidationWrapperInternal'; import { ValidationWrapper } from './ValidationWrapper'; -import { ValidationWrapperVirtualized } from './ValidationWrapperVirtualized'; +import { ValidationListWrapper } from './ValidationListWrapper'; import type { ValidationInfo, ValidationWrapperProps } from './ValidationWrapper'; -import type { ValidationWrapperVirtualizedProps } from './ValidationWrapperVirtualized'; +import type { ValidationListProps } from './ValidationListWrapper'; import type { ValidationContextType, ValidationContextWrapperProps } from './ValidationContextWrapper'; import { ValidationContext, ValidationContextWrapper, ValidationContextSettings } from './ValidationContextWrapper'; import { @@ -34,7 +34,7 @@ export type { ValidationContextType, ValidationContextWrapperProps, ValidationWrapperProps, - ValidationWrapperVirtualizedProps, + ValidationListProps, ValidationTooltipProps, ScrollOffset, ValidationLevel, @@ -57,7 +57,7 @@ export { ValidationContextWrapper, ValidationWrapper as ValidationWrapperV1, ValidationWrapper, - ValidationWrapperVirtualized, + ValidationListWrapper, ValidationTooltip, tooltip, text, diff --git a/packages/react-ui-validations/stories/VirtualizedTable.stories.tsx b/packages/react-ui-validations/stories/VirtualizedTable.stories.tsx index 87e7fb20230..2f23215103c 100644 --- a/packages/react-ui-validations/stories/VirtualizedTable.stories.tsx +++ b/packages/react-ui-validations/stories/VirtualizedTable.stories.tsx @@ -8,7 +8,7 @@ import { ValidationContainer, ValidationReader, ValidationWrapper, - ValidationWrapperVirtualized, + ValidationListWrapper, } from '../src'; interface Props { @@ -46,7 +46,7 @@ const Window: React.FC = ({ rowHeight, data, validationInfos }) => return (
{firstInvalidRow ? `first invalid row is ${firstInvalidRow}` : null} - setFirstInvalidRow(index)} scrollToElement={(index) => { @@ -73,7 +73,7 @@ const Window: React.FC = ({ rowHeight, data, validationInfos }) => ); }} /> - +
); }; @@ -155,5 +155,6 @@ export const Default = () => { }; export default { - title: 'Virtualized', + title: 'Virtualize Table', + parameters: { creevey: { skip: true } }, } as Meta; From 36562b35efd95e4feba034f3934fdf361a0435ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B5=D0=B2=20=D0=A2?= =?UTF-8?q?=D0=B8=D0=BC=D1=83=D1=80=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5?= =?UTF-8?q?=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 25 Dec 2024 14:53:51 +0500 Subject: [PATCH 3/3] =?UTF-8?q?fix(ValidationContextWrapper):=20=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D1=82=D0=B8=D0=BD=D0=B3,=20=D1=81=D0=BA=D1=80?= =?UTF-8?q?=D0=B8=D0=BD=D1=88=D0=BE=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ValidationContainer/Default/chromeDefault.png | 4 ++-- packages/react-ui-validations/.storybook/preview.tsx | 1 + packages/react-ui-validations/docs/Pages_NEW/Api.mdx | 3 +-- .../VirtualizedList/VirtualizedList.docs.stories.tsx | 11 +++++++---- .../src/ValidationContextWrapper.tsx | 10 ++-------- .../src/Validations/ValidationReader.ts | 4 ++-- 6 files changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/react-ui-validations/.creevey/images/ValidationContainer/Default/chromeDefault.png b/packages/react-ui-validations/.creevey/images/ValidationContainer/Default/chromeDefault.png index 56e3aba7db8..96b113f8980 100644 --- a/packages/react-ui-validations/.creevey/images/ValidationContainer/Default/chromeDefault.png +++ b/packages/react-ui-validations/.creevey/images/ValidationContainer/Default/chromeDefault.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15e8e5c41fd7ec98292916d9ea3dbbbabb6caf6b8cfc04b676c96ad690a624db -size 1149 +oid sha256:95dc696f146cfd2e3d16d2f2963d8782ab53dd6a7029c3896b7a87324b759f2d +size 4432 diff --git a/packages/react-ui-validations/.storybook/preview.tsx b/packages/react-ui-validations/.storybook/preview.tsx index d5e63bb3658..3884dc2222c 100644 --- a/packages/react-ui-validations/.storybook/preview.tsx +++ b/packages/react-ui-validations/.storybook/preview.tsx @@ -10,6 +10,7 @@ import { HandThumbDownIcon } from '@skbkontur/icons/icons/HandThumbDownIcon'; import { HandThumbUpIcon } from '@skbkontur/icons/icons/HandThumbUpIcon'; import ThumbUpIcon from '@skbkontur/react-icons/ThumbUp'; import { FixedSizeList as List } from 'react-window'; + import * as Validations from '../src/index'; import * as ReactUI from '../../react-ui/index'; import * as ControlsWithValidations from '../docs/Pages_NEW/Concepts/InlineValidations/ControlsWithValidations'; diff --git a/packages/react-ui-validations/docs/Pages_NEW/Api.mdx b/packages/react-ui-validations/docs/Pages_NEW/Api.mdx index 54ae7f99f15..5e957c34708 100644 --- a/packages/react-ui-validations/docs/Pages_NEW/Api.mdx +++ b/packages/react-ui-validations/docs/Pages_NEW/Api.mdx @@ -169,14 +169,13 @@ prop-ов ref и data-tid. Для работы с компонентом исп ### `validationInfos: ?ValidationReader` Результат возвращаемый из функции createValidator передает валидации в ValidationListWrapper. Если на любом уровне, вложенных в объект значений есть невалидное -- список считается невалидным. +- список считается невалидным. ### `onValidation?: (index: number | null, validation: Nullable) => void` Колбек для получения индекса невалидной строки и сообщения о невалидности - ### `level?: ValidationLevel` См. [Документацию](https://ui.gitlab-pages.kontur.host/storybook-documentation/?path=/docs/react_ui_validations_displaying-validation-level--docs) diff --git a/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.docs.stories.tsx b/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.docs.stories.tsx index 33a9693d6e2..491440a0175 100644 --- a/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.docs.stories.tsx +++ b/packages/react-ui-validations/docs/Pages_NEW/Examples/VirtualizedList/VirtualizedList.docs.stories.tsx @@ -11,7 +11,6 @@ export default { parameters: { creevey: { skip: true } }, } as Meta; - interface Data { title: string; value: { @@ -33,7 +32,7 @@ export const VirtualizedListExample: Story = () => { array: data, }; - const validator = createValidator<{ array: Data[]; }>((b) => { + const validator = createValidator<{ array: Data[] }>((b) => { b.prop( (x) => x.array, (b) => { @@ -63,7 +62,6 @@ export const VirtualizedListExample: Story = () => { ); }); - const validationRules = validator(values); const [isValid, setIsValid] = React.useState(true); const handleValidate = async () => { @@ -98,7 +96,12 @@ export const VirtualizedListExample: Story = () => {
{data[index].title} x.array).getNodeByIndex(index).getNode((x) => x.value.value).get()}> + validationInfo={validationRules + .getNode((x) => x.array) + .getNodeByIndex(index) + .getNode((x) => x.value.value) + .get()} + > {data[index].value.subtitle}
diff --git a/packages/react-ui-validations/src/ValidationContextWrapper.tsx b/packages/react-ui-validations/src/ValidationContextWrapper.tsx index 75146a89a06..71fbd2c9ae7 100644 --- a/packages/react-ui-validations/src/ValidationContextWrapper.tsx +++ b/packages/react-ui-validations/src/ValidationContextWrapper.tsx @@ -25,10 +25,7 @@ export interface ValidationContextType { unregister: (wrapper: ValidationWrapperInternal) => void; unregisterVirtual: (reader: ValidationListWrapperInternal) => void; instanceProcessBlur: (wrapper: ValidationWrapperInternal) => void; - onValidationUpdated: ( - wrapper: ValidationWrapperInternal | ValidationListWrapperInternal, - isValid: boolean, - ) => void; + onValidationUpdated: (wrapper: ValidationWrapperInternal | ValidationListWrapperInternal, isValid: boolean) => void; getSettings: () => ValidationContextSettings; isAnyWrapperInChangingMode: () => boolean; } @@ -96,10 +93,7 @@ export class ValidationContextWrapper extends React.Component { return null; } - public getFirstNodeWithValidation(level: "all" | "error" | "warning" = "all"): number | null { - const values = level === "all" ? ['invalid', 'warning'] : [level]; + public getFirstNodeWithValidation(level: 'all' | 'error' | 'warning' = 'all'): number | null { + const values = level === 'all' ? ['invalid', 'warning'] : [level]; if (!this.node) { return null; }