Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/modules/data-widgets/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Gallery

#### Changed

- The "Pagination" design property now offers Left, Center and Right as alignment buttons. Existing selections are preserved.

#### Fixed

- We fixed the pagination alignment design property, which had no effect on the position of the pagination controls.

## [3.11.3] DataWidgets - 2026-07-27

### [3.11.3] DatagridDropdownFilter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,16 @@
--gallery-gap: var(--spacing-large, $spacing-large);
}

// Pagination left
.widget-gallery-pagination-left {
.widget-gallery-pagination {
.pagination-bar {
justify-content: flex-start;
}
}
}

// Pagination center
.widget-gallery-pagination-center {
.widget-gallery-pagination {
.pagination-bar {
justify-content: center;
}
}
// Pagination alignment
//
// The widget renders the pagination controls in the bar zone named by these classes -- start for
// left, middle for center, end for right -- so alignment needs no CSS of its own. The classes stay
// declared here because they are the design property's stored values, and because the widget reads
// them from its root class list to decide placement.
.widget-gallery-pagination-left,
.widget-gallery-pagination-center,
.widget-gallery-pagination-right {
/* stylelint-disable-line no-empty-rules */
}

.widget-gallery-disable-selected-items-highlight {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,24 @@ $root: ".widget-gallery" !default;
align-items: center;
}

:where(.widget-gallery-fc-start, .widget-gallery-tb-start, .widget-gallery-fc-end, .widget-gallery-tb-end) {
// The middle zone holds centre-aligned content: the load more button, or the pagination controls
// when the pagination alignment is set to center. It is sized like the outer zones and does not
// shrink, so its content stays centred on the bar even when the selection counter is long.
:where(.widget-gallery-fc-middle, .widget-gallery-tb-middle) {
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
}

:where(
.widget-gallery-fc-start,
.widget-gallery-tb-start,
.widget-gallery-fc-middle,
.widget-gallery-tb-middle,
.widget-gallery-fc-end,
.widget-gallery-tb-end
) {
flex-grow: 1;
flex-basis: 33.33%;
}
Expand All @@ -122,7 +139,9 @@ $root: ".widget-gallery" !default;
align-items: center;
}

:where(.widget-gallery-fc-end, .widget-gallery-tb-end):not(:empty) {
:where(.widget-gallery-fc-middle, .widget-gallery-tb-middle, .widget-gallery-fc-end, .widget-gallery-tb-end):not(
:empty
) {
padding: var(--spacing-small) 0;
}

Expand Down Expand Up @@ -158,7 +177,7 @@ $root: ".widget-gallery" !default;
@container widget-gallery-header (width < 500px) {
.widget-gallery-top-bar-controls {
flex-direction: column-reverse;
:where(.widget-gallery-tb-start, .widget-gallery-tb-end) {
:where(.widget-gallery-tb-start, .widget-gallery-tb-middle, .widget-gallery-tb-end) {
width: 100%;
justify-content: center;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,23 @@
},
{
"name": "Pagination",
"type": "Dropdown",
"description": "Change the alignment of the pagination.",
"type": "ToggleButtonGroup",
"description": "Align the pagination controls. The selection counter and the load more button move aside to make room.",
"options": [
{
"name": "Left",
"icon": "Atlas_Core.Atlas.align-left",
"class": "widget-gallery-pagination-left"
},
{
"name": "Center",
"icon": "Atlas_Core.Atlas.align-center",
"class": "widget-gallery-pagination-center"
},
{
"name": "Right",
"icon": "Atlas_Core.Atlas.align-right",
"class": "widget-gallery-pagination-right"
}
]
},
Expand Down
8 changes: 8 additions & 0 deletions packages/pluggableWidgets/gallery-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Changed

- The pagination alignment design property now offers Left, Center and Right, and is applied above and below the gallery. Aligning the pagination moves the selection count or the load more button aside so both stay visible on the same row.

### Fixed

- We fixed an issue where the Gallery widget could crash with an "invalid attribute id" error when a stored sort order referenced an attribute that was no longer available. The widget now falls back to the default sort order instead.

- We fixed the pagination alignment design property, which had no effect on the position of the pagination controls.

- We fixed an issue where custom pagination widgets always rendered below the gallery, ignoring the "Position of pagination" setting. Selecting "Above grid" now renders them above the gallery, and the page editor shows them in the same place as the running app. With "Both" selected, custom pagination widgets render once, below the gallery.

## [3.11.3] - 2026-07-27

### Fixed
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-08-17
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Design

## Context

Pagination in Gallery renders inside a three-zone flex bar. Both bars are built the same way, except the top bar has no middle zone:

```
.widget-gallery-footer-controls display:flex; row nowrap
├─ .widget-gallery-fc-start grow:1 basis:33.33% selection counter
├─ .widget-gallery-fc-middle (no flex rules today) Load more button
└─ .widget-gallery-fc-end grow:1 basis:33.33% pagination / custom pagination
justify-content: flex-end

.widget-gallery-top-bar-controls
├─ .widget-gallery-tb-start grow:1 basis:33.33% selection counter
└─ .widget-gallery-tb-end grow:1 basis:33.33% pagination
justify-content: flex-end
```

The zone, not the bar, decides horizontal position. The old design property predates this structure: before the overhaul, `.widget-gallery-pagination` was a full-width row of its own directly under `.widget-gallery`, so justifying the bar inside it produced real left/centre alignment. That element is gone, which is why the property is inert.

Occupancy is dynamic:

| occupant | zone | condition |
| ----------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
| selection counter | `*-start` | `selectionCountPosition` matches the bar (defaults to `bottom`) **and** `selectedCount > 0` |
| Load more button | `fc-middle` | `pagination === "loadMore"` **and** `hasMoreItems` |
| pagination bar | `*-end` | `paginationVisible` — see `Pagination.viewModel.ts` |
| custom pagination | `fc-end` | `useCustomPagination`; suppresses the built-in bar, since `paginationVisible` returns `false` for `paginationKind === "custom"` |

## Goals / Non-Goals

Goals

- Left / Center / Right alignment that is actually where it claims to be, in both bars.
- Visual order and focus order stay in agreement.
- One placement rule shared by runtime and editor preview so they cannot drift.
- Existing app configurations keep working without migration.

Non-Goals

- Pagination alignment for DataGrid 2 (no such property exists there; adding one is a feature).
- Fixing DataGrid 2's custom-pagination position bug, or its `-padding-top` container-query typo (own branch and PR — DataGrid 2 changes are kept out of a Gallery PR).
- E2E coverage for design properties (no test project currently loads the module CSS for Gallery).
- Replacing the design property with an XML widget property (cleaner long-term, but that makes WC-3505 a feature rather than a fix).

## Decisions

### Displacement, not wrapping

Pagination claims the zone its alignment names; whatever was there moves to the end zone.

```
align = Left, counter visible, buttons mode
┌─────────────────┬─────────────────┬─────────────────┐
│ [1-10 of 42 ◀▶] │ │ 3 selected │
└─────────────────┴─────────────────┴─────────────────┘

align = Center, loadMore + total count + selection
┌─────────────────┬─────────────────┬─────────────────┐
│ 3 selected │ 1-10 of 42 │ [ Load more ] │
└─────────────────┴─────────────────┴─────────────────┘
```

The rule is total: at most three occupants exist, there are three zones, and custom pagination replaces the built-in bar rather than adding to it. Since pagination claims exactly one zone, at most one occupant is ever displaced, so the end zone never has to hold two things.

The alternative considered was wrapping the bar onto a second full-width row when the claimed zone is occupied. Rejected: the selection counter appears dynamically at `selectedCount > 0`, so wrapping would add a row — and shift the page — the moment a user selects their first item. Displacement keeps the bar one row tall at all times.

### Placement computed by a pure function, not expressed in CSS

Four mechanisms were considered:

| | TSX placement | CSS `order` | CSS grid areas | `margin: auto` |
| --------------------------------- | ----------------- | --------------- | -------------- | -------------- |
| DOM order matches visual | yes | no | no | no |
| Focus / reading order correct | yes | no | no | no |
| Needs to read the alignment value | yes | no | no | no |
| Release vehicle | widget + module | module only | module only | module only |
| Top bar Center | needs `tb-middle` | geometry rework | workable | fiddly |

The CSS mechanisms are cheaper — module-only release, no widget bump, no need to read the alignment at all — but every one of them reorders visually while leaving DOM order fixed. For a paging control that is a WCAG 2.4.3 (Focus Order) and 1.3.2 (Meaningful Sequence) defect: keyboard focus would jump right, then left, across the footer. TSX placement is chosen for that reason, and it also leaves the existing `< 500px` container queries untouched, since no new high-specificity selectors compete with them.

Placement logic is a pure function rather than inline JSX conditionals:

```
resolveZones({ alignment, hasCounter, hasLoadMore, hasPagination })
→ { start: "pagination" | "counter" | null,
middle: "pagination" | "loadMore" | null,
end: "pagination" | "counter" | "loadMore" | null }
```

Algorithm: map alignment to a target zone; if pagination is visible, it takes that zone; then place each remaining occupant in its natural home (counter → start, Load more → middle) or, if that home is taken, in the end zone.

This keeps every alignment × occupancy combination testable without rendering, and lets the footer, the top bar and `Gallery.editorPreview.tsx` consume one shared result — the divergence that produced the custom-pagination bug below came precisely from those three places each deciding placement for themselves.

### Alignment is parsed from the design-property class

Design-property selections arrive as class names on the widget root. `props.class` is already piped through the props gate and surfaced by `GalleryRootViewModel.className`, so a MobX computed can parse it for `widget-gallery-pagination-(left|center|right)` and default to `right`. Because it is a computed over a string, Design-mode edits are reflected live and the parser is unit-testable on its own.

Trade-off accepted: three class names in `data-widgets`' `design-properties.json` become an input to `gallery-web`'s render logic. Renaming them would silently break layout. Mitigation is to treat them as a documented contract, asserted by unit tests on both sides of the parse.

The alternative — a new `pagingAlignment` XML enum — is better long-term design: typed, discoverable in the properties pane, no cross-package coupling. It was rejected for this change because it converts a bug fix into a feature, needs the existing design property deprecated with a migration story, and drops the Atlas-style ToggleButtonGroup affordance.

### Top bar gains a middle zone

Center is not expressible in a two-zone bar, so `widget-gallery-tb-middle` is added. It also makes the two bars structurally symmetric, so `resolveZones` applies unchanged to both (the top bar simply never has a Load-more occupant).

### `Both` + custom pagination renders once, with an editor warning

Custom pagination is a `widgets` placeholder holding real widget instances. Rendering it in both bars would duplicate those instances, their DOM ids and their state, so `Both` renders once in the footer and `Gallery.editorConfig.ts` raises a `check()` warning explaining it. `Above grid` and `Below grid` are honoured exactly.

### Design property modernised rather than replaced

`Pagination` becomes a `ToggleButtonGroup` with `Atlas_Core.Atlas.align-left` / `align-center` / `align-right` icons — the form Atlas Core uses for every other alignment control — and gains an explicit `Right` option instead of relying on the implicit unset entry.

The property keeps its name, and so do the `Left` and `Center` options. Studio Pro stores a design property selection by **property name and option name**, not by CSS class, so renaming any of them orphans every existing selection. Verified in Studio Pro: renaming the property to "Pagination alignment" raised two errors on a page that already used it — `CE6083` "Design property Pagination is not supported by your theme" and `CE6087` "Design properties have been renamed in your theme and need to be updated". `oldNames` is honoured (Studio Pro offers "Update all renamed design properties in project"), but that is a migration the app developer has to run, and the app carries errors until they do. A bug-fix release should not impose that on every consumer, so the clearer label is left for a future deliberate revision of this property.

This is also why the class names cannot be renamed: they are the stored values, and they are simultaneously the contract the widget parses. Both halves of the property — names and classes — are frozen.

## Risks / Trade-offs

- **Focus order now varies with a styling-looking property.** `Left` places the paging controls before the Clear-selection button in the tab sequence. Accepted deliberately: the alternative is visual and focus order disagreeing.
- **Snapshot churn.** The new `tb-middle` node and zone reassignment change rendered DOM; component snapshots need regenerating and reviewing rather than blindly updating.
- **Cross-package class contract.** Covered above; mitigated by tests and documentation.
- **Centring depends on zone symmetry.** `fc-middle` is currently absent from the flex-sizing rules, so its centring is incidental — a counter long enough to hit min-content width skews it. The change gives the middle zones explicit sizing so Center holds by construction.
- **No automated regression guard for the CSS half.** Unit tests cover the placement map and the class parser; the rendered alignment itself is verified by manual Studio Pro QA. See the proposal for why E2E is impractical here.

## Open Questions

- ~~Whether the regenerated `tests/testProject/themesource/datawidgets/**` copy is committed alongside the source SCSS edit~~ — resolved: left to the module build. No previous commit touching `_gallery.scss` has updated that copy.
- Whether DataGrid 2's custom-pagination position bug is filed now or after this change lands.
Loading
Loading