diff --git a/e2e/pairwise.spec.ts b/e2e/pairwise.spec.ts
index 0414d01ca..91876c034 100644
--- a/e2e/pairwise.spec.ts
+++ b/e2e/pairwise.spec.ts
@@ -21,8 +21,7 @@ for (const [label, story] of stories) {
await page.getByRole('button', { name: 'Next' }).click();
await page.getByText('Sa fille, son fils').click();
await expect(page.getByText('Sa mère, son père')).toBeVisible();
- await gotoNextPage(page, 2);
- await expect(page.getByText('END')).toBeVisible();
+ await gotoNextPage(page);
await expectLunaticData(page, 'COLLECTED.LINKS.COLLECTED', [
[null, '3', null, null],
['2', null, null, null],
diff --git a/src/components/index.ts b/src/components/index.ts
index 29db8b9cb..e58adbd54 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -20,7 +20,7 @@ export { default as Switch } from './switch';
export { default as Textarea } from './textarea';
export { default as SuggesterLoaderWidget } from './suggester-loader-widget';
export { default as Roundabout } from './roundabout';
-export { default as Table } from './table';
+export { LunaticTable as Table } from './table/lunatic-table';
export { LunaticComponentSet as ComponentSet } from './component-set/lunatic-component-set';
export { default as Duration } from './duration';
export { Summary } from './summary';
diff --git a/src/components/loop/block-for-loop.tsx b/src/components/loop/block-for-loop.tsx
index 20497ae0c..5a0c8ccf9 100644
--- a/src/components/loop/block-for-loop.tsx
+++ b/src/components/loop/block-for-loop.tsx
@@ -27,8 +27,9 @@ export const BlockForLoop = createCustomizableLunaticField<
iterations,
getComponents,
} = props;
- const min = lines?.min;
- const max = lines?.max;
+ const min = lines?.min ?? 0;
+ const max = lines?.max ?? Infinity;
+ const canControlRows = min !== max;
const [nbRows, setNbRows] = useState(() => {
return Math.max(iterations, min);
@@ -60,6 +61,7 @@ export const BlockForLoop = createCustomizableLunaticField<
if (nbRows <= 0) {
return null;
}
+
return (
<>
@@ -72,7 +74,7 @@ export const BlockForLoop = createCustomizableLunaticField<
/>
))}
- {Number.isInteger(min) && Number.isInteger(max) && min !== max && (
+ {canControlRows && (
<>
{label || D.DEFAULT_BUTTON_ADD}
diff --git a/src/components/loop/roster-for-loop/roster-for-loop.tsx b/src/components/loop/roster-for-loop/roster-for-loop.tsx
index 99f7e4caf..b6e44ca3c 100644
--- a/src/components/loop/roster-for-loop/roster-for-loop.tsx
+++ b/src/components/loop/roster-for-loop/roster-for-loop.tsx
@@ -9,7 +9,7 @@ import { LoopButton } from '../loop-button';
import D from '../../../i18n';
import type { LunaticComponentProps } from '../../type';
import { Table, Tbody, Td, Tr } from '../../commons/components/html-table';
-import Header from '../../table/header';
+import { TableHeader } from '../../table/table-header';
import { times } from '../../../utils/array';
import { LunaticComponents } from '../../lunatic-components';
@@ -65,7 +65,7 @@ export const RosterForLoop = createCustomizableLunaticField<
-
+
{times(nbRows, (n) => (
diff --git a/src/components/lunatic-components.tsx b/src/components/lunatic-components.tsx
index d1cd88684..286bba655 100644
--- a/src/components/lunatic-components.tsx
+++ b/src/components/lunatic-components.tsx
@@ -1,23 +1,27 @@
import {
Fragment,
- useEffect,
- useRef,
type PropsWithChildren,
+ type ReactElement,
type ReactNode,
+ useEffect,
+ useRef,
} from 'react';
import * as lunaticComponents from './index';
import type { FilledLunaticComponentProps } from '../use-lunatic/commons/fill-components/fill-components';
+import { hasComponentType } from '../use-lunatic/commons/component';
type Props> = {
// List of components to display (coming from getComponents)
- components: FilledLunaticComponentProps[];
+ components: (FilledLunaticComponentProps | ReactElement)[];
// Key that trigger autofocus when it changes (pageTag)
autoFocusKey?: string;
// Returns the list of extra props to add to components
componentProps?: (component: FilledLunaticComponentProps) => T;
// Add additional wrapper around each component
wrapper?: (
- props: PropsWithChildren
+ props: PropsWithChildren<
+ FilledLunaticComponentProps & T & { index: number }
+ >
) => ReactNode;
};
@@ -54,16 +58,28 @@ export function LunaticComponents>({
ref={WrapperComponent === Fragment ? undefined : wrapperRef}
>
{components.map((component, k) => {
- const props = {
- ...component,
- ...componentProps?.(component),
- };
+ if (hasComponentType(component)) {
+ const props = {
+ ...component,
+ ...componentProps?.(component),
+ };
+ return (
+
+ {wrapper({
+ children: ,
+ index: k,
+ ...props,
+ })}
+
+ );
+ }
+ // In some case (table for instance) we have static component that only have a label (no componentType)
return (
-
+
{wrapper({
- children: ,
- ...props,
- })}
+ children: component,
+ index: k,
+ } as any)}
);
})}
diff --git a/src/components/table/cell.tsx b/src/components/table/cell.tsx
deleted file mode 100644
index f6d369182..000000000
--- a/src/components/table/cell.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { OrchestratedComponent } from '../commons';
-import { Td } from '../commons/components/html-table';
-import type { LunaticBaseProps } from '../type';
-import type {
- LunaticComponentDefinition,
- LunaticExpression,
-} from '../../use-lunatic/type';
-
-function collecteResponseValue(
- response: unknown,
- value?: Record
-): unknown {
- if (
- value &&
- typeof response === 'object' &&
- response &&
- 'name' in response &&
- typeof response.name === 'string' &&
- response.name in value
- ) {
- return value[response.name];
- }
-
- return undefined;
-}
-
-function collecteArrayResponseValue(
- responses: unknown[],
- value?: Record
-): unknown[] {
- const [response, ...rest] = responses;
-
- if (response) {
- return [
- collecteResponseValue(response, value),
- collecteArrayResponseValue(rest, value),
- ];
- }
- return [];
-}
-
-function collecteValue(
- component: LunaticComponentDefinition,
- value?: Record
-) {
- if ('responses' in component && Array.isArray(component.responses)) {
- return collecteArrayResponseValue(
- component.responses.map((v) => v.response),
- value
- );
- }
- if ('response' in component) {
- return collecteResponseValue(component.response, value);
- }
- return {};
-}
-
-type Props = {
- content:
- | LunaticComponentDefinition
- | { label: LunaticExpression; rowspan?: number; colspan?: number };
- id: string;
- executeExpression: LunaticBaseProps['executeExpression'];
- iteration?: number;
- value: Record;
- row?: string | number;
- index?: string | number;
- handleChange: LunaticBaseProps['handleChange'];
- errors?: LunaticBaseProps['errors'];
-};
-function Cell({
- content,
- id,
- executeExpression,
- iteration,
- value,
- row,
- index,
- handleChange,
- errors,
-}: Props) {
- if ('componentType' in content) {
- const valueField = collecteValue(content, value);
- return (
-
-
- |
- );
- }
-
- const getLabel = () => {
- try {
- return executeExpression(content.label, { iteration });
- } catch (e) {
- return (e as any).toString();
- }
- };
-
- return (
-
- {getLabel()}
- |
- );
-}
-
-export default Cell;
diff --git a/src/components/table/index.ts b/src/components/table/index.ts
deleted file mode 100644
index 998591d12..000000000
--- a/src/components/table/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './lunatic-table';
diff --git a/src/components/table/lunatic-table.tsx b/src/components/table/lunatic-table.tsx
index ba20eddff..381787fef 100644
--- a/src/components/table/lunatic-table.tsx
+++ b/src/components/table/lunatic-table.tsx
@@ -1,19 +1,16 @@
-import { Table, Tbody } from '../commons/components/html-table';
-import Header from './header';
+import { Table, Tbody, Td, Tr } from '../commons/components/html-table';
import LunaticComponent from '../commons/components/lunatic-component-with-label';
-import TableOrchestrator from './table-orchestrator';
import type { LunaticComponentProps } from '../type';
import { Errors } from '../commons';
+import { LunaticComponents } from '../lunatic-components';
+import { TableHeader } from './table-header';
-function LunaticTable(props: LunaticComponentProps<'Table'>) {
+export function LunaticTable(props: LunaticComponentProps<'Table'>) {
const {
id,
handleChange,
- value,
body,
header,
- executeExpression,
- iteration,
errors,
missing,
declarations,
@@ -35,16 +32,20 @@ function LunaticTable(props: LunaticComponentProps<'Table'>) {
handleChange={handleChange}
>
-
+
-
+ {body.map((row, rowIndex) => (
+
+ (
+
+ {children}
+ |
+ )}
+ />
+
+ ))}
diff --git a/src/components/table/row.tsx b/src/components/table/row.tsx
deleted file mode 100644
index 6a0219613..000000000
--- a/src/components/table/row.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import { Tr as HtmlTr } from '../commons/components/html-table';
-import Cell from './cell';
-import type {
- LunaticComponentDefinition,
- LunaticExpression,
-} from '../../use-lunatic/type';
-import type { LunaticBaseProps } from '../type';
-type Props = {
- components: Array<
- | LunaticComponentDefinition
- | { label: LunaticExpression; rowspan?: number; colspan?: number }
- >;
- id: string;
- executeExpression: LunaticBaseProps['executeExpression'];
- iteration?: number;
- valueMap: Record;
- rowIndex?: string | number;
- handleChange: LunaticBaseProps['handleChange'];
- errors?: LunaticBaseProps['errors'];
-};
-function Row({
- id,
- components,
- executeExpression,
- valueMap,
- rowIndex,
- iteration,
- handleChange,
- errors,
-}: Props) {
- const row = components.map(function (content, index) {
- return (
- |
- );
- });
- return (
-
- {row}
-
- );
-}
-
-export default Row;
diff --git a/src/components/table/header.tsx b/src/components/table/table-header.tsx
similarity index 62%
rename from src/components/table/header.tsx
rename to src/components/table/table-header.tsx
index 0e24766c0..99dc7bda9 100644
--- a/src/components/table/header.tsx
+++ b/src/components/table/table-header.tsx
@@ -1,22 +1,18 @@
import { type ReactNode } from 'react';
-import {
- Thead as HtmlThead,
- Tr as HtmlTr,
- Th as HtmlTh,
-} from '../commons/components/html-table';
+import { Thead, Tr, Th } from '../commons/components/html-table';
type Props = {
id?: string;
header?: Array<{ label?: ReactNode; colspan?: number; rowspan?: number }>;
};
-function Header({ id, header }: Props) {
+export function TableHeader({ id, header }: Props) {
if (!Array.isArray(header)) {
return null;
}
const content = header.map(function ({ label, rowspan, colspan }, index) {
return (
-
{label}
-
+
);
});
return (
-
-
+
+
{content}
-
-
+
+
);
}
-
-export default Header;
diff --git a/src/components/table/table-orchestrator.tsx b/src/components/table/table-orchestrator.tsx
deleted file mode 100644
index 3ca2a64d6..000000000
--- a/src/components/table/table-orchestrator.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import type { LunaticComponentProps } from '../type';
-import Row from './row';
-
-type Props = Pick<
- LunaticComponentProps<'Table'>,
- 'body' | 'id' | 'executeExpression' | 'value' | 'handleChange' | 'iteration'
->;
-function TableOrchestrator({
- body,
- id,
- executeExpression,
- value: valueMap,
- handleChange,
- iteration,
-}: Props) {
- if (!Array.isArray(body)) {
- return null;
- }
- return (
- <>
- {body.map(function (components, index) {
- return (
-
- );
- })}
- >
- );
-}
-
-export default TableOrchestrator;
diff --git a/src/components/type.ts b/src/components/type.ts
index 1ba3a98c3..48f13058a 100644
--- a/src/components/type.ts
+++ b/src/components/type.ts
@@ -114,7 +114,7 @@ type ComponentPropsByType = {
rowspan?: number;
colspan?: number;
}>;
- body: Array>;
+ body: FilledLunaticComponentProps[][];
executeExpression: LunaticState['executeExpression'];
iteration: LunaticState['pager']['iteration'];
};
diff --git a/src/use-lunatic/commons/component.ts b/src/use-lunatic/commons/component.ts
index ca92a38a1..d34868c08 100644
--- a/src/use-lunatic/commons/component.ts
+++ b/src/use-lunatic/commons/component.ts
@@ -1,4 +1,5 @@
import type { ReactNode } from 'react';
+import type { LunaticComponentDefinition } from '../type';
export function hasResponse(
component: unknown
@@ -22,3 +23,25 @@ export function hasResponses(component: unknown): component is {
!!component && typeof component === 'object' && 'responses' in component
);
}
+
+export function hasBody(component: unknown): component is {
+ body: LunaticComponentDefinition<'Table'>['body'];
+} {
+ return (
+ !!component &&
+ typeof component === 'object' &&
+ 'body' in component &&
+ Array.isArray(component.body)
+ );
+}
+
+export function hasComponentType(
+ component: unknown
+): component is { componentType: string } {
+ return (
+ !!component &&
+ typeof component === 'object' &&
+ 'componentType' in component &&
+ typeof component.componentType === 'string'
+ );
+}
diff --git a/src/use-lunatic/commons/fill-components/fill-component-value.ts b/src/use-lunatic/commons/fill-components/fill-component-value.ts
index 38ec93127..fe8b6770d 100644
--- a/src/use-lunatic/commons/fill-components/fill-component-value.ts
+++ b/src/use-lunatic/commons/fill-components/fill-component-value.ts
@@ -1,6 +1,5 @@
import type { LunaticComponentDefinition, LunaticState } from '../../type';
import { hasResponse, hasResponses } from '../component';
-import { string } from 'prop-types';
import { isNumber } from '../../../utils/number';
export type FilledProps = { value?: unknown };
diff --git a/src/use-lunatic/commons/fill-components/fill-components.ts b/src/use-lunatic/commons/fill-components/fill-components.ts
index 251ac863a..20a18a745 100644
--- a/src/use-lunatic/commons/fill-components/fill-components.ts
+++ b/src/use-lunatic/commons/fill-components/fill-components.ts
@@ -52,15 +52,15 @@ function compose(...fill: Function[]) {
*
* Force typing for this function since it's doo dynamic
*/
-const fillComponent = compose(
+export const fillComponent = compose(
fillFromState,
fillComponentExpressions,
fillPagination,
fillComponentValue,
fillMissingResponse,
fillManagement,
- fillSpecificExpressions,
- fillIterations
+ fillIterations,
+ fillSpecificExpressions
) as (
component: LunaticComponentDefinition,
state: LunaticState
diff --git a/src/use-lunatic/commons/fill-components/fill-specific-expression.ts b/src/use-lunatic/commons/fill-components/fill-specific-expression.ts
index ff0dca1aa..52136bd7c 100644
--- a/src/use-lunatic/commons/fill-components/fill-specific-expression.ts
+++ b/src/use-lunatic/commons/fill-components/fill-specific-expression.ts
@@ -1,7 +1,8 @@
import type { LunaticComponentDefinition, LunaticState } from '../../type';
import { type DeepTranslateExpression } from './fill-component-expressions';
-import fillComponents from './fill-components';
-import { setAtIndex } from '../../../utils/array';
+import fillComponents, { fillComponent } from './fill-components';
+import { hasComponentType } from '../component';
+import { getVTLCompatibleValue } from '../../../utils/vtl';
/**
* Fill props for roundabout
@@ -69,7 +70,7 @@ function fillChildComponentsWithIteration(
}
/**
- * Fill child components for nested component type
+ * For pairwise, inject a method to retrieve component at a specific iteration combination
*/
function fillPairwise(
component: DeepTranslateExpression<
@@ -109,6 +110,26 @@ function fillPairwise(
};
}
+/**
+ * For pairwise, inject a method to retrieve component at a specific iteration combination
+ */
+function fillTable(
+ component: DeepTranslateExpression>,
+ state: LunaticState
+) {
+ return {
+ ...component,
+ body: component.body.map((row) =>
+ row.map((component) => {
+ if (hasComponentType(component)) {
+ return fillComponent(component, state);
+ }
+ return state.executeExpression(getVTLCompatibleValue(component.label));
+ })
+ ),
+ };
+}
+
/**
* Fill component specific props (RoundAbout for instance)
*/
@@ -126,6 +147,8 @@ function fillSpecificExpressions(
return fillChildComponentsWithIteration(component, state);
case 'PairwiseLinks':
return fillPairwise(component, state);
+ case 'Table':
+ return fillTable(component, state);
default:
return component;
}
diff --git a/src/use-lunatic/commons/variables/behaviours/resizing-behaviour.ts b/src/use-lunatic/commons/variables/behaviours/resizing-behaviour.ts
index bffb1575e..0df7b6bcf 100644
--- a/src/use-lunatic/commons/variables/behaviours/resizing-behaviour.ts
+++ b/src/use-lunatic/commons/variables/behaviours/resizing-behaviour.ts
@@ -41,9 +41,7 @@ export function resizingBehaviour(
for (const variableName of resizingInfo.variables) {
const value = store.get(variableName);
if (!Array.isArray(value) || value.length !== newSize) {
- store.set(variableName, resizeArrayVariable(value, newSize, null), {
- iteration: e.detail.iteration,
- });
+ store.set(variableName, resizeArrayVariable(value, newSize, null));
}
}
});
@@ -72,6 +70,6 @@ function resizePairwise(
xSize,
new Array(ySize).fill(null)
);
- store.set(variable, resizedValue, { iteration: args.iteration });
+ store.set(variable, resizedValue);
});
}
diff --git a/src/use-lunatic/commons/variables/get-questionnaire-data.ts b/src/use-lunatic/commons/variables/get-questionnaire-data.ts
index 522589c98..c87519aee 100644
--- a/src/use-lunatic/commons/variables/get-questionnaire-data.ts
+++ b/src/use-lunatic/commons/variables/get-questionnaire-data.ts
@@ -21,6 +21,10 @@ export function getQuestionnaireData(
>,
};
+ if (!variables) {
+ return {};
+ }
+
for (const variable of variables) {
// Skip calculated value if necessary
if (variable.variableType === 'CALCULATED' && !withCalculated) {
diff --git a/src/use-lunatic/commons/variables/lunatic-variables-store.ts b/src/use-lunatic/commons/variables/lunatic-variables-store.ts
index 8f70e257c..fb63d2d7c 100644
--- a/src/use-lunatic/commons/variables/lunatic-variables-store.ts
+++ b/src/use-lunatic/commons/variables/lunatic-variables-store.ts
@@ -34,6 +34,9 @@ export class LunaticVariablesStore {
public static makeFromSource(source: LunaticSource, data: LunaticData) {
const store = new LunaticVariablesStore();
+ if (!source.variables) {
+ return store;
+ }
const initialValues = Object.fromEntries(
source.variables.map((variable) => [
variable.name,
diff --git a/src/use-lunatic/reducer/reduce-handle-change.ts b/src/use-lunatic/reducer/reduce-handle-change.ts
index 2eb70ef57..a6d60f7e3 100644
--- a/src/use-lunatic/reducer/reduce-handle-change.ts
+++ b/src/use-lunatic/reducer/reduce-handle-change.ts
@@ -9,8 +9,13 @@ export function reduceHandleChange(
state: LunaticState,
action: ActionHandleChange
): LunaticState {
+ let iteration = action.payload.iteration;
+ // Resolve iteration from pager for loops
+ if (!iteration && isNumber(state.pager.iteration)) {
+ iteration = [state.pager.iteration];
+ }
state.updateBindings(action.payload.name, action.payload.value, {
- iteration: action.payload.iteration,
+ iteration,
});
return {
diff --git a/src/use-lunatic/type-source.ts b/src/use-lunatic/type-source.ts
index 5d0fd61c5..cfe1110bf 100644
--- a/src/use-lunatic/type-source.ts
+++ b/src/use-lunatic/type-source.ts
@@ -1,3 +1,5 @@
+import type { LunaticComponentDefinition } from './type';
+
/**
* Types used for source data (lunatic models and data.json)
*/
@@ -172,24 +174,7 @@ export type ComponentRosterForLoopType = {
colspan?: number;
rowspan?: number;
}[];
- body: {
- label?: LabelType;
- value?: string;
- format?: string;
- dateFormat?: string;
- unit?: string;
- options: { value: string; label: LabelType }[];
- response: ResponseType;
- bindingDependencies: string[];
- componentType?: ComponentTypeEnum;
- maxLength?: number;
- min?: number;
- max?: number;
- decimals?: number;
- colspan?: number;
- rowspan?: number;
- id?: string;
- }[];
+ body: ({ label: LabelType } | ComponentType)[][];
positioning: 'HORIZONTAL';
};