Skip to content

Support merge/col span cells horizontal in grid and hide a column #4050

Open
lassopicasso wants to merge 10 commits intomainfrom
support-merge/colSpan-cells-horizontal-in-grid
Open

Support merge/col span cells horizontal in grid and hide a column #4050
lassopicasso wants to merge 10 commits intomainfrom
support-merge/colSpan-cells-horizontal-in-grid

Conversation

@lassopicasso
Copy link
Copy Markdown
Contributor

@lassopicasso lassopicasso commented Mar 9, 2026

Description

In this PR we extended GridComponent.tsx to support;

  • - colSpan on both text/label and component cells, so a cell can span multiple columns.
  • - Column-level hidden, so entire columns can be hidden based on expressions.

What is implemented

  1. colSpan support for grid cells
    We added colSpan to IGridColumnProperties in Common.ts
new CG.prop(
        'colSpan',
        new CG.expr(ExprVal.Number)
          .optional()
          .setTitle('Column span')
          .setDescription('Number of columns this cell should span. Defaults to 1 if not set.'),
      ),

This makes it possible to set colSpan directly on the cell: { "text": "Header 1", "colSpan": 2 } or via "gridColumnOptions": { "colSpan": 2 }

colSpan: We merge header columnOptions, per‑cell gridColumnOptions, and any cell‑level colSpan into columnStyleOptions, then evaluate columnStyleOptions.colSpan with useEvalExpression and pass the result as the <th>/<td> colSpan so both text/label and component cells can span multiple columns.

  1. Column-level hidden support
    We added a hidden property to IGridColumnProperties, aligned with ITableColumnProperties.hidden:
new CG.prop(
        'hidden',
        new CG.expr(ExprVal.Boolean)
          .optional()
          .setTitle('Hidden column')
          .setDescription(
            'Expression or boolean indicating whether this column should be hidden. Defaults to false if not set.',
          ),
      ),

This allows header cells to define visibility: "text": "heading2","gridColumnOptions": { "hidden": false }

  • Added also extraction helper in Grid/tools.ts called getGridCellHiddenExpr small helper that It returns the cell’s hidden expression, using gridColumnOptions.hidden if present, otherwise columnOptions.hidden.
  1. Docs PR

**After: colSpan/hidden in grid component **

Screen.Recording.2026-03-17.at.12.47.48.mov

**After: colSpan/hidden in rowsAfter/rowsBefore in repeating group in frontend-test app **

rowsAfter.befor.mov

Related Issue(s)

Verification/QA

  • Manual functionality testing
    • I have tested these changes manually
    • Creator of the original issue (or service owner) has been contacted for manual testing (or will be contacted when released in alpha)
    • No testing done/necessary
  • Automated tests
    • Unit test(s) have been added/updated
    • Cypress E2E test(s) have been added/updated
    • No automatic tests are needed here (no functional changes/additions)
    • I want someone to help me make some tests
  • UU/WCAG (follow these guidelines until we have our own)
    • I have tested with a screen reader/keyboard navigation/automated wcag validator
    • No testing done/necessary (no DOM/visual changes)
    • I want someone to help me perform accessibility testing
  • User documentation @ altinn-studio-docs
    • Has been added/updated
    • No functionality has been changed/added, so no documentation is needed
    • I will do that later/have created an issue
  • Support in Altinn Studio
    • Issue(s) created for support in Studio
    • This change/feature does not require any changes to Altinn Studio
  • Sprint board
    • The original issue (or this PR itself) has been added to the Team Apps project and to the current sprint board
    • I don't have permissions to do that, please help me out
  • Labels
    • I have added a kind/* and backport* label to this PR for proper release notes grouping
    • I don't have permissions to add labels, please help me out

Summary by CodeRabbit

  • New Features
    • Grid columns can now be dynamically hidden based on custom expressions.
    • Grid cells can now span across multiple columns.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

This change introduces support for colSpan and hidden properties in Grid components by defining a new public type IGridColumnProperties and implementing expression-based evaluation logic to dynamically hide columns and apply column spanning across Grid and RepeatingGroup table rendering.

Changes

Cohort / File(s) Summary
Type Definitions
src/codegen/Common.ts
Added new public type IGridColumnProperties with optional colSpan (number) and hidden (boolean/expression) properties. Modified GridComponentRef, GridCellLabelFrom, GridCellText, and GridCell to use optional gridColumnOptions property instead of extending ITableColumnProperties.
Grid Component Implementation
src/layout/Grid/GridComponent.tsx
Integrated expression evaluation for hidden columns and dynamic colSpan. Added columnHiddenExprs computation, hiddenColumnIndices filtering, and per-cell gridColumnOptions handling. Propagates hidden column metadata and colSpan expressions through GridRowsRenderer and individual cell renderers.
Grid Utilities
src/layout/Grid/tools.ts
Added new exported helper function getGridCellHiddenExpr that safely extracts the hidden expression from gridColumnOptions.hidden or columnOptions.hidden, prioritizing the former.
Grid Tests
src/layout/Grid/GridComponent.test.tsx, src/layout/Grid/tools.test.ts
Added comprehensive test coverage for hidden column logic (including invalid boolean expressions), colSpan application in text and component cells, and getGridCellHiddenExpr helper function behavior.
RepeatingGroup Table Implementation
src/layout/RepeatingGroup/Table/RepeatingGroupTable.tsx
Extended hidden column support to ExtraRows by computing hiddenFromExtraRows from gridColumnOptions.hidden expressions and merging with existing positional hiddenColumnIndices for GridRowsRenderer.
RepeatingGroup Table Tests
src/layout/RepeatingGroup/Table/RepeatingGroupTable.test.tsx
Added parameterized test coverage for ExtraRows with hidden column cells, verifying that cells with gridColumnOptions: { hidden: true } are correctly filtered from rendered output.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature additions: column spanning (colSpan) and column hiding support for the Grid component.
Linked Issues check ✅ Passed The PR fully implements the colSpan support and column-level hidden functionality required by issue #1187 for Grid and rowsBefore/rowsAfter in repeating groups.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing colSpan and column hiding; one unrelated shell command change in a commit message does not affect code scope.
Description check ✅ Passed The PR description includes a clear summary of changes, code examples, related issues, and incomplete verification/QA checklist that follows the repository template structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch support-merge/colSpan-cells-horizontal-in-grid

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@JamalAlabdullah JamalAlabdullah changed the title Support merge/col span cells horizontal in grid Support merge/col span cells horizontal in grid and hide a column Mar 16, 2026
@JamalAlabdullah JamalAlabdullah added kind/product-feature Pull requests containing new features backport-ignore This PR is a new feature and should not be cherry-picked onto release branches labels Mar 17, 2026
@JamalAlabdullah JamalAlabdullah marked this pull request as ready for review March 17, 2026 11:57
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/layout/Grid/tools.ts (1)

109-117: Consider reducing type casting by leveraging existing type guards.

The function uses as type casting which the coding guidelines recommend avoiding. Since all non-null GridCell variants (GridCellText, GridCellLabelFrom, GridComponentRef) have the same optional columnOptions and gridColumnOptions properties, you could use the existing type guards to narrow the type.

♻️ Suggested refactor using type guards
 export function getGridCellHiddenExpr(cell: GridCell) {
   if (!cell || typeof cell !== 'object') {
     return undefined;
   }
-  const options =
-    'columnOptions' in cell ? (cell as { columnOptions?: { hidden?: unknown } }).columnOptions : undefined;
-  const gridOpts =
-    'gridColumnOptions' in cell ? (cell as { gridColumnOptions?: { hidden?: unknown } }).gridColumnOptions : undefined;
-  return gridOpts?.hidden ?? options?.hidden;
+  if (isGridCellText(cell) || isGridCellLabelFrom(cell) || isGridCellNode(cell)) {
+    return cell.gridColumnOptions?.hidden ?? cell.columnOptions?.hidden;
+  }
+  return undefined;
 }

As per coding guidelines: "Avoid using any or type casting (as type) in TypeScript; instead, improve typing by removing such casts and anys to maintain proper type safety."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/layout/Grid/tools.ts` around lines 109 - 117, getGridCellHiddenExpr
currently uses 'as' casts to access columnOptions/gridColumnOptions; instead use
the existing type guards to narrow GridCell (e.g., isGridCellText,
isGridCellLabelFrom, isGridComponentRef or whatever project's guards are named)
and then read cell.columnOptions and cell.gridColumnOptions directly without
casting, returning gridColumnOptions?.hidden ?? columnOptions?.hidden; remove
the two '(cell as {...})' casts and the initial typeof check since the type
guards handle null/undefined checks.
src/layout/Grid/GridComponent.tsx (2)

255-260: Apply the same optional chaining and type safety improvements here.

This section has the same pattern as the text/label cell handling and should be refactored similarly.

♻️ Suggested refactor
-        const cellColSpan = (cell as { colSpan?: number } | null)?.colSpan;
-        if (cellColSpan !== undefined) {
+        const cellColSpan = cell && 'colSpan' in cell ? cell.colSpan : undefined;
+        if (cellColSpan !== undefined) {
           componentCellSettings = componentCellSettings
             ? { ...componentCellSettings, colSpan: cellColSpan }
             : { colSpan: cellColSpan };
         }

As per coding guidelines: "Avoid using any or type casting (as type) in TypeScript."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/layout/Grid/GridComponent.tsx` around lines 255 - 260, The code uses a
type cast and manual null checks for colSpan; change to use optional chaining
and a proper narrow type or type guard instead of "as". Replace the current
block in GridComponent (where cellColSpan and componentCellSettings are set)
with a check using cell?.colSpan (e.g., if (cell?.colSpan !== undefined) {
componentCellSettings = componentCellSettings ? { ...componentCellSettings,
colSpan: cell.colSpan } : { colSpan: cell.colSpan }; }) or introduce a
CellWithColSpan interface and a small type guard function to ensure type-safe
access to colSpan, avoiding any casts and preserving the existing merge logic
for componentCellSettings.

203-208: Use optional chaining and reduce type casting.

The SonarCloud analysis correctly identifies that optional chaining would be cleaner. Additionally, the as type casting could be avoided by using a more type-safe approach.

♻️ Suggested refactor
-          const cellWithColSpan = cell as { colSpan?: number } | null;
-          if (cellWithColSpan && cellWithColSpan.colSpan !== undefined) {
-            textCellSettings = textCellSettings
-              ? { ...textCellSettings, colSpan: cellWithColSpan.colSpan }
-              : { colSpan: cellWithColSpan.colSpan };
-          }
+          const cellColSpan = 'colSpan' in cell ? cell.colSpan : undefined;
+          if (cellColSpan !== undefined) {
+            textCellSettings = textCellSettings
+              ? { ...textCellSettings, colSpan: cellColSpan }
+              : { colSpan: cellColSpan };
+          }

As per coding guidelines: "Avoid using any or type casting (as type) in TypeScript."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/layout/Grid/GridComponent.tsx` around lines 203 - 208, Replace the ad-hoc
type cast and manual null check with a type-safe user-defined type guard and
optional chaining: add a small type guard function (e.g., hasColSpan(obj:
unknown): obj is { colSpan?: number }) that checks obj is non-null object and
has the 'colSpan' property, then use if (hasColSpan(cell) && cell.colSpan !==
undefined) { textCellSettings = textCellSettings ? { ...textCellSettings,
colSpan: cell.colSpan } : { colSpan: cell.colSpan }; } so you remove the "as"
cast and use optional access via the guard when updating textCellSettings in
GridComponent (reference symbols: hasColSpan, textCellSettings, cell).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/layout/Grid/GridComponent.tsx`:
- Around line 255-260: The code uses a type cast and manual null checks for
colSpan; change to use optional chaining and a proper narrow type or type guard
instead of "as". Replace the current block in GridComponent (where cellColSpan
and componentCellSettings are set) with a check using cell?.colSpan (e.g., if
(cell?.colSpan !== undefined) { componentCellSettings = componentCellSettings ?
{ ...componentCellSettings, colSpan: cell.colSpan } : { colSpan: cell.colSpan };
}) or introduce a CellWithColSpan interface and a small type guard function to
ensure type-safe access to colSpan, avoiding any casts and preserving the
existing merge logic for componentCellSettings.
- Around line 203-208: Replace the ad-hoc type cast and manual null check with a
type-safe user-defined type guard and optional chaining: add a small type guard
function (e.g., hasColSpan(obj: unknown): obj is { colSpan?: number }) that
checks obj is non-null object and has the 'colSpan' property, then use if
(hasColSpan(cell) && cell.colSpan !== undefined) { textCellSettings =
textCellSettings ? { ...textCellSettings, colSpan: cell.colSpan } : { colSpan:
cell.colSpan }; } so you remove the "as" cast and use optional access via the
guard when updating textCellSettings in GridComponent (reference symbols:
hasColSpan, textCellSettings, cell).

In `@src/layout/Grid/tools.ts`:
- Around line 109-117: getGridCellHiddenExpr currently uses 'as' casts to access
columnOptions/gridColumnOptions; instead use the existing type guards to narrow
GridCell (e.g., isGridCellText, isGridCellLabelFrom, isGridComponentRef or
whatever project's guards are named) and then read cell.columnOptions and
cell.gridColumnOptions directly without casting, returning
gridColumnOptions?.hidden ?? columnOptions?.hidden; remove the two '(cell as
{...})' casts and the initial typeof check since the type guards handle
null/undefined checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67925669-d359-425d-a239-078444973770

📥 Commits

Reviewing files that changed from the base of the PR and between 5e931c9 and 912170e.

📒 Files selected for processing (5)
  • src/codegen/Common.ts
  • src/layout/Grid/GridComponent.test.tsx
  • src/layout/Grid/GridComponent.tsx
  • src/layout/Grid/tools.test.ts
  • src/layout/Grid/tools.ts

@JamalAlabdullah JamalAlabdullah added the squad/utforming Issues that belongs to the named squad. label Mar 17, 2026
@JamalAlabdullah JamalAlabdullah moved this to 🔎 In review in Team Altinn Studio Mar 17, 2026
@JamalAlabdullah
Copy link
Copy Markdown
Contributor

/publish

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 19, 2026

PR release:

  • <link rel="stylesheet" type="text/css" href="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.912170e2/altinn-app-frontend.css">
  • <script src="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.912170e2/altinn-app-frontend.js"></script>

⚙️ Building...
❌ Failed: https://github.com/Altinn/app-frontend-react/actions/runs/23296570841

@github-actions
Copy link
Copy Markdown
Contributor

PR release:

  • <link rel="stylesheet" type="text/css" href="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.912170e2/altinn-app-frontend.css">
  • <script src="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.912170e2/altinn-app-frontend.js"></script>

⚙️ Building...

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 19, 2026

PR release:

  • <link rel="stylesheet" type="text/css" href="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.912170e2/altinn-app-frontend.css">
  • <script src="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.912170e2/altinn-app-frontend.js"></script>

⚙️ Building...
❌ Failed: https://github.com/Altinn/app-frontend-react/actions/runs/23296570841

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/scripts/release.sh:
- Line 55: The revert script fails validation for pre-release tags because it
doesn't lowercase APP_FULL like release.sh does; update revert.sh to normalize
APP_FULL to lowercase (e.g., APP_FULL=$(echo "$APP_FULL" | tr '[:upper:]'
'[:lower:]')) before the VERSION_REGEX check so pre-release identifiers (like
RC1 -> rc1) match the expected `[a-z0-9.\-]+` pattern and revert logic that uses
APP_FULL will behave identically to release.sh.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3b6945e3-6f8f-4875-9916-cbb56211666f

📥 Commits

Reviewing files that changed from the base of the PR and between 912170e and 8b3b5f3.

📒 Files selected for processing (1)
  • .github/scripts/release.sh

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 19, 2026

PR release:

  • <link rel="stylesheet" type="text/css" href="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.8b3b5f36/altinn-app-frontend.css">
  • <script src="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.8b3b5f36/altinn-app-frontend.js"></script>

⚙️ Building...
✅ Done!

@JamalAlabdullah
Copy link
Copy Markdown
Contributor

I send a message to ssb to test it , så we wait until they test

@JamalAlabdullah JamalAlabdullah self-assigned this Mar 23, 2026
@sonarqubecloud
Copy link
Copy Markdown

@JamalAlabdullah JamalAlabdullah removed their assignment Mar 24, 2026
@olemartinorg
Copy link
Copy Markdown
Contributor

Just a thought: gridColumnOptions seems a bit redundant to me. This is about config in the Grid component, so maybe there's no need to repeat that deep inside property names. Also, the entire layout files (and most files in apps) are just config, so 'options' here doesn't add anything either. In fact, the word 'options' is more often associated with 'code lists' in Altinn apps, so it might even confuse things.

TL:DR; I think "text": "heading2", "column": { "hidden": true } looks better and is just as clear. 🙌

@Jondyr Jondyr self-assigned this Mar 26, 2026
@JamalAlabdullah
Copy link
Copy Markdown
Contributor

@Jondyr @olemartinorg Regarding our meeting today, I asked Stian to share with us the app he used and what he tried to solve with the config colSpan/hidden;
He said;
"_jeg trenger dette til å skjule en kolonne som ikke skal vises i vise tilfeller i skjema, dvs i noen perioder
https://altinn.studio/repos/stian.vestli/ra1601-01.git men på branch v8update, side S06 har jeg lagt på dette
_"

@olemartinorg
Copy link
Copy Markdown
Contributor

olemartinorg commented Mar 26, 2026

Jeg fant ikke colSpan i den fila: https://altinn.studio/repos/stian.vestli/ra1601-01/src/branch/feature/v8update/App/ui/mainlayout/layouts/S06_AvvikForrigeRapportering.json.

@StianVestli Kan du forklare use-caset her? Vi har diskutert litt hvordan vi skal få dette på plass og designe det riktig fra starten av. Vi er ikke egentlig ute etter å vite hvilken konfigurasjon man trenger, eller hvilke sider/filer man trenger dem i, men om vi hadde skjønt litt bedre hva som er behovet bak dette ønsket kunne vi nok klart å lage noe bra og sammenhengende? Vi er altså redd for å lage noe som passer delvis, men som f.eks. er i konflikt med skjulte kolonner og som ender opp med å ikke fungere i all mulig praktisk bruk. Siden det er mye her vi ikke kan endre på etter det har blitt tatt i bruk, er det jo viktig at vi lager riktig løsning fra starten av, og da er det viktig for oss å vite hva dere ønsker å løse med denne konfigurasjonen.

@StianVestli
Copy link
Copy Markdown

@olemartinorg colspan? det er satt opp dynamikk på på hidden på en colonne i gridden. Eller snakker vi om forskjellig ting her ? :)

@StianVestli
Copy link
Copy Markdown

Men enkelt forklart så trenger jeg dette til å skjule en kolonne som ikke skal vises i visse tilfeller i skjema, feks i noen perioder i løpet av at år så skal ikke alle kolonner vises i gridden. Dette er jo det samma på repeterende grupper at man i noen situasjoner ikke vil vise en kolonne for en type respondenter.

@olemartinorg
Copy link
Copy Markdown
Contributor

olemartinorg commented Mar 26, 2026

@JamalAlabdullah @Jondyr Jeg fikk høre at ssb/ra-0692-01 også har en slik kolonne som skjules, så det kan være et annet use-case å se på. Dette gjelder RepeatingGroup da, usikker på om den har rowsBefore/rowsAfter. Det gjelder visst komponentene S04_Eksport_A-S04_Eksport_Z og S05_Import_A-S05_Import_Z. For alle sidene styres kolonneskjulingen av det samme Ja/Nei-spørsmålet på begynnelsen av skjemaet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-ignore This PR is a new feature and should not be cherry-picked onto release branches kind/product-feature Pull requests containing new features squad/utforming Issues that belongs to the named squad.

Projects

Status: 🔎 In review

Development

Successfully merging this pull request may close these issues.

Grid/rowsBefore/rowsAfter: Støtte for å slå sammen celler (colSpan/rowSpan)

6 participants