From 4966056d0d7e351f04438345a9aa71e290383865 Mon Sep 17 00:00:00 2001 From: Yordan Stoyanov Date: Thu, 22 Oct 2026 11:43:07 +0200 Subject: [PATCH] chore(openspec): archive 10 shipped changes and sync specs --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/calendar-view-switching/spec.md | 0 .../specs/calendar-year-view/spec.md | 0 .../tasks.md | 0 .../specs/calendar-view-switching/spec.md | 183 +++++++++++++ .../openspec/specs/calendar-year-view/spec.md | 251 ++++++++++++++++++ .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/widget-identity/spec.md | 0 .../tasks.md | 0 .../openspec/specs/widget-identity/spec.md | 81 ++++++ .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../multiselect-keyboard-interaction/spec.md | 0 .../tasks.md | 0 .../multiselect-keyboard-interaction/spec.md | 29 ++ .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/date-picker-integration/spec.md | 0 .../tasks.md | 0 .../specs/date-picker-integration/spec.md | 207 +++++++++++++++ .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/export-events/spec.md | 0 .../tasks.md | 0 .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/datagrid-excel-export-dates/spec.md | 0 .../tasks.md | 0 .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../spec.md | 0 .../tasks.md | 0 .../spec.md | 61 +++++ .../specs/datagrid-excel-export-dates/spec.md | 69 +++++ .../openspec/specs/export-events/spec.md | 110 ++++++++ .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../spec.md | 0 .../tasks.md | 0 .../spec.md | 111 ++++++++ .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../spec.md | 0 .../gallery-pagination-placement/spec.md | 0 .../tasks.md | 0 .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../gallery-sort-order-validation/spec.md | 0 .../tasks.md | 0 .../spec.md | 56 ++++ .../gallery-pagination-placement/spec.md | 112 ++++++++ .../gallery-sort-order-validation/spec.md | 37 +++ 64 files changed, 1307 insertions(+) rename packages/pluggableWidgets/calendar-web/openspec/changes/{add-calendar-year-view => archive/2026-10-22-add-calendar-year-view}/.openspec.yaml (100%) rename packages/pluggableWidgets/calendar-web/openspec/changes/{add-calendar-year-view => archive/2026-10-22-add-calendar-year-view}/design.md (100%) rename packages/pluggableWidgets/calendar-web/openspec/changes/{add-calendar-year-view => archive/2026-10-22-add-calendar-year-view}/proposal.md (100%) rename packages/pluggableWidgets/calendar-web/openspec/changes/{add-calendar-year-view => archive/2026-10-22-add-calendar-year-view}/specs/calendar-view-switching/spec.md (100%) rename packages/pluggableWidgets/calendar-web/openspec/changes/{add-calendar-year-view => archive/2026-10-22-add-calendar-year-view}/specs/calendar-year-view/spec.md (100%) rename packages/pluggableWidgets/calendar-web/openspec/changes/{add-calendar-year-view => archive/2026-10-22-add-calendar-year-view}/tasks.md (100%) create mode 100644 packages/pluggableWidgets/calendar-web/openspec/specs/calendar-view-switching/spec.md create mode 100644 packages/pluggableWidgets/calendar-web/openspec/specs/calendar-year-view/spec.md rename packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/{rename-option-selector => archive/2026-10-22-rename-option-selector}/.openspec.yaml (100%) rename packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/{rename-option-selector => archive/2026-10-22-rename-option-selector}/design.md (100%) rename packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/{rename-option-selector => archive/2026-10-22-rename-option-selector}/proposal.md (100%) rename packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/{rename-option-selector => archive/2026-10-22-rename-option-selector}/specs/widget-identity/spec.md (100%) rename packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/{rename-option-selector => archive/2026-10-22-rename-option-selector}/tasks.md (100%) create mode 100644 packages/pluggableWidgets/checkbox-radio-selection-web/openspec/specs/widget-identity/spec.md rename packages/pluggableWidgets/combobox-web/openspec/changes/{fix-combobox-chip-focus-after-removal => archive/2026-10-22-fix-combobox-chip-focus-after-removal}/.openspec.yaml (100%) rename packages/pluggableWidgets/combobox-web/openspec/changes/{fix-combobox-chip-focus-after-removal => archive/2026-10-22-fix-combobox-chip-focus-after-removal}/design.md (100%) rename packages/pluggableWidgets/combobox-web/openspec/changes/{fix-combobox-chip-focus-after-removal => archive/2026-10-22-fix-combobox-chip-focus-after-removal}/proposal.md (100%) rename packages/pluggableWidgets/combobox-web/openspec/changes/{fix-combobox-chip-focus-after-removal => archive/2026-10-22-fix-combobox-chip-focus-after-removal}/specs/multiselect-keyboard-interaction/spec.md (100%) rename packages/pluggableWidgets/combobox-web/openspec/changes/{fix-combobox-chip-focus-after-removal => archive/2026-10-22-fix-combobox-chip-focus-after-removal}/tasks.md (100%) rename packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/{update-react-datepicker-v9 => archive/2026-10-22-update-react-datepicker-v9}/.openspec.yaml (100%) rename packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/{update-react-datepicker-v9 => archive/2026-10-22-update-react-datepicker-v9}/design.md (100%) rename packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/{update-react-datepicker-v9 => archive/2026-10-22-update-react-datepicker-v9}/proposal.md (100%) rename packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/{update-react-datepicker-v9 => archive/2026-10-22-update-react-datepicker-v9}/specs/date-picker-integration/spec.md (100%) rename packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/{update-react-datepicker-v9 => archive/2026-10-22-update-react-datepicker-v9}/tasks.md (100%) create mode 100644 packages/pluggableWidgets/datagrid-date-filter-web/openspec/specs/date-picker-integration/spec.md rename packages/pluggableWidgets/datagrid-web/openspec/changes/{datagrid-export-events => archive/2026-10-22-datagrid-export-events}/.openspec.yaml (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{datagrid-export-events => archive/2026-10-22-datagrid-export-events}/design.md (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{datagrid-export-events => archive/2026-10-22-datagrid-export-events}/proposal.md (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{datagrid-export-events => archive/2026-10-22-datagrid-export-events}/specs/export-events/spec.md (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{datagrid-export-events => archive/2026-10-22-datagrid-export-events}/tasks.md (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{fix-excel-export-date-timezone => archive/2026-10-22-fix-excel-export-date-timezone}/.openspec.yaml (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{fix-excel-export-date-timezone => archive/2026-10-22-fix-excel-export-date-timezone}/design.md (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{fix-excel-export-date-timezone => archive/2026-10-22-fix-excel-export-date-timezone}/proposal.md (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{fix-excel-export-date-timezone => archive/2026-10-22-fix-excel-export-date-timezone}/specs/datagrid-excel-export-dates/spec.md (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{fix-excel-export-date-timezone => archive/2026-10-22-fix-excel-export-date-timezone}/tasks.md (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{pagination-position-not-honored => archive/2026-10-22-pagination-position-not-honored}/.openspec.yaml (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{pagination-position-not-honored => archive/2026-10-22-pagination-position-not-honored}/design.md (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{pagination-position-not-honored => archive/2026-10-22-pagination-position-not-honored}/proposal.md (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{pagination-position-not-honored => archive/2026-10-22-pagination-position-not-honored}/specs/datagrid-custom-pagination-position/spec.md (100%) rename packages/pluggableWidgets/datagrid-web/openspec/changes/{pagination-position-not-honored => archive/2026-10-22-pagination-position-not-honored}/tasks.md (100%) create mode 100644 packages/pluggableWidgets/datagrid-web/openspec/specs/datagrid-custom-pagination-position/spec.md create mode 100644 packages/pluggableWidgets/datagrid-web/openspec/specs/datagrid-excel-export-dates/spec.md create mode 100644 packages/pluggableWidgets/datagrid-web/openspec/specs/export-events/spec.md rename packages/pluggableWidgets/file-uploader-web/openspec/changes/{file-uploader-live-region => archive/2026-10-22-file-uploader-live-region}/.openspec.yaml (100%) rename packages/pluggableWidgets/file-uploader-web/openspec/changes/{file-uploader-live-region => archive/2026-10-22-file-uploader-live-region}/design.md (100%) rename packages/pluggableWidgets/file-uploader-web/openspec/changes/{file-uploader-live-region => archive/2026-10-22-file-uploader-live-region}/proposal.md (100%) rename packages/pluggableWidgets/file-uploader-web/openspec/changes/{file-uploader-live-region => archive/2026-10-22-file-uploader-live-region}/specs/file-uploader-status-announcements/spec.md (100%) rename packages/pluggableWidgets/file-uploader-web/openspec/changes/{file-uploader-live-region => archive/2026-10-22-file-uploader-live-region}/tasks.md (100%) create mode 100644 packages/pluggableWidgets/file-uploader-web/openspec/specs/file-uploader-status-announcements/spec.md rename packages/pluggableWidgets/gallery-web/openspec/changes/{fix-gallery-pagination-placement => archive/2026-10-22-fix-gallery-pagination-placement}/.openspec.yaml (100%) rename packages/pluggableWidgets/gallery-web/openspec/changes/{fix-gallery-pagination-placement => archive/2026-10-22-fix-gallery-pagination-placement}/design.md (100%) rename packages/pluggableWidgets/gallery-web/openspec/changes/{fix-gallery-pagination-placement => archive/2026-10-22-fix-gallery-pagination-placement}/proposal.md (100%) rename packages/pluggableWidgets/gallery-web/openspec/changes/{fix-gallery-pagination-placement => archive/2026-10-22-fix-gallery-pagination-placement}/specs/gallery-custom-pagination-position/spec.md (100%) rename packages/pluggableWidgets/gallery-web/openspec/changes/{fix-gallery-pagination-placement => archive/2026-10-22-fix-gallery-pagination-placement}/specs/gallery-pagination-placement/spec.md (100%) rename packages/pluggableWidgets/gallery-web/openspec/changes/{fix-gallery-pagination-placement => archive/2026-10-22-fix-gallery-pagination-placement}/tasks.md (100%) rename packages/pluggableWidgets/gallery-web/openspec/changes/{fix-stale-sort-order-attribute => archive/2026-10-22-fix-stale-sort-order-attribute}/.openspec.yaml (100%) rename packages/pluggableWidgets/gallery-web/openspec/changes/{fix-stale-sort-order-attribute => archive/2026-10-22-fix-stale-sort-order-attribute}/design.md (100%) rename packages/pluggableWidgets/gallery-web/openspec/changes/{fix-stale-sort-order-attribute => archive/2026-10-22-fix-stale-sort-order-attribute}/proposal.md (100%) rename packages/pluggableWidgets/gallery-web/openspec/changes/{fix-stale-sort-order-attribute => archive/2026-10-22-fix-stale-sort-order-attribute}/specs/gallery-sort-order-validation/spec.md (100%) rename packages/pluggableWidgets/gallery-web/openspec/changes/{fix-stale-sort-order-attribute => archive/2026-10-22-fix-stale-sort-order-attribute}/tasks.md (100%) create mode 100644 packages/pluggableWidgets/gallery-web/openspec/specs/gallery-custom-pagination-position/spec.md create mode 100644 packages/pluggableWidgets/gallery-web/openspec/specs/gallery-pagination-placement/spec.md create mode 100644 packages/pluggableWidgets/gallery-web/openspec/specs/gallery-sort-order-validation/spec.md diff --git a/packages/pluggableWidgets/calendar-web/openspec/changes/add-calendar-year-view/.openspec.yaml b/packages/pluggableWidgets/calendar-web/openspec/changes/archive/2026-10-22-add-calendar-year-view/.openspec.yaml similarity index 100% rename from packages/pluggableWidgets/calendar-web/openspec/changes/add-calendar-year-view/.openspec.yaml rename to packages/pluggableWidgets/calendar-web/openspec/changes/archive/2026-10-22-add-calendar-year-view/.openspec.yaml diff --git a/packages/pluggableWidgets/calendar-web/openspec/changes/add-calendar-year-view/design.md b/packages/pluggableWidgets/calendar-web/openspec/changes/archive/2026-10-22-add-calendar-year-view/design.md similarity index 100% rename from packages/pluggableWidgets/calendar-web/openspec/changes/add-calendar-year-view/design.md rename to packages/pluggableWidgets/calendar-web/openspec/changes/archive/2026-10-22-add-calendar-year-view/design.md diff --git a/packages/pluggableWidgets/calendar-web/openspec/changes/add-calendar-year-view/proposal.md b/packages/pluggableWidgets/calendar-web/openspec/changes/archive/2026-10-22-add-calendar-year-view/proposal.md similarity index 100% rename from packages/pluggableWidgets/calendar-web/openspec/changes/add-calendar-year-view/proposal.md rename to packages/pluggableWidgets/calendar-web/openspec/changes/archive/2026-10-22-add-calendar-year-view/proposal.md diff --git a/packages/pluggableWidgets/calendar-web/openspec/changes/add-calendar-year-view/specs/calendar-view-switching/spec.md b/packages/pluggableWidgets/calendar-web/openspec/changes/archive/2026-10-22-add-calendar-year-view/specs/calendar-view-switching/spec.md similarity index 100% rename from packages/pluggableWidgets/calendar-web/openspec/changes/add-calendar-year-view/specs/calendar-view-switching/spec.md rename to packages/pluggableWidgets/calendar-web/openspec/changes/archive/2026-10-22-add-calendar-year-view/specs/calendar-view-switching/spec.md diff --git a/packages/pluggableWidgets/calendar-web/openspec/changes/add-calendar-year-view/specs/calendar-year-view/spec.md b/packages/pluggableWidgets/calendar-web/openspec/changes/archive/2026-10-22-add-calendar-year-view/specs/calendar-year-view/spec.md similarity index 100% rename from packages/pluggableWidgets/calendar-web/openspec/changes/add-calendar-year-view/specs/calendar-year-view/spec.md rename to packages/pluggableWidgets/calendar-web/openspec/changes/archive/2026-10-22-add-calendar-year-view/specs/calendar-year-view/spec.md diff --git a/packages/pluggableWidgets/calendar-web/openspec/changes/add-calendar-year-view/tasks.md b/packages/pluggableWidgets/calendar-web/openspec/changes/archive/2026-10-22-add-calendar-year-view/tasks.md similarity index 100% rename from packages/pluggableWidgets/calendar-web/openspec/changes/add-calendar-year-view/tasks.md rename to packages/pluggableWidgets/calendar-web/openspec/changes/archive/2026-10-22-add-calendar-year-view/tasks.md diff --git a/packages/pluggableWidgets/calendar-web/openspec/specs/calendar-view-switching/spec.md b/packages/pluggableWidgets/calendar-web/openspec/specs/calendar-view-switching/spec.md new file mode 100644 index 0000000000..95f2991683 --- /dev/null +++ b/packages/pluggableWidgets/calendar-web/openspec/specs/calendar-view-switching/spec.md @@ -0,0 +1,183 @@ +# calendar-view-switching Specification + +## Purpose + +TBD - created by archiving change add-calendar-year-view. Update Purpose after archive. + +## Requirements + +### Requirement: Year view is available in Standard mode + +The calendar widget SHALL include year view as an option in Standard mode alongside day, week, and month views. + +#### Scenario: Standard mode toolbar displays year button + +- **WHEN** calendar is configured with view mode set to "Standard" +- **THEN** the toolbar displays four view buttons: Day, Week, Month, and Year + +#### Scenario: Year view can be set as default view in Standard mode + +- **WHEN** calendar is configured with view mode "Standard" and default view "Year" +- **THEN** the calendar initially displays in year view + +#### Scenario: User can switch to year view from any Standard view + +- **WHEN** calendar is in Standard mode and currently showing day, week, or month view +- **THEN** user can click the "Year" button in the toolbar to switch to year view + +### Requirement: Year view is available in Custom mode + +The calendar widget SHALL allow year view to be configured in Custom mode through toolbar items. + +#### Scenario: Custom mode can include year view button + +- **WHEN** calendar is configured with view mode "Custom" and a toolbar item with type "Year view button" +- **THEN** the toolbar displays the year view button with configurable caption, position, and style + +#### Scenario: Year view can be set as default view in Custom mode + +- **WHEN** calendar is configured with view mode "Custom" and default view "Year" +- **THEN** the calendar initially displays in year view + +#### Scenario: Year view button is omitted when not configured + +- **WHEN** calendar is in Custom mode and no toolbar item with type "Year view button" is configured +- **THEN** the year view button does not appear in the toolbar AND year view is not accessible + +### Requirement: View switching preserves date context + +When switching to or from year view, the calendar SHALL maintain appropriate date context. + +#### Scenario: Switching to year view preserves the date's year + +- **WHEN** user switches from day/week/month view to year view +- **THEN** year view displays the year of the currently selected date + +#### Scenario: Switching from year view after clicking a day + +- **WHEN** user clicks a day in year view AND the configured day-click target view is enabled +- **THEN** the configured target view displays the clicked date + +#### Scenario: Switching from year view via toolbar button + +- **WHEN** user switches from year view to day/week/month view using toolbar buttons +- **THEN** the target view displays the currently selected date or the first day of the currently displayed year + +### Requirement: Year view is enumerated in XML configuration + +The calendar widget XML SHALL include "year" as a valid enumeration value for view-related properties. + +#### Scenario: Default view Standard enum includes year + +- **WHEN** developer configures the calendar widget in Mendix Studio Pro +- **THEN** the "Initial selected view" dropdown for Standard mode includes "Day", "Week", "Month", and "Year" options + +#### Scenario: Default view Custom enum includes year + +- **WHEN** developer configures the calendar widget in Mendix Studio Pro with Custom mode +- **THEN** the "Initial selected view" dropdown includes "Day", "Week", "Month", "Work week", "Agenda", and "Year" options + +#### Scenario: Toolbar item type enum includes year + +- **WHEN** developer adds a toolbar item in Custom mode +- **THEN** the "Item" dropdown includes "Year view button" as an option + +### Requirement: TypeScript types include year view + +The auto-generated TypeScript types SHALL include "year" in all view-related union types. + +#### Scenario: DefaultViewStandardEnum includes year + +- **WHEN** the widget is built and types are generated +- **THEN** the `DefaultViewStandardEnum` type is `"day" | "week" | "month" | "year"` + +#### Scenario: DefaultViewCustomEnum includes year + +- **WHEN** the widget is built and types are generated +- **THEN** the `DefaultViewCustomEnum` type is `"day" | "week" | "month" | "work_week" | "agenda" | "year"` + +#### Scenario: ItemTypeEnum includes year + +- **WHEN** the widget is built and types are generated +- **THEN** the `ItemTypeEnum` type includes `"year"` as one of its values + +### Requirement: Year view button renders in toolbar + +The toolbar component SHALL render a year view button when year view is enabled. + +#### Scenario: Standard mode toolbar renders year button + +- **WHEN** calendar is in Standard mode +- **THEN** the toolbar right section includes a "Year" button using the localized message for "year" + +#### Scenario: Custom mode toolbar renders configured year button + +- **WHEN** calendar is in Custom mode with a year view toolbar item configured with caption "Annual View" +- **THEN** the toolbar renders a button with text "Annual View" at the configured position + +#### Scenario: Year view button is highlighted when active + +- **WHEN** calendar is displaying year view +- **THEN** the year view button has the "active" CSS class applied + +#### Scenario: Clicking year button switches to year view + +- **WHEN** user clicks the year view button +- **THEN** the calendar switches to year view using the `onView('year')` callback + +### Requirement: View registration integrates year view component + +The calendar widget SHALL register the year view component with react-big-calendar's view system. + +#### Scenario: Year view is registered in Standard mode + +- **WHEN** calendar is in Standard mode and builds visible views +- **THEN** the views object includes `year: YearViewController.getComponent()` + +#### Scenario: Year view is registered in Custom mode when configured + +- **WHEN** calendar is in Custom mode and a year view toolbar item is configured +- **THEN** the views object includes `year: YearViewController.getComponent()` + +#### Scenario: Year view is omitted when not enabled + +- **WHEN** calendar is in Custom mode and no year view toolbar item is configured +- **THEN** the views object does NOT include a year property OR sets `year: false` + +### Requirement: Year view handles react-big-calendar navigation events + +The year view component SHALL implement the required interface for react-big-calendar custom views. + +#### Scenario: Year view implements navigate method + +- **WHEN** react-big-calendar calls `YearView.navigate(date, action)` +- **THEN** the method returns the appropriate date based on the action (PREV → date - 1 year, NEXT → date + 1 year, TODAY → current date) + +#### Scenario: Year view implements title method + +- **WHEN** react-big-calendar calls `YearView.title(date, options)` +- **THEN** the method returns a string containing the four-digit year (e.g., "2026") + +#### Scenario: Year view implements range method + +- **WHEN** react-big-calendar calls `YearView.range(date)` +- **THEN** the method returns an array containing the first and last day of the year ([Jan 1, Dec 31]) + +### Requirement: Backward compatibility with existing views + +Adding year view SHALL NOT break or modify the behavior of existing day, week, month, work_week, or agenda views. + +#### Scenario: Existing views render unchanged + +- **WHEN** calendar displays day, week, month, work_week, or agenda views +- **THEN** the rendering and behavior remain identical to the previous version without year view + +#### Scenario: Existing view switching continues to work + +- **WHEN** user switches between day, week, and month views +- **THEN** the view switching behavior is unchanged from the previous version + +#### Scenario: Existing Custom mode configurations without year view continue to work + +- **WHEN** a calendar is configured in Custom mode without year view toolbar items +- **THEN** the calendar behaves identically to the previous version without year view support diff --git a/packages/pluggableWidgets/calendar-web/openspec/specs/calendar-year-view/spec.md b/packages/pluggableWidgets/calendar-web/openspec/specs/calendar-year-view/spec.md new file mode 100644 index 0000000000..ea1c664c54 --- /dev/null +++ b/packages/pluggableWidgets/calendar-web/openspec/specs/calendar-year-view/spec.md @@ -0,0 +1,251 @@ +# calendar-year-view Specification + +## Purpose + +TBD - created by archiving change add-calendar-year-view. Update Purpose after archive. + +## Requirements + +### Requirement: Year view displays 12-month grid + +The calendar widget SHALL render a year view showing all 12 months of the selected year in a responsive grid layout. + +#### Scenario: Desktop layout displays 4 columns + +- **WHEN** year view is rendered on a screen width ≥ 769px +- **THEN** the grid displays 4 columns × 3 rows (12 months total) + +#### Scenario: Tablet layout displays 2 columns + +- **WHEN** year view is rendered on a screen width between 481px and 768px +- **THEN** the grid displays 2 columns × 6 rows (12 months total) + +#### Scenario: Mobile layout displays 1 column + +- **WHEN** year view is rendered on a screen width ≤ 480px +- **THEN** the grid displays 1 column × 12 rows (12 months total) + +#### Scenario: Each month shows localized name + +- **WHEN** year view renders a month +- **THEN** the month header displays the localized month name (e.g., "January", "Enero", "Januar") according to the user's locale + +### Requirement: Month mini-grids show calendar structure + +Each month mini-grid SHALL display a standard calendar layout with weekday headers and day cells. + +#### Scenario: Weekday headers are localized + +- **WHEN** a month mini-grid is rendered +- **THEN** the weekday headers display localized abbreviated names (e.g., "Su Mo Tu We Th Fr Sa" for English, "Lu Ma Mi Ju Vi Sá Do" for Spanish) according to the user's locale + +#### Scenario: Day cells show day numbers + +- **WHEN** a month mini-grid renders day cells for the current month +- **THEN** each day cell displays the day number (1-31) + +#### Scenario: Leading days from previous month are shown in gray + +- **WHEN** a month mini-grid renders and the first day of the month is not Sunday +- **THEN** the grid displays days from the previous month in gray color to complete the first week + +#### Scenario: Trailing days from next month are shown in gray + +- **WHEN** a month mini-grid renders and the last day of the month does not complete the final week +- **THEN** the grid displays days from the next month in gray color to complete the final week + +### Requirement: Event indicators show as dots + +Days with events SHALL display a single dot indicator, regardless of the number of events on that day. + +#### Scenario: Day with events displays a dot + +- **WHEN** a day cell is rendered and one or more events occur on that day +- **THEN** a dot indicator is displayed below the day number using the primary brand color + +#### Scenario: Day without events has no dot + +- **WHEN** a day cell is rendered and no events occur on that day +- **THEN** no dot indicator is displayed + +#### Scenario: Multi-day events show dots on all affected days + +- **WHEN** an event spans multiple days (e.g., Jan 15-17) +- **THEN** dots are displayed on all days within the event's date range (Jan 15, 16, and 17) + +#### Scenario: All-day events are treated as full-day occurrences + +- **WHEN** an event is marked as all-day +- **THEN** the event is considered to occur on each calendar day from start date to end date (inclusive) and dots are displayed accordingly + +### Requirement: Day click navigates to the configured target view + +Clicking a day cell SHALL navigate the calendar to the author-configured day-click target view for the selected date, PROVIDED that target view is enabled. The target is configured per view mode via two properties, so the available choices always match the mode's available views: + +- **Standard mode** — `yearDayClickViewStandard`, choices: day, week, month (default day). All are always enabled in Standard mode, so the target is always honored. +- **Custom mode** — `yearDayClickViewCustom`, choices: day, week, work_week, month, agenda (default day). Only honored if that view is enabled via a toolbar item. + +The builder collapses the mode-specific property into a single effective target early (mirroring `defaultView`), so downstream logic is mode-agnostic. If the configured target view is not among the calendar's enabled views (only reachable in Custom mode), day-click SHALL be disabled: day cells are rendered as non-interactive (no button role, tab stop, click, or keyboard handler) while retaining their aria-label, and no navigation occurs. + +#### Scenario: User clicks a day in the current month + +- **WHEN** the configured target view is enabled AND user clicks a day cell representing a date in the current month +- **THEN** the calendar switches to the configured target view AND displays the selected date + +#### Scenario: User clicks a leading day from previous month + +- **WHEN** the configured target view is enabled AND user clicks a gray day cell from the previous month +- **THEN** the calendar switches to the configured target view AND displays the selected date from the previous month + +#### Scenario: User clicks a trailing day from next month + +- **WHEN** the configured target view is enabled AND user clicks a gray day cell from the next month +- **THEN** the calendar switches to the configured target view AND displays the selected date from the next month + +#### Scenario: Configured target view is not enabled + +- **WHEN** the configured day-click target view is not one of the calendar's enabled views +- **THEN** day cells are non-interactive AND clicking a day does nothing (no view registered on the author's behalf) + +### Requirement: Today indicator highlights current day + +The current day SHALL be visually highlighted in the year view. + +#### Scenario: Current day is highlighted with background color + +- **WHEN** year view is rendered and the current date falls within the displayed year +- **THEN** the day cell for today displays a light background color using the primary brand color + +#### Scenario: Current day in a different year is not highlighted + +- **WHEN** year view displays a year that is not the current year +- **THEN** no day cells are highlighted as "today" + +### Requirement: Year navigation via toolbar + +Users SHALL be able to navigate between years using previous/next buttons and a today button. + +#### Scenario: Previous button navigates to previous year + +- **WHEN** user clicks the "Previous" button in year view +- **THEN** the calendar displays the previous year (current year - 1) + +#### Scenario: Next button navigates to next year + +- **WHEN** user clicks the "Next" button in year view +- **THEN** the calendar displays the next year (current year + 1) + +#### Scenario: Today button navigates to current year + +- **WHEN** user clicks the "Today" button in year view +- **THEN** the calendar displays the current year + +#### Scenario: Toolbar title displays the year + +- **WHEN** year view is active +- **THEN** the toolbar center displays the four-digit year (e.g., "2026") + +### Requirement: Event filtering by year and month + +The calendar widget SHALL efficiently filter events to display only those relevant to each month in the year view. + +#### Scenario: Events are filtered to the displayed year + +- **WHEN** year view is rendered for year 2026 +- **THEN** only events with start or end dates in 2026 are considered for display + +#### Scenario: Events are grouped by month for rendering + +- **WHEN** year view prepares to render 12 months +- **THEN** events are grouped into 12 collections (one per month) based on which month(s) they occur in + +#### Scenario: Multi-month events appear in all relevant months + +- **WHEN** an event spans from March 25 to April 5 +- **THEN** the event is included in both the March and April event collections + +#### Scenario: Event filtering uses memoization to avoid re-computation + +- **WHEN** year view re-renders due to non-event prop changes +- **THEN** event filtering is not re-executed if the event list and year have not changed + +### Requirement: Keyboard accessibility for day cells + +Day cells SHALL be keyboard-accessible for users who navigate without a mouse. + +#### Scenario: Day cells are focusable via Tab key + +- **WHEN** user presses Tab while focused on a year view element +- **THEN** focus moves to the next day cell in document order + +#### Scenario: Focused day cell has visible focus indicator + +- **WHEN** a day cell receives keyboard focus +- **THEN** a visible focus outline is displayed around the cell + +#### Scenario: Enter or Space key activates day cell + +- **WHEN** the configured target view is enabled AND user presses Enter or Space while a day cell is focused +- **THEN** the calendar navigates to the configured target view for that date (same as clicking) + +### Requirement: Screen reader support + +The year view SHALL provide appropriate ARIA labels and semantic structure for screen reader users. + +#### Scenario: Each day cell has descriptive ARIA label + +- **WHEN** a screen reader focuses on a day cell +- **THEN** the ARIA label announces the full date and event count (e.g., "January 15, 2026, 3 events" or "January 15, 2026, no events") + +#### Scenario: Month headers are semantic headings + +- **WHEN** screen reader navigates the year view structure +- **THEN** month names are announced as headings at the appropriate level + +#### Scenario: Weekday headers are announced + +- **WHEN** screen reader navigates a month mini-grid +- **THEN** weekday header abbreviations are announced to provide context for day cells + +### Requirement: Atlas UI design system integration + +The year view SHALL use Atlas UI CSS variables and follow established styling patterns. + +#### Scenario: Event dots use primary brand color + +- **WHEN** an event dot is rendered +- **THEN** the dot color is determined by the `--brand-primary` CSS variable with fallback to `#0595db` + +#### Scenario: Today highlight uses lighter primary color + +- **WHEN** the current day is highlighted +- **THEN** the background color is determined by the `--color-primary-lighter` CSS variable + +#### Scenario: Month card borders use default border color + +- **WHEN** a month mini-grid is rendered +- **THEN** the border color is determined by the `--border-color-default` CSS variable with fallback to `#d7d7d7` + +#### Scenario: Grid gaps use standard spacing + +- **WHEN** the year grid layout is rendered +- **THEN** gaps between month cards use the `--spacing-medium` CSS variable with fallback to `10px` + +### Requirement: Performance with large event datasets + +The year view SHALL render efficiently even with large numbers of events (1000+). + +#### Scenario: Year view renders in under 200ms with 1000 events + +- **WHEN** year view is rendered with 1000 events spread across the year +- **THEN** initial render completes in under 200 milliseconds + +#### Scenario: Event filtering is memoized across re-renders + +- **WHEN** year view re-renders due to non-event prop changes +- **THEN** event filtering computation is skipped if events and year are unchanged + +#### Scenario: Month grids render day cells without per-day event filtering + +- **WHEN** a month mini-grid renders 30+ day cells +- **THEN** event presence is checked using pre-filtered month-level event list, not the full year's events diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/rename-option-selector/.openspec.yaml b/packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/archive/2026-10-22-rename-option-selector/.openspec.yaml similarity index 100% rename from packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/rename-option-selector/.openspec.yaml rename to packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/archive/2026-10-22-rename-option-selector/.openspec.yaml diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/rename-option-selector/design.md b/packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/archive/2026-10-22-rename-option-selector/design.md similarity index 100% rename from packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/rename-option-selector/design.md rename to packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/archive/2026-10-22-rename-option-selector/design.md diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/rename-option-selector/proposal.md b/packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/archive/2026-10-22-rename-option-selector/proposal.md similarity index 100% rename from packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/rename-option-selector/proposal.md rename to packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/archive/2026-10-22-rename-option-selector/proposal.md diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/rename-option-selector/specs/widget-identity/spec.md b/packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/archive/2026-10-22-rename-option-selector/specs/widget-identity/spec.md similarity index 100% rename from packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/rename-option-selector/specs/widget-identity/spec.md rename to packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/archive/2026-10-22-rename-option-selector/specs/widget-identity/spec.md diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/rename-option-selector/tasks.md b/packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/archive/2026-10-22-rename-option-selector/tasks.md similarity index 100% rename from packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/rename-option-selector/tasks.md rename to packages/pluggableWidgets/checkbox-radio-selection-web/openspec/changes/archive/2026-10-22-rename-option-selector/tasks.md diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/openspec/specs/widget-identity/spec.md b/packages/pluggableWidgets/checkbox-radio-selection-web/openspec/specs/widget-identity/spec.md new file mode 100644 index 0000000000..127e9f0497 --- /dev/null +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/openspec/specs/widget-identity/spec.md @@ -0,0 +1,81 @@ +# widget-identity Specification + +## Purpose + +TBD - created by archiving change rename-option-selector. Update Purpose after archive. + +## Requirements + +### Requirement: Studio Pro presentation name + +The widget SHALL present itself to app developers as "Option Selector" in every design-time surface: the Studio Pro +toolbox entry, the Marketplace listing title, and the design-time placeholder captions shown in the page editor when no +caption is configured. + +#### Scenario: Toolbox entry + +- **WHEN** an app developer opens the Studio Pro toolbox and browses the "Input elements" category +- **THEN** the widget is listed as "Option Selector" +- **AND** it is not listed under "Check box / radio selector" or "Checkbox Radio Selection" + +#### Scenario: Design-time placeholder captions + +- **WHEN** the widget is placed on a page with no caption attribute configured +- **THEN** the structure preview and editor preview show "Option Selector" as the fallback caption +- **AND** the custom-content dropzones read "Configure the option selector: Place widgets here" + +#### Scenario: Marketplace listing + +- **WHEN** the widget is published to the Mendix Marketplace +- **THEN** the listing under component number 245825 is titled "Option Selector" +- **AND** a new listing is NOT created + +### Requirement: Discoverability by control type + +The widget description SHALL name both control types it renders — checkbox and radio button — so that a developer +searching the Studio Pro toolbox or the Marketplace for either term finds the widget under its new name. + +#### Scenario: Searching for the old control names + +- **WHEN** a developer types "checkbox" or "radio" into the Studio Pro toolbox search +- **THEN** "Option Selector" appears in the results, matched via its description + +#### Scenario: Description is present + +- **WHEN** the widget XML is inspected +- **THEN** `` is non-empty +- **AND** it contains the words "checkbox" and "radio button" + +### Requirement: Stable widget identity across a rename + +Renaming the widget's presentation SHALL NOT change any value that an existing app resolves the widget by. The +following are frozen: the widget ID `com.mendix.widget.web.checkboxradioselection.CheckboxRadioSelection`, the +`clientModule name` / `widgetFile path` / `file path` entries in `package.xml`, `package.json`'s `widgetName`, +`mxpackage.name`, `mpkName`, `packagePath` and `marketplace.appNumber`, the npm package name +`@mendix/checkbox-radio-selection-web`, the package folder name, and the `widget-checkbox-radio-selection*` CSS class +prefix. + +#### Scenario: Existing app upgrades to the renamed version + +- **WHEN** an app already using the widget imports the MPK built after the rename +- **THEN** every existing widget instance on every page still resolves and renders +- **AND** no page reconfiguration is required +- **AND** custom CSS targeting `widget-checkbox-radio-selection*` classes still applies + +#### Scenario: Generated artifacts are unaffected + +- **WHEN** the package is rebuilt after the rename +- **THEN** `typings/CheckboxRadioSelectionProps.d.ts` is regenerated with identical content +- **AND** the generated locale file is still named + `com.mendix.widget.web.checkboxradioselection.checkboxradioselection.json` + +### Requirement: Stable help URL + +The widget's `helpUrl` SHALL continue to resolve to `https://docs.mendix.com/appstore/widgets/checkboxradioselection`. +The documentation page may be retitled to "Option Selector", but its URL slug is a contract shared with Studio Pro's +help action and MUST NOT change as part of a display-name rename. + +#### Scenario: Developer opens widget help from Studio Pro + +- **WHEN** a developer triggers the help action on the widget in Studio Pro +- **THEN** the browser opens `https://docs.mendix.com/appstore/widgets/checkboxradioselection` diff --git a/packages/pluggableWidgets/combobox-web/openspec/changes/fix-combobox-chip-focus-after-removal/.openspec.yaml b/packages/pluggableWidgets/combobox-web/openspec/changes/archive/2026-10-22-fix-combobox-chip-focus-after-removal/.openspec.yaml similarity index 100% rename from packages/pluggableWidgets/combobox-web/openspec/changes/fix-combobox-chip-focus-after-removal/.openspec.yaml rename to packages/pluggableWidgets/combobox-web/openspec/changes/archive/2026-10-22-fix-combobox-chip-focus-after-removal/.openspec.yaml diff --git a/packages/pluggableWidgets/combobox-web/openspec/changes/fix-combobox-chip-focus-after-removal/design.md b/packages/pluggableWidgets/combobox-web/openspec/changes/archive/2026-10-22-fix-combobox-chip-focus-after-removal/design.md similarity index 100% rename from packages/pluggableWidgets/combobox-web/openspec/changes/fix-combobox-chip-focus-after-removal/design.md rename to packages/pluggableWidgets/combobox-web/openspec/changes/archive/2026-10-22-fix-combobox-chip-focus-after-removal/design.md diff --git a/packages/pluggableWidgets/combobox-web/openspec/changes/fix-combobox-chip-focus-after-removal/proposal.md b/packages/pluggableWidgets/combobox-web/openspec/changes/archive/2026-10-22-fix-combobox-chip-focus-after-removal/proposal.md similarity index 100% rename from packages/pluggableWidgets/combobox-web/openspec/changes/fix-combobox-chip-focus-after-removal/proposal.md rename to packages/pluggableWidgets/combobox-web/openspec/changes/archive/2026-10-22-fix-combobox-chip-focus-after-removal/proposal.md diff --git a/packages/pluggableWidgets/combobox-web/openspec/changes/fix-combobox-chip-focus-after-removal/specs/multiselect-keyboard-interaction/spec.md b/packages/pluggableWidgets/combobox-web/openspec/changes/archive/2026-10-22-fix-combobox-chip-focus-after-removal/specs/multiselect-keyboard-interaction/spec.md similarity index 100% rename from packages/pluggableWidgets/combobox-web/openspec/changes/fix-combobox-chip-focus-after-removal/specs/multiselect-keyboard-interaction/spec.md rename to packages/pluggableWidgets/combobox-web/openspec/changes/archive/2026-10-22-fix-combobox-chip-focus-after-removal/specs/multiselect-keyboard-interaction/spec.md diff --git a/packages/pluggableWidgets/combobox-web/openspec/changes/fix-combobox-chip-focus-after-removal/tasks.md b/packages/pluggableWidgets/combobox-web/openspec/changes/archive/2026-10-22-fix-combobox-chip-focus-after-removal/tasks.md similarity index 100% rename from packages/pluggableWidgets/combobox-web/openspec/changes/fix-combobox-chip-focus-after-removal/tasks.md rename to packages/pluggableWidgets/combobox-web/openspec/changes/archive/2026-10-22-fix-combobox-chip-focus-after-removal/tasks.md diff --git a/packages/pluggableWidgets/combobox-web/openspec/specs/multiselect-keyboard-interaction/spec.md b/packages/pluggableWidgets/combobox-web/openspec/specs/multiselect-keyboard-interaction/spec.md index 0dc9d9cbd1..723fc8cff0 100644 --- a/packages/pluggableWidgets/combobox-web/openspec/specs/multiselect-keyboard-interaction/spec.md +++ b/packages/pluggableWidgets/combobox-web/openspec/specs/multiselect-keyboard-interaction/spec.md @@ -66,3 +66,32 @@ The single-select Combobox SHALL continue to clear its selection when Backspace - **WHEN** a clearable single-select Combobox has a selected value and the user presses Backspace with an empty filter input - **THEN** the selection is cleared and the placeholder is shown + +### Requirement: Keyboard focus survives chip removal + +When a selected item (chip) is removed with Backspace or Delete while it holds keyboard focus, focus SHALL remain inside the Combobox. Focus SHALL move to the chip that takes the removed chip's position, or to the chip to its left when the removed chip was the last one, matching the item the widget marks as active. When the removed chip was the only one, focus SHALL move to the filter input. Arrow-key navigation SHALL continue from the newly focused chip without the user re-entering the widget. + +#### Scenario: Removing a chip in the middle of the row keeps focus on the row + +- **WHEN** a multi-select Combobox has three or more selected chips, the user has moved keyboard focus to a chip that is neither the first nor the last, and presses Backspace or Delete +- **THEN** that chip is removed and keyboard focus moves to the chip that took its position, so the next ArrowLeft/ArrowRight press continues navigating the chips + +#### Scenario: Removing the first chip keeps focus on the row + +- **WHEN** a multi-select Combobox has two or more selected chips, keyboard focus is on the first chip, and the user presses Backspace or Delete +- **THEN** that chip is removed and keyboard focus moves to the chip that became the first one + +#### Scenario: Removing the last chip keeps focus on the row + +- **WHEN** a multi-select Combobox has two or more selected chips, keyboard focus is on the last chip, and the user presses Backspace or Delete +- **THEN** that chip is removed and keyboard focus moves to the chip on its left + +#### Scenario: Removing the only chip returns focus to the filter input + +- **WHEN** a multi-select Combobox has exactly one selected chip, keyboard focus is on it, and the user presses Backspace or Delete +- **THEN** the chip is removed and keyboard focus moves to the filter input + +#### Scenario: Focus is never dropped to the page + +- **WHEN** a chip is removed by keyboard in any of the situations above +- **THEN** keyboard focus is never left on the document body, so pressing Tab afterwards continues from within the Combobox rather than from the start of the page diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/update-react-datepicker-v9/.openspec.yaml b/packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/archive/2026-10-22-update-react-datepicker-v9/.openspec.yaml similarity index 100% rename from packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/update-react-datepicker-v9/.openspec.yaml rename to packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/archive/2026-10-22-update-react-datepicker-v9/.openspec.yaml diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/update-react-datepicker-v9/design.md b/packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/archive/2026-10-22-update-react-datepicker-v9/design.md similarity index 100% rename from packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/update-react-datepicker-v9/design.md rename to packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/archive/2026-10-22-update-react-datepicker-v9/design.md diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/update-react-datepicker-v9/proposal.md b/packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/archive/2026-10-22-update-react-datepicker-v9/proposal.md similarity index 100% rename from packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/update-react-datepicker-v9/proposal.md rename to packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/archive/2026-10-22-update-react-datepicker-v9/proposal.md diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/update-react-datepicker-v9/specs/date-picker-integration/spec.md b/packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/archive/2026-10-22-update-react-datepicker-v9/specs/date-picker-integration/spec.md similarity index 100% rename from packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/update-react-datepicker-v9/specs/date-picker-integration/spec.md rename to packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/archive/2026-10-22-update-react-datepicker-v9/specs/date-picker-integration/spec.md diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/update-react-datepicker-v9/tasks.md b/packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/archive/2026-10-22-update-react-datepicker-v9/tasks.md similarity index 100% rename from packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/update-react-datepicker-v9/tasks.md rename to packages/pluggableWidgets/datagrid-date-filter-web/openspec/changes/archive/2026-10-22-update-react-datepicker-v9/tasks.md diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/openspec/specs/date-picker-integration/spec.md b/packages/pluggableWidgets/datagrid-date-filter-web/openspec/specs/date-picker-integration/spec.md new file mode 100644 index 0000000000..e8baf00af8 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-date-filter-web/openspec/specs/date-picker-integration/spec.md @@ -0,0 +1,207 @@ +# date-picker-integration Specification + +## Purpose + +TBD - created by archiving change update-react-datepicker-v9. Update Purpose after archive. + +## Requirements + +### Requirement: Calendar popup renders into a widget-owned portal + +The widget SHALL render the calendar popup into a portal container it owns and identifies, so the popup escapes the Data Grid's clipping and scrolling ancestors. The portal container element SHALL carry the same id that the calendar toggle button references via `aria-controls`. + +#### Scenario: Popup escapes grid overflow + +- **WHEN** the filter is placed in a Data Grid header and the user opens the calendar +- **THEN** the calendar is rendered inside the widget's portal container rather than inline in the filter cell +- **AND** the full calendar is visible without being clipped by the grid's overflow + +#### Scenario: Popup positioning survives page scroll + +- **WHEN** the calendar is open and the surrounding page scrolls +- **THEN** the calendar stays anchored below the start of the filter input + +#### Scenario: Popup content is only mounted while expanded + +- **WHEN** the calendar is closed +- **THEN** no calendar content is present in the portal container + +### Requirement: Calendar toggle button exposes accessible expanded state + +The widget SHALL render a calendar toggle button that announces its popup relationship and current expanded state to assistive technology. The button SHALL reflect `aria-expanded` matching whether the calendar is open, reference the portal container through `aria-controls`, declare `aria-haspopup`, and carry a configurable accessible label. + +#### Scenario: Expanded state tracks calendar visibility + +- **WHEN** the user opens the calendar +- **THEN** the toggle button reports `aria-expanded` as true +- **AND** when the calendar closes the button reports `aria-expanded` as false + +#### Scenario: Author-configured screen reader captions are used + +- **WHEN** the widget is configured with a screen reader calendar caption and input caption +- **THEN** the toggle button uses the calendar caption as its accessible name +- **AND** the picker input is labelled by the input caption + +#### Scenario: Default captions when unconfigured + +- **WHEN** no screen reader captions are configured +- **THEN** the toggle button falls back to "Show calendar" +- **AND** the input falls back to a "date filter" label + +### Requirement: Keyboard and pointer activation of the calendar + +The widget SHALL open the calendar and move focus into the picker when the toggle button is activated by pointer or by keyboard. Pointer activation SHALL be handled on mouse-down rather than click to avoid racing the calendar's outside-click dismissal. Keyboard activation SHALL respond to Enter and Space and SHALL suppress the default browser action. + +#### Scenario: Mouse activation opens the calendar + +- **WHEN** the user presses the mouse down on the calendar toggle button while the calendar is closed +- **THEN** the picker receives focus and the calendar opens + +#### Scenario: Mouse-down while already open does not re-focus + +- **WHEN** the user presses the mouse down on the toggle button while the calendar is already open +- **THEN** the widget does not re-issue a focus request + +#### Scenario: Enter and Space open the calendar + +- **WHEN** the toggle button has focus and the user presses Enter or Space +- **THEN** the default action and propagation are suppressed +- **AND** the picker receives focus and the calendar opens + +### Requirement: Single-date and range filter modes drive the picker + +The widget SHALL configure the picker for range selection when the active filter function is `between`, and for single-date selection otherwise. In range mode the picker SHALL receive start and end dates and SHALL offer a clear affordance; in single-date mode it SHALL receive one selected date. When the filter function is `empty` or `notEmpty` the picker SHALL be disabled. + +#### Scenario: Between filter selects a range + +- **WHEN** the active filter function is `between` +- **THEN** the picker operates in range mode with the filter's first and second arguments as start and end dates +- **AND** a clear affordance is available + +#### Scenario: Non-range filter selects a single date + +- **WHEN** the active filter function is any comparison other than `between`, `empty`, or `notEmpty` +- **THEN** the picker operates in single-date mode with the filter's first argument as the selected date + +#### Scenario: Emptiness filters disable the picker + +- **WHEN** the active filter function is `empty` or `notEmpty` +- **THEN** the picker input is disabled + +#### Scenario: Switching filter function refocuses the picker + +- **WHEN** the user changes the filter function from the filter selector +- **THEN** the picker receives focus + +### Requirement: Picker change events map onto filter arguments + +The widget SHALL translate the picker's selection callback into filter argument updates for both selection modes. A single `Date` SHALL set the first filter argument. A start/end pair SHALL set the first and second arguments, mapping absent endpoints to undefined. A null selection SHALL clear both arguments. + +#### Scenario: Single date selection sets one argument + +- **WHEN** the picker reports a single `Date` selection +- **THEN** the filter's first argument is set to that date + +#### Scenario: Range selection sets both arguments + +- **WHEN** the picker reports a start/end pair +- **THEN** the filter's first argument is the start date and the second is the end date +- **AND** a null endpoint becomes undefined + +#### Scenario: Cleared selection resets both arguments + +- **WHEN** the picker reports a null selection +- **THEN** both filter arguments are cleared + +#### Scenario: Calendar stays open after picking a date + +- **WHEN** the user selects a date in the calendar +- **THEN** the calendar remains open so a range can be completed + +### Requirement: Range mode restricts direct text entry + +In range selection mode the widget SHALL suppress raw text input changes on the picker input, and SHALL clear the filter when Backspace is pressed while the input has focus. + +#### Scenario: Typing is suppressed in range mode + +- **WHEN** the picker is in range mode and a raw input change event occurs +- **THEN** the change is prevented + +#### Scenario: Backspace clears a range + +- **WHEN** the picker is in range mode and the user presses Backspace with the input focused +- **THEN** the filter is cleared + +#### Scenario: Typing is allowed in single-date mode + +- **WHEN** the picker is in single-date mode +- **THEN** raw text entry into the input is not suppressed + +### Requirement: Session locale and date format configure the calendar + +The widget SHALL derive the calendar's locale, first day of week, and accepted date formats from the active Mendix session locale. Short day and month tokens SHALL be widened so values with leading zeros parse, uppercase day-of-week tokens SHALL be lowercased to match the picker's format standard, and both the widened and original patterns SHALL be accepted when they differ. Date parsing SHALL be strict. The month and year selectors SHALL be rendered as dropdowns. + +#### Scenario: Session locale registers the calendar locale + +- **WHEN** the session locale language tag matches an available date library locale +- **THEN** that locale is registered and passed to the picker +- **AND** the calendar's first day of week comes from the session locale + +#### Scenario: Short format tokens accept padded input + +- **WHEN** the session date pattern uses single `d` or `M` tokens +- **THEN** the picker accepts both the widened pattern and the original session pattern + +#### Scenario: Uppercase day-of-week token is normalized + +- **WHEN** the session date pattern contains an uppercase `E` +- **THEN** the pattern passed to the picker uses a lowercase `e` + +#### Scenario: Month and year are chosen from dropdowns + +- **WHEN** the calendar is open +- **THEN** month and year are selectable via dropdown selects rather than scroll lists + +### Requirement: Picker stylesheet and class names remain available to the theme + +The widget SHALL depend on the picker's published stylesheet entry point, and the calendar markup SHALL keep the class names that the Data Widgets Atlas theme and the widget's own styles target. The popup wrapper SHALL continue to expose its resolved placement as a data attribute so placement-conditional theme rules apply. + +#### Scenario: Stylesheet import path resolves + +- **WHEN** the widget or its editor preview imports the picker's distributed stylesheet +- **THEN** the import resolves against the installed picker package + +#### Scenario: Theme-targeted class names are present + +- **WHEN** the calendar is rendered +- **THEN** the month select, year select, day, day-name, week, month container, header, and popper wrapper class names targeted by the theme are present + +#### Scenario: Placement is exposed for conditional styling + +- **WHEN** the calendar opens below the input +- **THEN** the popup wrapper exposes a placement data attribute beginning with `bottom` + +### Requirement: Picker dependency stays on a supported, current major + +The widget SHALL depend on a `react-datepicker` major that is actively supported, SHALL rely on the picker's bundled TypeScript types rather than a separate community types package, and SHALL declare a `date-fns` version compatible with the one the picker itself depends on. The picker SHALL NOT pull in a `react-onclickoutside` transitive dependency. + +#### Scenario: Types come from the picker package + +- **WHEN** the widget imports the picker's prop and instance types +- **THEN** those types resolve from the picker package's own type declarations +- **AND** no separate community types package is installed for it + +#### Scenario: Date library versions agree + +- **WHEN** dependencies are installed +- **THEN** the `date-fns` major the widget declares matches the major the picker depends on + +#### Scenario: Removed transitive dependency is absent + +- **WHEN** the dependency tree is inspected +- **THEN** `react-onclickoutside` is not present via the picker + +#### Scenario: React version support is retained + +- **WHEN** the widget is built against the repository's supported React version +- **THEN** the picker's peer range admits that version diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/datagrid-export-events/.openspec.yaml b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-datagrid-export-events/.openspec.yaml similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/datagrid-export-events/.openspec.yaml rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-datagrid-export-events/.openspec.yaml diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/datagrid-export-events/design.md b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-datagrid-export-events/design.md similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/datagrid-export-events/design.md rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-datagrid-export-events/design.md diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/datagrid-export-events/proposal.md b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-datagrid-export-events/proposal.md similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/datagrid-export-events/proposal.md rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-datagrid-export-events/proposal.md diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/datagrid-export-events/specs/export-events/spec.md b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-datagrid-export-events/specs/export-events/spec.md similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/datagrid-export-events/specs/export-events/spec.md rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-datagrid-export-events/specs/export-events/spec.md diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/datagrid-export-events/tasks.md b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-datagrid-export-events/tasks.md similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/datagrid-export-events/tasks.md rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-datagrid-export-events/tasks.md diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/fix-excel-export-date-timezone/.openspec.yaml b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-fix-excel-export-date-timezone/.openspec.yaml similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/fix-excel-export-date-timezone/.openspec.yaml rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-fix-excel-export-date-timezone/.openspec.yaml diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/fix-excel-export-date-timezone/design.md b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-fix-excel-export-date-timezone/design.md similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/fix-excel-export-date-timezone/design.md rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-fix-excel-export-date-timezone/design.md diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/fix-excel-export-date-timezone/proposal.md b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-fix-excel-export-date-timezone/proposal.md similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/fix-excel-export-date-timezone/proposal.md rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-fix-excel-export-date-timezone/proposal.md diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/fix-excel-export-date-timezone/specs/datagrid-excel-export-dates/spec.md b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-fix-excel-export-date-timezone/specs/datagrid-excel-export-dates/spec.md similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/fix-excel-export-date-timezone/specs/datagrid-excel-export-dates/spec.md rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-fix-excel-export-date-timezone/specs/datagrid-excel-export-dates/spec.md diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/fix-excel-export-date-timezone/tasks.md b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-fix-excel-export-date-timezone/tasks.md similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/fix-excel-export-date-timezone/tasks.md rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-fix-excel-export-date-timezone/tasks.md diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/pagination-position-not-honored/.openspec.yaml b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-pagination-position-not-honored/.openspec.yaml similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/pagination-position-not-honored/.openspec.yaml rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-pagination-position-not-honored/.openspec.yaml diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/pagination-position-not-honored/design.md b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-pagination-position-not-honored/design.md similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/pagination-position-not-honored/design.md rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-pagination-position-not-honored/design.md diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/pagination-position-not-honored/proposal.md b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-pagination-position-not-honored/proposal.md similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/pagination-position-not-honored/proposal.md rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-pagination-position-not-honored/proposal.md diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/pagination-position-not-honored/specs/datagrid-custom-pagination-position/spec.md b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-pagination-position-not-honored/specs/datagrid-custom-pagination-position/spec.md similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/pagination-position-not-honored/specs/datagrid-custom-pagination-position/spec.md rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-pagination-position-not-honored/specs/datagrid-custom-pagination-position/spec.md diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/pagination-position-not-honored/tasks.md b/packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-pagination-position-not-honored/tasks.md similarity index 100% rename from packages/pluggableWidgets/datagrid-web/openspec/changes/pagination-position-not-honored/tasks.md rename to packages/pluggableWidgets/datagrid-web/openspec/changes/archive/2026-10-22-pagination-position-not-honored/tasks.md diff --git a/packages/pluggableWidgets/datagrid-web/openspec/specs/datagrid-custom-pagination-position/spec.md b/packages/pluggableWidgets/datagrid-web/openspec/specs/datagrid-custom-pagination-position/spec.md new file mode 100644 index 0000000000..664cf1b4f0 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-web/openspec/specs/datagrid-custom-pagination-position/spec.md @@ -0,0 +1,61 @@ +# datagrid-custom-pagination-position Specification + +## Purpose + +TBD - created by archiving change pagination-position-not-honored. Update Purpose after archive. + +## Requirements + +### Requirement: Custom pagination honors the position setting + +When custom pagination is enabled, Data Grid 2 SHALL render the custom pagination widgets according to the `pagingPosition` property ("Above grid" / "Below grid" / "Both") instead of always rendering them in the footer. + +#### Scenario: Position set to "Above grid" + +- **WHEN** custom pagination is enabled and `pagingPosition` is `"top"` +- **THEN** the custom pagination widgets render in the top bar +- **AND** the custom pagination widgets do not render in the footer + +#### Scenario: Position set to "Below grid" + +- **WHEN** custom pagination is enabled and `pagingPosition` is `"bottom"` +- **THEN** the custom pagination widgets render in the footer +- **AND** the custom pagination widgets do not render in the top bar + +#### Scenario: Position set to "Both" + +- **WHEN** custom pagination is enabled and `pagingPosition` is `"both"` +- **THEN** the custom pagination widgets render exactly once, in the footer +- **AND** the custom pagination widgets do not also render in the top bar + +### Requirement: Editor preview matches runtime placement + +Studio Pro's editor preview for Data Grid 2 SHALL place the custom pagination placeholder using the same `pagingPosition` rule as the runtime, so the page editor never disagrees with the running app. + +#### Scenario: Preview reflects "Above grid" + +- **WHEN** custom pagination is enabled and `pagingPosition` is `"top"` in the page editor +- **THEN** the custom pagination placeholder renders above the grid preview + +#### Scenario: Preview reflects "Below grid" or "Both" + +- **WHEN** custom pagination is enabled and `pagingPosition` is `"bottom"` or `"both"` in the page editor +- **THEN** the custom pagination placeholder renders below the grid preview, exactly once + +### Requirement: Position property remains configurable with custom pagination + +The `pagingPosition` property SHALL remain visible and editable in the Studio Pro properties panel when custom pagination is enabled, since the property now affects rendering. + +#### Scenario: Custom pagination enabled + +- **WHEN** a developer enables custom pagination on a Data Grid 2 configured with `pagination` set to `"buttons"` +- **THEN** the "Position of pagination" property remains visible and editable in the properties panel + +### Requirement: Design-time warning for "Both" with custom pagination + +Data Grid 2's consistency check SHALL emit a warning when custom pagination is enabled and `pagingPosition` is `"both"`, explaining that the widgets render once, below the grid. + +#### Scenario: Custom pagination with "Both" position configured + +- **WHEN** a developer enables custom pagination and sets `pagingPosition` to `"both"` +- **THEN** Studio Pro shows a warning on the `pagingPosition` property explaining that custom pagination renders once, below the grid, and suggesting a single position instead diff --git a/packages/pluggableWidgets/datagrid-web/openspec/specs/datagrid-excel-export-dates/spec.md b/packages/pluggableWidgets/datagrid-web/openspec/specs/datagrid-excel-export-dates/spec.md new file mode 100644 index 0000000000..4d2eaa13aa --- /dev/null +++ b/packages/pluggableWidgets/datagrid-web/openspec/specs/datagrid-excel-export-dates/spec.md @@ -0,0 +1,69 @@ +# datagrid-excel-export-dates Specification + +## Purpose + +TBD - created by archiving change fix-excel-export-date-timezone. Update Purpose after archive. + +## Requirements + +### Requirement: An exported date cell carries the wall clock the grid displays + +When a Data Grid 2 column holding a date value is exported to Excel, the resulting cell SHALL represent the same wall clock the grid renders for that value. The session's UTC offset SHALL NOT appear in the exported cell, whether as a shifted calendar day or as a time component the grid does not show. + +This SHALL hold for every session UTC offset, including offsets that are not a whole number of hours, and for values on either side of a daylight-saving transition. Whether the attribute is configured as localized or non-localized SHALL NOT affect the exported wall clock. + +#### Scenario: Midnight value with a date-only export format + +- **WHEN** an attribute column whose value the grid renders as `1/1/2007` is exported with the export format `dd-MMM-yyyy` +- **THEN** the exported cell holds `01-Jan-2007`, on the same calendar day the grid shows, and carries no time component + +#### Scenario: Midnight value across a daylight-saving boundary + +- **WHEN** values the grid renders as `3/30/2004` and `9/3/2012` — one in each DST state for the session — are exported with the export format `dd-MMM-yyyy` +- **THEN** both export on their own calendar day, `30-Mar-2004` and `03-Sep-2012`, with no dependence on which offset was in effect + +#### Scenario: Non-midnight value with a date-only export format + +- **WHEN** a value the grid renders as `1/1/2007 14:35` is exported with the export format `dd-MMM-yyyy` +- **THEN** the exported cell holds `01-Jan-2007` — the time is dropped, and the calendar day is the one the grid shows + +#### Scenario: Value with a time-bearing export format + +- **WHEN** a value the grid renders as `1/1/2007 14:35` is exported with the export format `dd-MMM-yyyy hh:mm` +- **THEN** the exported cell holds `01-Jan-2007 14:35` — the displayed time is preserved exactly, not shifted by the session offset + +#### Scenario: Default export type on a date attribute + +- **WHEN** a date attribute column is exported with export type `Default`, so the format is taken from the attribute's own formatter +- **THEN** the exported cell is on the calendar day the grid shows, on the same terms as an explicit date export format + +### Requirement: Custom content date strings export on the day the string names + +When a column with custom content is exported with export type `Date`, the exported cell SHALL represent the wall clock named by the export value string, regardless of how ECMAScript resolves that string's timezone. + +A string carrying an explicit zone, and a date-only ISO string (which ECMAScript defines as UTC), SHALL be taken at the instant it resolves to. Any other string — which ECMAScript parses in the browser's local time — SHALL be re-anchored so its named wall clock survives into the cell. + +#### Scenario: Zoneless date and time string + +- **WHEN** the export value is `2007-01-01T00:00:00` and the export format is `dd-MMM-yyyy` +- **THEN** the exported cell holds `01-Jan-2007` + +#### Scenario: Date-only ISO string + +- **WHEN** the export value is `2007-01-01` and the export format is `dd-MMM-yyyy` +- **THEN** the exported cell holds `01-Jan-2007`, including in sessions at a negative UTC offset + +#### Scenario: String with an explicit zone + +- **WHEN** the export value is `2007-01-01T00:00:00Z` and the export format is `dd-MMM-yyyy` +- **THEN** the exported cell holds `01-Jan-2007` + +#### Scenario: Locale-style date string + +- **WHEN** the export value is `1/1/2007` and the export format is `dd-MMM-yyyy` +- **THEN** the exported cell holds `01-Jan-2007` + +#### Scenario: Unparseable string + +- **WHEN** the export value cannot be parsed as a date +- **THEN** the value is exported as a text cell, unchanged, rather than as a date cell diff --git a/packages/pluggableWidgets/datagrid-web/openspec/specs/export-events/spec.md b/packages/pluggableWidgets/datagrid-web/openspec/specs/export-events/spec.md new file mode 100644 index 0000000000..6dcc9c2496 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-web/openspec/specs/export-events/spec.md @@ -0,0 +1,110 @@ +# export-events Specification + +## Purpose + +TBD - created by archiving change datagrid-export-events. Update Purpose after archive. + +## Requirements + +### Requirement: onBeforeExport action fires before export starts + +The widget SHALL expose an optional `onBeforeExport` action property. When configured, the widget MUST call `onBeforeExport.execute(args)` once, fire-and-forget, immediately before the first datasource page fetch of an export operation. + +The action MUST receive the following variables: + +- `gridName` (String) — the Studio Pro widget name (`props.name`) +- `columnTitles` (String) — comma-separated header captions of the visible, exported columns in their current display order (e.g. `"First name,Last Name,Date of Birth"`). Columns hidden by the user SHALL NOT be included. +- `chunkSize` (Integer) — the effective number of rows fetched per datasource request during the export (`Math.max(requestedLimit, 10)`). +- `fileName` (String) — the target file name for the export (e.g. `"export.xlsx"`), as provided by the export caller. SHALL be an empty string when not provided. +- `sheetName` (String) — the target sheet/tab name within the export file (e.g. `"Sheet1"`), as provided by the export caller. SHALL be an empty string when not provided. +- `startTime` (DateTime) — the timestamp captured immediately before `req.send()` is called. + +The action execution MUST NOT block or delay the export flow. + +#### Scenario: onBeforeExport fires with correct variables on normal export + +- **WHEN** a configured `onBeforeExport` action exists and `canExecute` is true +- **AND** an export is triggered on the grid +- **THEN** `onBeforeExport.execute` is called once with `gridName`, `columnTitles`, `chunkSize`, `fileName`, `sheetName`, and `startTime` before any datasource page is fetched + +#### Scenario: onBeforeExport is skipped when not configured + +- **WHEN** `onBeforeExport` is not configured (optional property absent) +- **AND** an export is triggered +- **THEN** the export proceeds normally with no errors + +#### Scenario: onBeforeExport columnTitles excludes hidden columns + +- **WHEN** the user has hidden one or more columns +- **AND** an export is triggered +- **THEN** `columnTitles` contains only the headers of the currently visible, exported columns + +--- + +### Requirement: onAfterExport action fires after export completes + +The widget SHALL expose an optional `onAfterExport` action property. When configured, the widget MUST call `onAfterExport.execute(args)` once, fire-and-forget, after the export request resolves — whether it completed successfully or was aborted by the user. + +The action MUST receive the following variables: + +- `gridName` (String) — same as `onBeforeExport.gridName` +- `columnTitles` (String) — same as `onBeforeExport.columnTitles` +- `chunkSize` (Integer) — same as `onBeforeExport.chunkSize` +- `fileName` (String) — same as `onBeforeExport.fileName` +- `sheetName` (String) — same as `onBeforeExport.sheetName` +- `exportedItemCount` (Integer) — total number of rows actually streamed to the export handler before the request ended +- `status` (String) — `"success"` if all rows were exported; `"aborted"` if the user cancelled mid-export +- `startTime` (DateTime) — the same timestamp passed to `onBeforeExport` (enables duration calculation in a single microflow) +- `endTime` (DateTime) — the timestamp captured after the export request's `loadend` event fires + +#### Scenario: onAfterExport fires with success status after complete export + +- **WHEN** `onAfterExport` is configured and `canExecute` is true +- **AND** the export completes without interruption +- **THEN** `onAfterExport.execute` is called once with `status` equal to `"success"` and `exportedItemCount` equal to the total rows streamed + +#### Scenario: onAfterExport fires with aborted status when user cancels + +- **WHEN** the user clicks cancel on the export progress dialog mid-export +- **THEN** `onAfterExport.execute` is called once with `status` equal to `"aborted"` and `exportedItemCount` equal to the number of rows streamed before cancellation + +#### Scenario: onAfterExport is skipped when not configured + +- **WHEN** `onAfterExport` is not configured +- **AND** an export completes or is aborted +- **THEN** no error occurs and the export lifecycle completes normally + +#### Scenario: onAfterExport startTime matches onBeforeExport startTime + +- **WHEN** both `onBeforeExport` and `onAfterExport` are configured +- **AND** an export runs to completion +- **THEN** the `startTime` value in `onAfterExport` is identical to the `startTime` value in `onBeforeExport` + +#### Scenario: onAfterExport endTime is after startTime + +- **WHEN** `onAfterExport` fires after a completed export +- **THEN** `endTime` is greater than or equal to `startTime` + +--- + +### Requirement: Both export event actions are optional and independent + +The widget SHALL allow `onBeforeExport` and `onAfterExport` to be configured independently. Configuring one MUST NOT require configuring the other. + +#### Scenario: Only onBeforeExport configured + +- **WHEN** `onBeforeExport` is configured and `onAfterExport` is not +- **AND** an export runs to completion +- **THEN** `onBeforeExport` fires once and no error occurs for the missing `onAfterExport` + +#### Scenario: Only onAfterExport configured + +- **WHEN** `onAfterExport` is configured and `onBeforeExport` is not +- **AND** an export runs to completion +- **THEN** `onAfterExport` fires once and no error occurs for the missing `onBeforeExport` + +#### Scenario: Neither action configured + +- **WHEN** neither `onBeforeExport` nor `onAfterExport` is configured +- **AND** an export runs +- **THEN** the export behaves identically to before this feature was introduced diff --git a/packages/pluggableWidgets/file-uploader-web/openspec/changes/file-uploader-live-region/.openspec.yaml b/packages/pluggableWidgets/file-uploader-web/openspec/changes/archive/2026-10-22-file-uploader-live-region/.openspec.yaml similarity index 100% rename from packages/pluggableWidgets/file-uploader-web/openspec/changes/file-uploader-live-region/.openspec.yaml rename to packages/pluggableWidgets/file-uploader-web/openspec/changes/archive/2026-10-22-file-uploader-live-region/.openspec.yaml diff --git a/packages/pluggableWidgets/file-uploader-web/openspec/changes/file-uploader-live-region/design.md b/packages/pluggableWidgets/file-uploader-web/openspec/changes/archive/2026-10-22-file-uploader-live-region/design.md similarity index 100% rename from packages/pluggableWidgets/file-uploader-web/openspec/changes/file-uploader-live-region/design.md rename to packages/pluggableWidgets/file-uploader-web/openspec/changes/archive/2026-10-22-file-uploader-live-region/design.md diff --git a/packages/pluggableWidgets/file-uploader-web/openspec/changes/file-uploader-live-region/proposal.md b/packages/pluggableWidgets/file-uploader-web/openspec/changes/archive/2026-10-22-file-uploader-live-region/proposal.md similarity index 100% rename from packages/pluggableWidgets/file-uploader-web/openspec/changes/file-uploader-live-region/proposal.md rename to packages/pluggableWidgets/file-uploader-web/openspec/changes/archive/2026-10-22-file-uploader-live-region/proposal.md diff --git a/packages/pluggableWidgets/file-uploader-web/openspec/changes/file-uploader-live-region/specs/file-uploader-status-announcements/spec.md b/packages/pluggableWidgets/file-uploader-web/openspec/changes/archive/2026-10-22-file-uploader-live-region/specs/file-uploader-status-announcements/spec.md similarity index 100% rename from packages/pluggableWidgets/file-uploader-web/openspec/changes/file-uploader-live-region/specs/file-uploader-status-announcements/spec.md rename to packages/pluggableWidgets/file-uploader-web/openspec/changes/archive/2026-10-22-file-uploader-live-region/specs/file-uploader-status-announcements/spec.md diff --git a/packages/pluggableWidgets/file-uploader-web/openspec/changes/file-uploader-live-region/tasks.md b/packages/pluggableWidgets/file-uploader-web/openspec/changes/archive/2026-10-22-file-uploader-live-region/tasks.md similarity index 100% rename from packages/pluggableWidgets/file-uploader-web/openspec/changes/file-uploader-live-region/tasks.md rename to packages/pluggableWidgets/file-uploader-web/openspec/changes/archive/2026-10-22-file-uploader-live-region/tasks.md diff --git a/packages/pluggableWidgets/file-uploader-web/openspec/specs/file-uploader-status-announcements/spec.md b/packages/pluggableWidgets/file-uploader-web/openspec/specs/file-uploader-status-announcements/spec.md new file mode 100644 index 0000000000..0412d981b5 --- /dev/null +++ b/packages/pluggableWidgets/file-uploader-web/openspec/specs/file-uploader-status-announcements/spec.md @@ -0,0 +1,111 @@ +# file-uploader-status-announcements Specification + +## Purpose + +TBD - created by archiving change file-uploader-live-region. Update Purpose after archive. + +## Requirements + +### Requirement: Single polite live region + +The File Uploader SHALL render exactly one live region per widget instance, as an element with `role="status"`, `aria-live="polite"` and `aria-atomic="true"`. The region SHALL be visually hidden but exposed to assistive technology. The region SHALL be present in the DOM from the first render, including before any file is added, and SHALL be rendered in read-only mode as well. File entries SHALL NOT declare their own `aria-live` regions. + +#### Scenario: Region present on initial render + +- **WHEN** the File Uploader is rendered with no files +- **THEN** exactly one element with `role="status"` exists inside the widget +- **AND** it has `aria-live="polite"` and `aria-atomic="true"` +- **AND** its text content is empty + +#### Scenario: No per-entry live regions + +- **WHEN** the file list contains several entries in any status +- **THEN** no file entry or descendant of a file entry has an `aria-live` attribute + +### Requirement: Upload success is announced + +When a file finishes uploading successfully, the live region SHALL contain the configured **Uploading success** text (`uploadSuccessMessage`). + +#### Scenario: Single successful upload + +- **WHEN** the user adds a file and its upload completes successfully +- **THEN** the live region text equals the configured `uploadSuccessMessage` value + +### Requirement: Upload failure is announced + +When a file upload fails (object creation fails or file content cannot be saved), the live region SHALL contain the configured **Uploading unknown error** text (`uploadFailureGenericMessage`). + +#### Scenario: Upload content save fails + +- **WHEN** the user adds a file and saving its content fails +- **THEN** the live region text equals the configured `uploadFailureGenericMessage` value + +#### Scenario: Object creation fails + +- **WHEN** the user adds a file and the create-object action fails or times out +- **THEN** the live region text equals the configured `uploadFailureGenericMessage` value + +### Requirement: Rejection due to file limit is announced + +When one or more dropped files are rejected because the maximum file count is reached, the live region SHALL contain the configured **File limit reached** text (`uploadLimitReachedMessage`) with the maximum file count substituted. The announcement SHALL be made once per drop, regardless of how many files were rejected in that drop. + +#### Scenario: Drop exceeds the file limit + +- **WHEN** the maximum file count is 2, one file is already active, and the user drops 3 files +- **THEN** the live region text equals `uploadLimitReachedMessage` with `###` replaced by `2` + +#### Scenario: Drop within the limit + +- **WHEN** the user drops files that all fit within the file limit +- **THEN** no rejection announcement is made + +### Requirement: File removal is announced using File removal success + +When a file entry is removed from the list because its object was removed, the live region SHALL contain the configured **File removal success** text (`removeSuccessMessage`). This SHALL apply both to removal through the default remove button and to removal of an uploaded or existing file's object by any other means that makes it disappear from the data source (for example a custom button action that deletes the object). The `removeSuccessMessage` text SHALL NOT be rendered visually in the file list. + +#### Scenario: Remove with default remove button + +- **WHEN** the user removes an uploaded file with the default remove button and the removal succeeds +- **THEN** the entry disappears from the file list +- **AND** the live region text equals the configured `removeSuccessMessage` value + +#### Scenario: Object removed through the data source + +- **WHEN** an uploaded or existing file's object disappears from the data source +- **THEN** the entry disappears from the file list +- **AND** the live region text equals the configured `removeSuccessMessage` value + +#### Scenario: Removal fails + +- **WHEN** the user clicks the default remove button and the object cannot be removed +- **THEN** the entry stays in the list +- **AND** no removal announcement is made + +#### Scenario: Removal text is not shown visually + +- **WHEN** any file in the list is in any status +- **THEN** the configured `removeSuccessMessage` text does not appear in the visible file list + +### Requirement: Failed upload stays a failure after its object disappears + +When a file whose upload failed has its object disappear from the data source, the file entry SHALL keep displaying the upload failure message and SHALL NOT switch to a removal message. + +#### Scenario: Failed upload object is cleaned up + +- **WHEN** a file is in the upload-failed state and its object disappears from the data source +- **THEN** the entry still shows the configured `uploadFailureGenericMessage` +- **AND** no removal announcement is made + +### Requirement: Announcements are events, not progress + +The live region SHALL be updated only for the events listed in this capability. Upload progress, queuing, adding files to the list, and file count totals SHALL NOT be announced. Each event SHALL replace the previous announcement, and a repeated event with the same text SHALL still be announced again. + +#### Scenario: Progress is not announced + +- **WHEN** a file is queued and then uploading +- **THEN** the live region text does not change + +#### Scenario: Same message twice in a row + +- **WHEN** two files finish uploading successfully one after the other +- **THEN** the live region content changes on each completion so assistive technology announces both diff --git a/packages/pluggableWidgets/gallery-web/openspec/changes/fix-gallery-pagination-placement/.openspec.yaml b/packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-gallery-pagination-placement/.openspec.yaml similarity index 100% rename from packages/pluggableWidgets/gallery-web/openspec/changes/fix-gallery-pagination-placement/.openspec.yaml rename to packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-gallery-pagination-placement/.openspec.yaml diff --git a/packages/pluggableWidgets/gallery-web/openspec/changes/fix-gallery-pagination-placement/design.md b/packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-gallery-pagination-placement/design.md similarity index 100% rename from packages/pluggableWidgets/gallery-web/openspec/changes/fix-gallery-pagination-placement/design.md rename to packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-gallery-pagination-placement/design.md diff --git a/packages/pluggableWidgets/gallery-web/openspec/changes/fix-gallery-pagination-placement/proposal.md b/packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-gallery-pagination-placement/proposal.md similarity index 100% rename from packages/pluggableWidgets/gallery-web/openspec/changes/fix-gallery-pagination-placement/proposal.md rename to packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-gallery-pagination-placement/proposal.md diff --git a/packages/pluggableWidgets/gallery-web/openspec/changes/fix-gallery-pagination-placement/specs/gallery-custom-pagination-position/spec.md b/packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-gallery-pagination-placement/specs/gallery-custom-pagination-position/spec.md similarity index 100% rename from packages/pluggableWidgets/gallery-web/openspec/changes/fix-gallery-pagination-placement/specs/gallery-custom-pagination-position/spec.md rename to packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-gallery-pagination-placement/specs/gallery-custom-pagination-position/spec.md diff --git a/packages/pluggableWidgets/gallery-web/openspec/changes/fix-gallery-pagination-placement/specs/gallery-pagination-placement/spec.md b/packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-gallery-pagination-placement/specs/gallery-pagination-placement/spec.md similarity index 100% rename from packages/pluggableWidgets/gallery-web/openspec/changes/fix-gallery-pagination-placement/specs/gallery-pagination-placement/spec.md rename to packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-gallery-pagination-placement/specs/gallery-pagination-placement/spec.md diff --git a/packages/pluggableWidgets/gallery-web/openspec/changes/fix-gallery-pagination-placement/tasks.md b/packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-gallery-pagination-placement/tasks.md similarity index 100% rename from packages/pluggableWidgets/gallery-web/openspec/changes/fix-gallery-pagination-placement/tasks.md rename to packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-gallery-pagination-placement/tasks.md diff --git a/packages/pluggableWidgets/gallery-web/openspec/changes/fix-stale-sort-order-attribute/.openspec.yaml b/packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-stale-sort-order-attribute/.openspec.yaml similarity index 100% rename from packages/pluggableWidgets/gallery-web/openspec/changes/fix-stale-sort-order-attribute/.openspec.yaml rename to packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-stale-sort-order-attribute/.openspec.yaml diff --git a/packages/pluggableWidgets/gallery-web/openspec/changes/fix-stale-sort-order-attribute/design.md b/packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-stale-sort-order-attribute/design.md similarity index 100% rename from packages/pluggableWidgets/gallery-web/openspec/changes/fix-stale-sort-order-attribute/design.md rename to packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-stale-sort-order-attribute/design.md diff --git a/packages/pluggableWidgets/gallery-web/openspec/changes/fix-stale-sort-order-attribute/proposal.md b/packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-stale-sort-order-attribute/proposal.md similarity index 100% rename from packages/pluggableWidgets/gallery-web/openspec/changes/fix-stale-sort-order-attribute/proposal.md rename to packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-stale-sort-order-attribute/proposal.md diff --git a/packages/pluggableWidgets/gallery-web/openspec/changes/fix-stale-sort-order-attribute/specs/gallery-sort-order-validation/spec.md b/packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-stale-sort-order-attribute/specs/gallery-sort-order-validation/spec.md similarity index 100% rename from packages/pluggableWidgets/gallery-web/openspec/changes/fix-stale-sort-order-attribute/specs/gallery-sort-order-validation/spec.md rename to packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-stale-sort-order-attribute/specs/gallery-sort-order-validation/spec.md diff --git a/packages/pluggableWidgets/gallery-web/openspec/changes/fix-stale-sort-order-attribute/tasks.md b/packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-stale-sort-order-attribute/tasks.md similarity index 100% rename from packages/pluggableWidgets/gallery-web/openspec/changes/fix-stale-sort-order-attribute/tasks.md rename to packages/pluggableWidgets/gallery-web/openspec/changes/archive/2026-10-22-fix-stale-sort-order-attribute/tasks.md diff --git a/packages/pluggableWidgets/gallery-web/openspec/specs/gallery-custom-pagination-position/spec.md b/packages/pluggableWidgets/gallery-web/openspec/specs/gallery-custom-pagination-position/spec.md new file mode 100644 index 0000000000..09c445c484 --- /dev/null +++ b/packages/pluggableWidgets/gallery-web/openspec/specs/gallery-custom-pagination-position/spec.md @@ -0,0 +1,56 @@ +# gallery-custom-pagination-position Specification + +## Purpose + +TBD - created by archiving change fix-gallery-pagination-placement. Update Purpose after archive. + +## Requirements + +### Requirement: Custom pagination honours the configured pagination position + +When custom pagination is enabled, the custom pagination widgets SHALL render in the bar named by `Position of pagination` — the top bar for `Above grid`, the footer for `Below grid` — rather than always in the footer. + +#### Scenario: Custom pagination above the gallery + +- **WHEN** custom pagination is enabled and `Position of pagination` is `Above grid` +- **THEN** the custom pagination widgets render in the top bar and not in the footer + +#### Scenario: Custom pagination below the gallery + +- **WHEN** custom pagination is enabled and `Position of pagination` is `Below grid` +- **THEN** the custom pagination widgets render in the footer and not in the top bar + +### Requirement: Custom pagination renders once when both positions are requested + +When custom pagination is enabled and `Position of pagination` is `Both`, the widget SHALL render the custom pagination widgets exactly once, in the footer, so that the configured widget instances are not duplicated across two bars. + +Studio Pro SHALL surface this limitation at design time as a warning on the widget, explaining that custom pagination cannot be shown in both positions and will render below the gallery. + +#### Scenario: Both positions requested with custom pagination + +- **WHEN** custom pagination is enabled and `Position of pagination` is `Both` +- **THEN** the custom pagination widgets render once in the footer, and the top bar contains no custom pagination + +#### Scenario: Design-time warning for the unsupported combination + +- **WHEN** a page configures custom pagination together with `Position of pagination` set to `Both` +- **THEN** Studio Pro reports a warning on the Gallery widget describing that the widgets will render below the gallery + +#### Scenario: No warning for supported combinations + +- **WHEN** custom pagination is enabled and `Position of pagination` is `Above grid` or `Below grid` +- **THEN** no such warning is reported + +### Requirement: Editor preview agrees with runtime on custom pagination position + +The widget's editor preview SHALL place custom pagination in the same bar the running app would use for the same configuration, including for `Both`. + +#### Scenario: Preview and runtime agree for Above grid + +- **WHEN** custom pagination is enabled with `Position of pagination` set to `Above grid` +- **THEN** the page editor shows the custom pagination placeholder in the top bar, matching the running app + +#### Scenario: Preview and runtime agree for Both + +- **WHEN** custom pagination is enabled with `Position of pagination` set to `Both` +- **THEN** the page editor shows a single custom pagination placeholder in the footer, matching the running app diff --git a/packages/pluggableWidgets/gallery-web/openspec/specs/gallery-pagination-placement/spec.md b/packages/pluggableWidgets/gallery-web/openspec/specs/gallery-pagination-placement/spec.md new file mode 100644 index 0000000000..e698dc5b52 --- /dev/null +++ b/packages/pluggableWidgets/gallery-web/openspec/specs/gallery-pagination-placement/spec.md @@ -0,0 +1,112 @@ +# gallery-pagination-placement Specification + +## Purpose + +TBD - created by archiving change fix-gallery-pagination-placement. Update Purpose after archive. + +## Requirements + +### Requirement: Pagination renders in the bar slot named by its alignment + +The Gallery widget SHALL derive a pagination alignment of `left`, `center` or `right` and SHALL render the pagination control in the corresponding slot of the bar it occupies — start slot for `left`, middle slot for `center`, end slot for `right`. This SHALL apply to the top bar and the footer alike, so that a widget configured with `Position of pagination` set to `Both` aligns both bars identically. + +When no alignment is configured, the widget SHALL behave as if `right` were selected, preserving the position pagination has today. + +#### Scenario: Left alignment places pagination in the start slot + +- **WHEN** the pagination alignment is `left` and the pagination control is visible +- **THEN** the pagination control renders in the bar's start slot + +#### Scenario: Center alignment places pagination in the middle slot + +- **WHEN** the pagination alignment is `center` and the pagination control is visible +- **THEN** the pagination control renders in the bar's middle slot, including in the top bar, which SHALL provide a middle slot for this purpose + +#### Scenario: Right alignment and no alignment both place pagination in the end slot + +- **WHEN** the pagination alignment is `right`, or no pagination alignment class is present on the widget +- **THEN** the pagination control renders in the bar's end slot + +#### Scenario: Alignment applies to both bars + +- **WHEN** `Position of pagination` is `Both` and the pagination alignment is `center` +- **THEN** the pagination control is centred in the top bar and in the footer + +### Requirement: Elements of a claimed slot are displaced to the end slot + +When the pagination control claims a slot that another element would otherwise use, that element SHALL be rendered in the end slot instead. The selection counter's natural slot is the start slot and the Load-more button's natural slot is the middle slot; each SHALL stay there unless pagination claims it. + +The widget SHALL NOT wrap the bar onto an additional row to resolve such a collision, so that the bar's height does not change when the selection counter appears or disappears. + +#### Scenario: Left alignment displaces the selection counter + +- **WHEN** the pagination alignment is `left`, the selection counter is visible in this bar, and the pagination control is visible +- **THEN** pagination renders in the start slot and the selection counter renders in the end slot, on the same row + +#### Scenario: Center alignment displaces the Load-more button + +- **WHEN** the pagination alignment is `center`, pagination is set to Load more with more items available, and the paging status is visible +- **THEN** the paging status renders in the middle slot and the Load-more button renders in the end slot + +#### Scenario: All three elements present + +- **WHEN** the pagination alignment is `left`, the selection counter is visible, and the Load-more button is visible +- **THEN** pagination renders in the start slot, the Load-more button stays in the middle slot, and the selection counter renders in the end slot + +#### Scenario: Right alignment displaces nothing + +- **WHEN** the pagination alignment is `right` and both the selection counter and the Load-more button are visible +- **THEN** the selection counter renders in the start slot, the Load-more button in the middle slot, and pagination in the end slot + +#### Scenario: Pagination not visible + +- **WHEN** the pagination control is not visible — for example virtual scrolling without a total count — regardless of alignment +- **THEN** the selection counter renders in its start slot and the Load-more button in its middle slot, and no slot is reserved for pagination + +### Requirement: Custom pagination is positioned as the pagination control + +When custom pagination is enabled, the custom pagination widgets SHALL take the place of the built-in pagination control for placement purposes, occupying the slot named by the pagination alignment and displacing elements of that slot in the same way. The built-in pagination bar SHALL NOT render alongside them. + +#### Scenario: Custom pagination follows the alignment + +- **WHEN** custom pagination is enabled and the pagination alignment is `center` +- **THEN** the custom pagination widgets render in the middle slot and the built-in pagination bar does not render + +### Requirement: Placement is computed once and shared by runtime and editor preview + +Slot placement SHALL be derived from a single pure function of the alignment and of which elements are visible, and that same function SHALL drive the footer, the top bar and the widget's editor preview, so the page editor and the running app cannot disagree about placement. + +#### Scenario: Editor preview matches runtime placement + +- **WHEN** a given alignment and set of visible elements is rendered in Studio Pro's page editor and in the running app +- **THEN** both place every element in the same slot + +### Requirement: Visual order and focus order agree + +The rendered document order SHALL follow the visual order of the slots — start, then middle, then end — so that keyboard focus order and assistive-technology reading order match what is seen on screen for every alignment. + +#### Scenario: Left-aligned pagination is reached first by keyboard + +- **WHEN** the pagination alignment is `left`, the selection counter is displaced to the end slot, and the user tabs through the bar +- **THEN** focus reaches the pagination controls before the Clear-selection button + +### Requirement: Alignment is read from the pagination alignment design property + +The widget SHALL determine its pagination alignment from the design-property classes applied to its root element — `widget-gallery-pagination-left`, `widget-gallery-pagination-center` and `widget-gallery-pagination-right` — and SHALL react to changes to those classes without remounting, so that selecting a value in Studio Pro's Design mode updates placement immediately. + +Class names not among those three SHALL be ignored for alignment purposes. + +#### Scenario: Alignment class recognised + +- **WHEN** the widget root carries `widget-gallery-pagination-center` among its classes +- **THEN** the widget resolves its alignment as `center` + +#### Scenario: Unrelated classes ignored + +- **WHEN** the widget root carries only unrelated classes such as `widget-gallery-striped` +- **THEN** the widget resolves its alignment as `right` + +#### Scenario: Design mode edit takes effect without remount + +- **WHEN** the alignment class on the root changes while the widget is mounted +- **THEN** placement updates to the newly named slot diff --git a/packages/pluggableWidgets/gallery-web/openspec/specs/gallery-sort-order-validation/spec.md b/packages/pluggableWidgets/gallery-web/openspec/specs/gallery-sort-order-validation/spec.md new file mode 100644 index 0000000000..4da49de334 --- /dev/null +++ b/packages/pluggableWidgets/gallery-web/openspec/specs/gallery-sort-order-validation/spec.md @@ -0,0 +1,37 @@ +# gallery-sort-order-validation Specification + +## Purpose + +TBD - created by archiving change fix-stale-sort-order-attribute. Update Purpose after archive. + +## Requirements + +### Requirement: Applying a restored sort order never surfaces an uncaught reaction error + +When the Gallery widget forwards a restored (persisted or props-provided) sort order to the datasource, a rejection by the Mendix runtime — such as a sort instruction whose attribute id is no longer valid for the current app build — SHALL NOT escape as an uncaught MobX reaction exception. The widget SHALL catch the rejection and recover to the datasource's default sort order so it remains usable. + +This guard SHALL apply on the path every sort order takes into the datasource, regardless of whether a sort widget (DropdownSort) is configured — i.e. it SHALL NOT depend on a `SortOrderStore` being instantiated. + +#### Scenario: Restored sort order contains a stale attribute id + +- **WHEN** the widget is initialized with a `datasource.sortOrder` restored from a previous session/run that references an attribute id no longer present in the current app build +- **THEN** the runtime rejection is caught, the query is set to the default (unsorted) order, and no uncaught reaction exception is surfaced + +#### Scenario: Restored sort order contains only currently valid attribute ids + +- **WHEN** the widget is initialized with a `datasource.sortOrder` where every attribute id is valid for the current build +- **THEN** the full sort order is applied to the datasource unchanged, preserving order and direction + +#### Scenario: No sort widget configured + +- **WHEN** the widget has no DropdownSort configured (so no `SortOrderStore` exists) and the props-provided sort order references a stale attribute id +- **THEN** the guard still catches the runtime rejection and recovers to the default order, because the guard sits on the shared forwarding path rather than in the sort store + +### Requirement: Sort-order fallback is diagnosable, not silent + +When the widget recovers from a rejected sort order, it SHALL emit a `console.warn` explaining that the stored sort order was ignored because it references an attribute that is no longer available. This warning SHALL fire only on the rejection/fallback path, not during normal sort application. + +#### Scenario: Fallback emits a warning + +- **WHEN** a restored sort order is rejected by the runtime and the widget falls back to the default order +- **THEN** a `console.warn` is emitted describing the ignored sort order and including the underlying error, so the reset is diagnosable from the browser console