Skip to content

Commit

Permalink
Implement paneldynamic and matrixdynamic animations (#8126)
Browse files Browse the repository at this point in the history
* Refactor animation utils + Implement AnimationTab class

* Implement animations in PanelDynamic

* Implement matrixdynamic animations

* Fix collapsed styles for matrix questions

* Fix box-sizing for question's content

* Fix padding animation is set incorrectly for complex questions

* Fix separator animation causing scrollbar appear

* Fix overflow while animating height

* Fix angular and knockout builds

* Fix scrolling during paneldynamic animations

* Fix vr tests

* Fix focus new panel doesnt work when animation is disabled

* Fix styles

* Fix vr test

* small refactor

* Add unit test on onNextRender method
  • Loading branch information
dk981234 committed Apr 22, 2024
1 parent 38ba3f1 commit c754243
Show file tree
Hide file tree
Showing 47 changed files with 1,530 additions and 561 deletions.
@@ -1,6 +1,6 @@
<ng-template #template>
<tr *ngIf="model.visible" [class]="model.className" (pointerdown)="question.onPointerDown($event, row)"
[attr.data-sv-drop-target-matrix-row]="row && row.id">
[attr.data-sv-drop-target-matrix-row]="row && row.id" #container>
<sv-ng-matrixdropdown-cell [cell]="cell" [question]="question"
*ngFor="let cell of model.cells; trackBy: trackCellBy"></sv-ng-matrixdropdown-cell>
</tr>
Expand Down
21 changes: 19 additions & 2 deletions packages/survey-angular-ui/src/questions/matrix-row.component.ts
Expand Up @@ -2,9 +2,7 @@ import { Component, ElementRef, Input, ViewChild } from "@angular/core";
import { BaseAngular } from "../base-angular";
import {
MatrixDropdownRowModelBase,
Question,
QuestionMatrixDropdownModelBase,
QuestionMatrixDropdownRenderedCell,
QuestionMatrixDropdownRenderedRow
} from "survey-core";

Expand All @@ -16,6 +14,7 @@ import {
export class MatrixRowComponent extends BaseAngular<QuestionMatrixDropdownRenderedRow> {
@Input() model!: QuestionMatrixDropdownRenderedRow;
@Input() question!: QuestionMatrixDropdownModelBase;
@ViewChild("container", { static: false, read: ElementRef }) container!: ElementRef<HTMLTableRowElement>;
protected getModel(): QuestionMatrixDropdownRenderedRow {
return this.model;
}
Expand All @@ -25,4 +24,22 @@ export class MatrixRowComponent extends BaseAngular<QuestionMatrixDropdownRender
public trackCellBy(_: number, cell: any): string {
return cell.id;
}
protected override onModelChanged(): void {
super.onModelChanged();
if(this.previousModel) {
this.previousModel.setRootElement(undefined as any)
}
if(this.model && this.container?.nativeElement) {
this.model.setRootElement(this.container.nativeElement);
}
}
public ngAfterViewInit(): void {
if(this.model && this.container?.nativeElement) {
this.model.setRootElement(this.container.nativeElement)
}
}
public override ngOnDestroy(): void {
super.ngOnDestroy();
this.model.setRootElement(undefined as any);
}
}
32 changes: 17 additions & 15 deletions packages/survey-angular-ui/src/questions/matrixtable.component.html
@@ -1,30 +1,32 @@
<div [style]="{ overflowX: question.showHorizontalScroll ? 'scroll' : '' }" #contentElement [class]="question.cssClasses.tableWrapper">
<div [style]="{ overflowX: question.showHorizontalScroll ? 'scroll' : '' }" #contentElement
[class]="question.cssClasses.tableWrapper">
<table [class]="question.getTableCss()">
<thead *ngIf="table.showHeader">
<tr>
<ng-container
*ngFor="let cell of table.headerRow.cells; trackBy: trackCellBy"
>
<th *ngIf="cell.hasTitle" [class]="cell.className" [style]="{ minWidth: cell.minWidth, width: cell.width }">
<ng-template [component]="{ name: question.getColumnHeaderWrapperComponentName($any(cell)), data: { componentData: question.getColumnHeaderWrapperComponentData($any(cell)) } }">
<sv-ng-string [model]="cell.locTitle"></sv-ng-string>
<sv-ng-matrixheaderrequired *ngIf="!!cell.column" [column]="cell.column" [question]="question">
</sv-ng-matrixheaderrequired>
</ng-template>
</th>
<td *ngIf="!cell.hasTitle" [class]="cell.className" [style]="{ minWidth: cell.minWidth, width: cell.width }"></td>
<ng-container *ngFor="let cell of table.headerRow.cells; trackBy: trackCellBy">
<th *ngIf="cell.hasTitle" [class]="cell.className" [style]="{ minWidth: cell.minWidth, width: cell.width }">
<ng-template
[component]="{ name: question.getColumnHeaderWrapperComponentName($any(cell)), data: { componentData: question.getColumnHeaderWrapperComponentData($any(cell)) } }">
<sv-ng-string [model]="cell.locTitle"></sv-ng-string>
<sv-ng-matrixheaderrequired *ngIf="!!cell.column" [column]="cell.column" [question]="question">
</sv-ng-matrixheaderrequired>
</ng-template>
</th>

<td *ngIf="!cell.hasTitle" [class]="cell.className" [style]="{ minWidth: cell.minWidth, width: cell.width }">
</td>
</ng-container>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let row of table.rows; trackBy: trackRowBy">
<ng-container *ngFor="let row of table.renderedRows; trackBy: trackRowBy">
<sv-ng-matrix-row [model]="row" [question]="question"></sv-ng-matrix-row>
</ng-container>
</tbody>
<tfoot *ngIf="table.showFooter">
<tr>
<sv-ng-matrixdropdown-cell [cell]="cell" [question]="question" *ngFor="let cell of table.footerRow.cells; trackBy: trackCellBy">
<sv-ng-matrixdropdown-cell [cell]="cell" [question]="question"
*ngFor="let cell of table.footerRow.cells; trackBy: trackCellBy">
</sv-ng-matrixdropdown-cell>
</tr>
</tfoot>
Expand Down
Expand Up @@ -3,39 +3,38 @@
<span [model]="model.locNoEntriesText" sv-ng-string></span>
<sv-ng-paneldynamic-add-btn *ngIf="model.canAddPanel" [data]="{ question: model }"></sv-ng-paneldynamic-add-btn>
</div>
<div [class]="model.cssClasses.progress" *ngIf="!model.showLegacyNavigation && model.isProgressTopShowing && model.isRangeShowing">
<div
[class]="model.cssClasses.progressBar"
[style]="{ width: model.progress }"
role="progressbar"
></div>
<div [class]="model.cssClasses.progress"
*ngIf="!model.showLegacyNavigation && model.isProgressTopShowing && model.isRangeShowing">
<div [class]="model.cssClasses.progressBar" [style]="{ width: model.progress }" role="progressbar"></div>
</div>
<ng-container [ngTemplateOutlet]="progress" *ngIf="model.showLegacyNavigation && model.isProgressTopShowing"></ng-container>
<!-- add track by [key]="panel.id" -->
<ng-container *ngFor="let panel of renderedPanels; index as index">
<div [class]="model.getPanelWrapperCss(panel)">
<ng-template [component]="{ name: getPanelComponentName(panel), data: getPanelComponentData(panel) }"></ng-template>
<ng-container *ngIf="model.panelRemoveButtonLocation ==='right' && model.canRemovePanel && panel.state != 'collapsed'">
<ng-template [component]="{ name: 'sv-paneldynamic-remove-btn', data: { data: { panel, question: model }}}"></ng-template>
</ng-container>
</div>
<!-- add track by [key]="'separator' + panel.id" -->
<hr [class]="model.cssClasses.separator" *ngIf="model.showSeparator(index)"/>
</ng-container>
<ng-container [ngTemplateOutlet]="progress" *ngIf="model.showLegacyNavigation && model.isProgressBottomShowing"></ng-container>
<sv-ng-paneldynamic-add-btn *ngIf="model.showLegacyNavigation && model.isRenderModeList" [data]="{ question: model }"></sv-ng-paneldynamic-add-btn>
<ng-container [ngTemplateOutlet]="progress"
*ngIf="model.showLegacyNavigation && model.isProgressTopShowing"></ng-container>
<div [class]="model.cssClasses.panelsContainer">
<ng-container *ngFor="let panel of model.renderedPanels; index as index; trackBy: trackPanelBy">
<div [class]="model.getPanelWrapperCss(panel)">
<ng-template
[component]="{ name: getPanelComponentName(panel), data: getPanelComponentData(panel) }"></ng-template>
<ng-container
*ngIf="model.panelRemoveButtonLocation ==='right' && model.canRemovePanel && panel.state != 'collapsed'">
<ng-template
[component]="{ name: 'sv-paneldynamic-remove-btn', data: { data: { panel, question: model }}}"></ng-template>
</ng-container>
</div>
<hr [class]="model.cssClasses.separator" *ngIf="model.showSeparator(index)" />
</ng-container>
</div>
<ng-container [ngTemplateOutlet]="progress"
*ngIf="model.showLegacyNavigation && model.isProgressBottomShowing"></ng-container>
<sv-ng-paneldynamic-add-btn *ngIf="model.showLegacyNavigation && model.isRenderModeList"
[data]="{ question: model }"></sv-ng-paneldynamic-add-btn>
<ng-container [ngTemplateOutlet]="progressV2" *ngIf="model.showNavigation"></ng-container>
</div>

<ng-template #progressV2>
<div [class]="model.cssClasses.footer" *ngIf="!!model.cssClasses.footer">
<hr [class]="model.cssClasses.separator"/>
<hr [class]="model.cssClasses.separator" />
<div [class]="model.cssClasses.progress" *ngIf="model.isRangeShowing && model.isProgressBottomShowing">
<div
[class]="model.cssClasses.progressBar"
[style]="{ width: model.progress }"
role="progressbar"
></div>
<div [class]="model.cssClasses.progressBar" [style]="{ width: model.progress }" role="progressbar"></div>
</div>
<div *ngIf="model.footerToolbar.visibleActions.length" [class]="model.cssClasses.footerButtonsContainer">
<sv-ng-action-bar [model]="model.footerToolbar"></sv-ng-action-bar>
Expand All @@ -48,13 +47,9 @@
<div [class]="model.cssClasses.progressContainer">
<sv-ng-paneldynamic-prev-btn [data]="{ question: model }"></sv-ng-paneldynamic-prev-btn>
<div [class]="model.cssClasses.progress" *ngIf="model.isRangeShowing">
<div
[class]="model.cssClasses.progressBar"
[style]="{ width: model.progress }"
role="progressbar"
></div>
<div [class]="model.cssClasses.progressBar" [style]="{ width: model.progress }" role="progressbar"></div>
</div>
<sv-ng-paneldynamic-next-btn [data]="{ question: model }"></sv-ng-paneldynamic-next-btn>
<sv-ng-paneldynamic-next-btn [data]="{ question: model }"></sv-ng-paneldynamic-next-btn>
</div>
<sv-ng-paneldynamic-add-btn [data]="{ question: model }"></sv-ng-paneldynamic-add-btn>
<sv-ng-paneldynamic-progress-text [data]="{ question: model }"></sv-ng-paneldynamic-progress-text>
Expand Down
Expand Up @@ -8,13 +8,8 @@ import { AngularComponentFactory } from "../component-factory";
templateUrl: "./paneldynamic.component.html"
})
export class PanelDynamicQuestionComponent extends QuestionAngular<QuestionPanelDynamicModel | any> implements OnInit {
get renderedPanels(): PanelModel[] {
if (this.model.isRenderModeList) return this.model.visiblePanels;
const panels = [];
if (this.model.currentPanel) {
panels.push(this.model.currentPanel);
}
return panels;
public trackPanelBy(_: number, panel: PanelModel) {
return panel.id;
}
protected override onModelChanged(): void {
super.onModelChanged();
Expand Down
1 change: 0 additions & 1 deletion packages/survey-vue3-ui/src/App.vue
Expand Up @@ -2,7 +2,6 @@
import { defineComponent } from "vue";
import { SurveyModel, StylesManager } from "survey-core";
import Survey from "./Survey.vue";
import "./index.css"
const json = {
"logoPosition": "right",
Expand Down
3 changes: 2 additions & 1 deletion packages/survey-vue3-ui/src/Element.vue
Expand Up @@ -70,7 +70,7 @@ const componentData = computed(() => {
};
});
watch(
const stopWatch = watch(
() => props.element,
(newValue, oldValue) => {
if (oldValue) {
Expand All @@ -85,5 +85,6 @@ onMounted(() => {
});
onUnmounted(() => {
props.element.setWrapperElement(undefined);
stopWatch();
});
</script>
49 changes: 49 additions & 0 deletions packages/survey-vue3-ui/src/MatrixRow.vue
@@ -0,0 +1,49 @@
<template>
<tr
:data-sv-drop-target-matrix-row="row.row && row.row.id"
@pointerdown="question.onPointerDown($event, row.row)"
:class="row.className"
v-if="row.visible"
ref="root"
>
<survey-matrixdropdown-cell
:cell="cell"
:question="question"
v-for="(cell, cellIndex) in row.cells"
:key="row.id + '_' + cellIndex"
/>
</tr>
</template>
<script lang="ts" setup>
import type {
QuestionMatrixDropdownRenderedRow,
QuestionMatrixDropdownModel,
} from "survey-core";
import { useBase } from "./base";
import { watch, onMounted, onUnmounted, ref } from "vue";
const props = defineProps<{
question: QuestionMatrixDropdownModel;
row: QuestionMatrixDropdownRenderedRow;
}>();
const root = ref<HTMLElement>();
useBase(() => props.row);
const stopWatch = watch(
() => props.row,
(newValue, oldValue) => {
if (oldValue) {
oldValue.setRootElement(undefined as any);
}
newValue.setRootElement(root.value as any);
}
);
onMounted(() => {
props.row.setRootElement(root.value as any);
});
onUnmounted(() => {
props.row.setRootElement(undefined as any);
stopWatch();
});
</script>
19 changes: 5 additions & 14 deletions packages/survey-vue3-ui/src/MatrixTable.vue
Expand Up @@ -36,22 +36,13 @@
</thead>
<tbody>
<template
v-for="row in table.rows"
v-for="row in table.renderedRows"
:key="question.inputId + '_' + row.id"
>
<tr
:data-sv-drop-target-matrix-row="row.row && row.row.id"
@pointerdown="question.onPointerDown($event, row.row)"
:class="row.className"
v-if="row.visible"
>
<survey-matrixdropdown-cell
:cell="cell"
:question="question"
v-for="(cell, cellIndex) in row.cells"
:key="row.id + '_' + cellIndex"
/>
</tr>
<survey-matrix-row
:row="row"
:question="question"
></survey-matrix-row>
</template>
</tbody>
<tfoot v-if="table.showFooter">
Expand Down
54 changes: 25 additions & 29 deletions packages/survey-vue3-ui/src/PanelDynamic.vue
Expand Up @@ -27,29 +27,34 @@
v-if="getShowLegacyNavigation() && question.isProgressTopShowing"
:question="question"
/>
<template v-for="(panel, index) in renderedPanels" :key="panel.id">
<div :class="question.getPanelWrapperCss(panel)">
<component
:is="getPanelComponentName(panel)"
v-bind="getPanelComponentData(panel)"
></component>
<sv-paneldynamic-remove-btn
<div :class="question.cssClasses.panelsContainer">
<template
v-for="(panel, index) in question.renderedPanels"
:key="panel.id"
>
<div :class="question.getPanelWrapperCss(panel)">
<component
:is="getPanelComponentName(panel)"
v-bind="getPanelComponentData(panel)"
></component>
<sv-paneldynamic-remove-btn
v-if="
question.panelRemoveButtonLocation === 'right' &&
question.canRemovePanel &&
panel.state !== 'collapsed'
"
:data="{ question, panel }"
/>
</div>
<hr
:class="question.cssClasses.separator"
v-if="
question.panelRemoveButtonLocation === 'right' &&
question.canRemovePanel &&
panel.state !== 'collapsed'
question.isRenderModeList && index < question.visiblePanelCount - 1
"
:data="{ question, panel }"
:key="'separator' + panel.id"
/>
</div>
<hr
:class="question.cssClasses.separator"
v-if="
question.isRenderModeList && index < question.visiblePanelCount - 1
"
:key="'separator' + panel.id"
/>
</template>
</template>
</div>
<survey-paneldynamicprogress
v-if="getShowLegacyNavigation() && question.isProgressBottomShowing"
:question="question"
Expand Down Expand Up @@ -99,15 +104,6 @@ useQuestion(
}
);
const renderedPanels = computed(() => {
if (props.question.isRenderModeList) return props.question.visiblePanels;
const panels = [];
if (props.question.currentPanel) {
panels.push(props.question.currentPanel);
}
return panels;
});
const getShowLegacyNavigation = () => {
return props.question["showLegacyNavigation"];
};
Expand Down
2 changes: 2 additions & 0 deletions packages/survey-vue3-ui/src/index.ts
Expand Up @@ -46,6 +46,7 @@ import MultipleText from "./Multipletext.vue";
import MultipletextItem from "./MultipletextItem.vue";

import Matrix from "./Matrix.vue";
import MatrixRow from "./MatrixRow.vue";
import MatrixCell from "./MatrixCell.vue";
import MatrixDropdown from "./MatrixDropdown.vue";
import MatrixTable from "./MatrixTable.vue";
Expand Down Expand Up @@ -193,6 +194,7 @@ function registerComponents(app: App) {
app.component("survey-multipletext-item", MultipletextItem);

app.component("survey-matrix", Matrix);
app.component("survey-matrix-row", MatrixRow);
app.component("survey-matrix-cell", MatrixCell);
app.component("survey-matrixdropdown", MatrixDropdown);
app.component("survey-matrixtable", MatrixTable);
Expand Down

0 comments on commit c754243

Please sign in to comment.