diff --git a/packages/survey-vue3-ui/src/MatrixTable.vue b/packages/survey-vue3-ui/src/MatrixTable.vue index bc3e7ae9fb..249d04e743 100644 --- a/packages/survey-vue3-ui/src/MatrixTable.vue +++ b/packages/survey-vue3-ui/src/MatrixTable.vue @@ -68,16 +68,5 @@ const props = defineProps<{ question: QuestionMatrixDropdownModelBase }>(); const table = computed(() => { return props.question.renderedTable; }); -useBase( - () => table.value, - (newValue) => { - const instance = getCurrentInstance(); - newValue.renderedRowsChangedCallback = () => { - instance?.proxy?.$forceUpdate(); - }; - }, - (value) => { - value.renderedRowsChangedCallback = () => {}; - } -); +useBase(() => table.value); diff --git a/src/panel.ts b/src/panel.ts index 4621ed923f..eea15b6a76 100644 --- a/src/panel.ts +++ b/src/panel.ts @@ -126,6 +126,7 @@ export class QuestionRowModel extends Base { return { getRerenderEvent: () => this.onElementRerendered, isAnimationEnabled: () => this.animationAllowed, + allowSyncRemovalAddition: false, getAnimatedElement: (element: IElement) => (element as any as SurveyElement).getWrapperElement(), getLeaveOptions: (element: IElement) => { const surveyElement = element as unknown as SurveyElement; @@ -146,8 +147,8 @@ export class QuestionRowModel extends Base { }; } public visibleElementsAnimation: AnimationGroup = new AnimationGroup(this.getVisibleElementsAnimationOptions(), (value) => { + this.setWidth(value); this.setPropertyValue("visibleElements", value); - this.setWidth(); }, () => this.visibleElements); public set visibleElements(val: Array) { if(!val.length) { @@ -195,10 +196,10 @@ export class QuestionRowModel extends Base { public get index(): number { return this.panel.rows.indexOf(this); } - private setWidth() { - var visCount = this.visibleElements.length; + private setWidth(visibleElement: Array) { + var visCount = visibleElement.length; if (visCount == 0) return; - const isSingleInRow = this.visibleElements.length === 1; + const isSingleInRow = visibleElement.length === 1; var counter = 0; var preSetWidthElements = []; for (var i = 0; i < this.elements.length; i++) { diff --git a/src/question_matrixdropdownrendered.ts b/src/question_matrixdropdownrendered.ts index f6ec87082e..0098f22048 100644 --- a/src/question_matrixdropdownrendered.ts +++ b/src/question_matrixdropdownrendered.ts @@ -220,10 +220,8 @@ export class QuestionMatrixDropdownRenderedTable extends Base { private hasRemoveRowsValue: boolean; private rowsActions: Array>; private cssClasses: any; - public renderedRowsChangedCallback = (): void => { }; @propertyArray({ onPush: (_: any, i: number, target: QuestionMatrixDropdownRenderedTable) => { - target.renderedRowsChangedCallback(); target.updateRenderedRows(); }, onRemove: (_: any, i: number, target: QuestionMatrixDropdownRenderedTable) => { @@ -262,7 +260,6 @@ export class QuestionMatrixDropdownRenderedTable extends Base { } private renderedRowsAnimation = new AnimationGroup(this.getRenderedRowsAnimationOptions(), (val) => { this._renderedRows = val; - this.renderedRowsChangedCallback(); }, () => this._renderedRows) public get renderedRows(): Array { diff --git a/src/question_ranking.ts b/src/question_ranking.ts index e28a02f5b2..5df00bdef1 100644 --- a/src/question_ranking.ts +++ b/src/question_ranking.ts @@ -253,7 +253,8 @@ export class QuestionRankingModel extends QuestionCheckboxModel { } const index = isRankingChoices ? this.renderedRankingChoices.indexOf(item) : this.renderedUnRankingChoices.indexOf(item); return this.domNode?.querySelector(`${containerSelector} [data-sv-drop-target-ranking-item='${index}']`); - } + }, + allowSyncRemovalAddition: true }; } diff --git a/src/react/reactquestion_matrixdropdownbase.tsx b/src/react/reactquestion_matrixdropdownbase.tsx index a1b21449a6..d60e1a395f 100644 --- a/src/react/reactquestion_matrixdropdownbase.tsx +++ b/src/react/reactquestion_matrixdropdownbase.tsx @@ -1,9 +1,10 @@ import * as React from "react"; import { ReactSurveyElement, - SurveyQuestionElementBase + SurveyElementBase, + SurveyQuestionElementBase, } from "./reactquestion_element"; -import { SurveyElementErrors, SurveyQuestion, SurveyQuestionAndErrorsCell, SurveyQuestionErrorCell } from "./reactquestion"; +import { ISurveyCreator, SurveyQuestion, SurveyQuestionAndErrorsCell, SurveyQuestionErrorCell } from "./reactquestion"; import { QuestionMatrixDropdownModelBase, QuestionMatrixDropdownRenderedRow, @@ -11,7 +12,7 @@ import { MatrixDropdownColumn, AdaptiveActionContainer, Question, - Base + Base, } from "survey-core"; import { SurveyQuestionCheckboxItem } from "./reactquestion_checkbox"; import { SurveyQuestionRadioItem } from "./reactquestion_radiogroup"; @@ -22,80 +23,38 @@ import { SurveyQuestionMatrixDynamicDragDropIcon } from "./components/matrix-act import { SurveyQuestionOtherValueItem } from "./reactquestion_comment"; import { ReactElementFactory } from "./element-factory"; -export class SurveyQuestionMatrixDropdownBase extends SurveyQuestionElementBase { - constructor(props: any) { - super(props); - //Create rendered table in contructor and not on rendering - const table = this.question.renderedTable; - this.state = this.getState(); - } - protected get question(): QuestionMatrixDropdownModelBase { - return this.questionBase as QuestionMatrixDropdownModelBase; - } - private getState(prevState: any = null) { - return { rowCounter: !prevState ? 0 : prevState.rowCounter + 1 }; - } - private updateStateOnCallback() { - if (this.isRendering) return; - this.setState(this.getState(this.state)); +class SurveyQuestionMatrixTable extends SurveyElementBase<{ question: QuestionMatrixDropdownModelBase, wrapCell: (cell: QuestionMatrixDropdownRenderedCell, element: JSX.Element, reason: string) => JSX.Element, creator: ISurveyCreator}, any> { + protected get question() { + return this.props.question; } - componentDidMount() { - super.componentDidMount(); - this.question.visibleRowsChangedCallback = () => { - this.updateStateOnCallback(); - }; - this.question.onRenderedTableResetCallback = () => { - this.question.renderedTable.renderedRowsChangedCallback = () => { - this.updateStateOnCallback(); - }; - this.updateStateOnCallback(); - }; - this.question.renderedTable.renderedRowsChangedCallback = () => { - this.updateStateOnCallback(); - }; + protected get creator() { + return this.props.creator; } - componentWillUnmount() { - super.componentWillUnmount(); - this.question.visibleRowsChangedCallback = () => {}; - this.question.onRenderedTableResetCallback = () => {}; - this.question.renderedTable.renderedRowsChangedCallback = () => {}; + protected get table() { + return this.question.renderedTable; } - protected renderElement(): JSX.Element { - return this.renderTableDiv(); + protected getStateElement() { + return this.table; } - renderTableDiv(): JSX.Element { - var header = this.renderHeader(); - var footers = this.renderFooter(); - var rows = this.renderRows(); - var divStyle = this.question.showHorizontalScroll - ? ({ overflowX: "scroll" } as React.CSSProperties) - : ({} as React.CSSProperties); - return ( -
(this.setControl(root))}> - - {header} - {rows} - {footers} -
-
- ); + protected wrapCell(cell: QuestionMatrixDropdownRenderedCell, element: JSX.Element, reason: string): JSX.Element { + return this.props.wrapCell(cell, element, reason); } renderHeader(): JSX.Element | null { - var table = this.question.renderedTable; + const table = this.question.renderedTable; if (!table.showHeader) return null; - var headers: any[] = []; - var cells = table.headerRow.cells; + const headers: any[] = []; + const cells = table.headerRow.cells; for (var i = 0; i < cells.length; i++) { - var cell = cells[i]; - var key = "column" + i; - var columnStyle: any = {}; + const cell = cells[i]; + const key = "column" + i; + const columnStyle: any = {}; if (!!cell.width) { columnStyle.width = cell.width; } if (!!cell.minWidth) { columnStyle.minWidth = cell.minWidth; } - var cellContent = this.renderCellContent(cell, "column-header", {}); + const cellContent = this.renderCellContent(cell, "column-header", {}); const header = cell.hasTitle ? {cellContent} : ; @@ -108,9 +67,9 @@ export class SurveyQuestionMatrixDropdownBase extends SurveyQuestionElementBase ); } renderFooter(): JSX.Element | null { - var table = this.question.renderedTable; + const table = this.question.renderedTable; if (!table.showFooter) return null; - var row = this.renderRow( + const row = this.renderRow( "footer", table.footerRow, this.question.cssClasses, @@ -119,9 +78,9 @@ export class SurveyQuestionMatrixDropdownBase extends SurveyQuestionElementBase return {row}; } renderRows(): JSX.Element { - var cssClasses = this.question.cssClasses; - var rows:Array = []; - var renderedRows = this.question.renderedTable.renderedRows; + const cssClasses = this.question.cssClasses; + const rows:Array = []; + const renderedRows = this.question.renderedTable.renderedRows; for (var i = 0; i < renderedRows.length; i++) { rows.push( this.renderRow(renderedRows[i].id, renderedRows[i], cssClasses) @@ -135,13 +94,13 @@ export class SurveyQuestionMatrixDropdownBase extends SurveyQuestionElementBase cssClasses: any, reason?: string ): JSX.Element { - var matrixrow:Array = []; - var cells = row.cells; + const matrixrow:Array = []; + const cells = row.cells; for (var i = 0; i < cells.length; i++) { matrixrow.push(this.renderCell(cells[i], i, cssClasses, reason)); } - var key = "row" + keyValue; + const key = "row" + keyValue; return ( @@ -156,7 +115,7 @@ export class SurveyQuestionMatrixDropdownBase extends SurveyQuestionElementBase cssClasses: any, reason?: string ): JSX.Element { - var key = "cell" + index; + const key = "cell" + index; if (cell.hasQuestion) { return ( + {header} + {rows} + {footers} + + ); + } +} + +export class SurveyQuestionMatrixDropdownBase extends SurveyQuestionElementBase { + constructor(props: any) { + super(props); + //Create rendered table in contructor and not on rendering + const table = this.question.renderedTable; + this.state = this.getState(); + } + protected get question(): QuestionMatrixDropdownModelBase { + return this.questionBase as QuestionMatrixDropdownModelBase; + } + private getState(prevState: any = null) { + return { rowCounter: !prevState ? 0 : prevState.rowCounter + 1 }; + } + private updateStateOnCallback() { + if (this.isRendering) return; + this.setState(this.getState(this.state)); + } + componentDidMount(): void { + super.componentDidMount(); + this.question.onRenderedTableResetCallback = () => { + this.updateStateOnCallback(); + }; + } + componentWillUnmount(): void { + super.componentWillUnmount(); + this.question.onRenderedTableResetCallback = () => {}; + } + protected renderElement(): JSX.Element { + return this.renderTableDiv(); + } + renderTableDiv(): JSX.Element { + var divStyle = this.question.showHorizontalScroll + ? ({ overflowX: "scroll" } as React.CSSProperties) + : ({} as React.CSSProperties); + return ( +
(this.setControl(root))}> + this.wrapCell(cell, element, reason)}> +
+ ); + } } class SurveyQuestionMatrixActionsCell extends ReactSurveyElement { diff --git a/src/utils/animation.ts b/src/utils/animation.ts index 737aac7c8b..1b0004602e 100644 --- a/src/utils/animation.ts +++ b/src/utils/animation.ts @@ -27,6 +27,7 @@ export interface IAnimationGroupConsumer extends IAnimationConsumer<[T]> { getEnterOptions?(item: T, info?: IGroupAnimationInfo): AnimationOptions; getReorderOptions?(item: T, movedForward: boolean, info?: IGroupAnimationInfo): AnimationOptions; getKey?: (item: T) => any; + allowSyncRemovalAddition?: boolean; } export class AnimationUtils { @@ -229,15 +230,19 @@ export abstract class AnimationProperty = I protected onNextRender(callback: () => void, onCancel?: () => void): void { const rerenderEvent = this.animationOptions.getRerenderEvent(); if(!rerenderEvent) { - const raf = requestAnimationFrame(() => { - callback(); - this.cancelCallback = undefined; - }); - this.cancelCallback = () => { - onCancel && onCancel(); - cancelAnimationFrame(raf); - this.cancelCallback = undefined; - }; + if(DomWindowHelper.isAvailable()) { + const raf = DomWindowHelper.requestAnimationFrame(() => { + callback(); + this.cancelCallback = undefined; + }); + this.cancelCallback = () => { + onCancel && onCancel(); + cancelAnimationFrame(raf); + this.cancelCallback = undefined; + }; + } else { + throw new Error("Can't get next render"); + } } else { const clear = () => { rerenderEvent.remove(nextRenderCallback); @@ -257,7 +262,11 @@ export abstract class AnimationProperty = I protected abstract _sync(newValue: T): void; private _debouncedSync = debounce((newValue: T) => { this.animation.cancel(); - this._sync(newValue); + try { + this._sync(newValue); + } catch { + this.update(newValue); + } }) sync(newValue: T): void { if(this.animationOptions.isAnimationEnabled()) { @@ -299,29 +308,29 @@ export class AnimationGroup extends AnimationProperty, IAnimationGro protected animation: AnimationGroupUtils = new AnimationGroupUtils(); protected _sync (newValue: Array): void { const oldValue = this.getCurrentValue(); - try { - const { addedItems, deletedItems, reorderedItems, mergedItems } = compareArrays(oldValue, newValue, this.animationOptions.getKey ?? ((item: T) => item)); - const runAnimationCallback = () => { - this.animation.runGroupAnimation(this.animationOptions, addedItems, deletedItems, reorderedItems, () => { - if(deletedItems.length > 0) { - this.update(newValue); - } - }); - }; - if([addedItems, deletedItems, reorderedItems].some((arr) => arr.length > 0)) { - if(deletedItems.length <= 0 || reorderedItems.length > 0 || addedItems.length > 0) { - this.onNextRender(runAnimationCallback, () => { - this.update(newValue); - }); - this.update(mergedItems); - } else { - runAnimationCallback(); + const allowSyncRemovalAddition = this.animationOptions.allowSyncRemovalAddition ?? true; + let { addedItems, deletedItems, reorderedItems, mergedItems } = compareArrays(oldValue, newValue, this.animationOptions.getKey ?? ((item: T) => item)); + if(!allowSyncRemovalAddition && (reorderedItems.length > 0 || addedItems.length > 0)) { + deletedItems = []; + mergedItems = newValue; + } + const runAnimationCallback = () => { + this.animation.runGroupAnimation(this.animationOptions, addedItems, deletedItems, reorderedItems, () => { + if(deletedItems.length > 0) { + this.update(newValue); } + }); + }; + if([addedItems, deletedItems, reorderedItems].some((arr) => arr.length > 0)) { + if(deletedItems.length <= 0 || reorderedItems.length > 0 || addedItems.length > 0) { + this.onNextRender(runAnimationCallback, () => { + this.update(newValue); + }); + this.update(mergedItems); } else { - this.update(newValue); + runAnimationCallback(); } - - } catch { + } else { this.update(newValue); } } @@ -341,6 +350,7 @@ export class AnimationTab extends AnimationProperty, IAnimationGroup }); }, () => this.update(newValue)); this.update(tempValue, true); + } else { this.update(newValue); }