From 33e489bb2c1c69fad912a7d58697a2ed5e7a09e8 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 8 Nov 2024 21:45:01 +0100 Subject: [PATCH 1/7] feat(articulation): roundabout articulation poc --- src/hooks/useArticulation.ts | 122 ++++++ .../articulation/articulation.stories.tsx | 103 ++++++ .../behaviour/articulation/roundabout.json | 348 ++++++++++++++++++ .../variables/lunatic-variables-store.ts | 2 +- .../reducer/reducerInitializer.tsx | 69 ++-- 5 files changed, 614 insertions(+), 30 deletions(-) create mode 100644 src/hooks/useArticulation.ts create mode 100644 src/stories/behaviour/articulation/articulation.stories.tsx create mode 100644 src/stories/behaviour/articulation/roundabout.json diff --git a/src/hooks/useArticulation.ts b/src/hooks/useArticulation.ts new file mode 100644 index 000000000..be2763e1e --- /dev/null +++ b/src/hooks/useArticulation.ts @@ -0,0 +1,122 @@ +import type { + ComponentDefinition, + ComponentRoundaboutDefinition, + LunaticSource, +} from '../type.source'; +import type { LunaticData } from '../use-lunatic/type'; +import { reducerInitializer } from '../use-lunatic/reducer/reducerInitializer'; +import { type ReactNode, useMemo } from 'react'; +import { times } from '../utils/array'; +import { forceInt } from '../utils/number'; + +type ArticulationItem = { + label: string; + value: string; +}; + +type Articulation = { + source: string; + items: ArticulationItem[]; +}; + +type Item = { + cells: { + label: string; + value: ReactNode; + page: string; + }[]; + progress: number; // -1: not completed, 0: started, 1: finished +}; + +/** + * Hook to get articulation state + * + * ## Why this hook + * + * The goal of this hook is to provide insights about a roundabout using extra information inserted in the JSON source + * provided to Lunatic. + * + * For instance + * + * ``` + * "articulation": { + * "source": "roundabout", + * "items": [ + * { + * "label": "Prénom", + * "value": "PRENOMS" + * }, + * { + * "label": "Sexe", + * "value": "if SEXE = \"H\" then \"Homme\" else \"Femme\"" + * }, + * { + * "label": "Age", + * "value": "cast(AGE, string) || \" ans\"" + * } + * ] + * }, + * ``` + * + * - source is the ID of the roundabout component + * - items define the field to extract from the roundabout data + */ +export function useArticulation( + source: LunaticSource & { articulation: Articulation }, + data: LunaticData +): { items: Item[] } { + const roundabout = useMemo( + () => findComponentById(source.components, source.articulation.source), + [source] + ); + const { variables } = useMemo( + () => reducerInitializer({ source, data }), + [source, data] + ); + + const iterations = useMemo( + () => forceInt(variables.run(roundabout?.iterations.value ?? '0')), + [source, data] + ); + + const rows = useMemo(() => { + return times(iterations, (k) => + source.articulation.items.map((item) => ({ + label: item.label, + value: variables.run(item.value, { iteration: [k] }) as ReactNode, + })) + ); + }, [source, data, iterations, roundabout?.progressVariable]); + + if (!roundabout) { + return { + items: [], + }; + } + + return { + items: rows.map((row, k) => ({ + cells: row, + progress: forceInt(variables.get(roundabout.progressVariable, [k]) ?? -1), + page: roundabout.page ? `${roundabout.page}.1#${k + 1}` : '1', + })), + }; +} + +function findComponentById( + components: ComponentDefinition[], + id: string +): (ComponentRoundaboutDefinition & { page?: string }) | null { + for (const c of components) { + if ('id' in c && c.id === id && c.componentType === 'Roundabout') { + return c; + } + if ('components' in c) { + const child = findComponentById(c.components, id); + if (child) { + return child; + } + } + } + return null; +} diff --git a/src/stories/behaviour/articulation/articulation.stories.tsx b/src/stories/behaviour/articulation/articulation.stories.tsx new file mode 100644 index 000000000..bfd145dc5 --- /dev/null +++ b/src/stories/behaviour/articulation/articulation.stories.tsx @@ -0,0 +1,103 @@ +import Orchestrator from '../../utils/orchestrator'; +import source from './roundabout.json'; +import type { Meta, StoryObj } from '@storybook/react'; +import { useState } from 'react'; +import { useArticulation } from '../../../hooks/useArticulation'; + +type Source = Parameters[0]; +type Data = Parameters[1]; + +type Props = { + source: Source; + data: Data; +}; + +function StoryComponent({ source, data }: Props) { + const [page, setPage] = useState(null as null | string); + const gotoNav = () => setPage(null); + const { items } = useArticulation(source, data); + + if (page) { + return ( +
+ {page} + + +
+ ); + } + + const progressLabel = (n: number) => { + if (n === -1) { + return 'Commencer'; + } + if (n === 0) { + return 'Continuer'; + } + return 'Complété'; + }; + + return ( +
+

Articulation

+ + + + {items[0].cells.map((cell, k) => ( + + ))} + + + + + {items.map((item, k) => ( + + {item.cells.map((cell, kk) => ( + + ))} + + + ))} + +
+ {cell.label} + Actions
+ {cell.value} + + +
+
+ ); +} + +const meta: Meta = { + title: 'Behaviour/Articulation', + component: StoryComponent, +}; + +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + source: source as Source, + data: {}, + }, +}; diff --git a/src/stories/behaviour/articulation/roundabout.json b/src/stories/behaviour/articulation/roundabout.json new file mode 100644 index 000000000..e34a50895 --- /dev/null +++ b/src/stories/behaviour/articulation/roundabout.json @@ -0,0 +1,348 @@ +{ + "$schema": "../../../../lunatic-schema.json", + "maxPage": "4", + "articulation": { + "source": "roundabout", + "items": [ + { + "label": "Prénom", + "value": "PRENOMS" + }, + { + "label": "Sexe", + "value": "if SEXE = \"H\" then \"Homme\" else \"Femme\"" + }, + { + "label": "Age", + "value": "cast(AGE, string) || \" ans\"" + } + ] + }, + "components": [ + { + "id": "how", + "componentType": "InputNumber", + "mandatory": false, + "page": "1", + "min": 1, + "max": 10, + "decimals": 0, + "label": { + "value": "\"Combien de personnes vivent habituellement à votre adresse ?\"", + "type": "VTL|MD" + }, + "conditionFilter": { "value": "true", "type": "VTL" }, + "response": { "name": "NB_HAB" } + }, + { + "id": "loop", + "componentType": "Loop", + "page": "2", + "depth": 1, + "paginatedLoop": false, + "conditionFilter": { "value": "true", "type": "VTL" }, + "loopDependencies": ["NHAB"], + "lines": { + "min": { "value": "NB_HAB", "type": "VTL" }, + "max": { "value": "NB_HAB", "type": "VTL" } + }, + "components": [ + { + "id": "prenom", + "componentType": "Input", + "mandatory": false, + "maxLength": 20, + "label": { + "value": "\"Prénom\"))", + "type": "VTL|MD" + }, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "response": { "name": "PRENOMS" } + }, + { + "id": "sexe", + "componentType": "CheckboxOne", + "mandatory": false, + "maxLength": 20, + "label": { + "value": "\"Sexe\"", + "type": "VTL|MD" + }, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "options": [ + { + "value": "H", + "label": { "value": "\"Homme\"", "type": "VTL|MD" } + }, + + { + "value": "F", + "label": { "value": "\"Femme\"", "type": "VTL|MD" } + } + ], + "response": { "name": "SEXE" } + }, + { + "id": "age", + "componentType": "InputNumber", + "maxLength": 3, + "label": { + "value": "\"Age\"", + "type": "VTL|MD" + }, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "response": { "name": "AGE" } + } + ] + }, + { + "id": "roundabout", + "componentType": "Roundabout", + "page": "3", + "conditionFilter": { "value": "true", "type": "VTL" }, + "iterations": { "value": "NB_HAB", "type": "VTL" }, + "label": { "value": "\"Libellé du rondpoint\"", "type": "VTL" }, + "locked": true, + "progressVariable": "PROGRESS", + "item": { + "label": { + "value": "\"Questions de \" || PRENOMS", + "type": "VTL" + }, + "description": { + "value": "if AGE > 18 then \"Aller aux question destinées à \" || PRENOMS else PRENOMS || \" n'est pas majeur, il/elle n'a pas à répondre aux questions\"", + "type": "VTL" + }, + "disabled": { + "value": "AGE < 18", + "type": "VTL" + } + }, + "controls": [], + "components": [ + { + "id": "radio", + "componentType": "Radio", + "mandatory": false, + "page": "3.1", + "label": { + "value": "\"Connaissez-vous le recensement de la population ?\"", + "type": "VTL|MD" + }, + + "conditionFilter": { "value": "true", "type": "VTL" }, + + "options": [ + { "value": "1", "label": { "value": "\"oui\"", "type": "VTL|MD" } }, + + { "value": "2", "label": { "value": "\"non\"", "type": "VTL|MD" } } + ], + "response": { "name": "KNOWREC" } + }, + { + "id": "jsygk7m7", + "componentType": "Subsequence", + "page": "3.2", + "label": { + "value": "\"Deuxième page de questions pour \"|| PRENOMS", + "type": "VTL|MD" + }, + "conditionFilter": { "value": "true", "type": "VTL" } + }, + { + "id": "sexe", + "componentType": "Radio", + "page": "3.2", + "label": { + "value": "\"Sexe\"", + "type": "VTL" + }, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "options": [ + { + "value": "1", + "label": { "value": "\"Homme\"", "type": "VTL|MD" } + }, + { + "value": "2", + "label": { "value": "\"Femme\"", "type": "VTL|MD" } + } + ], + "response": { "name": "SEXE" } + }, + { + "id": "jsygk7m7", + "componentType": "Subsequence", + "page": "3.3", + "label": { + "value": "\"Troisième page de questions \" || PRENOMS", + "type": "VTL|MD" + }, + "conditionFilter": { "value": "true", "type": "VTL" } + }, + { + "id": "kmno1n7m", + "componentType": "Input", + "maxLength": 30, + "page": "3.3", + "label": { + "value": "\"Dites quelque chose.\"))", + "type": "VTL|MD" + }, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "response": { "name": "SOMETHING" } + } + ] + }, + { + "id": "seq", + "componentType": "Sequence", + "label": { + "value": "\"Merci !\"", + "type": "VTL|MD" + }, + "conditionFilter": { "value": "true", "type": "VTL" }, + "page": "4" + } + ], + "variables": [ + { + "variableType": "COLLECTED", + "name": "NB_HAB", + "values": { + "PREVIOUS": null, + "COLLECTED": 2, + "FORCED": null, + "EDITED": null, + "INPUTTED": null + } + }, + { + "variableType": "COLLECTED", + "name": "SOMETHING", + "values": { + "PREVIOUS": [], + "COLLECTED": [], + "FORCED": [], + "EDITED": [], + "INPUTTED": [] + } + }, + { + "variableType": "COLLECTED", + "name": "SEXE", + "values": { + "PREVIOUS": null, + "COLLECTED": ["H", "F"], + "FORCED": null, + "EDITED": null, + "INPUTTED": null + } + }, + { + "variableType": "COLLECTED", + "name": "AGE", + "values": { + "PREVIOUS": null, + "COLLECTED": [24, 24], + "FORCED": null, + "EDITED": null, + "INPUTTED": null + } + }, + { + "variableType": "COLLECTED", + "name": "SEXE", + "values": { + "PREVIOUS": [], + "COLLECTED": [], + "FORCED": [], + "EDITED": [], + "INPUTTED": [] + } + }, + { + "variableType": "COLLECTED", + "name": "PRENOMS", + "values": { + "PREVIOUS": null, + "COLLECTED": ["Fanny", "Ines"], + "FORCED": null, + "EDITED": null, + "INPUTTED": null + } + }, + { + "variableType": "COLLECTED", + "name": "KNOWREC", + "values": { + "PREVIOUS": [], + "COLLECTED": [], + "FORCED": [], + "EDITED": [], + "INPUTTED": [] + } + }, + { + "variableType": "COLLECTED", + "name": "PROGRESS", + "values": { + "PREVIOUS": [], + "COLLECTED": [0, -1], + "FORCED": [], + "EDITED": [], + "INPUTTED": [] + } + }, + { + "variableType": "CALCULATED", + "name": "PRENOMREF", + "expression": { "value": "first_value(PRENOMS over())", "type": "VTL" }, + "bindingDependencies": ["PRENOMS"], + "inFilter": "true" + }, + { + "variableType": "CALCULATED", + "name": "COMPLETE", + "expression": { + "value": "not(isnull(KNOWREC)) and not(isnull(SEXE)) and not(isnull(SOMETHING))", + "type": "VTL" + }, + "bindingDependencies": ["KNOWREC", "SEXE", "SOMETHING"], + "shapeFrom": "PRENOMS", + "inFilter": "true" + }, + { + "variableType": "CALCULATED", + "name": "PARTIAL", + "expression": { + "value": "not(isnull(KNOWREC)) or not(isnull(SEXE)) or not(isnull(SOMETHING))", + "type": "VTL" + }, + "bindingDependencies": ["KNOWREC", "SEXE", "SOMETHING"], + "shapeFrom": "PRENOMS", + "inFilter": "true" + } + ], + "resizing": { + "NB_HAB": { + "size": "NB_HAB", + "variables": ["PRENOMS", "AGE", "SEXE", "SOMETHING", "DATNAIS"] + } + } +} diff --git a/src/use-lunatic/commons/variables/lunatic-variables-store.ts b/src/use-lunatic/commons/variables/lunatic-variables-store.ts index 45cd7bdb0..5790caf4b 100644 --- a/src/use-lunatic/commons/variables/lunatic-variables-store.ts +++ b/src/use-lunatic/commons/variables/lunatic-variables-store.ts @@ -60,7 +60,7 @@ export class LunaticVariablesStore { if (!source.variables) { return store; } - // Source data (picked from "variables" in the source.json)s + // Source data (picked from "variables" in the source.json) const sourceValues: Record = {}; // Starting data for the form (merged with data.json or injected data) const initialValues: Record = {}; diff --git a/src/use-lunatic/reducer/reducerInitializer.tsx b/src/use-lunatic/reducer/reducerInitializer.tsx index fa3c1bef3..954bf855d 100644 --- a/src/use-lunatic/reducer/reducerInitializer.tsx +++ b/src/use-lunatic/reducer/reducerInitializer.tsx @@ -37,6 +37,8 @@ const baseState = { executeExpression: () => null as T, } satisfies LunaticReducerState; +const onChange = { current: () => {} }; + export function reducerInitializer({ source, data, @@ -45,8 +47,8 @@ export function reducerInitializer({ lastReachedPage = undefined, withOverview = false, getReferentiel, - onVariableChange, - logger, + onVariableChange = onChange, + logger = console.error, }: { source: LunaticSource; data: LunaticData; @@ -55,8 +57,8 @@ export function reducerInitializer({ lastReachedPage?: LunaticOptions['lastReachedPage']; withOverview?: LunaticOptions['withOverview']; getReferentiel?: LunaticOptions['getReferentiel']; - onVariableChange: RefObject; - logger: LunaticLogger; + onVariableChange?: RefObject; + logger?: LunaticLogger; }): LunaticReducerState { const variables = LunaticVariablesStore.makeFromSource( source, @@ -107,11 +109,13 @@ export function reducerInitializer({ } return result as any; } catch (e) { - // If there is an error interpreting a variable, return the raw expression - logger({ - type: 'ERROR', - error: e as Error, - }); + if (logger) { + // If there is an error interpreting a variable, return the raw expression + logger({ + type: 'ERROR', + error: e as Error, + }); + } return expressionString; } }; @@ -128,9 +132,9 @@ export function reducerInitializer({ const pager = { page: initialPager?.page ?? 1, maxPage: source.maxPage ? parseInt(source.maxPage, 10) : 1, - subPage: undefined, + subPage: initialPager?.subPage, nbSubPages: undefined, - iteration: undefined, + iteration: initialPager?.iteration, nbIterations: undefined, lastReachedPage: lastReachedPage ?? initialPage, }; @@ -140,7 +144,7 @@ export function reducerInitializer({ pager, previousPager: pager, pages, - isInLoop: false, + isInLoop: pager.subPage !== undefined, updatedAt: Date.now(), overview: withOverview ? buildOverview(source) : [], updateBindings, @@ -157,22 +161,29 @@ function fillPagerForLoop(state: LunaticReducerState): LunaticReducerState { return state; } const { isLoop, subPages, iterations, loopDependencies } = pages[pager.page]; - if (!isLoop) { - return state; + + if ( + // For loop, jump at the first page + isLoop || + // For roundabout, jump at the desired iteration / subpage (only if defined) + (pager?.iteration !== undefined && subPages) + ) { + return { + ...state, + isInLoop: true, + pager: { + ...pager, + subPage: pager?.subPage ?? 1, + nbSubPages: (subPages ?? []).length, + iteration: pager?.iteration ?? 0, + nbIterations: forceInt( + state.executeExpression(iterations, { + deps: loopDependencies, + }) + ), + }, + }; } - return { - ...state, - isInLoop: true, - pager: { - ...pager, - subPage: pager?.subPage ?? 0, - nbSubPages: (subPages ?? []).length, - iteration: pager?.iteration ?? 0, - nbIterations: forceInt( - state.executeExpression(iterations, { - deps: loopDependencies, - }) - ), - }, - }; + + return state; } From fc3fde90d95684b3cbe498375f5e742680220d36 Mon Sep 17 00:00:00 2001 From: Laurent Caouissin <38245508+laurentC35@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:17:30 +0100 Subject: [PATCH 2/7] bump: 3.5.0-rc.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ea1247195..ebf666d0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@inseefr/lunatic", - "version": "3.4.10", + "version": "3.5.0-rc.0, "description": "Library of questionnaire components", "repository": { "type": "git", From b027b6474aee32946208b66086e21c6f3251125e Mon Sep 17 00:00:00 2001 From: Laurent Caouissin <38245508+laurentC35@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:18:08 +0100 Subject: [PATCH 3/7] bump: 3.5.0-rc.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ebf666d0e..779ccccc2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@inseefr/lunatic", - "version": "3.5.0-rc.0, + "version": "3.5.0-rc.0", "description": "Library of questionnaire components", "repository": { "type": "git", From ce745e34f0054fde65990d765f834ffa5650c32b Mon Sep 17 00:00:00 2001 From: Laurent Caouissin Date: Fri, 20 Dec 2024 12:56:00 +0100 Subject: [PATCH 4/7] fix: types (ignore) and pass to tsx --- src/hooks/useArticulation.ts | 4 +++- .../articulation/articulation.stories.tsx | 4 +++- .../performance/performance.stories.jsx | 2 +- ...chemaValidator.jsx => SchemaValidator.tsx} | 2 +- .../{orchestrator.jsx => orchestrator.tsx} | 22 +++++++++++-------- .../utils/{overview.jsx => overview.tsx} | 2 ++ 6 files changed, 23 insertions(+), 13 deletions(-) rename src/stories/utils/{SchemaValidator.jsx => SchemaValidator.tsx} (93%) rename src/stories/utils/{orchestrator.jsx => orchestrator.tsx} (91%) rename src/stories/utils/{overview.jsx => overview.tsx} (96%) diff --git a/src/hooks/useArticulation.ts b/src/hooks/useArticulation.ts index be2763e1e..ec2323d36 100644 --- a/src/hooks/useArticulation.ts +++ b/src/hooks/useArticulation.ts @@ -23,7 +23,7 @@ type Item = { cells: { label: string; value: ReactNode; - page: string; + page?: string; }[]; progress: number; // -1: not completed, 0: started, 1: finished }; @@ -76,6 +76,7 @@ export function useArticulation( const iterations = useMemo( () => forceInt(variables.run(roundabout?.iterations.value ?? '0')), + // eslint-disable-next-line react-hooks/exhaustive-deps [source, data] ); @@ -86,6 +87,7 @@ export function useArticulation( value: variables.run(item.value, { iteration: [k] }) as ReactNode, })) ); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [source, data, iterations, roundabout?.progressVariable]); if (!roundabout) { diff --git a/src/stories/behaviour/articulation/articulation.stories.tsx b/src/stories/behaviour/articulation/articulation.stories.tsx index bfd145dc5..bced0d172 100644 --- a/src/stories/behaviour/articulation/articulation.stories.tsx +++ b/src/stories/behaviour/articulation/articulation.stories.tsx @@ -22,6 +22,8 @@ function StoryComponent({ source, data }: Props) {
{page} + {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} + {/*/ @ts-ignore */}
); @@ -74,7 +76,7 @@ function StoryComponent({ source, data }: Props) { padding: '.5rem 1rem', }} > - diff --git a/src/stories/behaviour/performance/performance.stories.jsx b/src/stories/behaviour/performance/performance.stories.jsx index c5c15f88f..065bf88b1 100644 --- a/src/stories/behaviour/performance/performance.stories.jsx +++ b/src/stories/behaviour/performance/performance.stories.jsx @@ -1,8 +1,8 @@ import React from 'react'; -import Orchestrator from '../../utils/orchestrator'; import source from './source.json'; import { generateData } from '../../../tests/utils/lunatic'; import { times } from '../../../utils/array'; +import Orchestrator from '../../utils/orchestrator'; const stories = { title: 'Behaviour/Performance', diff --git a/src/stories/utils/SchemaValidator.jsx b/src/stories/utils/SchemaValidator.tsx similarity index 93% rename from src/stories/utils/SchemaValidator.jsx rename to src/stories/utils/SchemaValidator.tsx index cb51bd4f6..82a1b872e 100644 --- a/src/stories/utils/SchemaValidator.jsx +++ b/src/stories/utils/SchemaValidator.tsx @@ -2,7 +2,7 @@ import Ajv from 'ajv/dist/2020.js'; import { useMemo } from 'react'; import LunaticSchema from '../../../lunatic-schema.json'; -export function SchemaValidator({ source }) { +export function SchemaValidator({ source }: { source: any }) { const errors = useMemo(() => { const ajv = new Ajv({ removeAdditional: true, diff --git a/src/stories/utils/orchestrator.jsx b/src/stories/utils/orchestrator.tsx similarity index 91% rename from src/stories/utils/orchestrator.jsx rename to src/stories/utils/orchestrator.tsx index 92df20fd4..7bee6b72d 100644 --- a/src/stories/utils/orchestrator.jsx +++ b/src/stories/utils/orchestrator.tsx @@ -1,3 +1,5 @@ +/* eslint-disable */ +// @ts-nocheck import './custom-lunatic.scss'; import './orchestrator.scss'; @@ -7,19 +9,19 @@ import { LunaticComponents, ModalControls, useLunatic, -} from '../..'; -import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; +} from '../../index.js'; +import { memo, useCallback, useEffect, useState } from 'react'; -import { Logger } from '../../utils/logger'; -import { Overview } from './overview'; +import { Logger } from '../../utils/logger.js'; +import { Overview } from './overview.js'; import { SchemaValidator } from './SchemaValidator.jsx'; const Input = components.Input; -function DevOptions({ goToPage, getData }) { +function DevOptions({ goToPage, getData }: { goToPage: any; getData: any }) { const [toPage, setToPage] = useState(1); - function handleChange(_, value) { + function handleChange(_, value: any) { setToPage(value); } @@ -33,6 +35,8 @@ function DevOptions({ goToPage, getData }) { Date: Mon, 30 Dec 2024 11:14:13 +0100 Subject: [PATCH 5/7] fix(articulation): add export for useArticulation hook --- package.json | 2 +- src/index.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 779ccccc2..4803098d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@inseefr/lunatic", - "version": "3.5.0-rc.0", + "version": "3.5.0-rc.1", "description": "Library of questionnaire components", "repository": { "type": "git", diff --git a/src/index.ts b/src/index.ts index 2ed08d5b0..2004cc925 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ export { Button } from './components/shared/Button/Button'; export { LunaticComponents } from './components/LunaticComponents'; export { useLunatic } from './use-lunatic/use-lunatic'; +export { useArticulation } from './hooks/useArticulation'; export type { LunaticComponentDefinition, From 3662a47503c0c86407739271eea3c2e8ae397027 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Mon, 30 Dec 2024 11:17:56 +0100 Subject: [PATCH 6/7] bump: 3.5.0-rc.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4803098d6..92dcc7db6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@inseefr/lunatic", - "version": "3.5.0-rc.1", + "version": "3.5.0-rc.2", "description": "Library of questionnaire components", "repository": { "type": "git", From 8665edd98b0fb694af5b3021d8d731cc331b4c3d Mon Sep 17 00:00:00 2001 From: Jonathan Date: Mon, 30 Dec 2024 15:58:34 +0100 Subject: [PATCH 7/7] bump: 3.5.0-rc.3 --- package.json | 2 +- src/index.ts | 2 +- .../articulation/articulation.stories.tsx | 13 +++-- .../getArticulation.ts} | 49 ++++++++----------- 4 files changed, 31 insertions(+), 35 deletions(-) rename src/{hooks/useArticulation.ts => utils/getArticulation.ts} (64%) diff --git a/package.json b/package.json index 92dcc7db6..283beb0db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@inseefr/lunatic", - "version": "3.5.0-rc.2", + "version": "3.5.0-rc.3", "description": "Library of questionnaire components", "repository": { "type": "git", diff --git a/src/index.ts b/src/index.ts index 2004cc925..11c9cbea3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ export { Button } from './components/shared/Button/Button'; export { LunaticComponents } from './components/LunaticComponents'; export { useLunatic } from './use-lunatic/use-lunatic'; -export { useArticulation } from './hooks/useArticulation'; +export { getArticulation } from './utils/getArticulation'; export type { LunaticComponentDefinition, diff --git a/src/stories/behaviour/articulation/articulation.stories.tsx b/src/stories/behaviour/articulation/articulation.stories.tsx index bced0d172..75d1dcb11 100644 --- a/src/stories/behaviour/articulation/articulation.stories.tsx +++ b/src/stories/behaviour/articulation/articulation.stories.tsx @@ -1,11 +1,11 @@ import Orchestrator from '../../utils/orchestrator'; import source from './roundabout.json'; import type { Meta, StoryObj } from '@storybook/react'; -import { useState } from 'react'; -import { useArticulation } from '../../../hooks/useArticulation'; +import { useMemo, useState } from 'react'; +import { getArticulation } from '../../../utils/getArticulation'; -type Source = Parameters[0]; -type Data = Parameters[1]; +type Source = Parameters[0]; +type Data = Parameters[1]; type Props = { source: Source; @@ -15,7 +15,10 @@ type Props = { function StoryComponent({ source, data }: Props) { const [page, setPage] = useState(null as null | string); const gotoNav = () => setPage(null); - const { items } = useArticulation(source, data); + const { items } = useMemo( + () => getArticulation(source, data), + [source, data] + ); if (page) { return ( diff --git a/src/hooks/useArticulation.ts b/src/utils/getArticulation.ts similarity index 64% rename from src/hooks/useArticulation.ts rename to src/utils/getArticulation.ts index ec2323d36..dbefba3cb 100644 --- a/src/hooks/useArticulation.ts +++ b/src/utils/getArticulation.ts @@ -3,11 +3,11 @@ import type { ComponentRoundaboutDefinition, LunaticSource, } from '../type.source'; -import type { LunaticData } from '../use-lunatic/type'; +import type { LunaticData, PageTag } from '../use-lunatic/type'; import { reducerInitializer } from '../use-lunatic/reducer/reducerInitializer'; -import { type ReactNode, useMemo } from 'react'; -import { times } from '../utils/array'; -import { forceInt } from '../utils/number'; +import { type ReactNode } from 'react'; +import { times } from './array'; +import { forceInt } from './number'; type ArticulationItem = { label: string; @@ -23,13 +23,13 @@ type Item = { cells: { label: string; value: ReactNode; - page?: string; }[]; progress: number; // -1: not completed, 0: started, 1: finished + page: PageTag; }; /** - * Hook to get articulation state + * Retrieve the articulation state * * ## Why this hook * @@ -61,35 +61,26 @@ type Item = { * - source is the ID of the roundabout component * - items define the field to extract from the roundabout data */ -export function useArticulation( +export function getArticulation( source: LunaticSource & { articulation: Articulation }, data: LunaticData ): { items: Item[] } { - const roundabout = useMemo( - () => findComponentById(source.components, source.articulation.source), - [source] + const roundabout = findComponentById( + source.components, + source.articulation.source ); - const { variables } = useMemo( - () => reducerInitializer({ source, data }), - [source, data] + const { variables } = reducerInitializer({ source, data }); + const iterations = forceInt( + variables.run(roundabout?.iterations.value ?? '0') ); - const iterations = useMemo( - () => forceInt(variables.run(roundabout?.iterations.value ?? '0')), - // eslint-disable-next-line react-hooks/exhaustive-deps - [source, data] + const rows = times(iterations, (k) => + source.articulation.items.map((item) => ({ + label: item.label, + value: variables.run(item.value, { iteration: [k] }) as ReactNode, + })) ); - const rows = useMemo(() => { - return times(iterations, (k) => - source.articulation.items.map((item) => ({ - label: item.label, - value: variables.run(item.value, { iteration: [k] }) as ReactNode, - })) - ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [source, data, iterations, roundabout?.progressVariable]); - if (!roundabout) { return { items: [], @@ -100,7 +91,9 @@ export function useArticulation( items: rows.map((row, k) => ({ cells: row, progress: forceInt(variables.get(roundabout.progressVariable, [k]) ?? -1), - page: roundabout.page ? `${roundabout.page}.1#${k + 1}` : '1', + page: (roundabout.page + ? `${roundabout.page}.1#${k + 1}` + : '1') as PageTag, })), }; }