From 6c20948a55c1ecc8ecd585fbe192008dcab01e67 Mon Sep 17 00:00:00 2001 From: gjulivan Date: Thu, 10 Jul 2025 13:15:36 +0200 Subject: [PATCH 01/20] feat: initial selection controls version --- automation/utils/bin/rui-prepare-release.ts | 0 .../selection-controls-web/.prettierrc.js | 1 + .../selection-controls-web/CHANGELOG.md | 21 ++ .../selection-controls-web/README.md | 42 +++ .../e2e/SelectionControls.spec.js | 65 ++++ .../selection-controls-web/eslint.config.mjs | 1 + .../selection-controls-web/package.json | 65 ++++ .../playwright.config.cjs | 1 + .../selection-controls-web/rollup.config.js | 20 ++ .../src/SelectionControls.dark.png | 2 + .../src/SelectionControls.editorConfig.ts | 183 ++++++++++ .../src/SelectionControls.editorPreview.tsx | 76 ++++ .../src/SelectionControls.icon.png | 2 + .../src/SelectionControls.png | 2 + .../src/SelectionControls.tile.dark.png | 2 + .../src/SelectionControls.tile.png | 2 + .../src/SelectionControls.tsx | 51 +++ .../src/SelectionControls.xml | 332 ++++++++++++++++++ .../src/__tests__/SelectionControls.spec.tsx | 68 ++++ .../CheckboxSelection/CheckboxSelection.tsx | 101 ++++++ .../src/components/Placeholder.tsx | 13 + .../RadioSelection/RadioSelection.tsx | 88 +++++ .../Association/AssociationMultiSelector.ts | 28 ++ .../Association/AssociationOptionsProvider.ts | 33 ++ .../AssociationSimpleCaptionsProvider.tsx | 65 ++++ .../Association/AssociationSingleSelector.ts | 21 ++ .../Association/BaseAssociationSelector.ts | 76 ++++ .../src/helpers/Association/utils.ts | 52 +++ .../src/helpers/BaseOptionsProvider.ts | 59 ++++ .../Database/DatabaseCaptionsProvider.tsx | 68 ++++ .../helpers/Database/DatabaseMultiSelector.ts | 114 ++++++ .../Database/DatabaseOptionsProvider.ts | 33 ++ .../Database/DatabaseSingleSelector.ts | 124 +++++++ .../Database/DatabaseValuesProvider.ts | 38 ++ .../src/helpers/Database/utils.ts | 73 ++++ .../EnumAndBooleanSimpleCaptionsProvider.tsx | 36 ++ .../EnumBool/EnumBoolOptionsProvider.ts | 29 ++ .../EnumBool/EnumBooleanSingleSelector.ts | 71 ++++ .../src/helpers/EnumBool/utils.ts | 22 ++ .../helpers/Static/StaticCaptionsProvider.tsx | 64 ++++ .../helpers/Static/StaticOptionsProvider.ts | 41 +++ .../helpers/Static/StaticSingleSelector.ts | 93 +++++ .../src/helpers/Static/utils.ts | 35 ++ .../src/helpers/getSelector.ts | 32 ++ .../src/helpers/types.ts | 102 ++++++ .../src/helpers/utils.ts | 20 ++ .../src/hooks/useActionEvents.ts | 40 +++ .../src/hooks/useGetSelector.ts | 15 + .../selection-controls-web/src/package.xml | 11 + .../src/ui/SelectionControls.scss | 123 +++++++ .../selection-controls-web/tsconfig.json | 31 ++ .../typings/SelectionControlsProps.d.ts | 121 +++++++ .../typings/declare-svg.ts | 4 + pnpm-lock.yaml | 44 ++- 54 files changed, 2854 insertions(+), 2 deletions(-) mode change 100644 => 100755 automation/utils/bin/rui-prepare-release.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/.prettierrc.js create mode 100644 packages/pluggableWidgets/selection-controls-web/CHANGELOG.md create mode 100644 packages/pluggableWidgets/selection-controls-web/README.md create mode 100644 packages/pluggableWidgets/selection-controls-web/e2e/SelectionControls.spec.js create mode 100644 packages/pluggableWidgets/selection-controls-web/eslint.config.mjs create mode 100644 packages/pluggableWidgets/selection-controls-web/package.json create mode 100644 packages/pluggableWidgets/selection-controls-web/playwright.config.cjs create mode 100644 packages/pluggableWidgets/selection-controls-web/rollup.config.js create mode 100644 packages/pluggableWidgets/selection-controls-web/src/SelectionControls.dark.png create mode 100644 packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx create mode 100644 packages/pluggableWidgets/selection-controls-web/src/SelectionControls.icon.png create mode 100644 packages/pluggableWidgets/selection-controls-web/src/SelectionControls.png create mode 100644 packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tile.dark.png create mode 100644 packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tile.png create mode 100644 packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx create mode 100644 packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml create mode 100644 packages/pluggableWidgets/selection-controls-web/src/__tests__/SelectionControls.spec.tsx create mode 100644 packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx create mode 100644 packages/pluggableWidgets/selection-controls-web/src/components/Placeholder.tsx create mode 100644 packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationMultiSelector.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationOptionsProvider.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSingleSelector.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Association/utils.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/BaseOptionsProvider.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseCaptionsProvider.tsx create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseMultiSelector.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseOptionsProvider.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseSingleSelector.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseValuesProvider.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Database/utils.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumAndBooleanSimpleCaptionsProvider.tsx create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBoolOptionsProvider.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/utils.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticOptionsProvider.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Static/utils.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/getSelector.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/hooks/useActionEvents.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/hooks/useGetSelector.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/package.xml create mode 100644 packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss create mode 100644 packages/pluggableWidgets/selection-controls-web/tsconfig.json create mode 100644 packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/typings/declare-svg.ts diff --git a/automation/utils/bin/rui-prepare-release.ts b/automation/utils/bin/rui-prepare-release.ts old mode 100644 new mode 100755 diff --git a/packages/pluggableWidgets/selection-controls-web/.prettierrc.js b/packages/pluggableWidgets/selection-controls-web/.prettierrc.js new file mode 100644 index 0000000000..0892704ab0 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/.prettierrc.js @@ -0,0 +1 @@ +module.exports = require("@mendix/prettier-config-web-widgets"); diff --git a/packages/pluggableWidgets/selection-controls-web/CHANGELOG.md b/packages/pluggableWidgets/selection-controls-web/CHANGELOG.md new file mode 100644 index 0000000000..ec9eaf9f34 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + +All notable changes to this widget will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.0.0] - 2025-01-20 + +### Added + +- Initial release of Selection Controls widget +- Support for radio button lists (single selection) +- Support for checkbox lists (multiple selection) +- Context data source support (associations) +- Database data source support +- Static data source support +- Custom content options +- Accessibility features with ARIA labels +- Keyboard navigation support diff --git a/packages/pluggableWidgets/selection-controls-web/README.md b/packages/pluggableWidgets/selection-controls-web/README.md new file mode 100644 index 0000000000..8e8e747a08 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/README.md @@ -0,0 +1,42 @@ +# Selection Controls + +A widget for displaying radio button lists (single selection) and checkbox lists (multiple selection) based on different data sources. + +## Features + +- **Single Selection**: Radio button list for exclusive selection +- **Multiple Selection**: Checkbox list for multiple selection +- **Data Sources**: Support for context (association), database, and static data +- **Custom Content**: Ability to add custom content for options +- **Accessibility**: Full accessibility support with ARIA labels and keyboard navigation + +## Configuration + +The widget supports various data source types: + +- **Context**: Use associations from your entity +- **Database**: Query database for selectable objects +- **Static**: Define static values directly in the widget + +## Usage + +1. Add the Selection Controls widget to your page +2. Configure the data source (Context, Database, or Static) +3. Set up caption and value attributes +4. Configure selection method (single or multiple) +5. Customize styling and accessibility options + +For detailed configuration options, please refer to the widget properties in Studio Pro. + +## Browser Support + +- Modern browsers supporting ES6+ +- Internet Explorer 11+ (with polyfills) + +## Development + +This widget is built using: + +- React 18+ +- TypeScript +- Mendix Pluggable Widgets API diff --git a/packages/pluggableWidgets/selection-controls-web/e2e/SelectionControls.spec.js b/packages/pluggableWidgets/selection-controls-web/e2e/SelectionControls.spec.js new file mode 100644 index 0000000000..bdda75f78c --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/e2e/SelectionControls.spec.js @@ -0,0 +1,65 @@ +import { test, expect } from "@playwright/test"; + +test.afterEach("Cleanup session", async ({ page }) => { + // Because the test isolation that will open a new session for every test executed, and that exceeds Mendix's license limit of 5 sessions, so we need to force logout after each test. + await page.evaluate(() => window.mx.session.logout()); +}); + +test.describe("selection-controls-web", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/p/selectioncontrols"); + await page.waitForLoadState("networkidle"); + }); + + test.describe("data source types", () => { + test("renders selection controls using association", async ({ page }) => { + const selectionControls = page.locator(".mx-name-selectionControls1"); + await expect(selectionControls).toBeVisible({ timeout: 10000 }); + await expect(selectionControls).toHaveScreenshot(`selectionControlsAssociation.png`); + }); + + test("renders selection controls using enum", async ({ page }) => { + const selectionControls = page.locator(".mx-name-selectionControls2"); + await expect(selectionControls).toBeVisible({ timeout: 10000 }); + await expect(selectionControls).toHaveScreenshot(`selectionControlsEnum.png`); + }); + + test("renders selection controls using boolean", async ({ page }) => { + const selectionControls = page.locator(".mx-name-selectionControls3"); + await expect(selectionControls).toBeVisible({ timeout: 10000 }); + await expect(selectionControls).toHaveScreenshot(`selectionControlsBoolean.png`); + }); + + test("renders selection controls using static values", async ({ page }) => { + const selectionControls = page.locator(".mx-name-selectionControls4"); + await expect(selectionControls).toBeVisible({ timeout: 10000 }); + await expect(selectionControls).toHaveScreenshot(`selectionControlsStatic.png`); + }); + + test("renders selection controls using database", async ({ page }) => { + const selectionControls = page.locator(".mx-name-selectionControls5"); + await expect(selectionControls).toBeVisible({ timeout: 10000 }); + await expect(selectionControls).toHaveScreenshot(`selectionControlsDatabase.png`); + }); + + test.describe("selection behavior", () => { + test("handles radio button selection", async ({ page }) => { + const selectionControls = page.locator(".mx-name-selectionControls1"); + await expect(selectionControls).toBeVisible({ timeout: 10000 }); + + const radioOption = selectionControls.locator('input[type="radio"]').first(); + await radioOption.click(); + await expect(radioOption).toBeChecked(); + }); + + test("handles checkbox selection", async ({ page }) => { + const selectionControls = page.locator(".mx-name-selectionControls6"); // multi selection + await expect(selectionControls).toBeVisible({ timeout: 10000 }); + + const checkboxOption = selectionControls.locator('input[type="checkbox"]').first(); + await checkboxOption.click(); + await expect(checkboxOption).toBeChecked(); + }); + }); + }); +}); diff --git a/packages/pluggableWidgets/selection-controls-web/eslint.config.mjs b/packages/pluggableWidgets/selection-controls-web/eslint.config.mjs new file mode 100644 index 0000000000..59cd869516 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/eslint.config.mjs @@ -0,0 +1 @@ +export { default } from "@mendix/eslint-config-web-widgets"; diff --git a/packages/pluggableWidgets/selection-controls-web/package.json b/packages/pluggableWidgets/selection-controls-web/package.json new file mode 100644 index 0000000000..99b9fbb054 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/package.json @@ -0,0 +1,65 @@ +{ + "name": "@mendix/selection-controls-web", + "widgetName": "SelectionControls", + "version": "1.0.0", + "description": "Configurable radio buttons and check box widget", + "copyright": "© Mendix Technology BV 2025. All rights reserved.", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/mendix/web-widgets.git" + }, + "config": { + "developmentPort": 3000, + "mendixHost": "http://localhost:8080" + }, + "mxpackage": { + "name": "SelectionControls", + "type": "widget", + "mpkName": "com.mendix.widget.web.SelectionControls.mpk" + }, + "packagePath": "com.mendix.widget.web", + "marketplace": { + "minimumMXVersion": "10.7.0", + "appNumber": 219304, + "appName": "Selection Controls", + "reactReady": true + }, + "testProject": { + "githubUrl": "https://github.com/mendix/testProjects", + "branchName": "selection-controls-web" + }, + "scripts": { + "prebuild": "rui-create-translation", + "build": "pluggable-widgets-tools build:web", + "create-gh-release": "rui-create-gh-release", + "create-translation": "rui-create-translation", + "dev": "pluggable-widgets-tools start:web", + "e2e": "run-e2e ci", + "e2edev": "run-e2e dev --with-preps", + "format": "prettier --ignore-path ./node_modules/@mendix/prettier-config-web-widgets/global-prettierignore --write .", + "lint": "eslint src/ package.json", + "publish-marketplace": "rui-publish-marketplace", + "release": "pluggable-widgets-tools release:web", + "start": "pluggable-widgets-tools start:server", + "test": "jest --projects jest.config.js", + "update-changelog": "rui-update-changelog-widget", + "verify": "rui-verify-package-format" + }, + "dependencies": { + "classnames": "^2.3.2" + }, + "devDependencies": { + "@mendix/automation-utils": "workspace:*", + "@mendix/eslint-config-web-widgets": "workspace:*", + "@mendix/pluggable-widgets-tools": "*", + "@mendix/prettier-config-web-widgets": "workspace:*", + "@mendix/run-e2e": "workspace:^*", + "@mendix/widget-plugin-component-kit": "workspace:*", + "@mendix/widget-plugin-grid": "workspace:*", + "@mendix/widget-plugin-hooks": "workspace:*", + "@mendix/widget-plugin-platform": "workspace:*", + "@mendix/widget-plugin-test-utils": "workspace:*", + "cross-env": "^7.0.3" + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/playwright.config.cjs b/packages/pluggableWidgets/selection-controls-web/playwright.config.cjs new file mode 100644 index 0000000000..29045fc372 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/playwright.config.cjs @@ -0,0 +1 @@ +module.exports = require("@mendix/run-e2e/playwright.config.cjs"); diff --git a/packages/pluggableWidgets/selection-controls-web/rollup.config.js b/packages/pluggableWidgets/selection-controls-web/rollup.config.js new file mode 100644 index 0000000000..48e21a9f79 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/rollup.config.js @@ -0,0 +1,20 @@ +const { join } = require("path"); +const { cp, mkdir, rm } = require("shelljs"); + +const sourcePath = process.cwd(); +const outDir = join(sourcePath, "/dist/tmp/widgets/"); + +module.exports = args => { + const result = args.configDefaultConfig; + + const localesDir = join(outDir, "locales/"); + mkdir("-p", localesDir); + + const translationFiles = join(sourcePath, "dist/locales/**/*"); + // copy everything under dist/locales to dist/tmp/widgets/locales for the widget mpk + cp("-r", translationFiles, localesDir); + // remove root level *.json locales files (duplicate with language specific files (e.g. en-US/*.json)) + rm("-f", join(outDir, "locales/*.json"), localesDir); + + return result; +}; diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.dark.png b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.dark.png new file mode 100644 index 0000000000..91d519192d --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.dark.png @@ -0,0 +1,2 @@ +// Placeholder for SelectionControls.dark.png - widget icon for dark mode +// In a real implementation, this would be a proper PNG image file \ No newline at end of file diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts new file mode 100644 index 0000000000..bb9218790d --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts @@ -0,0 +1,183 @@ +import { Properties, hideNestedPropertiesIn, hidePropertiesIn } from "@mendix/pluggable-widgets-tools"; +import { + ContainerProps, + StructurePreviewProps, + structurePreviewPalette, + container +} from "@mendix/widget-plugin-platform/preview/structure-preview-api"; +import { SelectionControlsPreviewProps } from "../typings/SelectionControlsProps"; + +const DATABASE_SOURCE_CONFIG: Array = [ + "optionsSourceDatabaseCaptionAttribute", + "optionsSourceDatabaseCaptionExpression", + "optionsSourceDatabaseCaptionType", + "optionsSourceDatabaseCustomContent", + "optionsSourceDatabaseCustomContentType", + "optionsSourceDatabaseDataSource", + "optionsSourceDatabaseValueAttribute", + "optionsSourceDatabaseItemSelection", + "databaseAttributeString", + "onChangeDatabaseEvent" +]; + +const ASSOCIATION_SOURCE_CONFIG: Array = [ + "optionsSourceAssociationCaptionAttribute", + "optionsSourceAssociationCaptionExpression", + "optionsSourceAssociationCaptionType", + "optionsSourceAssociationCustomContent", + "optionsSourceAssociationCustomContentType", + "optionsSourceAssociationDataSource", + "attributeAssociation" +]; + +export function getProperties( + values: SelectionControlsPreviewProps & { Editability?: unknown }, + defaultProperties: Properties +): Properties { + // Basic property hiding logic - can be expanded later + if (values.source !== "database") { + hidePropertiesIn(defaultProperties, values, ["customEditability", "customEditabilityExpression"]); + } + + if (values.source === "context") { + hidePropertiesIn(defaultProperties, values, [ + "staticAttribute", + "staticDataSourceCustomContentType", + "optionsSourceStaticDataSource", + ...DATABASE_SOURCE_CONFIG + ]); + if (["enumeration", "boolean"].includes(values.optionsSourceType)) { + hidePropertiesIn(defaultProperties, values, [...ASSOCIATION_SOURCE_CONFIG]); + if (values.optionsSourceType === "boolean") { + hidePropertiesIn(defaultProperties, values, ["attributeEnumeration"]); + } else { + hidePropertiesIn(defaultProperties, values, ["attributeBoolean"]); + } + } else if (values.optionsSourceType === "association") { + hidePropertiesIn(defaultProperties, values, ["attributeEnumeration", "attributeBoolean"]); + if (values.optionsSourceAssociationCaptionType === "attribute") { + hidePropertiesIn(defaultProperties, values, ["optionsSourceAssociationCaptionExpression"]); + } else { + hidePropertiesIn(defaultProperties, values, ["optionsSourceAssociationCaptionAttribute"]); + } + + if (values.optionsSourceAssociationDataSource === null) { + hidePropertiesIn(defaultProperties, values, ["optionsSourceAssociationCaptionType"]); + } + + if (values.optionsSourceAssociationCustomContentType === "no") { + hidePropertiesIn(defaultProperties, values, ["optionsSourceAssociationCustomContent"]); + } + } + } else if (values.source === "database") { + hidePropertiesIn(defaultProperties, values, [ + "attributeEnumeration", + "attributeBoolean", + "optionsSourceType", + "staticAttribute", + "staticDataSourceCustomContentType", + "optionsSourceStaticDataSource", + "onChangeEvent", + ...ASSOCIATION_SOURCE_CONFIG + ]); + if (values.optionsSourceDatabaseDataSource === null) { + hidePropertiesIn(defaultProperties, values, ["optionsSourceDatabaseCaptionType"]); + } + if (values.optionsSourceDatabaseCaptionType === "attribute") { + hidePropertiesIn(defaultProperties, values, ["optionsSourceDatabaseCaptionExpression"]); + } else { + hidePropertiesIn(defaultProperties, values, ["optionsSourceDatabaseCaptionAttribute"]); + } + if (values.optionsSourceDatabaseCustomContentType === "no") { + hidePropertiesIn(defaultProperties, values, ["optionsSourceDatabaseCustomContent"]); + } + if (values.optionsSourceDatabaseItemSelection === "Multi") { + hidePropertiesIn(defaultProperties, values, [ + "optionsSourceDatabaseValueAttribute", + "databaseAttributeString" + ]); + } + if (values.databaseAttributeString.length === 0) { + hidePropertiesIn(defaultProperties, values, ["optionsSourceDatabaseValueAttribute"]); + hidePropertiesIn(defaultProperties, values, ["Editability"]); + if (values.customEditability !== "conditionally") { + hidePropertiesIn(defaultProperties, values, ["customEditabilityExpression"]); + } + } else { + hidePropertiesIn(defaultProperties, values, ["customEditability", "customEditabilityExpression"]); + } + } else if (values.source === "static") { + hidePropertiesIn(defaultProperties, values, [ + "attributeEnumeration", + "attributeBoolean", + "optionsSourceType", + ...ASSOCIATION_SOURCE_CONFIG, + ...DATABASE_SOURCE_CONFIG + ]); + } + + if (values.staticDataSourceCustomContentType === "no") { + values.optionsSourceStaticDataSource.forEach((_, index) => { + hideNestedPropertiesIn(defaultProperties, values, "optionsSourceStaticDataSource", index, [ + "staticDataSourceCustomContent" + ]); + }); + } + + return defaultProperties; +} + +function getIconPreview(_isDarkMode: boolean): ContainerProps { + return { + type: "Container", + children: [ + container({ padding: 1 })(), + { + type: "Text", + content: "☑", + fontSize: 16 + } + ] + }; +} + +export function getPreview(_values: SelectionControlsPreviewProps, isDarkMode: boolean): StructurePreviewProps { + const palette = structurePreviewPalette[isDarkMode ? "dark" : "light"]; + + return { + type: "RowLayout", + columnSize: "fixed", + backgroundColor: palette.background.containerFill, + children: [ + { + type: "RowLayout", + columnSize: "grow", + children: [ + getIconPreview(isDarkMode), + { + type: "Container", + padding: 4, + children: [ + { + type: "Text", + content: "Selection Controls", + fontColor: palette.text.primary, + fontSize: 10 + } + ] + } + ] + } + ] + }; +} + +export function getCustomCaption(values: SelectionControlsPreviewProps): string { + if (values.source === "static" && values.optionsSourceStaticDataSource.length > 0) { + return "Selection Controls (Static)"; + } + if (values.source === "database") { + return "Selection Controls (Database)"; + } + return "Selection Controls"; +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx new file mode 100644 index 0000000000..aa4992bdc5 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx @@ -0,0 +1,76 @@ +import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; +import { ReactElement, createElement, useMemo } from "react"; +import { SelectionControlsPreviewProps } from "../typings/SelectionControlsProps"; +import { RadioSelection } from "./components/RadioSelection/RadioSelection"; +import { dynamic } from "@mendix/widget-plugin-test-utils"; +import { SingleSelector, SelectionBaseProps } from "./helpers/types"; +import "./ui/SelectionControls.scss"; + +// Preview selector implementation - simplified for preview +class PreviewSelector implements SingleSelector { + type = "single" as const; + status = "available" as const; + readOnly = false; + validation = undefined; + clearable = false; + currentId = null; + customContentType = "no" as const; + + constructor(_props: SelectionControlsPreviewProps) {} + + updateProps() {} + setValue() {} + onEnterEvent() {} + onLeaveEvent() {} + + options = { + status: "available" as const, + searchTerm: "", + getAll: () => ["Option 1", "Option 2", "Option 3"], + setSearchTerm: () => {}, + onAfterSearchTermChange: () => {}, + isLoading: false, + _updateProps: () => {}, + _optionToValue: () => undefined, + _valueToOption: () => null + }; + + caption = { + get: (value: string | null) => value || "Preview Option", + render: (value: string | null) => value || "Preview Option", + emptyCaption: "Select an option" + }; +} + +export const preview = (props: SelectionControlsPreviewProps): ReactElement => { + const id = generateUUID().toString(); + const commonProps: Omit, "selector"> = { + tabIndex: 1, + inputId: id, + labelId: `${id}-label`, + readOnlyStyle: props.readOnlyStyle, + ariaRequired: dynamic(false), + a11yConfig: { + ariaLabels: { + clearSelection: props.clearButtonAriaLabel, + removeSelection: props.removeValueAriaLabel + }, + a11yStatusMessage: { + a11ySelectedValue: props.a11ySelectedValue, + a11yOptionsAvailable: props.a11yOptionsAvailable, + a11yInstructions: props.a11yInstructions + } + } + }; + + // eslint-disable-next-line react-hooks/rules-of-hooks + const selector: SingleSelector = useMemo(() => { + return new PreviewSelector(props); + }, [props]); + + return ( +
+ +
+ ); +}; diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.icon.png b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.icon.png new file mode 100644 index 0000000000..1d794f2faa --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.icon.png @@ -0,0 +1,2 @@ +// Placeholder for SelectionControls.icon.png - widget small icon +// In a real implementation, this would be a proper PNG image file \ No newline at end of file diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.png b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.png new file mode 100644 index 0000000000..3879a20612 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.png @@ -0,0 +1,2 @@ +// Placeholder for SelectionControls.png - widget icon +// In a real implementation, this would be a proper PNG image file \ No newline at end of file diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tile.dark.png b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tile.dark.png new file mode 100644 index 0000000000..a3d5da808d --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tile.dark.png @@ -0,0 +1,2 @@ +// Placeholder for SelectionControls.tile.dark.png - widget tile icon for dark mode +// In a real implementation, this would be a proper PNG image file \ No newline at end of file diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tile.png b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tile.png new file mode 100644 index 0000000000..cc15910ddc --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tile.png @@ -0,0 +1,2 @@ +// Placeholder for SelectionControls.tile.png - widget tile icon +// In a real implementation, this would be a proper PNG image file \ No newline at end of file diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx new file mode 100644 index 0000000000..664d67decf --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx @@ -0,0 +1,51 @@ +import { createElement, ReactElement } from "react"; + +import { SelectionControlsContainerProps } from "../typings/SelectionControlsProps"; + +import { CheckboxSelection } from "./components/CheckboxSelection/CheckboxSelection"; +import { Placeholder } from "./components/Placeholder"; +import { RadioSelection } from "./components/RadioSelection/RadioSelection"; +import { SelectionBaseProps } from "./helpers/types"; +import { useActionEvents } from "./hooks/useActionEvents"; +import { useGetSelector } from "./hooks/useGetSelector"; + +import "./ui/SelectionControls.scss"; + +export default function SelectionControls(props: SelectionControlsContainerProps): ReactElement { + const selector = useGetSelector(props); + const actionEvents = useActionEvents({ + onEnterEvent: props.onEnterEvent, + onLeaveEvent: props.onLeaveEvent, + selector + }); + const commonProps: Omit, "selector"> = { + tabIndex: props.tabIndex!, + inputId: props.id, + labelId: `${props.id}-label`, + readOnlyStyle: props.readOnlyStyle, + ariaRequired: props.ariaRequired, + a11yConfig: { + ariaLabels: { + clearSelection: props.clearButtonAriaLabel?.value ?? "", + removeSelection: props.removeValueAriaLabel?.value ?? "" + }, + a11yStatusMessage: { + a11ySelectedValue: props.a11ySelectedValue?.value ?? "", + a11yOptionsAvailable: props.a11yOptionsAvailable?.value ?? "", + a11yInstructions: props.a11yInstructions?.value ?? "" + } + } + }; + + return ( +
+ {selector.status === "unavailable" ? ( + + ) : selector.type === "single" ? ( + + ) : ( + + )} +
+ ); +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml new file mode 100644 index 0000000000..1735b021fd --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml @@ -0,0 +1,332 @@ + + + Selection Controls + + Input elements + Display + https://docs.mendix.com/appstore/widgets/selectioncontrols + + + + + + Source + + + Context + Database + Static + + + + + Type + + + Association + Enumeration + Boolean + + + + + + Attribute + + + + + + + Attribute + + + + + + + + + Selectable objects + + + + Selection type + + + + + + + + + + + Caption type + + + Attribute + Expression + + + + Caption type + + + Attribute + Expression + + + + Caption + + + + + + + Caption + + + + + + + Caption + + + + + Caption + + + + + + + + Value + + + + + + + + + + Target attribute + + + + + + + + + + + + + + Entity + + + + + + + + Selectable objects + + + + + + + + Attribute + + + + + + + + + + + + + Values + + + + + Value + Value to be set + + + + Custom content + + + + Caption + Caption to be shown + + + + + + + + + + Custom content + + + Yes + List items only + No + + + + Custom content + + + + Custom content + + + Yes + List items only + No + + + + Custom content + + + + Custom content + + + Yes + List items only + No + + + + + + + + + + + + + + + + + + Editable + + + Default + Never + Conditionally + + + + Condition + + + + + Read-only style + How the combo box will appear in read-only mode. + + Control + Content only + + + + + + + + + On change action + + + + On change action + + + + On enter action + + + + On leave action + + + + + + + Aria required + + + + + + + Clear selection button + Used to clear all selected values. + + Clear selection + Selectie wissen + + + + Remove value button + Used to remove individual selected values when using labels with multi-selection. + + Remove value + Waarde verwijderen + + + + + + Selected value + Output example: "Selected value: Avocado, Apple, Banana." + + Selected value: + Geselecteerde waarde: + + + + Options available + Output example: "Number of options available: 1" + + Number of options available: + Aantal beschikbare opties: + + + + Instructions + Instructions to be read after announcing the status. + + Use up and down arrow keys to navigate. Press Enter or Space Bar keys to select. + Gebruik de pijltjestoetsen (omhoog en omlaag) om te navigeren. Druk op Enter of de spatiebalk om de waarde te selecteren. + + + + + + diff --git a/packages/pluggableWidgets/selection-controls-web/src/__tests__/SelectionControls.spec.tsx b/packages/pluggableWidgets/selection-controls-web/src/__tests__/SelectionControls.spec.tsx new file mode 100644 index 0000000000..d63c12c7bd --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/__tests__/SelectionControls.spec.tsx @@ -0,0 +1,68 @@ +import { render } from "@testing-library/react"; +import { createElement } from "react"; +import { SelectionControlsContainerProps } from "../../typings/SelectionControlsProps"; +import SelectionControls from "../SelectionControls"; + +// Mock the selector to avoid implementation dependencies for basic tests +jest.mock("../helpers/getSelector", () => ({ + getSelector: jest.fn(() => ({ + type: "single", + status: "available", + updateProps: jest.fn(), + options: { + onAfterSearchTermChange: jest.fn() + } + })) +})); + +describe("SelectionControls", () => { + const defaultProps: SelectionControlsContainerProps = { + name: "selectionControls1", + id: "selectionControls1", + source: "context" as const, + optionsSourceType: "enumeration" as const, + attributeEnumeration: { + status: "available", + value: "option1", + validation: undefined, + readOnly: false, + displayValue: "Option 1", + setValue: jest.fn(), + formatter: { + format: jest.fn(), + type: "enum" + }, + universe: ["option1", "option2", "option3"] + } as any, + attributeBoolean: {} as any, + attributeAssociation: {} as any, + staticAttribute: {} as any, + optionsSourceStaticDataSource: [], + optionsSourceAssociationCaptionType: "attribute" as const, + optionsSourceDatabaseCaptionType: "attribute" as const, + optionsSourceAssociationCustomContentType: "no" as const, + optionsSourceDatabaseCustomContentType: "no" as const, + staticDataSourceCustomContentType: "no" as const, + customEditability: "default" as const, + customEditabilityExpression: { status: "available", value: false } as any, + readOnlyStyle: "bordered" as const, + ariaRequired: { status: "available", value: false } as any + }; + + it("renders without crashing", () => { + const component = render(); + expect(component.container.querySelector(".widget-selection-controls")).toBeTruthy(); + }); + + it("renders placeholder when selector status is unavailable", () => { + // This test would need more setup to properly mock the unavailable state + const component = render(); + expect(component.container).toBeDefined(); + }); + + it("applies correct CSS class", () => { + const component = render(); + const widget = component.container.querySelector(".widget-selection-controls"); + expect(widget?.className).toContain("widget-selection-controls"); + }); +}); diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx b/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx new file mode 100644 index 0000000000..d6ae5193c5 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx @@ -0,0 +1,101 @@ +import classNames from "classnames"; +import { ReactElement, createElement } from "react"; +import { SelectionBaseProps, MultiSelector } from "../../helpers/types"; + +export function CheckboxSelection({ + selector, + tabIndex = 0, + inputId, + ariaRequired, + a11yConfig, + readOnlyStyle +}: SelectionBaseProps): ReactElement { + const options = selector.getOptions(); + const currentIds = selector.currentId || []; + const isReadOnly = selector.readOnly; + + const handleChange = (optionId: string, checked: boolean): void => { + if (!isReadOnly) { + const newSelection = checked ? [...currentIds, optionId] : currentIds.filter(id => id !== optionId); + selector.setValue(newSelection); + } + }; + + if (selector.status === "unavailable") { + return ( +
+
Loading...
+
+ ); + } + + return ( +
+
+ {options.map((optionId, index) => { + const caption = selector.caption.get(optionId); + const isSelected = currentIds.includes(optionId); + const checkboxId = `${inputId}-checkbox-${index}`; + + return ( +
+ handleChange(optionId, e.target.checked)} + aria-describedby={`${inputId}-description`} + /> + +
+ ); + })} + {options.length === 0 && ( +
No options available
+ )} +
+ + {/* Clear all button */} + {!isReadOnly && currentIds.length > 0 && ( + + )} + + {/* Accessibility status message */} +
+ {currentIds.length > 0 && + `${a11yConfig.a11yStatusMessage.a11ySelectedValue} ${currentIds.map(id => selector.caption.get(id)).join(", ")}`} + {` ${a11yConfig.a11yStatusMessage.a11yOptionsAvailable} ${options.length}`} + {` ${a11yConfig.a11yStatusMessage.a11yInstructions}`} +
+
+ ); +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/Placeholder.tsx b/packages/pluggableWidgets/selection-controls-web/src/components/Placeholder.tsx new file mode 100644 index 0000000000..6edd8810b6 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/components/Placeholder.tsx @@ -0,0 +1,13 @@ +import { ReactElement, createElement } from "react"; + +export function Placeholder(): ReactElement { + return ( +
+
Loading...
+
+ ); +} + +export function NoOptionsPlaceholder(): ReactElement { + return
No options available
; +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx b/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx new file mode 100644 index 0000000000..99c1716a2e --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx @@ -0,0 +1,88 @@ +import classNames from "classnames"; +import { ReactElement, createElement } from "react"; +import { SelectionBaseProps, SingleSelector } from "../../helpers/types"; + +export function RadioSelection({ + selector, + tabIndex = 0, + inputId, + ariaRequired, + a11yConfig, + readOnlyStyle +}: SelectionBaseProps): ReactElement { + const options = selector.options.getAll(); + const currentId = selector.currentId; + const isReadOnly = selector.readOnly; + + const handleChange = (optionId: string): void => { + if (!isReadOnly) { + selector.setValue(optionId); + } + }; + + if (selector.status === "unavailable") { + return ( +
+
Loading...
+
+ ); + } + + return ( +
+
+ {options.map((optionId, index) => { + const caption = selector.caption.get(optionId); + const isSelected = currentId === optionId; + const radioId = `${inputId}-radio-${index}`; + + return ( +
+ handleChange(optionId)} + aria-describedby={`${inputId}-description`} + /> + +
+ ); + })} + {options.length === 0 && ( +
No options available
+ )} +
+ + {/* Accessibility status message */} +
+ {currentId && `${a11yConfig.a11yStatusMessage.a11ySelectedValue} ${selector.caption.get(currentId)}`} + {` ${a11yConfig.a11yStatusMessage.a11yOptionsAvailable} ${options.length}`} + {` ${a11yConfig.a11yStatusMessage.a11yInstructions}`} +
+
+ ); +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationMultiSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationMultiSelector.ts new file mode 100644 index 0000000000..8f5ab4e3ea --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationMultiSelector.ts @@ -0,0 +1,28 @@ +import { ReferenceSetValue } from "mendix"; +import { SelectionControlsContainerProps } from "../../../typings/SelectionControlsProps"; +import { MultiSelector } from "../types"; +import { BaseAssociationSelector } from "./BaseAssociationSelector"; + +export class AssociationMultiSelector + extends BaseAssociationSelector + implements MultiSelector +{ + type = "multi" as const; + + updateProps(props: SelectionControlsContainerProps): void { + super.updateProps(props); + + // Convert reference set value to array of IDs + this.currentId = this._attr?.value?.map(item => item.id) ?? []; + } + + setValue(value: string[] | null): void { + const newValue = value?.map(v => this.options._optionToValue(v)!); + this._attr?.setValue(newValue); + super.setValue(value); + } + + getOptions(): string[] { + return this.options.getAll(); + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationOptionsProvider.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationOptionsProvider.ts new file mode 100644 index 0000000000..b68f20aea2 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationOptionsProvider.ts @@ -0,0 +1,33 @@ +import { ListValue, ObjectItem } from "mendix"; +import { BaseOptionsProvider } from "../BaseOptionsProvider"; +import { CaptionsProvider } from "../types"; + +export class AssociationOptionsProvider extends BaseOptionsProvider { + constructor( + caption: CaptionsProvider, + private _objectsMap: Map + ) { + super(caption); + } + + _updateProps(props: { ds: ListValue }): void { + this._objectsMap.clear(); + this.options = []; + + if (props.ds && props.ds.status === "available") { + props.ds.items?.forEach(item => { + const key = item.id; + this._objectsMap.set(key, item); + this.options.push(key); + }); + } + } + + _optionToValue(option: string | null): ObjectItem | undefined { + return this._objectsMap.get(option || ""); + } + + _valueToOption(value: ObjectItem | undefined): string | null { + return value?.id ?? null; + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx new file mode 100644 index 0000000000..8623ff088b --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx @@ -0,0 +1,65 @@ +import { DynamicValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, ObjectItem } from "mendix"; +import { ReactNode, createElement } from "react"; +import { OptionsSourceAssociationCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; +import { CaptionsProvider } from "../types"; + +interface AssociationSimpleCaptionsProviderProps { + emptyOptionText?: DynamicValue; + formattingAttributeOrExpression?: ListAttributeValue | ListExpressionValue; + customContent?: ListWidgetValue; + customContentType: OptionsSourceAssociationCustomContentTypeEnum; +} + +export class AssociationSimpleCaptionsProvider implements CaptionsProvider { + emptyCaption = ""; + formatter?: ListAttributeValue | ListExpressionValue; + private _objectsMap: Map; + private customContent?: ListWidgetValue; + private customContentType: OptionsSourceAssociationCustomContentTypeEnum = "no"; + + constructor(objectsMap: Map) { + this._objectsMap = objectsMap; + } + + updateProps(props: AssociationSimpleCaptionsProviderProps): void { + if (!props.emptyOptionText || props.emptyOptionText.status === "unavailable") { + this.emptyCaption = ""; + } else { + this.emptyCaption = props.emptyOptionText.value!; + } + this.formatter = props.formattingAttributeOrExpression; + this.customContent = props.customContent; + this.customContentType = props.customContentType; + } + + get(value: string | null): string { + if (value === null) { + return this.emptyCaption; + } + + const item = this._objectsMap.get(value); + if (!item || !this.formatter) { + return ""; + } + + return this.formatter.get(item).value || ""; + } + + render(value: string | null, _placement?: "label" | "options", _htmlFor?: string): ReactNode { + if (value === null) { + return {this.emptyCaption}; + } + + const item = this._objectsMap.get(value); + if (!item) { + return ; + } + + if (this.customContentType === "yes" && this.customContent) { + return this.customContent.get(item); + } + + const caption = this.formatter?.get(item).value || ""; + return {caption}; + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSingleSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSingleSelector.ts new file mode 100644 index 0000000000..939aa43ff4 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSingleSelector.ts @@ -0,0 +1,21 @@ +import { ReferenceValue } from "mendix"; +import { SelectionControlsContainerProps } from "../../../typings/SelectionControlsProps"; +import { SingleSelector } from "../types"; +import { BaseAssociationSelector } from "./BaseAssociationSelector"; + +export class AssociationSingleSelector + extends BaseAssociationSelector + implements SingleSelector +{ + type = "single" as const; + + updateProps(props: SelectionControlsContainerProps): void { + super.updateProps(props); + this.currentId = this._attr?.value?.id ?? null; + } + + setValue(value: string | null): void { + this._attr?.setValue(this.options._optionToValue(value)); + super.setValue(value); + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts new file mode 100644 index 0000000000..39bef68629 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts @@ -0,0 +1,76 @@ +import { ActionValue, ObjectItem, ReferenceSetValue, ReferenceValue } from "mendix"; +import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action"; +import { + SelectionControlsContainerProps, + OptionsSourceAssociationCustomContentTypeEnum +} from "../../../typings/SelectionControlsProps"; +import { Status } from "../types"; +import { AssociationOptionsProvider } from "./AssociationOptionsProvider"; +import { AssociationSimpleCaptionsProvider } from "./AssociationSimpleCaptionsProvider"; +import { extractAssociationProps } from "./utils"; + +export class BaseAssociationSelector { + status: Status = "unavailable"; + options: AssociationOptionsProvider; + clearable = false; + currentId: T | null = null; + caption: AssociationSimpleCaptionsProvider; + readOnly = false; + customContentType: OptionsSourceAssociationCustomContentTypeEnum = "no"; + validation?: string = undefined; + onEnterEvent?: () => void; + onLeaveEvent?: () => void; + protected _attr: R | undefined; + private onChangeEvent?: ActionValue; + private _valuesMap: Map = new Map(); + + constructor() { + this.caption = new AssociationSimpleCaptionsProvider(this._valuesMap); + this.options = new AssociationOptionsProvider(this.caption, this._valuesMap); + } + + updateProps(props: SelectionControlsContainerProps): void { + const [attr, ds, captionProvider, emptyOption, clearable, onChangeEvent, customContent, customContentType] = + extractAssociationProps(props); + + this._attr = attr as R; + this.caption.updateProps({ + emptyOptionText: emptyOption, + formattingAttributeOrExpression: captionProvider, + customContent, + customContentType + }); + + this.options._updateProps({ + ds + }); + + if ( + !attr || + attr.status === "unavailable" || + !ds || + ds.status === "unavailable" || + !captionProvider || + !emptyOption || + emptyOption.status === "unavailable" + ) { + this.status = "unavailable"; + this.currentId = null; + this.clearable = false; + return; + } + + this.clearable = clearable; + this.status = attr.status; + this.readOnly = attr.readOnly; + this.onChangeEvent = onChangeEvent; + this.onEnterEvent = props.onEnterEvent ? () => executeAction(props.onEnterEvent) : undefined; + this.onLeaveEvent = props.onLeaveEvent ? () => executeAction(props.onLeaveEvent) : undefined; + this.customContentType = customContentType; + this.validation = attr.validation; + } + + setValue(_value: T | null): void { + executeAction(this.onChangeEvent); + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/utils.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/utils.ts new file mode 100644 index 0000000000..8546a72058 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/utils.ts @@ -0,0 +1,52 @@ +import { + ActionValue, + DynamicValue, + ListAttributeValue, + ListExpressionValue, + ListValue, + ListWidgetValue, + ReferenceSetValue, + ReferenceValue, + ValueStatus +} from "mendix"; +import { + SelectionControlsContainerProps, + OptionsSourceAssociationCustomContentTypeEnum +} from "../../../typings/SelectionControlsProps"; + +export function extractAssociationProps( + props: SelectionControlsContainerProps +): [ + ReferenceValue | ReferenceSetValue, + ListValue, + ListAttributeValue | ListExpressionValue, + DynamicValue, + boolean, + ActionValue | undefined, + ListWidgetValue | undefined, + OptionsSourceAssociationCustomContentTypeEnum +] { + const attr = props.attributeAssociation; + const ds = props.optionsSourceAssociationDataSource!; + + // Determine caption provider based on caption type + const captionProvider = + props.optionsSourceAssociationCaptionType === "attribute" + ? props.optionsSourceAssociationCaptionAttribute! + : props.optionsSourceAssociationCaptionExpression!; + + // For simplicity, we'll create a basic empty option + const emptyOption: DynamicValue = { + status: ValueStatus.Available, + value: "" + }; + + // Selection controls don't need clearable like combobox does + const clearable = false; + + const onChangeEvent = props.onChangeEvent; + const customContent = props.optionsSourceAssociationCustomContent; + const customContentType = props.optionsSourceAssociationCustomContentType; + + return [attr, ds, captionProvider, emptyOption, clearable, onChangeEvent, customContent, customContentType]; +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/BaseOptionsProvider.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/BaseOptionsProvider.ts new file mode 100644 index 0000000000..86ba5d8137 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/BaseOptionsProvider.ts @@ -0,0 +1,59 @@ +import { CaptionsProvider, OptionsProvider, Status } from "./types"; + +export class BaseOptionsProvider implements OptionsProvider { + protected options: string[] = []; + private trigger?: () => void; + + searchTerm = ""; + + constructor(_caption: CaptionsProvider) { + // Caption provider stored for potential future use + } + + get hasMore(): boolean { + return false; + } + + get isLoading(): boolean { + return false; + } + + get status(): Status { + return "available"; + } + + get sortOrder() { + return undefined; + } + + get datasourceFilter() { + return undefined; + } + + getAll(): string[] { + return this.options; + } + + setSearchTerm(term: string): void { + this.searchTerm = term; + this.trigger?.(); + } + + onAfterSearchTermChange(callback: () => void): void { + this.trigger = callback; + } + + loadMore(): void { + return undefined; + } + + _updateProps(_props: P): void { + throw new Error("_updateProps not implemented"); + } + _optionToValue(_option: string | null): T | undefined { + throw new Error("_optionToValue not implemented"); + } + _valueToOption(_value: T | undefined): string | null { + throw new Error("_valueToOption not implemented"); + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseCaptionsProvider.tsx new file mode 100644 index 0000000000..3126745a8c --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseCaptionsProvider.tsx @@ -0,0 +1,68 @@ +import { DynamicValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, ObjectItem } from "mendix"; +import { ReactNode, createElement } from "react"; +import { Big } from "big.js"; +import { OptionsSourceDatabaseCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; +import { CaptionsProvider } from "../types"; + +interface DatabaseCaptionsProviderProps { + emptyOptionText?: DynamicValue; + formattingAttributeOrExpression?: ListAttributeValue | ListExpressionValue; + customContent?: ListWidgetValue; + customContentType: OptionsSourceDatabaseCustomContentTypeEnum; + attribute?: ListAttributeValue; + caption?: string; +} + +export class DatabaseCaptionsProvider implements CaptionsProvider { + emptyCaption = ""; + formatter?: ListAttributeValue | ListExpressionValue; + private _objectsMap: Map; + private customContent?: ListWidgetValue; + private customContentType: OptionsSourceDatabaseCustomContentTypeEnum = "no"; + + constructor(objectsMap: Map) { + this._objectsMap = objectsMap; + } + + updateProps(props: DatabaseCaptionsProviderProps): void { + if (!props.emptyOptionText || props.emptyOptionText.status === "unavailable") { + this.emptyCaption = ""; + } else { + this.emptyCaption = props.emptyOptionText.value!; + } + this.formatter = props.formattingAttributeOrExpression; + this.customContent = props.customContent; + this.customContentType = props.customContentType; + } + + get(value: string | null): string { + if (value === null) { + return this.emptyCaption; + } + + const item = this._objectsMap.get(value); + if (!item || !this.formatter) { + return ""; + } + + return this.formatter.get(item).value || ""; + } + + render(value: string | null, _placement?: "label" | "options", _htmlFor?: string): ReactNode { + if (value === null) { + return {this.emptyCaption}; + } + + const item = this._objectsMap.get(value); + if (!item) { + return ; + } + + if (this.customContentType === "yes" && this.customContent) { + return this.customContent.get(item); + } + + const caption = this.formatter?.get(item).value || ""; + return {caption}; + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseMultiSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseMultiSelector.ts new file mode 100644 index 0000000000..ac13c22b5a --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseMultiSelector.ts @@ -0,0 +1,114 @@ +import { EditableValue, ObjectItem, SelectionMultiValue } from "mendix"; +import { Big } from "big.js"; +import { + SelectionControlsContainerProps, + OptionsSourceDatabaseCustomContentTypeEnum +} from "../../../typings/SelectionControlsProps"; +import { MultiSelector, Status } from "../types"; +import { _valuesIsEqual } from "../utils"; +import { DatabaseCaptionsProvider } from "./DatabaseCaptionsProvider"; +import { DatabaseOptionsProvider } from "./DatabaseOptionsProvider"; +import { DatabaseValuesProvider } from "./DatabaseValuesProvider"; +import { extractDatabaseProps, getReadonly } from "./utils"; + +export class DatabaseMultiSelector> implements MultiSelector { + type = "multi" as const; + attributeType: "string" | "big" | "boolean" | "date" = "string"; + selectorType: "context" | "database" | "static" = "database"; + status: Status = "unavailable"; + options: DatabaseOptionsProvider; + caption: DatabaseCaptionsProvider; + clearable = false; + currentId: string[] | null = null; + readOnly = false; + customContentType: OptionsSourceDatabaseCustomContentTypeEnum = "no"; + validation?: string = undefined; + onEnterEvent?: () => void; + onLeaveEvent?: () => void; + values: DatabaseValuesProvider; + protected _objectsMap: Map = new Map(); + protected _attr: R | undefined; + private selection?: SelectionMultiValue; + + constructor() { + this.caption = new DatabaseCaptionsProvider(this._objectsMap); + this.options = new DatabaseOptionsProvider(this.caption, this._objectsMap); + this.values = new DatabaseValuesProvider(this._objectsMap); + } + + updateProps(props: SelectionControlsContainerProps): void { + const { + targetAttribute, + captionProvider, + clearable, + customContent, + customContentType, + ds, + emptyOption, + valueSourceAttribute + } = extractDatabaseProps(props); + + if (ds.status === "loading") { + return; + } + + this._attr = targetAttribute as R; + this.readOnly = getReadonly(targetAttribute, props.customEditability, props.customEditabilityExpression); + + this.caption.updateProps({ + emptyOptionText: emptyOption, + formattingAttributeOrExpression: captionProvider, + customContent, + customContentType, + attribute: valueSourceAttribute, + caption: targetAttribute?.displayValue + }); + + this.options._updateProps({ + ds + }); + + this.values.updateProps({ + valueAttribute: valueSourceAttribute + }); + + if (!ds || ds.status === "unavailable" || !emptyOption || emptyOption.status !== "available") { + this.status = "unavailable"; + this.currentId = null; + this.clearable = false; + return; + } + + // For multi-selection, we need to handle arrays of values + if (targetAttribute?.status === "available") { + // In a multi-selector context, targetAttribute.value would typically be an array + // For now, we'll initialize as empty array + this.currentId = []; + } + + this.status = targetAttribute?.status ?? ds.status; + this.validation = targetAttribute?.validation; + this.selection = props.optionsSourceDatabaseItemSelection as SelectionMultiValue; + + this.clearable = clearable; + this.customContentType = customContentType; + } + + setValue(objectIds: string[] | null): void { + // For multi-selection, we would need to handle multiple values + // This is a simplified implementation + this.currentId = objectIds; + + if (this.selection) { + const objects = + objectIds + ?.map(id => this.options._optionToValue(id)) + .filter((obj): obj is ObjectItem => obj !== undefined) || []; + this.selection.setSelection(objects); + } + } + + getOptions(): string[] { + return this.options.getAll(); + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseOptionsProvider.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseOptionsProvider.ts new file mode 100644 index 0000000000..aa3aae52b4 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseOptionsProvider.ts @@ -0,0 +1,33 @@ +import { ListValue, ObjectItem } from "mendix"; +import { BaseOptionsProvider } from "../BaseOptionsProvider"; +import { CaptionsProvider } from "../types"; + +export class DatabaseOptionsProvider extends BaseOptionsProvider { + constructor( + caption: CaptionsProvider, + private _objectsMap: Map + ) { + super(caption); + } + + _updateProps(props: { ds: ListValue }): void { + this._objectsMap.clear(); + this.options = []; + + if (props.ds && props.ds.status === "available") { + props.ds.items?.forEach(item => { + const key = item.id; + this._objectsMap.set(key, item); + this.options.push(key); + }); + } + } + + _optionToValue(option: string | null): ObjectItem | undefined { + return this._objectsMap.get(option || ""); + } + + _valueToOption(value: ObjectItem | undefined): string | null { + return value?.id ?? null; + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseSingleSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseSingleSelector.ts new file mode 100644 index 0000000000..e9d2c86168 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseSingleSelector.ts @@ -0,0 +1,124 @@ +import { EditableValue, ObjectItem, SelectionSingleValue } from "mendix"; +import { Big } from "big.js"; +import { + SelectionControlsContainerProps, + OptionsSourceDatabaseCustomContentTypeEnum +} from "../../../typings/SelectionControlsProps"; +import { SingleSelector, Status } from "../types"; +import { _valuesIsEqual } from "../utils"; +import { DatabaseCaptionsProvider } from "./DatabaseCaptionsProvider"; +import { DatabaseOptionsProvider } from "./DatabaseOptionsProvider"; +import { DatabaseValuesProvider } from "./DatabaseValuesProvider"; +import { extractDatabaseProps, getReadonly } from "./utils"; + +export class DatabaseSingleSelector> implements SingleSelector { + type = "single" as const; + attributeType: "string" | "big" | "boolean" | "date" = "string"; + selectorType: "context" | "database" | "static" = "database"; + status: Status = "unavailable"; + options: DatabaseOptionsProvider; + caption: DatabaseCaptionsProvider; + clearable = false; + currentId: string | null = null; + readOnly = false; + customContentType: OptionsSourceDatabaseCustomContentTypeEnum = "no"; + validation?: string = undefined; + onEnterEvent?: () => void; + onLeaveEvent?: () => void; + values: DatabaseValuesProvider; + protected _objectsMap: Map = new Map(); + protected _attr: R | undefined; + private selection?: SelectionSingleValue; + + constructor() { + this.caption = new DatabaseCaptionsProvider(this._objectsMap); + this.options = new DatabaseOptionsProvider(this.caption, this._objectsMap); + this.values = new DatabaseValuesProvider(this._objectsMap); + } + + updateProps(props: SelectionControlsContainerProps): void { + const { + targetAttribute, + captionProvider, + clearable, + customContent, + customContentType, + ds, + emptyOption, + valueSourceAttribute + } = extractDatabaseProps(props); + + if (ds.status === "loading") { + return; + } + + this._attr = targetAttribute as R; + this.readOnly = getReadonly(targetAttribute, props.customEditability, props.customEditabilityExpression); + + this.caption.updateProps({ + emptyOptionText: emptyOption, + formattingAttributeOrExpression: captionProvider, + customContent, + customContentType, + attribute: valueSourceAttribute, + caption: targetAttribute?.displayValue + }); + + this.options._updateProps({ + ds + }); + + this.values.updateProps({ + valueAttribute: valueSourceAttribute + }); + + if (!ds || ds.status === "unavailable" || !emptyOption || emptyOption.status !== "available") { + this.status = "unavailable"; + this.currentId = null; + this.clearable = false; + return; + } + + if (targetAttribute?.status === "available") { + if (targetAttribute.value && !this.currentId) { + const allOptions = this.options.getAll(); + const obj = allOptions.find(option => { + return _valuesIsEqual(targetAttribute.value, this.values.get(option)); + }); + if (obj) { + this.currentId = obj; + } + } else if (!targetAttribute.value && this.currentId) { + this.currentId = null; + if (this.selection?.selection) { + this.selection.setSelection(undefined); + } + } + } + + this.status = targetAttribute?.status ?? ds.status; + this.validation = targetAttribute?.validation; + this.selection = props.optionsSourceDatabaseItemSelection as SelectionSingleValue; + + this.clearable = clearable; + this.customContentType = customContentType; + + if (this.selection && this.selection.selection === undefined) { + const objectId = this.options.getAll().find(option => { + return targetAttribute && _valuesIsEqual(targetAttribute?.value, this.values.get(option)); + }); + if (objectId) { + this.selection.setSelection(this.options._optionToValue(objectId)); + } + } + } + + setValue(objectId: string | null): void { + const value = this.values.get(objectId) as T; + this._attr?.setValue(value); + if (objectId !== (this.selection?.selection?.id ?? "")) { + this.selection?.setSelection(this.options._optionToValue(objectId)); + } + this.currentId = objectId; + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseValuesProvider.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseValuesProvider.ts new file mode 100644 index 0000000000..a506bb019e --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseValuesProvider.ts @@ -0,0 +1,38 @@ +import { ListAttributeValue, ObjectItem } from "mendix"; +import { Big } from "big.js"; +import { ValuesProvider } from "../types"; + +interface DatabaseValuesProviderProps { + valueAttribute?: ListAttributeValue; +} + +export class DatabaseValuesProvider implements ValuesProvider { + private _objectsMap: Map; + private valueAttribute?: ListAttributeValue; + + constructor(objectsMap: Map) { + this._objectsMap = objectsMap; + } + + updateProps(props: DatabaseValuesProviderProps): void { + this.valueAttribute = props.valueAttribute; + } + + get(key: string | null): string | Big | undefined { + if (!key) { + return undefined; + } + + const item = this._objectsMap.get(key); + if (!item) { + return undefined; + } + + if (this.valueAttribute) { + return this.valueAttribute.get(item).value; + } + + // Default to using the object ID if no value attribute is specified + return item.id; + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/utils.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/utils.ts new file mode 100644 index 0000000000..ca7fb069a7 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/utils.ts @@ -0,0 +1,73 @@ +import { + DynamicValue, + EditableValue, + ListAttributeValue, + ListExpressionValue, + ListValue, + ListWidgetValue, + ValueStatus +} from "mendix"; +import { Big } from "big.js"; +import { + SelectionControlsContainerProps, + CustomEditabilityEnum, + OptionsSourceDatabaseCustomContentTypeEnum +} from "../../../typings/SelectionControlsProps"; + +export function extractDatabaseProps(props: SelectionControlsContainerProps): { + targetAttribute: EditableValue | undefined; + captionProvider: ListAttributeValue | ListExpressionValue; + clearable: boolean; + customContent: ListWidgetValue | undefined; + customContentType: OptionsSourceDatabaseCustomContentTypeEnum; + ds: ListValue; + emptyOption: DynamicValue; + valueSourceAttribute: ListAttributeValue | undefined; +} { + const targetAttribute = props.databaseAttributeString; + const ds = props.optionsSourceDatabaseDataSource!; + + // Determine caption provider based on caption type + const captionProvider = + props.optionsSourceDatabaseCaptionType === "attribute" + ? props.optionsSourceDatabaseCaptionAttribute! + : props.optionsSourceDatabaseCaptionExpression!; + + // For simplicity, we'll create a basic empty option + const emptyOption: DynamicValue = { + status: ValueStatus.Available, + value: "" + }; + + // Selection controls don't need clearable like combobox does + const clearable = false; + + const customContent = props.optionsSourceDatabaseCustomContent; + const customContentType = props.optionsSourceDatabaseCustomContentType; + const valueSourceAttribute = props.optionsSourceDatabaseValueAttribute; + + return { + targetAttribute, + captionProvider, + clearable, + customContent, + customContentType, + ds, + emptyOption, + valueSourceAttribute + }; +} + +export function getReadonly( + attribute: EditableValue | undefined, + customEditability: CustomEditabilityEnum, + customEditabilityExpression: DynamicValue +): boolean { + if (customEditability === "never") { + return true; + } + if (customEditability === "conditionally" && customEditabilityExpression.value === false) { + return true; + } + return attribute?.readOnly ?? false; +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumAndBooleanSimpleCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumAndBooleanSimpleCaptionsProvider.tsx new file mode 100644 index 0000000000..26b8397d7c --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumAndBooleanSimpleCaptionsProvider.tsx @@ -0,0 +1,36 @@ +import { DynamicValue, EditableValue } from "mendix"; +import { ReactNode, createElement } from "react"; +import { CaptionsProvider } from "../types"; + +interface EnumAndBooleanSimpleCaptionsProviderProps { + emptyOptionText?: DynamicValue; + attribute: EditableValue; +} + +export class EnumAndBooleanSimpleCaptionsProvider implements CaptionsProvider { + private attr?: EditableValue; + emptyCaption = ""; + formatter?: undefined; + + updateProps(props: EnumAndBooleanSimpleCaptionsProviderProps): void { + this.attr = props.attribute; + if (!props.emptyOptionText || props.emptyOptionText.status === "unavailable") { + this.emptyCaption = ""; + } else { + this.emptyCaption = props.emptyOptionText.value!; + } + } + + get(value: string | boolean | null): string { + if (value === null) { + return this.emptyCaption; + } + + return this.attr?.formatter.format(value) ?? ""; + } + + render(value: string | null, _placement?: "label" | "options", _htmlFor?: string): ReactNode { + const caption = this.get(value); + return {caption}; + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBoolOptionsProvider.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBoolOptionsProvider.ts new file mode 100644 index 0000000000..aee367bfef --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBoolOptionsProvider.ts @@ -0,0 +1,29 @@ +import { EditableValue } from "mendix"; +import { BaseOptionsProvider } from "../BaseOptionsProvider"; + +export class EnumBoolOptionsProvider extends BaseOptionsProvider< + T, + { attribute: EditableValue } +> { + private isBoolean = false; + + _updateProps(props: { attribute: EditableValue; filterType: "none" }): void { + if (props.attribute.status === "unavailable") { + this.options = []; + } + this.options = (props.attribute.universe ?? []).map(o => o.toString()); + this.isBoolean = typeof props.attribute.universe?.[0] === "boolean"; + } + + _optionToValue(value: string | null): T | undefined { + if (this.isBoolean) { + return (value === "true") as T; + } else { + return (value ?? undefined) as T; + } + } + + _valueToOption(value: string | boolean | undefined): string | null { + return value?.toString() ?? null; + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts new file mode 100644 index 0000000000..06972a19d4 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts @@ -0,0 +1,71 @@ +import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action"; +import { ActionValue, EditableValue } from "mendix"; +import { + SelectionControlsContainerProps, + OptionsSourceAssociationCustomContentTypeEnum +} from "../../../typings/SelectionControlsProps"; +import { SingleSelector, Status } from "../types"; +import { EnumAndBooleanSimpleCaptionsProvider } from "./EnumAndBooleanSimpleCaptionsProvider"; +import { EnumBoolOptionsProvider } from "./EnumBoolOptionsProvider"; +import { extractEnumerationProps } from "./utils"; + +export class EnumBooleanSingleSelector implements SingleSelector { + status: Status = "unavailable"; + type = "single" as const; + validation?: string = undefined; + private isBoolean = false; + private _attr: EditableValue | undefined; + private onChangeEvent?: ActionValue; + onEnterEvent?: () => void; + onLeaveEvent?: () => void; + + currentId: string | null = null; + caption: EnumAndBooleanSimpleCaptionsProvider; + options: EnumBoolOptionsProvider; + customContentType: OptionsSourceAssociationCustomContentTypeEnum = "no"; + clearable = true; + readOnly = false; + + constructor() { + this.caption = new EnumAndBooleanSimpleCaptionsProvider(); + this.options = new EnumBoolOptionsProvider(this.caption); + } + + updateProps(props: SelectionControlsContainerProps): void { + const [attr, emptyOption, clearable, filterType] = extractEnumerationProps(props); + this._attr = attr; + + this.caption.updateProps({ + attribute: attr, + emptyOptionText: emptyOption + }); + + this.options._updateProps({ + attribute: attr, + filterType + }); + + if (!attr || attr.status === "unavailable" || !emptyOption || emptyOption.status === "unavailable") { + this.status = "unavailable"; + this.currentId = null; + this.clearable = true; + + return; + } + + this.onChangeEvent = props.onChangeEvent; + this.onEnterEvent = props.onEnterEvent ? () => executeAction(props.onEnterEvent) : undefined; + this.onLeaveEvent = props.onLeaveEvent ? () => executeAction(props.onLeaveEvent) : undefined; + this.status = attr.status; + this.isBoolean = typeof attr.universe?.[0] === "boolean"; + this.clearable = this.isBoolean ? false : clearable; + this.currentId = attr.value?.toString() ?? null; + this.readOnly = attr.readOnly; + this.validation = attr.validation; + } + + setValue(value: string | null): void { + this._attr?.setValue(this.options._optionToValue(value)); + executeAction(this.onChangeEvent); + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/utils.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/utils.ts new file mode 100644 index 0000000000..cf181720a6 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/utils.ts @@ -0,0 +1,22 @@ +import { DynamicValue, EditableValue, ValueStatus } from "mendix"; +import { SelectionControlsContainerProps } from "../../../typings/SelectionControlsProps"; + +export function extractEnumerationProps( + props: SelectionControlsContainerProps +): [EditableValue, DynamicValue, boolean, "none"] { + const attribute = props.optionsSourceType === "enumeration" ? props.attributeEnumeration : props.attributeBoolean; + + // For simplicity, we'll create a basic empty option + const emptyOption: DynamicValue = { + status: ValueStatus.Available, + value: "" + }; + + // Selection controls don't need clearable like combobox does + const clearable = false; + + // No filtering needed for radio buttons/checkboxes + const filterType = "none" as const; + + return [attribute, emptyOption, clearable, filterType]; +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx new file mode 100644 index 0000000000..0e805dcf20 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx @@ -0,0 +1,64 @@ +import { DynamicValue } from "mendix"; +import { ReactNode, createElement } from "react"; +import { + StaticDataSourceCustomContentTypeEnum, + OptionsSourceStaticDataSourceType +} from "../../../typings/SelectionControlsProps"; +import { CaptionsProvider } from "../types"; + +interface StaticCaptionsProviderProps { + emptyOptionText?: DynamicValue; + customContentType: StaticDataSourceCustomContentTypeEnum; + caption?: string; +} + +export class StaticCaptionsProvider implements CaptionsProvider { + emptyCaption = ""; + formatter?: undefined; + private _objectsMap: Map; + private customContentType: StaticDataSourceCustomContentTypeEnum = "no"; + + constructor(objectsMap: Map) { + this._objectsMap = objectsMap; + } + + updateProps(props: StaticCaptionsProviderProps): void { + if (!props.emptyOptionText || props.emptyOptionText.status === "unavailable") { + this.emptyCaption = ""; + } else { + this.emptyCaption = props.emptyOptionText.value!; + } + this.customContentType = props.customContentType; + } + + get(value: string | null): string { + if (value === null) { + return this.emptyCaption; + } + + const item = this._objectsMap.get(value); + if (!item) { + return ""; + } + + return item.staticDataSourceCaption.value || ""; + } + + render(value: string | null, _placement?: "label" | "options", _htmlFor?: string): ReactNode { + if (value === null) { + return {this.emptyCaption}; + } + + const item = this._objectsMap.get(value); + if (!item) { + return ; + } + + if (this.customContentType === "yes" && item.staticDataSourceCustomContent) { + return item.staticDataSourceCustomContent; + } + + const caption = item.staticDataSourceCaption.value || ""; + return {caption}; + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticOptionsProvider.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticOptionsProvider.ts new file mode 100644 index 0000000000..173b1dd515 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticOptionsProvider.ts @@ -0,0 +1,41 @@ +import { OptionsSourceStaticDataSourceType } from "../../../typings/SelectionControlsProps"; +import { BaseOptionsProvider } from "../BaseOptionsProvider"; +import { CaptionsProvider } from "../types"; + +export class StaticOptionsProvider extends BaseOptionsProvider< + OptionsSourceStaticDataSourceType | undefined, + { ds: OptionsSourceStaticDataSourceType[] } +> { + constructor( + caption: CaptionsProvider, + private _objectsMap: Map + ) { + super(caption); + } + + _updateProps(props: { ds: OptionsSourceStaticDataSourceType[] }): void { + this._objectsMap.clear(); + this.options = []; + + props.ds.forEach((item, index) => { + const key = index.toString(); + this._objectsMap.set(key, item); + this.options.push(key); + }); + } + + _optionToValue(option: string | null): OptionsSourceStaticDataSourceType | undefined { + return this._objectsMap.get(option || ""); + } + + _valueToOption(value: OptionsSourceStaticDataSourceType | undefined): string | null { + if (!value) return null; + + for (const [key, item] of this._objectsMap.entries()) { + if (item === value) { + return key; + } + } + return null; + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts new file mode 100644 index 0000000000..b5c528d0fa --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts @@ -0,0 +1,93 @@ +import { ActionValue, EditableValue } from "mendix"; +import { Big } from "big.js"; +import { + SelectionControlsContainerProps, + StaticDataSourceCustomContentTypeEnum, + OptionsSourceStaticDataSourceType +} from "../../../typings/SelectionControlsProps"; +import { SingleSelector, Status } from "../types"; +import { StaticOptionsProvider } from "./StaticOptionsProvider"; +import { StaticCaptionsProvider } from "./StaticCaptionsProvider"; +import { extractStaticProps } from "./utils"; +import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action"; +import { _valuesIsEqual } from "../utils"; + +export class StaticSingleSelector implements SingleSelector { + type = "single" as const; + attributeType: "string" | "big" | "boolean" | "date" = "string"; + selectorType: "context" | "database" | "static" = "static"; + status: Status = "unavailable"; + options: StaticOptionsProvider; + caption: StaticCaptionsProvider; + clearable = false; + currentId: string | null = null; + readOnly = false; + customContentType: StaticDataSourceCustomContentTypeEnum = "no"; + validation?: string = undefined; + onEnterEvent?: () => void; + onLeaveEvent?: () => void; + protected _attr: EditableValue | undefined; + private onChangeEvent?: ActionValue; + private _objectsMap: Map = new Map(); + + constructor() { + this.caption = new StaticCaptionsProvider(this._objectsMap); + this.options = new StaticOptionsProvider(this.caption, this._objectsMap); + } + + updateProps(props: SelectionControlsContainerProps): void { + const [attr, ds, emptyOption, clearable, onChangeEvent, customContentType] = extractStaticProps(props); + this._attr = attr; + this.caption.updateProps({ + emptyOptionText: emptyOption, + customContentType, + caption: this._attr.displayValue + }); + + this.options._updateProps({ + ds + }); + + if ( + !attr || + attr.status === "unavailable" || + !ds || + ds[0].staticDataSourceValue.status === "unavailable" || + ds[0].staticDataSourceCaption.status === "unavailable" || + !emptyOption || + emptyOption.status === "unavailable" + ) { + this.status = "unavailable"; + this.currentId = null; + this.clearable = false; + return; + } + if (ds.length > 0 && ds[0].staticDataSourceValue.status === "available" && attr.value !== "") { + const index = ds.findIndex(option => _valuesIsEqual(option.staticDataSourceValue.value, attr.value)); + if (index !== -1) { + this.currentId = index.toString(); + } + } + this.clearable = clearable; + this.status = attr.status; + this.readOnly = attr.readOnly; + this.onChangeEvent = onChangeEvent; + this.onEnterEvent = props.onEnterEvent ? () => executeAction(props.onEnterEvent) : undefined; + this.onLeaveEvent = props.onLeaveEvent ? () => executeAction(props.onLeaveEvent) : undefined; + this.customContentType = customContentType; + this.validation = attr.validation; + this.attributeType = + typeof attr.universe?.[0] === "boolean" + ? "boolean" + : attr.formatter?.type === "datetime" + ? "date" + : "string"; + } + + setValue(key: string | null): void { + const value = this._objectsMap.get(key || ""); + this._attr?.setValue(value?.staticDataSourceValue.value); + this.currentId = key; + executeAction(this.onChangeEvent); + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/utils.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/utils.ts new file mode 100644 index 0000000000..3666c0f780 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/utils.ts @@ -0,0 +1,35 @@ +import { ActionValue, DynamicValue, EditableValue, ValueStatus } from "mendix"; +import { Big } from "big.js"; +import { + SelectionControlsContainerProps, + StaticDataSourceCustomContentTypeEnum, + OptionsSourceStaticDataSourceType +} from "../../../typings/SelectionControlsProps"; + +export function extractStaticProps( + props: SelectionControlsContainerProps +): [ + EditableValue, + OptionsSourceStaticDataSourceType[], + DynamicValue, + boolean, + ActionValue | undefined, + StaticDataSourceCustomContentTypeEnum +] { + const attribute = props.staticAttribute; + const ds = props.optionsSourceStaticDataSource; + + // For simplicity, we'll create a basic empty option + const emptyOption: DynamicValue = { + status: ValueStatus.Available, + value: "" + }; + + // Selection controls don't need clearable like combobox does + const clearable = false; + + const onChangeEvent = props.onChangeEvent; + const customContentType = props.staticDataSourceCustomContentType; + + return [attribute, ds, emptyOption, clearable, onChangeEvent, customContentType]; +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/getSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/getSelector.ts new file mode 100644 index 0000000000..62d8ed807c --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/getSelector.ts @@ -0,0 +1,32 @@ +import { SelectionControlsContainerProps } from "../../typings/SelectionControlsProps"; +import { EnumBooleanSingleSelector } from "./EnumBool/EnumBooleanSingleSelector"; +import { StaticSingleSelector } from "./Static/StaticSingleSelector"; +import { AssociationSingleSelector } from "./Association/AssociationSingleSelector"; +import { AssociationMultiSelector } from "./Association/AssociationMultiSelector"; +import { DatabaseSingleSelector } from "./Database/DatabaseSingleSelector"; +import { DatabaseMultiSelector } from "./Database/DatabaseMultiSelector"; +import { Selector } from "./types"; + +export function getSelector(props: SelectionControlsContainerProps): Selector { + if (props.source === "context") { + if (["enumeration", "boolean"].includes(props.optionsSourceType)) { + return new EnumBooleanSingleSelector(); + } else if (props.optionsSourceType === "association") { + return props.attributeAssociation.type === "Reference" + ? new AssociationSingleSelector() + : new AssociationMultiSelector(); + } else { + throw new Error(`'optionsSourceType' of type '${props.optionsSourceType}' is not supported`); + } + } else if (props.source === "database") { + if (props.optionsSourceDatabaseItemSelection?.type === "Multi") { + return new DatabaseMultiSelector(); + } else { + return new DatabaseSingleSelector(); + } + } else if (props.source === "static") { + return new StaticSingleSelector(); + } + + throw new Error(`Source of type '${props.source}' is not supported`); +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts new file mode 100644 index 0000000000..5e1a329b6a --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts @@ -0,0 +1,102 @@ +import { DynamicValue, ListAttributeValue, ListExpressionValue, ListValue } from "mendix"; +import { ReactNode } from "react"; +import { + SelectionControlsContainerProps, + OptionsSourceAssociationCustomContentTypeEnum, + ReadOnlyStyleEnum +} from "../../typings/SelectionControlsProps"; + +export type Status = "unavailable" | "loading" | "available"; +export type CaptionPlacement = "label" | "options"; +export type SelectionType = "single" | "multi"; +export type Selector = SingleSelector | MultiSelector; +export type SortOrder = "asc" | "desc"; + +export interface CaptionsProvider { + get(value: string | null): string; + render(value: (string | null) | (number | null), placement?: CaptionPlacement, htmlFor?: string): ReactNode; + emptyCaption: string; + formatter?: ListExpressionValue | ListAttributeValue; +} + +export interface ValuesProvider { + get(key: string | null): T | undefined; +} + +export interface OptionsProvider { + status: Status; + searchTerm: string; + sortOrder?: SortOrder; + + getAll(): string[]; + datasourceFilter?: ListValue["filter"] | undefined; + + // search related + setSearchTerm(term: string): void; + onAfterSearchTermChange(callback: () => void): void; + + // lazy loading related + hasMore?: boolean; + loadMore?(): void; + isLoading: boolean; + + // for private use + _updateProps(props: P): void; + _optionToValue(option: string | null): T | undefined; + _valueToOption(value: T | undefined): string | null; +} + +interface SelectorBase { + updateProps(props: SelectionControlsContainerProps): void; + status: Status; + attributeType?: "string" | "big" | "boolean" | "date"; + selectorType?: "context" | "database" | "static"; + type: T; + readOnly: boolean; + validation?: string; + + // options related + options: OptionsProvider; + + // caption related + caption: CaptionsProvider; + + // value related + clearable: boolean; + + currentId: V | null; + setValue(value: V | null): void; + + customContentType: OptionsSourceAssociationCustomContentTypeEnum; + + onEnterEvent?: () => void; + onLeaveEvent?: () => void; +} + +export interface SingleSelector extends SelectorBase<"single", string> {} + +export interface MultiSelector extends SelectorBase<"multi", string[]> { + getOptions(): string[]; +} + +export interface SelectionBaseProps { + inputId: string; + labelId?: string; + readOnlyStyle: ReadOnlyStyleEnum; + selector: Selector; + tabIndex: number; + ariaRequired: DynamicValue; + a11yConfig: { + ariaLabels: { + clearSelection: string; + removeSelection: string; + }; + a11yStatusMessage: A11yStatusMessage; + }; +} + +export interface A11yStatusMessage { + a11ySelectedValue: string; + a11yOptionsAvailable: string; + a11yInstructions: string; +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts new file mode 100644 index 0000000000..7a9669a541 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts @@ -0,0 +1,20 @@ +import { Big } from "big.js"; + +export function _valuesIsEqual( + value1: string | Big | boolean | Date | undefined, + value2: string | Big | boolean | Date | undefined +): boolean { + if (value1 === value2) { + return true; + } + + if (value1 instanceof Big && value2 instanceof Big) { + return value1.eq(value2); + } + + if (value1 instanceof Date && value2 instanceof Date) { + return value1.getTime() === value2.getTime(); + } + + return false; +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/hooks/useActionEvents.ts b/packages/pluggableWidgets/selection-controls-web/src/hooks/useActionEvents.ts new file mode 100644 index 0000000000..892533668e --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/hooks/useActionEvents.ts @@ -0,0 +1,40 @@ +import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action"; +import { FocusEvent, useMemo } from "react"; +import { SelectionControlsContainerProps } from "../../typings/SelectionControlsProps"; +import { Selector } from "../helpers/types"; + +type UseActionEventsReturnValue = { + onFocus: (e: FocusEvent) => void; + onBlur: (e: FocusEvent) => void; +}; + +interface useActionEventsProps extends Pick { + selector: Selector; +} + +export function useActionEvents(props: useActionEventsProps): UseActionEventsReturnValue { + return useMemo(() => { + return { + onFocus: (e: FocusEvent): void => { + const { relatedTarget, currentTarget } = e; + if (!currentTarget?.contains(relatedTarget)) { + executeAction(props.onEnterEvent); + + if (props.selector.onEnterEvent) { + props.selector.onEnterEvent(); + } + } + }, + onBlur: (e: FocusEvent): void => { + const { relatedTarget, currentTarget } = e; + if (!currentTarget?.contains(relatedTarget)) { + executeAction(props.onLeaveEvent); + + if (props.selector.onLeaveEvent) { + props.selector.onLeaveEvent(); + } + } + } + }; + }, [props.onEnterEvent, props.onLeaveEvent, props.selector]); +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/hooks/useGetSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/hooks/useGetSelector.ts new file mode 100644 index 0000000000..92b491c3ba --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/hooks/useGetSelector.ts @@ -0,0 +1,15 @@ +import { useRef, useState } from "react"; +import { SelectionControlsContainerProps } from "../../typings/SelectionControlsProps"; +import { getSelector } from "../helpers/getSelector"; +import { Selector } from "../helpers/types"; + +export function useGetSelector(props: SelectionControlsContainerProps): Selector { + const selectorRef = useRef(undefined); + const [, setInput] = useState({}); + if (!selectorRef.current) { + selectorRef.current = getSelector(props); + selectorRef.current.options.onAfterSearchTermChange(() => setInput({})); + } + selectorRef.current.updateProps(props); + return selectorRef.current; +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/package.xml b/packages/pluggableWidgets/selection-controls-web/src/package.xml new file mode 100644 index 0000000000..a11d2e0592 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/package.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss b/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss new file mode 100644 index 0000000000..93ef068c7f --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss @@ -0,0 +1,123 @@ +// Selection Controls Widget Styles +.widget-selection-controls { + display: block; + position: relative; + + // Placeholder styles + &-placeholder { + padding: 12px; + background-color: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 4px; + color: #6c757d; + + &-content { + text-align: center; + } + } + + // No options placeholder + &-no-options { + padding: 12px; + text-align: center; + color: #6c757d; + font-style: italic; + } + + // Radio button styles + &-radio { + &-list { + display: flex; + flex-direction: column; + gap: 8px; + } + + &-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + border-radius: 4px; + cursor: pointer; + + &:hover { + background-color: #f8f9fa; + } + + &[aria-checked="true"] { + background-color: #e3f2fd; + } + + input[type="radio"] { + margin: 0; + } + + label { + margin: 0; + cursor: pointer; + flex: 1; + } + } + } + + // Checkbox styles + &-checkbox { + &-list { + display: flex; + flex-direction: column; + gap: 8px; + } + + &-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + border-radius: 4px; + cursor: pointer; + + &:hover { + background-color: #f8f9fa; + } + + &[aria-checked="true"] { + background-color: #e3f2fd; + } + + input[type="checkbox"] { + margin: 0; + } + + label { + margin: 0; + cursor: pointer; + flex: 1; + } + } + } + + // Read-only styles + &.readonly { + .widget-selection-controls-radio-item, + .widget-selection-controls-checkbox-item { + cursor: default; + + &:hover { + background-color: transparent; + } + } + + input { + pointer-events: none; + } + } + + // Accessibility improvements + &-radio-item, + &-checkbox-item { + &:focus-within { + outline: 2px solid #0066cc; + outline-offset: 2px; + } + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/tsconfig.json b/packages/pluggableWidgets/selection-controls-web/tsconfig.json new file mode 100644 index 0000000000..a2a5b87e60 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/tsconfig.json @@ -0,0 +1,31 @@ +{ + "include": ["./src", "./typings"], + "compilerOptions": { + "baseUrl": "./", + "noEmitOnError": true, + "sourceMap": true, + "module": "esnext", + "target": "es6", + "lib": ["esnext", "dom"], + "types": ["jest", "node"], + "moduleResolution": "node", + "declaration": false, + "noLib": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "strict": true, + "strictFunctionTypes": false, + "skipLibCheck": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "jsx": "react", + "jsxFactory": "createElement", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "useUnknownInCatchVariables": false, + "exactOptionalPropertyTypes": false, + "paths": { + "react-hot-loader/root": ["./hot-typescript.ts"] + } + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts b/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts new file mode 100644 index 0000000000..513d9dc8fb --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts @@ -0,0 +1,121 @@ +/** + * This file was generated from SelectionControls.xml + * WARNING: All changes made to this file will be overwritten + * @author Mendix Widgets Framework Team + */ +import { ComponentType, ReactNode } from "react"; +import { ActionValue, DynamicValue, EditableValue, ListValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, ReferenceValue, ReferenceSetValue, SelectionSingleValue, SelectionMultiValue } from "mendix"; +import { Big } from "big.js"; + +export type SourceEnum = "context" | "database" | "static"; + +export type OptionsSourceTypeEnum = "association" | "enumeration" | "boolean"; + +export type OptionsSourceAssociationCaptionTypeEnum = "attribute" | "expression"; + +export type OptionsSourceDatabaseCaptionTypeEnum = "attribute" | "expression"; + +export interface OptionsSourceStaticDataSourceType { + staticDataSourceValue: DynamicValue; + staticDataSourceCustomContent: ReactNode; + staticDataSourceCaption: DynamicValue; +} + +export type OptionsSourceAssociationCustomContentTypeEnum = "yes" | "listItem" | "no"; + +export type OptionsSourceDatabaseCustomContentTypeEnum = "yes" | "listItem" | "no"; + +export type StaticDataSourceCustomContentTypeEnum = "yes" | "listItem" | "no"; + +export type CustomEditabilityEnum = "default" | "never" | "conditionally"; + +export type ReadOnlyStyleEnum = "bordered" | "text"; + +export interface OptionsSourceStaticDataSourcePreviewType { + staticDataSourceValue: string; + staticDataSourceCustomContent: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; + staticDataSourceCaption: string; +} + +export interface SelectionControlsContainerProps { + name: string; + tabIndex?: number; + id: string; + source: SourceEnum; + optionsSourceType: OptionsSourceTypeEnum; + attributeEnumeration: EditableValue; + attributeBoolean: EditableValue; + optionsSourceDatabaseDataSource?: ListValue; + optionsSourceDatabaseItemSelection?: SelectionSingleValue | SelectionMultiValue; + optionsSourceAssociationCaptionType: OptionsSourceAssociationCaptionTypeEnum; + optionsSourceDatabaseCaptionType: OptionsSourceDatabaseCaptionTypeEnum; + optionsSourceAssociationCaptionAttribute?: ListAttributeValue; + optionsSourceDatabaseCaptionAttribute?: ListAttributeValue; + optionsSourceAssociationCaptionExpression?: ListExpressionValue; + optionsSourceDatabaseCaptionExpression?: ListExpressionValue; + optionsSourceDatabaseValueAttribute?: ListAttributeValue; + databaseAttributeString?: EditableValue; + attributeAssociation: ReferenceValue | ReferenceSetValue; + optionsSourceAssociationDataSource?: ListValue; + staticAttribute: EditableValue; + optionsSourceStaticDataSource: OptionsSourceStaticDataSourceType[]; + optionsSourceAssociationCustomContentType: OptionsSourceAssociationCustomContentTypeEnum; + optionsSourceAssociationCustomContent?: ListWidgetValue; + optionsSourceDatabaseCustomContentType: OptionsSourceDatabaseCustomContentTypeEnum; + optionsSourceDatabaseCustomContent?: ListWidgetValue; + staticDataSourceCustomContentType: StaticDataSourceCustomContentTypeEnum; + customEditability: CustomEditabilityEnum; + customEditabilityExpression: DynamicValue; + readOnlyStyle: ReadOnlyStyleEnum; + onChangeEvent?: ActionValue; + onEnterEvent?: ActionValue; + onLeaveEvent?: ActionValue; + ariaRequired: DynamicValue; + clearButtonAriaLabel?: DynamicValue; + removeValueAriaLabel?: DynamicValue; + a11ySelectedValue?: DynamicValue; + a11yOptionsAvailable?: DynamicValue; + a11yInstructions?: DynamicValue; +} + +export interface SelectionControlsPreviewProps { + readOnly: boolean; + renderMode: "design" | "xray" | "structure"; + translate: (text: string) => string; + source: SourceEnum; + optionsSourceType: OptionsSourceTypeEnum; + attributeEnumeration: string; + attributeBoolean: string; + optionsSourceDatabaseDataSource: {} | { caption: string } | { type: string } | null; + optionsSourceDatabaseItemSelection: "Single" | "Multi" | "None"; + optionsSourceAssociationCaptionType: OptionsSourceAssociationCaptionTypeEnum; + optionsSourceDatabaseCaptionType: OptionsSourceDatabaseCaptionTypeEnum; + optionsSourceAssociationCaptionAttribute: string; + optionsSourceDatabaseCaptionAttribute: string; + optionsSourceAssociationCaptionExpression: string; + optionsSourceDatabaseCaptionExpression: string; + optionsSourceDatabaseValueAttribute: string; + databaseAttributeString: string; + attributeAssociation: string; + optionsSourceAssociationDataSource: {} | { caption: string } | { type: string } | null; + staticAttribute: string; + optionsSourceStaticDataSource: OptionsSourceStaticDataSourcePreviewType[]; + optionsSourceAssociationCustomContentType: OptionsSourceAssociationCustomContentTypeEnum; + optionsSourceAssociationCustomContent: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; + optionsSourceDatabaseCustomContentType: OptionsSourceDatabaseCustomContentTypeEnum; + optionsSourceDatabaseCustomContent: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; + staticDataSourceCustomContentType: StaticDataSourceCustomContentTypeEnum; + customEditability: CustomEditabilityEnum; + customEditabilityExpression: string; + readOnlyStyle: ReadOnlyStyleEnum; + onChangeEvent: {} | null; + onChangeDatabaseEvent: {} | null; + onEnterEvent: {} | null; + onLeaveEvent: {} | null; + ariaRequired: string; + clearButtonAriaLabel: string; + removeValueAriaLabel: string; + a11ySelectedValue: string; + a11yOptionsAvailable: string; + a11yInstructions: string; +} diff --git a/packages/pluggableWidgets/selection-controls-web/typings/declare-svg.ts b/packages/pluggableWidgets/selection-controls-web/typings/declare-svg.ts new file mode 100644 index 0000000000..6949b2e571 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/typings/declare-svg.ts @@ -0,0 +1,4 @@ +declare module "*.svg" { + const value: string; + export default value; +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 07ca88ba50..2d5e5ef71c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2136,6 +2136,46 @@ importers: specifier: ^1.1.3 version: 1.1.3(rollup@3.29.5) + packages/pluggableWidgets/selection-controls-web: + dependencies: + classnames: + specifier: ^2.3.2 + version: 2.5.1 + devDependencies: + '@mendix/automation-utils': + specifier: workspace:* + version: link:../../../automation/utils + '@mendix/eslint-config-web-widgets': + specifier: workspace:* + version: link:../../shared/eslint-config-web-widgets + '@mendix/pluggable-widgets-tools': + specifier: 10.21.2 + version: 10.21.2(@jest/transform@29.7.0)(@jest/types@29.6.3)(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/babel__core@7.20.3)(@types/node@22.14.1)(picomatch@4.0.2)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.3(@babel/core@7.27.4)(@babel/preset-env@7.26.9(@babel/core@7.27.4))(@types/react@18.2.36)(react@18.2.0))(react@18.2.0)(tslib@2.8.1) + '@mendix/prettier-config-web-widgets': + specifier: workspace:* + version: link:../../shared/prettier-config-web-widgets + '@mendix/run-e2e': + specifier: workspace:^* + version: link:../../../automation/run-e2e + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit + '@mendix/widget-plugin-grid': + specifier: workspace:* + version: link:../../shared/widget-plugin-grid + '@mendix/widget-plugin-hooks': + specifier: workspace:* + version: link:../../shared/widget-plugin-hooks + '@mendix/widget-plugin-platform': + specifier: workspace:* + version: link:../../shared/widget-plugin-platform + '@mendix/widget-plugin-test-utils': + specifier: workspace:* + version: link:../../shared/widget-plugin-test-utils + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + packages/pluggableWidgets/selection-helper-web: devDependencies: '@mendix/automation-utils': @@ -12990,7 +13030,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/gen-mapping@0.3.8': @@ -14939,7 +14979,7 @@ snapshots: acorn-globals@7.0.1: dependencies: - acorn: 8.14.0 + acorn: 8.15.0 acorn-walk: 8.2.0 acorn-import-attributes@1.9.5(acorn@8.12.1): From 27211bb404bc6c1dc06b8f931f3078067f6d301c Mon Sep 17 00:00:00 2001 From: gjulivan Date: Fri, 11 Jul 2025 11:18:15 +0200 Subject: [PATCH 02/20] feat: initial update --- .../src/SelectionControls.editorConfig.ts | 10 +- .../src/SelectionControls.xml | 21 +- .../CheckboxSelection/CheckboxSelection.tsx | 9 +- .../RadioSelection/RadioSelection.tsx | 5 +- .../AssociationSimpleCaptionsProvider.tsx | 6 +- .../Association/BaseAssociationSelector.ts | 4 +- .../src/helpers/Association/utils.ts | 6 +- .../Database/DatabaseCaptionsProvider.tsx | 6 +- .../helpers/Database/DatabaseMultiSelector.ts | 4 +- .../Database/DatabaseSingleSelector.ts | 4 +- .../src/helpers/Database/utils.ts | 6 +- .../EnumBool/EnumBooleanSingleSelector.ts | 4 +- .../helpers/Static/StaticCaptionsProvider.tsx | 6 +- .../helpers/Static/StaticSingleSelector.ts | 4 +- .../src/helpers/Static/utils.ts | 6 +- .../src/helpers/types.ts | 4 +- .../src/ui/SelectionControls.scss | 246 +++++++++--------- .../typings/SelectionControlsProps.d.ts | 14 +- 18 files changed, 166 insertions(+), 199 deletions(-) diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts index bb9218790d..c7ef279d48 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts @@ -12,7 +12,6 @@ const DATABASE_SOURCE_CONFIG: Array = [ "optionsSourceDatabaseCaptionExpression", "optionsSourceDatabaseCaptionType", "optionsSourceDatabaseCustomContent", - "optionsSourceDatabaseCustomContentType", "optionsSourceDatabaseDataSource", "optionsSourceDatabaseValueAttribute", "optionsSourceDatabaseItemSelection", @@ -25,7 +24,6 @@ const ASSOCIATION_SOURCE_CONFIG: Array = [ "optionsSourceAssociationCaptionExpression", "optionsSourceAssociationCaptionType", "optionsSourceAssociationCustomContent", - "optionsSourceAssociationCustomContentType", "optionsSourceAssociationDataSource", "attributeAssociation" ]; @@ -42,7 +40,6 @@ export function getProperties( if (values.source === "context") { hidePropertiesIn(defaultProperties, values, [ "staticAttribute", - "staticDataSourceCustomContentType", "optionsSourceStaticDataSource", ...DATABASE_SOURCE_CONFIG ]); @@ -65,7 +62,7 @@ export function getProperties( hidePropertiesIn(defaultProperties, values, ["optionsSourceAssociationCaptionType"]); } - if (values.optionsSourceAssociationCustomContentType === "no") { + if (values.optionsSourceCustomContentType === "no") { hidePropertiesIn(defaultProperties, values, ["optionsSourceAssociationCustomContent"]); } } @@ -75,7 +72,6 @@ export function getProperties( "attributeBoolean", "optionsSourceType", "staticAttribute", - "staticDataSourceCustomContentType", "optionsSourceStaticDataSource", "onChangeEvent", ...ASSOCIATION_SOURCE_CONFIG @@ -88,7 +84,7 @@ export function getProperties( } else { hidePropertiesIn(defaultProperties, values, ["optionsSourceDatabaseCaptionAttribute"]); } - if (values.optionsSourceDatabaseCustomContentType === "no") { + if (values.optionsSourceCustomContentType === "no") { hidePropertiesIn(defaultProperties, values, ["optionsSourceDatabaseCustomContent"]); } if (values.optionsSourceDatabaseItemSelection === "Multi") { @@ -116,7 +112,7 @@ export function getProperties( ]); } - if (values.staticDataSourceCustomContentType === "no") { + if (values.optionsSourceCustomContentType === "no") { values.optionsSourceStaticDataSource.forEach((_, index) => { hideNestedPropertiesIn(defaultProperties, values, "optionsSourceStaticDataSource", index, [ "staticDataSourceCustomContent" diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml index 1735b021fd..235d94a7e7 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml @@ -182,12 +182,11 @@ - + Custom content Yes - List items only No @@ -195,28 +194,10 @@ Custom content - - Custom content - - - Yes - List items only - No - - Custom content - - Custom content - - - Yes - List items only - No - - diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx b/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx index d6ae5193c5..d35cf05201 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx @@ -7,7 +7,6 @@ export function CheckboxSelection({ tabIndex = 0, inputId, ariaRequired, - a11yConfig, readOnlyStyle }: SelectionBaseProps): ReactElement { const options = selector.getOptions(); @@ -78,7 +77,7 @@ export function CheckboxSelection({ {/* Clear all button */} - {!isReadOnly && currentIds.length > 0 && ( + {/* {!isReadOnly && currentIds.length > 0 && ( - )} + )} */} {/* Accessibility status message */} -
+ {/*
{currentIds.length > 0 && `${a11yConfig.a11yStatusMessage.a11ySelectedValue} ${currentIds.map(id => selector.caption.get(id)).join(", ")}`} {` ${a11yConfig.a11yStatusMessage.a11yOptionsAvailable} ${options.length}`} {` ${a11yConfig.a11yStatusMessage.a11yInstructions}`} -
+
*/} ); } diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx b/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx index 99c1716a2e..9045cc9dff 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx @@ -7,7 +7,6 @@ export function RadioSelection({ tabIndex = 0, inputId, ariaRequired, - a11yConfig, readOnlyStyle }: SelectionBaseProps): ReactElement { const options = selector.options.getAll(); @@ -78,11 +77,11 @@ export function RadioSelection({ {/* Accessibility status message */} -
+ {/*
{currentId && `${a11yConfig.a11yStatusMessage.a11ySelectedValue} ${selector.caption.get(currentId)}`} {` ${a11yConfig.a11yStatusMessage.a11yOptionsAvailable} ${options.length}`} {` ${a11yConfig.a11yStatusMessage.a11yInstructions}`} -
+
*/} ); } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx index 8623ff088b..8a10d6b129 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx @@ -1,13 +1,13 @@ import { DynamicValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, ObjectItem } from "mendix"; import { ReactNode, createElement } from "react"; -import { OptionsSourceAssociationCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; +import { OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; import { CaptionsProvider } from "../types"; interface AssociationSimpleCaptionsProviderProps { emptyOptionText?: DynamicValue; formattingAttributeOrExpression?: ListAttributeValue | ListExpressionValue; customContent?: ListWidgetValue; - customContentType: OptionsSourceAssociationCustomContentTypeEnum; + customContentType: OptionsSourceCustomContentTypeEnum; } export class AssociationSimpleCaptionsProvider implements CaptionsProvider { @@ -15,7 +15,7 @@ export class AssociationSimpleCaptionsProvider implements CaptionsProvider { formatter?: ListAttributeValue | ListExpressionValue; private _objectsMap: Map; private customContent?: ListWidgetValue; - private customContentType: OptionsSourceAssociationCustomContentTypeEnum = "no"; + private customContentType: OptionsSourceCustomContentTypeEnum = "no"; constructor(objectsMap: Map) { this._objectsMap = objectsMap; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts index 39bef68629..a168bb4ffb 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts @@ -2,7 +2,7 @@ import { ActionValue, ObjectItem, ReferenceSetValue, ReferenceValue } from "mend import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action"; import { SelectionControlsContainerProps, - OptionsSourceAssociationCustomContentTypeEnum + OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; import { Status } from "../types"; import { AssociationOptionsProvider } from "./AssociationOptionsProvider"; @@ -16,7 +16,7 @@ export class BaseAssociationSelector void; onLeaveEvent?: () => void; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/utils.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/utils.ts index 8546a72058..02ee96e6de 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/utils.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/utils.ts @@ -11,7 +11,7 @@ import { } from "mendix"; import { SelectionControlsContainerProps, - OptionsSourceAssociationCustomContentTypeEnum + OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; export function extractAssociationProps( @@ -24,7 +24,7 @@ export function extractAssociationProps( boolean, ActionValue | undefined, ListWidgetValue | undefined, - OptionsSourceAssociationCustomContentTypeEnum + OptionsSourceCustomContentTypeEnum ] { const attr = props.attributeAssociation; const ds = props.optionsSourceAssociationDataSource!; @@ -46,7 +46,7 @@ export function extractAssociationProps( const onChangeEvent = props.onChangeEvent; const customContent = props.optionsSourceAssociationCustomContent; - const customContentType = props.optionsSourceAssociationCustomContentType; + const customContentType = props.optionsSourceCustomContentType; return [attr, ds, captionProvider, emptyOption, clearable, onChangeEvent, customContent, customContentType]; } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseCaptionsProvider.tsx index 3126745a8c..9e795b4fe9 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseCaptionsProvider.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseCaptionsProvider.tsx @@ -1,14 +1,14 @@ import { DynamicValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, ObjectItem } from "mendix"; import { ReactNode, createElement } from "react"; import { Big } from "big.js"; -import { OptionsSourceDatabaseCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; +import { OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; import { CaptionsProvider } from "../types"; interface DatabaseCaptionsProviderProps { emptyOptionText?: DynamicValue; formattingAttributeOrExpression?: ListAttributeValue | ListExpressionValue; customContent?: ListWidgetValue; - customContentType: OptionsSourceDatabaseCustomContentTypeEnum; + customContentType: OptionsSourceCustomContentTypeEnum; attribute?: ListAttributeValue; caption?: string; } @@ -18,7 +18,7 @@ export class DatabaseCaptionsProvider implements CaptionsProvider { formatter?: ListAttributeValue | ListExpressionValue; private _objectsMap: Map; private customContent?: ListWidgetValue; - private customContentType: OptionsSourceDatabaseCustomContentTypeEnum = "no"; + private customContentType: OptionsSourceCustomContentTypeEnum = "no"; constructor(objectsMap: Map) { this._objectsMap = objectsMap; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseMultiSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseMultiSelector.ts index ac13c22b5a..776451c57d 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseMultiSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseMultiSelector.ts @@ -2,7 +2,7 @@ import { EditableValue, ObjectItem, SelectionMultiValue } from "mendix"; import { Big } from "big.js"; import { SelectionControlsContainerProps, - OptionsSourceDatabaseCustomContentTypeEnum + OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; import { MultiSelector, Status } from "../types"; import { _valuesIsEqual } from "../utils"; @@ -21,7 +21,7 @@ export class DatabaseMultiSelector void; onLeaveEvent?: () => void; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseSingleSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseSingleSelector.ts index e9d2c86168..101ac14deb 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseSingleSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseSingleSelector.ts @@ -2,7 +2,7 @@ import { EditableValue, ObjectItem, SelectionSingleValue } from "mendix"; import { Big } from "big.js"; import { SelectionControlsContainerProps, - OptionsSourceDatabaseCustomContentTypeEnum + OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; import { SingleSelector, Status } from "../types"; import { _valuesIsEqual } from "../utils"; @@ -21,7 +21,7 @@ export class DatabaseSingleSelector void; onLeaveEvent?: () => void; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/utils.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/utils.ts index ca7fb069a7..904ed19804 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/utils.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/utils.ts @@ -11,7 +11,7 @@ import { Big } from "big.js"; import { SelectionControlsContainerProps, CustomEditabilityEnum, - OptionsSourceDatabaseCustomContentTypeEnum + OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; export function extractDatabaseProps(props: SelectionControlsContainerProps): { @@ -19,7 +19,7 @@ export function extractDatabaseProps(props: SelectionControlsContainerProps): { captionProvider: ListAttributeValue | ListExpressionValue; clearable: boolean; customContent: ListWidgetValue | undefined; - customContentType: OptionsSourceDatabaseCustomContentTypeEnum; + customContentType: OptionsSourceCustomContentTypeEnum; ds: ListValue; emptyOption: DynamicValue; valueSourceAttribute: ListAttributeValue | undefined; @@ -43,7 +43,7 @@ export function extractDatabaseProps(props: SelectionControlsContainerProps): { const clearable = false; const customContent = props.optionsSourceDatabaseCustomContent; - const customContentType = props.optionsSourceDatabaseCustomContentType; + const customContentType = props.optionsSourceCustomContentType; const valueSourceAttribute = props.optionsSourceDatabaseValueAttribute; return { diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts index 06972a19d4..cedbc5c1c0 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts @@ -2,7 +2,7 @@ import { executeAction } from "@mendix/widget-plugin-platform/framework/execute- import { ActionValue, EditableValue } from "mendix"; import { SelectionControlsContainerProps, - OptionsSourceAssociationCustomContentTypeEnum + OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; import { SingleSelector, Status } from "../types"; import { EnumAndBooleanSimpleCaptionsProvider } from "./EnumAndBooleanSimpleCaptionsProvider"; @@ -22,7 +22,7 @@ export class EnumBooleanSingleSelector implements SingleSelector { currentId: string | null = null; caption: EnumAndBooleanSimpleCaptionsProvider; options: EnumBoolOptionsProvider; - customContentType: OptionsSourceAssociationCustomContentTypeEnum = "no"; + customContentType: OptionsSourceCustomContentTypeEnum = "no"; clearable = true; readOnly = false; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx index 0e805dcf20..96d68e11c1 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx @@ -1,14 +1,14 @@ import { DynamicValue } from "mendix"; import { ReactNode, createElement } from "react"; import { - StaticDataSourceCustomContentTypeEnum, + OptionsSourceCustomContentTypeEnum, OptionsSourceStaticDataSourceType } from "../../../typings/SelectionControlsProps"; import { CaptionsProvider } from "../types"; interface StaticCaptionsProviderProps { emptyOptionText?: DynamicValue; - customContentType: StaticDataSourceCustomContentTypeEnum; + customContentType: OptionsSourceCustomContentTypeEnum; caption?: string; } @@ -16,7 +16,7 @@ export class StaticCaptionsProvider implements CaptionsProvider { emptyCaption = ""; formatter?: undefined; private _objectsMap: Map; - private customContentType: StaticDataSourceCustomContentTypeEnum = "no"; + private customContentType: OptionsSourceCustomContentTypeEnum = "no"; constructor(objectsMap: Map) { this._objectsMap = objectsMap; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts index b5c528d0fa..8a5fb0b72e 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts @@ -2,7 +2,7 @@ import { ActionValue, EditableValue } from "mendix"; import { Big } from "big.js"; import { SelectionControlsContainerProps, - StaticDataSourceCustomContentTypeEnum, + OptionsSourceCustomContentTypeEnum, OptionsSourceStaticDataSourceType } from "../../../typings/SelectionControlsProps"; import { SingleSelector, Status } from "../types"; @@ -22,7 +22,7 @@ export class StaticSingleSelector implements SingleSelector { clearable = false; currentId: string | null = null; readOnly = false; - customContentType: StaticDataSourceCustomContentTypeEnum = "no"; + customContentType: OptionsSourceCustomContentTypeEnum = "no"; validation?: string = undefined; onEnterEvent?: () => void; onLeaveEvent?: () => void; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/utils.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/utils.ts index 3666c0f780..bfd7dedf07 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/utils.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/utils.ts @@ -2,7 +2,7 @@ import { ActionValue, DynamicValue, EditableValue, ValueStatus } from "mendix"; import { Big } from "big.js"; import { SelectionControlsContainerProps, - StaticDataSourceCustomContentTypeEnum, + OptionsSourceCustomContentTypeEnum, OptionsSourceStaticDataSourceType } from "../../../typings/SelectionControlsProps"; @@ -14,7 +14,7 @@ export function extractStaticProps( DynamicValue, boolean, ActionValue | undefined, - StaticDataSourceCustomContentTypeEnum + OptionsSourceCustomContentTypeEnum ] { const attribute = props.staticAttribute; const ds = props.optionsSourceStaticDataSource; @@ -29,7 +29,7 @@ export function extractStaticProps( const clearable = false; const onChangeEvent = props.onChangeEvent; - const customContentType = props.staticDataSourceCustomContentType; + const customContentType = props.optionsSourceCustomContentType; return [attribute, ds, emptyOption, clearable, onChangeEvent, customContentType]; } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts index 5e1a329b6a..40fcc07acf 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts @@ -2,7 +2,7 @@ import { DynamicValue, ListAttributeValue, ListExpressionValue, ListValue } from import { ReactNode } from "react"; import { SelectionControlsContainerProps, - OptionsSourceAssociationCustomContentTypeEnum, + OptionsSourceCustomContentTypeEnum, ReadOnlyStyleEnum } from "../../typings/SelectionControlsProps"; @@ -67,7 +67,7 @@ interface SelectorBase { currentId: V | null; setValue(value: V | null): void; - customContentType: OptionsSourceAssociationCustomContentTypeEnum; + customContentType: OptionsSourceCustomContentTypeEnum; onEnterEvent?: () => void; onLeaveEvent?: () => void; diff --git a/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss b/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss index 93ef068c7f..477a0de295 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss +++ b/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss @@ -1,123 +1,123 @@ -// Selection Controls Widget Styles -.widget-selection-controls { - display: block; - position: relative; - - // Placeholder styles - &-placeholder { - padding: 12px; - background-color: #f8f9fa; - border: 1px solid #dee2e6; - border-radius: 4px; - color: #6c757d; - - &-content { - text-align: center; - } - } - - // No options placeholder - &-no-options { - padding: 12px; - text-align: center; - color: #6c757d; - font-style: italic; - } - - // Radio button styles - &-radio { - &-list { - display: flex; - flex-direction: column; - gap: 8px; - } - - &-item { - display: flex; - align-items: center; - gap: 8px; - padding: 8px; - border-radius: 4px; - cursor: pointer; - - &:hover { - background-color: #f8f9fa; - } - - &[aria-checked="true"] { - background-color: #e3f2fd; - } - - input[type="radio"] { - margin: 0; - } - - label { - margin: 0; - cursor: pointer; - flex: 1; - } - } - } - - // Checkbox styles - &-checkbox { - &-list { - display: flex; - flex-direction: column; - gap: 8px; - } - - &-item { - display: flex; - align-items: center; - gap: 8px; - padding: 8px; - border-radius: 4px; - cursor: pointer; - - &:hover { - background-color: #f8f9fa; - } - - &[aria-checked="true"] { - background-color: #e3f2fd; - } - - input[type="checkbox"] { - margin: 0; - } - - label { - margin: 0; - cursor: pointer; - flex: 1; - } - } - } - - // Read-only styles - &.readonly { - .widget-selection-controls-radio-item, - .widget-selection-controls-checkbox-item { - cursor: default; - - &:hover { - background-color: transparent; - } - } - - input { - pointer-events: none; - } - } - - // Accessibility improvements - &-radio-item, - &-checkbox-item { - &:focus-within { - outline: 2px solid #0066cc; - outline-offset: 2px; - } - } -} +// // Selection Controls Widget Styles +// .widget-selection-controls { +// display: block; +// position: relative; + +// // Placeholder styles +// &-placeholder { +// padding: 12px; +// background-color: #f8f9fa; +// border: 1px solid #dee2e6; +// border-radius: 4px; +// color: #6c757d; + +// &-content { +// text-align: center; +// } +// } + +// // No options placeholder +// &-no-options { +// padding: 12px; +// text-align: center; +// color: #6c757d; +// font-style: italic; +// } + +// // Radio button styles +// &-radio { +// &-list { +// display: flex; +// flex-direction: column; +// gap: 8px; +// } + +// &-item { +// display: flex; +// align-items: center; +// gap: 8px; +// padding: 8px; +// border-radius: 4px; +// cursor: pointer; + +// &:hover { +// background-color: #f8f9fa; +// } + +// &[aria-checked="true"] { +// background-color: #e3f2fd; +// } + +// input[type="radio"] { +// margin: 0; +// } + +// label { +// margin: 0; +// cursor: pointer; +// flex: 1; +// } +// } +// } + +// // Checkbox styles +// &-checkbox { +// &-list { +// display: flex; +// flex-direction: column; +// gap: 8px; +// } + +// &-item { +// display: flex; +// align-items: center; +// gap: 8px; +// padding: 8px; +// border-radius: 4px; +// cursor: pointer; + +// &:hover { +// background-color: #f8f9fa; +// } + +// &[aria-checked="true"] { +// background-color: #e3f2fd; +// } + +// input[type="checkbox"] { +// margin: 0; +// } + +// label { +// margin: 0; +// cursor: pointer; +// flex: 1; +// } +// } +// } + +// // Read-only styles +// &.readonly { +// .widget-selection-controls-radio-item, +// .widget-selection-controls-checkbox-item { +// cursor: default; + +// &:hover { +// background-color: transparent; +// } +// } + +// input { +// pointer-events: none; +// } +// } + +// // Accessibility improvements +// &-radio-item, +// &-checkbox-item { +// &:focus-within { +// outline: 2px solid #0066cc; +// outline-offset: 2px; +// } +// } +// } diff --git a/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts b/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts index 513d9dc8fb..ef6b39fed2 100644 --- a/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts +++ b/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts @@ -21,11 +21,7 @@ export interface OptionsSourceStaticDataSourceType { staticDataSourceCaption: DynamicValue; } -export type OptionsSourceAssociationCustomContentTypeEnum = "yes" | "listItem" | "no"; - -export type OptionsSourceDatabaseCustomContentTypeEnum = "yes" | "listItem" | "no"; - -export type StaticDataSourceCustomContentTypeEnum = "yes" | "listItem" | "no"; +export type OptionsSourceCustomContentTypeEnum = "yes" | "no"; export type CustomEditabilityEnum = "default" | "never" | "conditionally"; @@ -59,11 +55,9 @@ export interface SelectionControlsContainerProps { optionsSourceAssociationDataSource?: ListValue; staticAttribute: EditableValue; optionsSourceStaticDataSource: OptionsSourceStaticDataSourceType[]; - optionsSourceAssociationCustomContentType: OptionsSourceAssociationCustomContentTypeEnum; + optionsSourceCustomContentType: OptionsSourceCustomContentTypeEnum; optionsSourceAssociationCustomContent?: ListWidgetValue; - optionsSourceDatabaseCustomContentType: OptionsSourceDatabaseCustomContentTypeEnum; optionsSourceDatabaseCustomContent?: ListWidgetValue; - staticDataSourceCustomContentType: StaticDataSourceCustomContentTypeEnum; customEditability: CustomEditabilityEnum; customEditabilityExpression: DynamicValue; readOnlyStyle: ReadOnlyStyleEnum; @@ -100,11 +94,9 @@ export interface SelectionControlsPreviewProps { optionsSourceAssociationDataSource: {} | { caption: string } | { type: string } | null; staticAttribute: string; optionsSourceStaticDataSource: OptionsSourceStaticDataSourcePreviewType[]; - optionsSourceAssociationCustomContentType: OptionsSourceAssociationCustomContentTypeEnum; + optionsSourceCustomContentType: OptionsSourceCustomContentTypeEnum; optionsSourceAssociationCustomContent: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; - optionsSourceDatabaseCustomContentType: OptionsSourceDatabaseCustomContentTypeEnum; optionsSourceDatabaseCustomContent: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; - staticDataSourceCustomContentType: StaticDataSourceCustomContentTypeEnum; customEditability: CustomEditabilityEnum; customEditabilityExpression: string; readOnlyStyle: ReadOnlyStyleEnum; From 981bb7c60f2e34d175827b78985bd28be9994f8c Mon Sep 17 00:00:00 2001 From: gjulivan Date: Fri, 11 Jul 2025 13:30:36 +0200 Subject: [PATCH 03/20] feat: update dropzone for custom content --- .../src/SelectionControls.editorConfig.ts | 102 +++++++++++++++--- .../src/SelectionControls.editorPreview.tsx | 47 ++------ .../Preview/AssociationPreviewSelector.ts | 41 +++++++ .../Preview/DatabasePreviewSelector.ts | 41 +++++++ .../Preview/PreviewCaptionsProvider.tsx | 42 ++++++++ .../helpers/Preview/PreviewOptionsProvider.ts | 32 ++++++ .../Preview/SimpleCaptionsProvider.tsx | 76 +++++++++++++ .../Static/Preview/StaticPreviewSelector.ts | 36 +++++++ .../src/helpers/utils.ts | 23 ++++ 9 files changed, 390 insertions(+), 50 deletions(-) create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewCaptionsProvider.tsx create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewOptionsProvider.ts create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx create mode 100644 packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts index c7ef279d48..80308a918b 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts @@ -3,6 +3,7 @@ import { ContainerProps, StructurePreviewProps, structurePreviewPalette, + dropzone, container } from "@mendix/widget-plugin-platform/preview/structure-preview-api"; import { SelectionControlsPreviewProps } from "../typings/SelectionControlsProps"; @@ -139,29 +140,104 @@ function getIconPreview(_isDarkMode: boolean): ContainerProps { export function getPreview(_values: SelectionControlsPreviewProps, isDarkMode: boolean): StructurePreviewProps { const palette = structurePreviewPalette[isDarkMode ? "dark" : "light"]; + const structurePreviewChildren: StructurePreviewProps[] = []; + let readOnly = _values.readOnly; + // Handle custom content dropzones when enabled + if (_values.optionsSourceCustomContentType !== "no") { + if (_values.source === "context" && _values.optionsSourceType === "association") { + structurePreviewChildren.push( + dropzone( + dropzone.placeholder("Configure the selection controls: Place widgets here"), + dropzone.hideDataSourceHeaderIf(false) + )(_values.optionsSourceAssociationCustomContent) + ); + } else if (_values.source === "database") { + structurePreviewChildren.push( + dropzone( + dropzone.placeholder("Configure the selection controls: Place widgets here"), + dropzone.hideDataSourceHeaderIf(false) + )(_values.optionsSourceDatabaseCustomContent) + ); + } else if (_values.source === "static") { + _values.optionsSourceStaticDataSource.forEach(value => { + structurePreviewChildren.push( + container({ + borders: true, + borderWidth: 1, + borderRadius: 2 + })( + dropzone( + dropzone.placeholder( + `Configure the selection controls: Place widgets for option ${value.staticDataSourceCaption} here` + ), + dropzone.hideDataSourceHeaderIf(false) + )(value.staticDataSourceCustomContent) + ) + ); + }); + } + } + + // Handle database-specific read-only logic + if (_values.source === "database" && _values.databaseAttributeString.length === 0) { + readOnly = _values.customEditability === "never"; + } + + // If no custom content dropzones, show default preview + if (structurePreviewChildren.length === 0) { + return { + type: "RowLayout", + columnSize: "fixed", + backgroundColor: readOnly ? palette.background.containerDisabled : palette.background.containerFill, + children: [ + { + type: "RowLayout", + columnSize: "grow", + children: [ + getIconPreview(isDarkMode), + { + type: "Container", + padding: 4, + children: [ + { + type: "Text", + content: "Selection Controls", + fontColor: palette.text.primary, + fontSize: 10 + } + ] + } + ] + } + ] + }; + } + + // Return container with dropzones return { - type: "RowLayout", - columnSize: "fixed", - backgroundColor: palette.background.containerFill, + type: "Container", children: [ { type: "RowLayout", columnSize: "grow", + borders: true, + borderWidth: 1, + borderRadius: 2, + backgroundColor: readOnly ? palette.background.containerDisabled : palette.background.container, children: [ - getIconPreview(isDarkMode), { type: "Container", + grow: 1, padding: 4, - children: [ - { - type: "Text", - content: "Selection Controls", - fontColor: palette.text.primary, - fontSize: 10 - } - ] - } + children: structurePreviewChildren + }, + readOnly && _values.readOnlyStyle === "text" + ? container({ grow: 0, padding: 4 })() + : { + ...getIconPreview(isDarkMode), + ...{ grow: 0, padding: 4 } + } ] } ] diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx index aa4992bdc5..f319e5192c 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx @@ -4,44 +4,11 @@ import { SelectionControlsPreviewProps } from "../typings/SelectionControlsProps import { RadioSelection } from "./components/RadioSelection/RadioSelection"; import { dynamic } from "@mendix/widget-plugin-test-utils"; import { SingleSelector, SelectionBaseProps } from "./helpers/types"; +import { StaticPreviewSelector } from "./helpers/Static/Preview/StaticPreviewSelector"; +import { DatabasePreviewSelector } from "./helpers/Database/Preview/DatabasePreviewSelector"; +import { AssociationPreviewSelector } from "./helpers/Association/Preview/AssociationPreviewSelector"; import "./ui/SelectionControls.scss"; -// Preview selector implementation - simplified for preview -class PreviewSelector implements SingleSelector { - type = "single" as const; - status = "available" as const; - readOnly = false; - validation = undefined; - clearable = false; - currentId = null; - customContentType = "no" as const; - - constructor(_props: SelectionControlsPreviewProps) {} - - updateProps() {} - setValue() {} - onEnterEvent() {} - onLeaveEvent() {} - - options = { - status: "available" as const, - searchTerm: "", - getAll: () => ["Option 1", "Option 2", "Option 3"], - setSearchTerm: () => {}, - onAfterSearchTermChange: () => {}, - isLoading: false, - _updateProps: () => {}, - _optionToValue: () => undefined, - _valueToOption: () => null - }; - - caption = { - get: (value: string | null) => value || "Preview Option", - render: (value: string | null) => value || "Preview Option", - emptyCaption: "Select an option" - }; -} - export const preview = (props: SelectionControlsPreviewProps): ReactElement => { const id = generateUUID().toString(); const commonProps: Omit, "selector"> = { @@ -65,7 +32,13 @@ export const preview = (props: SelectionControlsPreviewProps): ReactElement => { // eslint-disable-next-line react-hooks/rules-of-hooks const selector: SingleSelector = useMemo(() => { - return new PreviewSelector(props); + if (props.source === "static") { + return new StaticPreviewSelector(props); + } + if (props.source === "database") { + return new DatabasePreviewSelector(props); + } + return new AssociationPreviewSelector(props); }, [props]); return ( diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts new file mode 100644 index 0000000000..cd263719d0 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts @@ -0,0 +1,41 @@ +import { SingleSelector, Status, CaptionsProvider, OptionsProvider } from "../../types"; +import { + SelectionControlsPreviewProps, + OptionsSourceCustomContentTypeEnum +} from "../../../../typings/SelectionControlsProps"; +import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; +import { PreviewCaptionsProvider } from "../../Preview/PreviewCaptionsProvider"; +import { PreviewOptionsProvider } from "../../Preview/PreviewOptionsProvider"; + +export class AssociationPreviewSelector implements SingleSelector { + type = "single" as const; + status: Status = "available"; + attributeType?: "string" | "boolean" | "big" | "date" | undefined; + selectorType?: "context" | "database" | "static" | undefined; + // type: "single"; + readOnly: boolean; + validation?: string | undefined; + clearable: boolean = false; + currentId: string | null; + customContentType: OptionsSourceCustomContentTypeEnum; + caption: CaptionsProvider; + options: OptionsProvider; + + constructor(props: SelectionControlsPreviewProps) { + this.readOnly = props.readOnly; + this.currentId = `single-${generateUUID()}`; + this.customContentType = props.optionsSourceCustomContentType; + this.readOnly = props.readOnly; + this.caption = new PreviewCaptionsProvider(new Map()); + this.options = new PreviewOptionsProvider(this.caption, new Map()); + (this.caption as PreviewCaptionsProvider).updatePreviewProps({ + customContentRenderer: props.optionsSourceAssociationCustomContent?.renderer, + customContentType: props.optionsSourceCustomContentType + }); + } + + updateProps() {} + setValue() {} + onEnterEvent() {} + onLeaveEvent() {} +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts new file mode 100644 index 0000000000..a876465d04 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts @@ -0,0 +1,41 @@ +import { SingleSelector, Status, CaptionsProvider, OptionsProvider } from "../../types"; +import { + SelectionControlsPreviewProps, + OptionsSourceCustomContentTypeEnum +} from "../../../../typings/SelectionControlsProps"; +import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; +import { PreviewCaptionsProvider } from "../../Preview/PreviewCaptionsProvider"; +import { PreviewOptionsProvider } from "../../Preview/PreviewOptionsProvider"; + +export class DatabasePreviewSelector implements SingleSelector { + type = "single" as const; + status: Status = "available"; + attributeType?: "string" | "boolean" | "big" | "date" | undefined; + selectorType?: "context" | "database" | "static" | undefined; + // type: "single"; + readOnly: boolean; + validation?: string | undefined; + clearable: boolean = false; + currentId: string | null; + customContentType: OptionsSourceCustomContentTypeEnum; + caption: CaptionsProvider; + options: OptionsProvider; + + constructor(props: SelectionControlsPreviewProps) { + this.currentId = `single-${generateUUID()}`; + this.customContentType = props.optionsSourceCustomContentType; + this.readOnly = props.readOnly; + this.caption = new PreviewCaptionsProvider(new Map()); + this.options = new PreviewOptionsProvider(this.caption, new Map()); + (this.caption as PreviewCaptionsProvider).updatePreviewProps({ + customContentRenderer: props.optionsSourceDatabaseCustomContent?.renderer, + customContentType: props.optionsSourceCustomContentType + }); + // Show dropzones in design mode when custom content is enabled + } + + updateProps() {} + setValue() {} + onEnterEvent() {} + onLeaveEvent() {} +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewCaptionsProvider.tsx new file mode 100644 index 0000000000..24defe2d45 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewCaptionsProvider.tsx @@ -0,0 +1,42 @@ +import { OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; +import { SimpleCaptionsProvider } from "./SimpleCaptionsProvider"; +import { createElement, ReactNode, ComponentType } from "react"; +interface PreviewProps { + customContentRenderer: + | ComponentType<{ children: ReactNode; caption?: string }> + | Array>; + customContentType: OptionsSourceCustomContentTypeEnum; +} + +export class PreviewCaptionsProvider extends SimpleCaptionsProvider { + emptyCaption = "Combo box"; + private customContentRenderer: ComponentType<{ children: ReactNode; caption?: string }> = () =>
; + get(value: string | null): string { + return value || this.emptyCaption; + } + + getCustomContent(value: string | null): ReactNode | null { + if (value === null) { + return null; + } + if (this.customContentType !== "no") { + return ( + +
+ + ); + } + } + + updatePreviewProps(props: PreviewProps): void { + this.customContentRenderer = props.customContentRenderer as ComponentType<{ + children: ReactNode; + caption?: string | undefined; + }>; + this.customContentType = props.customContentType; + } + + render(value: string | null, htmlFor?: string): ReactNode { + return super.render(value, htmlFor); + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewOptionsProvider.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewOptionsProvider.ts new file mode 100644 index 0000000000..0f696a0fa7 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewOptionsProvider.ts @@ -0,0 +1,32 @@ +import { ObjectItem } from "mendix"; +import { BaseOptionsProvider } from "../BaseOptionsProvider"; +import { CaptionsProvider, OptionsProvider, Status } from "../types"; + +export class PreviewOptionsProvider implements OptionsProvider { + hasMore?: boolean | undefined = undefined; + searchTerm: string = ""; + status: Status = "available"; + isLoading: boolean = false; + + constructor( + protected caption: CaptionsProvider, + protected valuesMap: Map + ) {} + onAfterSearchTermChange(_callback: () => void): void {} + setSearchTerm(_value: string): void {} + loadMore?(): void { + throw new Error("Method not implemented."); + } + _updateProps(_: BaseOptionsProvider): void { + throw new Error("Method not implemented."); + } + _optionToValue(_value: string | null): ObjectItem | undefined { + throw new Error("Method not implemented."); + } + _valueToOption(_value: ObjectItem | undefined): string | null { + throw new Error("Method not implemented."); + } + getAll(): string[] { + return ["..."]; + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx new file mode 100644 index 0000000000..84cdad0c0b --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx @@ -0,0 +1,76 @@ +import { DynamicValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, ObjectItem } from "mendix"; +import { ReactNode, createElement } from "react"; +import { OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; +import { CaptionsProvider } from "../types"; +import { CaptionContent } from "../utils"; + +interface Props { + emptyOptionText?: DynamicValue; + formattingAttributeOrExpression: ListExpressionValue | ListAttributeValue; + customContent?: ListWidgetValue | undefined; + customContentType: OptionsSourceCustomContentTypeEnum; +} + +export class SimpleCaptionsProvider implements CaptionsProvider { + private unavailableCaption = "<...>"; + formatter?: ListExpressionValue | ListAttributeValue; + protected customContent?: ListWidgetValue; + protected customContentType: OptionsSourceCustomContentTypeEnum = "no"; + emptyCaption = ""; + + constructor(private optionsMap: Map) {} + + updateProps(props: Props): void { + if (!props.emptyOptionText || props.emptyOptionText.status === "unavailable") { + this.emptyCaption = ""; + } else { + this.emptyCaption = props.emptyOptionText.value!; + } + + this.formatter = props.formattingAttributeOrExpression; + this.customContent = props.customContent; + this.customContentType = props.customContentType; + } + + get(value: string | null): string { + if (value === null) { + return this.emptyCaption; + } + if (!this.formatter) { + throw new Error("SimpleCaptionsProvider: no formatter available."); + } + const item = this.optionsMap.get(value); + if (!item) { + return this.unavailableCaption; + } + + const captionValue = this.formatter.get(item); + if (!captionValue || captionValue.status === "unavailable") { + return this.unavailableCaption; + } + + return captionValue.value ?? ""; + } + + getCustomContent(value: string | null): ReactNode | null { + if (value === null) { + return null; + } + const item = this.optionsMap.get(value); + if (!item) { + return null; + } + + return this.customContent?.get(item); + } + + render(value: string | null, htmlFor?: string): ReactNode { + const { customContentType } = this; + + return customContentType === "no" || value === null ? ( + {this.get(value)} + ) : ( +
{this.getCustomContent(value)}
+ ); + } +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts new file mode 100644 index 0000000000..5aa2dfa152 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts @@ -0,0 +1,36 @@ +import { SingleSelector, Status, CaptionsProvider, OptionsProvider } from "../../types"; +import { + SelectionControlsPreviewProps, + OptionsSourceCustomContentTypeEnum +} from "../../../../typings/SelectionControlsProps"; +import { PreviewCaptionsProvider } from "../../Preview/PreviewCaptionsProvider"; +import { PreviewOptionsProvider } from "../../Preview/PreviewOptionsProvider"; +import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; + +export class StaticPreviewSelector implements SingleSelector { + type = "single" as const; + status: Status = "available"; + attributeType?: "string" | "boolean" | "big" | "date" | undefined; + selectorType?: "context" | "database" | "static" | undefined; + // type: "single"; + readOnly: boolean; + validation?: string | undefined; + clearable: boolean = false; + currentId: string | null; + customContentType: OptionsSourceCustomContentTypeEnum; + caption: CaptionsProvider; + options: OptionsProvider; + + constructor(props: SelectionControlsPreviewProps) { + this.currentId = `single-${generateUUID()}`; + this.customContentType = props.optionsSourceCustomContentType; + this.readOnly = props.readOnly; + this.caption = new PreviewCaptionsProvider(new Map()); + this.options = new PreviewOptionsProvider(this.caption, new Map()); + } + + updateProps() {} + setValue() {} + onEnterEvent() {} + onLeaveEvent() {} +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts index 7a9669a541..a96963494f 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts @@ -1,4 +1,27 @@ import { Big } from "big.js"; +import { createElement, PropsWithChildren, ReactElement } from "react"; +import { MouseEvent } from "react"; + +export interface CaptionContentProps extends PropsWithChildren { + htmlFor?: string; + onClick?: (e: MouseEvent) => void; +} + +export function CaptionContent(props: CaptionContentProps): ReactElement { + const { htmlFor, children, onClick } = props; + return createElement(htmlFor == null ? "span" : "label", { + children, + className: "widget-controls-caption-text", + htmlFor, + onClick: onClick + ? onClick + : htmlFor + ? (e: MouseEvent) => { + e.preventDefault(); + } + : undefined + }); +} export function _valuesIsEqual( value1: string | Big | boolean | Date | undefined, From f1bba5da5be0189b51c9c9f0425eef8dcfa10e64 Mon Sep 17 00:00:00 2001 From: gjulivan Date: Fri, 11 Jul 2025 14:47:42 +0200 Subject: [PATCH 04/20] chore: updating code for selection controls for cleaner caption --- .../src/SelectionControls.editorConfig.ts | 5 +++- .../src/SelectionControls.editorPreview.tsx | 4 --- .../src/SelectionControls.tsx | 4 --- .../src/SelectionControls.xml | 18 ------------ .../src/components/CaptionContent.tsx | 23 +++++++++++++++ .../CheckboxSelection/CheckboxSelection.tsx | 28 ++----------------- .../RadioSelection/RadioSelection.tsx | 15 ++-------- .../AssociationSimpleCaptionsProvider.tsx | 19 +++++++------ .../Database/DatabaseCaptionsProvider.tsx | 21 +++++++------- .../EnumAndBooleanSimpleCaptionsProvider.tsx | 7 ++--- .../Preview/PreviewCaptionsProvider.tsx | 8 +++--- .../Preview/SimpleCaptionsProvider.tsx | 23 +++++++++------ .../helpers/Static/StaticCaptionsProvider.tsx | 21 +++++++------- .../src/helpers/types.ts | 7 +---- .../src/helpers/utils.ts | 23 --------------- .../typings/SelectionControlsProps.d.ts | 4 --- 16 files changed, 86 insertions(+), 144 deletions(-) create mode 100644 packages/pluggableWidgets/selection-controls-web/src/components/CaptionContent.tsx diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts index 80308a918b..682dcf1c8b 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts @@ -45,7 +45,10 @@ export function getProperties( ...DATABASE_SOURCE_CONFIG ]); if (["enumeration", "boolean"].includes(values.optionsSourceType)) { - hidePropertiesIn(defaultProperties, values, [...ASSOCIATION_SOURCE_CONFIG]); + hidePropertiesIn(defaultProperties, values, [ + "optionsSourceCustomContentType", + ...ASSOCIATION_SOURCE_CONFIG + ]); if (values.optionsSourceType === "boolean") { hidePropertiesIn(defaultProperties, values, ["attributeEnumeration"]); } else { diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx index f319e5192c..84e76d3626 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx @@ -18,10 +18,6 @@ export const preview = (props: SelectionControlsPreviewProps): ReactElement => { readOnlyStyle: props.readOnlyStyle, ariaRequired: dynamic(false), a11yConfig: { - ariaLabels: { - clearSelection: props.clearButtonAriaLabel, - removeSelection: props.removeValueAriaLabel - }, a11yStatusMessage: { a11ySelectedValue: props.a11ySelectedValue, a11yOptionsAvailable: props.a11yOptionsAvailable, diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx index 664d67decf..83c9a0bed9 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx @@ -25,10 +25,6 @@ export default function SelectionControls(props: SelectionControlsContainerProps readOnlyStyle: props.readOnlyStyle, ariaRequired: props.ariaRequired, a11yConfig: { - ariaLabels: { - clearSelection: props.clearButtonAriaLabel?.value ?? "", - removeSelection: props.removeValueAriaLabel?.value ?? "" - }, a11yStatusMessage: { a11ySelectedValue: props.a11ySelectedValue?.value ?? "", a11yOptionsAvailable: props.a11yOptionsAvailable?.value ?? "", diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml index 235d94a7e7..5ee533098f 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml @@ -264,24 +264,6 @@ - - - Clear selection button - Used to clear all selected values. - - Clear selection - Selectie wissen - - - - Remove value button - Used to remove individual selected values when using labels with multi-selection. - - Remove value - Waarde verwijderen - - - Selected value diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/CaptionContent.tsx b/packages/pluggableWidgets/selection-controls-web/src/components/CaptionContent.tsx new file mode 100644 index 0000000000..b429fa481f --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/components/CaptionContent.tsx @@ -0,0 +1,23 @@ +import { createElement, PropsWithChildren, ReactElement } from "react"; +import { MouseEvent } from "react"; + +export interface CaptionContentProps extends PropsWithChildren { + htmlFor?: string; + onClick?: (e: MouseEvent) => void; +} + +export function CaptionContent(props: CaptionContentProps): ReactElement { + const { htmlFor, children, onClick } = props; + return createElement(htmlFor == null ? "span" : "label", { + children, + className: "widget-controls-caption-text", + htmlFor, + onClick: onClick + ? onClick + : htmlFor + ? (e: MouseEvent) => { + e.preventDefault(); + } + : undefined + }); +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx b/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx index d35cf05201..1dba13bafb 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx @@ -1,6 +1,7 @@ import classNames from "classnames"; import { ReactElement, createElement } from "react"; import { SelectionBaseProps, MultiSelector } from "../../helpers/types"; +import { CaptionContent } from "../CaptionContent"; export function CheckboxSelection({ selector, @@ -42,7 +43,6 @@ export function CheckboxSelection({ aria-required={ariaRequired?.value} > {options.map((optionId, index) => { - const caption = selector.caption.get(optionId); const isSelected = currentIds.includes(optionId); const checkboxId = `${inputId}-checkbox-${index}`; @@ -63,11 +63,7 @@ export function CheckboxSelection({ onChange={e => handleChange(optionId, e.target.checked)} aria-describedby={`${inputId}-description`} /> - + {selector.caption.render(optionId)}
); })} @@ -75,26 +71,6 @@ export function CheckboxSelection({
No options available
)} - - {/* Clear all button */} - {/* {!isReadOnly && currentIds.length > 0 && ( - - )} */} - - {/* Accessibility status message */} - {/*
- {currentIds.length > 0 && - `${a11yConfig.a11yStatusMessage.a11ySelectedValue} ${currentIds.map(id => selector.caption.get(id)).join(", ")}`} - {` ${a11yConfig.a11yStatusMessage.a11yOptionsAvailable} ${options.length}`} - {` ${a11yConfig.a11yStatusMessage.a11yInstructions}`} -
*/} ); } diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx b/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx index 9045cc9dff..80307b05e7 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx @@ -1,6 +1,7 @@ import classNames from "classnames"; import { ReactElement, createElement } from "react"; import { SelectionBaseProps, SingleSelector } from "../../helpers/types"; +import { CaptionContent } from "../CaptionContent"; export function RadioSelection({ selector, @@ -41,7 +42,6 @@ export function RadioSelection({ aria-required={ariaRequired?.value} > {options.map((optionId, index) => { - const caption = selector.caption.get(optionId); const isSelected = currentId === optionId; const radioId = `${inputId}-radio-${index}`; @@ -63,11 +63,7 @@ export function RadioSelection({ onChange={() => handleChange(optionId)} aria-describedby={`${inputId}-description`} /> - + {selector.caption.render(optionId)} ); })} @@ -75,13 +71,6 @@ export function RadioSelection({
No options available
)} - - {/* Accessibility status message */} - {/*
- {currentId && `${a11yConfig.a11yStatusMessage.a11ySelectedValue} ${selector.caption.get(currentId)}`} - {` ${a11yConfig.a11yStatusMessage.a11yOptionsAvailable} ${options.length}`} - {` ${a11yConfig.a11yStatusMessage.a11yInstructions}`} -
*/} ); } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx index 8a10d6b129..1806fc9529 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx @@ -1,5 +1,5 @@ import { DynamicValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, ObjectItem } from "mendix"; -import { ReactNode, createElement } from "react"; +import { ReactNode } from "react"; import { OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; import { CaptionsProvider } from "../types"; @@ -45,21 +45,22 @@ export class AssociationSimpleCaptionsProvider implements CaptionsProvider { return this.formatter.get(item).value || ""; } - render(value: string | null, _placement?: "label" | "options", _htmlFor?: string): ReactNode { + getCustomContent(value: string | null): ReactNode | null { if (value === null) { - return {this.emptyCaption}; + return null; } - const item = this._objectsMap.get(value); if (!item) { - return ; + return null; } + return this.customContent?.get(item); + } + + render(value: string | null): ReactNode { if (this.customContentType === "yes" && this.customContent) { - return this.customContent.get(item); + return this.getCustomContent(value); } - - const caption = this.formatter?.get(item).value || ""; - return {caption}; + return this.get(value); } } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseCaptionsProvider.tsx index 9e795b4fe9..76dac3490e 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseCaptionsProvider.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseCaptionsProvider.tsx @@ -1,6 +1,6 @@ -import { DynamicValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, ObjectItem } from "mendix"; -import { ReactNode, createElement } from "react"; import { Big } from "big.js"; +import { DynamicValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, ObjectItem } from "mendix"; +import { ReactNode } from "react"; import { OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; import { CaptionsProvider } from "../types"; @@ -48,21 +48,22 @@ export class DatabaseCaptionsProvider implements CaptionsProvider { return this.formatter.get(item).value || ""; } - render(value: string | null, _placement?: "label" | "options", _htmlFor?: string): ReactNode { + getCustomContent(value: string | null): ReactNode | null { if (value === null) { - return {this.emptyCaption}; + return null; } - const item = this._objectsMap.get(value); if (!item) { - return ; + return null; } + return this.customContent?.get(item); + } + + render(value: string | null): ReactNode { if (this.customContentType === "yes" && this.customContent) { - return this.customContent.get(item); + return this.getCustomContent(value); } - - const caption = this.formatter?.get(item).value || ""; - return {caption}; + return this.get(value); } } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumAndBooleanSimpleCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumAndBooleanSimpleCaptionsProvider.tsx index 26b8397d7c..7519acdb21 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumAndBooleanSimpleCaptionsProvider.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumAndBooleanSimpleCaptionsProvider.tsx @@ -1,5 +1,5 @@ import { DynamicValue, EditableValue } from "mendix"; -import { ReactNode, createElement } from "react"; +import { ReactNode } from "react"; import { CaptionsProvider } from "../types"; interface EnumAndBooleanSimpleCaptionsProviderProps { @@ -29,8 +29,7 @@ export class EnumAndBooleanSimpleCaptionsProvider implements CaptionsProvider { return this.attr?.formatter.format(value) ?? ""; } - render(value: string | null, _placement?: "label" | "options", _htmlFor?: string): ReactNode { - const caption = this.get(value); - return {caption}; + render(value: string | null): ReactNode { + return this.get(value); } } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewCaptionsProvider.tsx index 24defe2d45..c825c838bd 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewCaptionsProvider.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewCaptionsProvider.tsx @@ -9,8 +9,8 @@ interface PreviewProps { } export class PreviewCaptionsProvider extends SimpleCaptionsProvider { - emptyCaption = "Combo box"; - private customContentRenderer: ComponentType<{ children: ReactNode; caption?: string }> = () =>
; + emptyCaption = "Selection controls"; + private customContentRenderer: ComponentType<{ children: ReactNode; caption?: string }> = () =>
Dropzone
; get(value: string | null): string { return value || this.emptyCaption; } @@ -36,7 +36,7 @@ export class PreviewCaptionsProvider extends SimpleCaptionsProvider { this.customContentType = props.customContentType; } - render(value: string | null, htmlFor?: string): ReactNode { - return super.render(value, htmlFor); + render(value: string | null): ReactNode { + return super.render(value); } } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx index 84cdad0c0b..d9c0a3c283 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx @@ -2,7 +2,6 @@ import { DynamicValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, import { ReactNode, createElement } from "react"; import { OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; import { CaptionsProvider } from "../types"; -import { CaptionContent } from "../utils"; interface Props { emptyOptionText?: DynamicValue; @@ -64,13 +63,21 @@ export class SimpleCaptionsProvider implements CaptionsProvider { return this.customContent?.get(item); } - render(value: string | null, htmlFor?: string): ReactNode { - const { customContentType } = this; + render(value: string | null): ReactNode { + if (value === null) { + return {this.emptyCaption}; + } + + const item = this.optionsMap.get(value); + if (!item) { + return ; + } + + if (this.customContentType === "yes" && this.customContent) { + return this.customContent.get(item); + } - return customContentType === "no" || value === null ? ( - {this.get(value)} - ) : ( -
{this.getCustomContent(value)}
- ); + const caption = this.formatter?.get(item).value || ""; + return {caption}; } } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx index 96d68e11c1..895d90a49b 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx @@ -1,5 +1,5 @@ import { DynamicValue } from "mendix"; -import { ReactNode, createElement } from "react"; +import { ReactNode } from "react"; import { OptionsSourceCustomContentTypeEnum, OptionsSourceStaticDataSourceType @@ -44,21 +44,22 @@ export class StaticCaptionsProvider implements CaptionsProvider { return item.staticDataSourceCaption.value || ""; } - render(value: string | null, _placement?: "label" | "options", _htmlFor?: string): ReactNode { + getCustomContent(value: string | null): ReactNode | null { if (value === null) { - return {this.emptyCaption}; + return null; } - const item = this._objectsMap.get(value); if (!item) { - return ; + return null; } - if (this.customContentType === "yes" && item.staticDataSourceCustomContent) { - return item.staticDataSourceCustomContent; - } + return item.staticDataSourceCustomContent; + } - const caption = item.staticDataSourceCaption.value || ""; - return {caption}; + render(value: string | null): ReactNode { + if (this.customContentType === "yes") { + return this.getCustomContent(value); + } + return this.get(value); } } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts index 40fcc07acf..cec778fa55 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts @@ -7,14 +7,13 @@ import { } from "../../typings/SelectionControlsProps"; export type Status = "unavailable" | "loading" | "available"; -export type CaptionPlacement = "label" | "options"; export type SelectionType = "single" | "multi"; export type Selector = SingleSelector | MultiSelector; export type SortOrder = "asc" | "desc"; export interface CaptionsProvider { get(value: string | null): string; - render(value: (string | null) | (number | null), placement?: CaptionPlacement, htmlFor?: string): ReactNode; + render(value: (string | null) | (number | null)): ReactNode; emptyCaption: string; formatter?: ListExpressionValue | ListAttributeValue; } @@ -87,10 +86,6 @@ export interface SelectionBaseProps { tabIndex: number; ariaRequired: DynamicValue; a11yConfig: { - ariaLabels: { - clearSelection: string; - removeSelection: string; - }; a11yStatusMessage: A11yStatusMessage; }; } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts index a96963494f..7a9669a541 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts @@ -1,27 +1,4 @@ import { Big } from "big.js"; -import { createElement, PropsWithChildren, ReactElement } from "react"; -import { MouseEvent } from "react"; - -export interface CaptionContentProps extends PropsWithChildren { - htmlFor?: string; - onClick?: (e: MouseEvent) => void; -} - -export function CaptionContent(props: CaptionContentProps): ReactElement { - const { htmlFor, children, onClick } = props; - return createElement(htmlFor == null ? "span" : "label", { - children, - className: "widget-controls-caption-text", - htmlFor, - onClick: onClick - ? onClick - : htmlFor - ? (e: MouseEvent) => { - e.preventDefault(); - } - : undefined - }); -} export function _valuesIsEqual( value1: string | Big | boolean | Date | undefined, diff --git a/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts b/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts index ef6b39fed2..7d6ece2a72 100644 --- a/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts +++ b/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts @@ -65,8 +65,6 @@ export interface SelectionControlsContainerProps { onEnterEvent?: ActionValue; onLeaveEvent?: ActionValue; ariaRequired: DynamicValue; - clearButtonAriaLabel?: DynamicValue; - removeValueAriaLabel?: DynamicValue; a11ySelectedValue?: DynamicValue; a11yOptionsAvailable?: DynamicValue; a11yInstructions?: DynamicValue; @@ -105,8 +103,6 @@ export interface SelectionControlsPreviewProps { onEnterEvent: {} | null; onLeaveEvent: {} | null; ariaRequired: string; - clearButtonAriaLabel: string; - removeValueAriaLabel: string; a11ySelectedValue: string; a11yOptionsAvailable: string; a11yInstructions: string; From fcb26cb222f5229cf73b65d44009d133d5520aff Mon Sep 17 00:00:00 2001 From: gjulivan Date: Fri, 11 Jul 2025 15:00:35 +0200 Subject: [PATCH 05/20] chore: removing onleave and onenter event --- .../src/SelectionControls.tsx | 8 +- .../src/SelectionControls.xml | 8 -- .../Association/BaseAssociationSelector.ts | 4 - .../Preview/AssociationPreviewSelector.ts | 2 - .../helpers/Database/DatabaseMultiSelector.ts | 2 - .../Database/DatabaseSingleSelector.ts | 2 - .../Preview/DatabasePreviewSelector.ts | 2 - .../EnumBool/EnumBooleanSingleSelector.ts | 4 - .../Static/Preview/StaticPreviewSelector.ts | 2 - .../helpers/Static/StaticSingleSelector.ts | 4 - .../src/helpers/types.ts | 3 - .../src/hooks/useActionEvents.ts | 40 ------ .../src/ui/SelectionControls.scss | 128 +----------------- .../typings/SelectionControlsProps.d.ts | 4 - 14 files changed, 6 insertions(+), 207 deletions(-) delete mode 100644 packages/pluggableWidgets/selection-controls-web/src/hooks/useActionEvents.ts diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx index 83c9a0bed9..fd0d57bd12 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx @@ -6,18 +6,12 @@ import { CheckboxSelection } from "./components/CheckboxSelection/CheckboxSelect import { Placeholder } from "./components/Placeholder"; import { RadioSelection } from "./components/RadioSelection/RadioSelection"; import { SelectionBaseProps } from "./helpers/types"; -import { useActionEvents } from "./hooks/useActionEvents"; import { useGetSelector } from "./hooks/useGetSelector"; import "./ui/SelectionControls.scss"; export default function SelectionControls(props: SelectionControlsContainerProps): ReactElement { const selector = useGetSelector(props); - const actionEvents = useActionEvents({ - onEnterEvent: props.onEnterEvent, - onLeaveEvent: props.onLeaveEvent, - selector - }); const commonProps: Omit, "selector"> = { tabIndex: props.tabIndex!, inputId: props.id, @@ -34,7 +28,7 @@ export default function SelectionControls(props: SelectionControlsContainerProps }; return ( -
+
{selector.status === "unavailable" ? ( ) : selector.type === "single" ? ( diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml index 5ee533098f..c1aa96918c 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml @@ -247,14 +247,6 @@ On change action - - On enter action - - - - On leave action - - diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts index a168bb4ffb..6733f4f3b0 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts @@ -18,8 +18,6 @@ export class BaseAssociationSelector void; - onLeaveEvent?: () => void; protected _attr: R | undefined; private onChangeEvent?: ActionValue; private _valuesMap: Map = new Map(); @@ -64,8 +62,6 @@ export class BaseAssociationSelector executeAction(props.onEnterEvent) : undefined; - this.onLeaveEvent = props.onLeaveEvent ? () => executeAction(props.onLeaveEvent) : undefined; this.customContentType = customContentType; this.validation = attr.validation; } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts index cd263719d0..d80fb91e1c 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts @@ -36,6 +36,4 @@ export class AssociationPreviewSelector implements SingleSelector { updateProps() {} setValue() {} - onEnterEvent() {} - onLeaveEvent() {} } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseMultiSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseMultiSelector.ts index 776451c57d..51454b708c 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseMultiSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseMultiSelector.ts @@ -23,8 +23,6 @@ export class DatabaseMultiSelector void; - onLeaveEvent?: () => void; values: DatabaseValuesProvider; protected _objectsMap: Map = new Map(); protected _attr: R | undefined; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseSingleSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseSingleSelector.ts index 101ac14deb..aaee43bfef 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseSingleSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/DatabaseSingleSelector.ts @@ -23,8 +23,6 @@ export class DatabaseSingleSelector void; - onLeaveEvent?: () => void; values: DatabaseValuesProvider; protected _objectsMap: Map = new Map(); protected _attr: R | undefined; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts index a876465d04..73fbeeb749 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts @@ -36,6 +36,4 @@ export class DatabasePreviewSelector implements SingleSelector { updateProps() {} setValue() {} - onEnterEvent() {} - onLeaveEvent() {} } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts index cedbc5c1c0..4761f10554 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts @@ -16,8 +16,6 @@ export class EnumBooleanSingleSelector implements SingleSelector { private isBoolean = false; private _attr: EditableValue | undefined; private onChangeEvent?: ActionValue; - onEnterEvent?: () => void; - onLeaveEvent?: () => void; currentId: string | null = null; caption: EnumAndBooleanSimpleCaptionsProvider; @@ -54,8 +52,6 @@ export class EnumBooleanSingleSelector implements SingleSelector { } this.onChangeEvent = props.onChangeEvent; - this.onEnterEvent = props.onEnterEvent ? () => executeAction(props.onEnterEvent) : undefined; - this.onLeaveEvent = props.onLeaveEvent ? () => executeAction(props.onLeaveEvent) : undefined; this.status = attr.status; this.isBoolean = typeof attr.universe?.[0] === "boolean"; this.clearable = this.isBoolean ? false : clearable; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts index 5aa2dfa152..e38a625827 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts @@ -31,6 +31,4 @@ export class StaticPreviewSelector implements SingleSelector { updateProps() {} setValue() {} - onEnterEvent() {} - onLeaveEvent() {} } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts index 8a5fb0b72e..a214856ab6 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts @@ -24,8 +24,6 @@ export class StaticSingleSelector implements SingleSelector { readOnly = false; customContentType: OptionsSourceCustomContentTypeEnum = "no"; validation?: string = undefined; - onEnterEvent?: () => void; - onLeaveEvent?: () => void; protected _attr: EditableValue | undefined; private onChangeEvent?: ActionValue; private _objectsMap: Map = new Map(); @@ -72,8 +70,6 @@ export class StaticSingleSelector implements SingleSelector { this.status = attr.status; this.readOnly = attr.readOnly; this.onChangeEvent = onChangeEvent; - this.onEnterEvent = props.onEnterEvent ? () => executeAction(props.onEnterEvent) : undefined; - this.onLeaveEvent = props.onLeaveEvent ? () => executeAction(props.onLeaveEvent) : undefined; this.customContentType = customContentType; this.validation = attr.validation; this.attributeType = diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts index cec778fa55..78357f79df 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts @@ -67,9 +67,6 @@ interface SelectorBase { setValue(value: V | null): void; customContentType: OptionsSourceCustomContentTypeEnum; - - onEnterEvent?: () => void; - onLeaveEvent?: () => void; } export interface SingleSelector extends SelectorBase<"single", string> {} diff --git a/packages/pluggableWidgets/selection-controls-web/src/hooks/useActionEvents.ts b/packages/pluggableWidgets/selection-controls-web/src/hooks/useActionEvents.ts deleted file mode 100644 index 892533668e..0000000000 --- a/packages/pluggableWidgets/selection-controls-web/src/hooks/useActionEvents.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action"; -import { FocusEvent, useMemo } from "react"; -import { SelectionControlsContainerProps } from "../../typings/SelectionControlsProps"; -import { Selector } from "../helpers/types"; - -type UseActionEventsReturnValue = { - onFocus: (e: FocusEvent) => void; - onBlur: (e: FocusEvent) => void; -}; - -interface useActionEventsProps extends Pick { - selector: Selector; -} - -export function useActionEvents(props: useActionEventsProps): UseActionEventsReturnValue { - return useMemo(() => { - return { - onFocus: (e: FocusEvent): void => { - const { relatedTarget, currentTarget } = e; - if (!currentTarget?.contains(relatedTarget)) { - executeAction(props.onEnterEvent); - - if (props.selector.onEnterEvent) { - props.selector.onEnterEvent(); - } - } - }, - onBlur: (e: FocusEvent): void => { - const { relatedTarget, currentTarget } = e; - if (!currentTarget?.contains(relatedTarget)) { - executeAction(props.onLeaveEvent); - - if (props.selector.onLeaveEvent) { - props.selector.onLeaveEvent(); - } - } - } - }; - }, [props.onEnterEvent, props.onLeaveEvent, props.selector]); -} diff --git a/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss b/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss index 477a0de295..e41e4dcad1 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss +++ b/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss @@ -1,123 +1,5 @@ -// // Selection Controls Widget Styles -// .widget-selection-controls { -// display: block; -// position: relative; - -// // Placeholder styles -// &-placeholder { -// padding: 12px; -// background-color: #f8f9fa; -// border: 1px solid #dee2e6; -// border-radius: 4px; -// color: #6c757d; - -// &-content { -// text-align: center; -// } -// } - -// // No options placeholder -// &-no-options { -// padding: 12px; -// text-align: center; -// color: #6c757d; -// font-style: italic; -// } - -// // Radio button styles -// &-radio { -// &-list { -// display: flex; -// flex-direction: column; -// gap: 8px; -// } - -// &-item { -// display: flex; -// align-items: center; -// gap: 8px; -// padding: 8px; -// border-radius: 4px; -// cursor: pointer; - -// &:hover { -// background-color: #f8f9fa; -// } - -// &[aria-checked="true"] { -// background-color: #e3f2fd; -// } - -// input[type="radio"] { -// margin: 0; -// } - -// label { -// margin: 0; -// cursor: pointer; -// flex: 1; -// } -// } -// } - -// // Checkbox styles -// &-checkbox { -// &-list { -// display: flex; -// flex-direction: column; -// gap: 8px; -// } - -// &-item { -// display: flex; -// align-items: center; -// gap: 8px; -// padding: 8px; -// border-radius: 4px; -// cursor: pointer; - -// &:hover { -// background-color: #f8f9fa; -// } - -// &[aria-checked="true"] { -// background-color: #e3f2fd; -// } - -// input[type="checkbox"] { -// margin: 0; -// } - -// label { -// margin: 0; -// cursor: pointer; -// flex: 1; -// } -// } -// } - -// // Read-only styles -// &.readonly { -// .widget-selection-controls-radio-item, -// .widget-selection-controls-checkbox-item { -// cursor: default; - -// &:hover { -// background-color: transparent; -// } -// } - -// input { -// pointer-events: none; -// } -// } - -// // Accessibility improvements -// &-radio-item, -// &-checkbox-item { -// &:focus-within { -// outline: 2px solid #0066cc; -// outline-offset: 2px; -// } -// } -// } +// Selection Controls Widget Styles +.widget-selection-controls { + display: block; + position: relative; +} diff --git a/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts b/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts index 7d6ece2a72..486f65919d 100644 --- a/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts +++ b/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts @@ -62,8 +62,6 @@ export interface SelectionControlsContainerProps { customEditabilityExpression: DynamicValue; readOnlyStyle: ReadOnlyStyleEnum; onChangeEvent?: ActionValue; - onEnterEvent?: ActionValue; - onLeaveEvent?: ActionValue; ariaRequired: DynamicValue; a11ySelectedValue?: DynamicValue; a11yOptionsAvailable?: DynamicValue; @@ -100,8 +98,6 @@ export interface SelectionControlsPreviewProps { readOnlyStyle: ReadOnlyStyleEnum; onChangeEvent: {} | null; onChangeDatabaseEvent: {} | null; - onEnterEvent: {} | null; - onLeaveEvent: {} | null; ariaRequired: string; a11ySelectedValue: string; a11yOptionsAvailable: string; From e49213f4e6908d15aff47da2706ab0151f4f85e1 Mon Sep 17 00:00:00 2001 From: gjulivan Date: Mon, 14 Jul 2025 23:55:12 +0200 Subject: [PATCH 06/20] chore: update preview --- .../src/SelectionControls.editorConfig.ts | 132 ++++++------------ .../src/SelectionControls.editorPreview.tsx | 31 ++-- .../src/SelectionControls.tsx | 9 +- .../src/SelectionControls.xml | 26 ---- .../src/assets/checkbox.svg | 7 + .../src/assets/radiobutton.svg | 4 + .../CheckboxSelection/CheckboxSelection.tsx | 11 +- .../src/components/Placeholder.tsx | 4 - .../RadioSelection/RadioSelection.tsx | 12 +- .../Preview/AssociationPreviewSelector.ts | 3 +- .../Preview/DatabasePreviewSelector.ts | 38 ++++- .../Preview/SimpleCaptionsProvider.tsx | 23 ++- .../Static/Preview/StaticPreviewSelector.ts | 3 +- .../src/helpers/types.ts | 9 -- .../src/helpers/utils.ts | 36 +++++ .../typings/SelectionControlsProps.d.ts | 6 - 16 files changed, 163 insertions(+), 191 deletions(-) create mode 100644 packages/pluggableWidgets/selection-controls-web/src/assets/checkbox.svg create mode 100644 packages/pluggableWidgets/selection-controls-web/src/assets/radiobutton.svg diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts index 682dcf1c8b..6b7f7edc5b 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts @@ -4,9 +4,15 @@ import { StructurePreviewProps, structurePreviewPalette, dropzone, - container + container, + rowLayout, + text, + svgImage } from "@mendix/widget-plugin-platform/preview/structure-preview-api"; import { SelectionControlsPreviewProps } from "../typings/SelectionControlsProps"; +import { getCustomCaption } from "./helpers/utils"; +import IconRadioButtonSVG from "./assets/radiobutton.svg"; +import IconCheckboxSVG from "./assets/checkbox.svg"; const DATABASE_SOURCE_CONFIG: Array = [ "optionsSourceDatabaseCaptionAttribute", @@ -127,43 +133,40 @@ export function getProperties( return defaultProperties; } -function getIconPreview(_isDarkMode: boolean): ContainerProps { - return { - type: "Container", - children: [ - container({ padding: 1 })(), - { - type: "Text", - content: "☑", - fontSize: 16 - } - ] - }; +function getIconPreview(isMultiSelect: boolean): ContainerProps { + return container({ grow: 0 })( + container({ padding: 3 })(), + svgImage({ width: 16, height: 16, grow: 0 })( + decodeURIComponent( + (isMultiSelect ? IconCheckboxSVG : IconRadioButtonSVG).replace("data:image/svg+xml,", "") + ) + ) + ); } -export function getPreview(_values: SelectionControlsPreviewProps, isDarkMode: boolean): StructurePreviewProps { +export function getPreview(values: SelectionControlsPreviewProps, isDarkMode: boolean): StructurePreviewProps { const palette = structurePreviewPalette[isDarkMode ? "dark" : "light"]; const structurePreviewChildren: StructurePreviewProps[] = []; - let readOnly = _values.readOnly; + let readOnly = values.readOnly; // Handle custom content dropzones when enabled - if (_values.optionsSourceCustomContentType !== "no") { - if (_values.source === "context" && _values.optionsSourceType === "association") { + if (values.optionsSourceCustomContentType !== "no") { + if (values.source === "context" && values.optionsSourceType === "association") { structurePreviewChildren.push( dropzone( dropzone.placeholder("Configure the selection controls: Place widgets here"), dropzone.hideDataSourceHeaderIf(false) - )(_values.optionsSourceAssociationCustomContent) + )(values.optionsSourceAssociationCustomContent) ); - } else if (_values.source === "database") { + } else if (values.source === "database") { structurePreviewChildren.push( dropzone( dropzone.placeholder("Configure the selection controls: Place widgets here"), dropzone.hideDataSourceHeaderIf(false) - )(_values.optionsSourceDatabaseCustomContent) + )(values.optionsSourceDatabaseCustomContent) ); - } else if (_values.source === "static") { - _values.optionsSourceStaticDataSource.forEach(value => { + } else if (values.source === "static") { + values.optionsSourceStaticDataSource.forEach(value => { structurePreviewChildren.push( container({ borders: true, @@ -183,76 +186,31 @@ export function getPreview(_values: SelectionControlsPreviewProps, isDarkMode: b } // Handle database-specific read-only logic - if (_values.source === "database" && _values.databaseAttributeString.length === 0) { - readOnly = _values.customEditability === "never"; + if (values.source === "database" && values.databaseAttributeString.length === 0) { + readOnly = values.customEditability === "never"; } // If no custom content dropzones, show default preview if (structurePreviewChildren.length === 0) { - return { - type: "RowLayout", - columnSize: "fixed", - backgroundColor: readOnly ? palette.background.containerDisabled : palette.background.containerFill, - children: [ - { - type: "RowLayout", - columnSize: "grow", - children: [ - getIconPreview(isDarkMode), - { - type: "Container", - padding: 4, - children: [ - { - type: "Text", - content: "Selection Controls", - fontColor: palette.text.primary, - fontSize: 10 - } - ] - } - ] - } - ] - }; + const isMultiSelect = values.optionsSourceDatabaseItemSelection === "Multi"; + return container()( + rowLayout({ + columnSize: "grow", + backgroundColor: palette.background.container + })( + getIconPreview(isMultiSelect), + container()(container({ padding: 3 })(), text()(getCustomCaption(values))) + ) + ); } // Return container with dropzones - return { - type: "Container", - children: [ - { - type: "RowLayout", - columnSize: "grow", - borders: true, - borderWidth: 1, - borderRadius: 2, - backgroundColor: readOnly ? palette.background.containerDisabled : palette.background.container, - children: [ - { - type: "Container", - grow: 1, - padding: 4, - children: structurePreviewChildren - }, - readOnly && _values.readOnlyStyle === "text" - ? container({ grow: 0, padding: 4 })() - : { - ...getIconPreview(isDarkMode), - ...{ grow: 0, padding: 4 } - } - ] - } - ] - }; -} - -export function getCustomCaption(values: SelectionControlsPreviewProps): string { - if (values.source === "static" && values.optionsSourceStaticDataSource.length > 0) { - return "Selection Controls (Static)"; - } - if (values.source === "database") { - return "Selection Controls (Database)"; - } - return "Selection Controls"; + return container()( + rowLayout({ + columnSize: "grow", + borders: true, + borderWidth: 1, + borderRadius: 2 + })(container({ grow: 1, padding: 4 })(...structurePreviewChildren)) + ); } diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx index 84e76d3626..7619894405 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx @@ -3,11 +3,15 @@ import { ReactElement, createElement, useMemo } from "react"; import { SelectionControlsPreviewProps } from "../typings/SelectionControlsProps"; import { RadioSelection } from "./components/RadioSelection/RadioSelection"; import { dynamic } from "@mendix/widget-plugin-test-utils"; -import { SingleSelector, SelectionBaseProps } from "./helpers/types"; +import { SingleSelector, SelectionBaseProps, MultiSelector } from "./helpers/types"; import { StaticPreviewSelector } from "./helpers/Static/Preview/StaticPreviewSelector"; -import { DatabasePreviewSelector } from "./helpers/Database/Preview/DatabasePreviewSelector"; +import { + DatabaseMultiPreviewSelector, + DatabasePreviewSelector +} from "./helpers/Database/Preview/DatabasePreviewSelector"; import { AssociationPreviewSelector } from "./helpers/Association/Preview/AssociationPreviewSelector"; import "./ui/SelectionControls.scss"; +import { CheckboxSelection } from "./components/CheckboxSelection/CheckboxSelection"; export const preview = (props: SelectionControlsPreviewProps): ReactElement => { const id = generateUUID().toString(); @@ -16,30 +20,31 @@ export const preview = (props: SelectionControlsPreviewProps): ReactElement => { inputId: id, labelId: `${id}-label`, readOnlyStyle: props.readOnlyStyle, - ariaRequired: dynamic(false), - a11yConfig: { - a11yStatusMessage: { - a11ySelectedValue: props.a11ySelectedValue, - a11yOptionsAvailable: props.a11yOptionsAvailable, - a11yInstructions: props.a11yInstructions - } - } + ariaRequired: dynamic(false) }; // eslint-disable-next-line react-hooks/rules-of-hooks - const selector: SingleSelector = useMemo(() => { + const selector: SingleSelector | MultiSelector = useMemo(() => { if (props.source === "static") { return new StaticPreviewSelector(props); } if (props.source === "database") { - return new DatabasePreviewSelector(props); + if (props.optionsSourceDatabaseItemSelection === "Multi") { + return new DatabaseMultiPreviewSelector(props); + } else { + return new DatabasePreviewSelector(props); + } } return new AssociationPreviewSelector(props); }, [props]); return (
- + {selector.type === "single" ? ( + + ) : ( + + )}
); }; diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx index fd0d57bd12..b06e44d857 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx @@ -17,14 +17,7 @@ export default function SelectionControls(props: SelectionControlsContainerProps inputId: props.id, labelId: `${props.id}-label`, readOnlyStyle: props.readOnlyStyle, - ariaRequired: props.ariaRequired, - a11yConfig: { - a11yStatusMessage: { - a11ySelectedValue: props.a11ySelectedValue?.value ?? "", - a11yOptionsAvailable: props.a11yOptionsAvailable?.value ?? "", - a11yInstructions: props.a11yInstructions?.value ?? "" - } - } + ariaRequired: props.ariaRequired }; return ( diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml index c1aa96918c..0c95135c1e 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml +++ b/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml @@ -256,32 +256,6 @@
- - - Selected value - Output example: "Selected value: Avocado, Apple, Banana." - - Selected value: - Geselecteerde waarde: - - - - Options available - Output example: "Number of options available: 1" - - Number of options available: - Aantal beschikbare opties: - - - - Instructions - Instructions to be read after announcing the status. - - Use up and down arrow keys to navigate. Press Enter or Space Bar keys to select. - Gebruik de pijltjestoetsen (omhoog en omlaag) om te navigeren. Druk op Enter of de spatiebalk om de waarde te selecteren. - - -
diff --git a/packages/pluggableWidgets/selection-controls-web/src/assets/checkbox.svg b/packages/pluggableWidgets/selection-controls-web/src/assets/checkbox.svg new file mode 100644 index 0000000000..e9b1a6c656 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/assets/checkbox.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/pluggableWidgets/selection-controls-web/src/assets/radiobutton.svg b/packages/pluggableWidgets/selection-controls-web/src/assets/radiobutton.svg new file mode 100644 index 0000000000..fb3def4bf0 --- /dev/null +++ b/packages/pluggableWidgets/selection-controls-web/src/assets/radiobutton.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx b/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx index 1dba13bafb..a3949548bb 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx @@ -21,14 +21,6 @@ export function CheckboxSelection({ } }; - if (selector.status === "unavailable") { - return ( -
-
Loading...
-
- ); - } - return (
{ const isSelected = currentIds.includes(optionId); const checkboxId = `${inputId}-checkbox-${index}`; + const name = selector.caption.get(optionId); return (
0 ? name : inputId} value={optionId} checked={isSelected} disabled={isReadOnly} tabIndex={tabIndex} onChange={e => handleChange(optionId, e.target.checked)} - aria-describedby={`${inputId}-description`} /> {selector.caption.render(optionId)}
diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/Placeholder.tsx b/packages/pluggableWidgets/selection-controls-web/src/components/Placeholder.tsx index 6edd8810b6..fef47d463c 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/components/Placeholder.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/components/Placeholder.tsx @@ -7,7 +7,3 @@ export function Placeholder(): ReactElement {
); } - -export function NoOptionsPlaceholder(): ReactElement { - return
No options available
; -} diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx b/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx index 80307b05e7..fe70b5224f 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx @@ -20,14 +20,6 @@ export function RadioSelection({ } }; - if (selector.status === "unavailable") { - return ( -
-
Loading...
-
- ); - } - return (
{ const isSelected = currentId === optionId; const radioId = `${inputId}-radio-${index}`; + const name = selector.caption.get(optionId); return (
0 ? name : inputId} value={optionId} checked={isSelected} disabled={isReadOnly} tabIndex={tabIndex} onChange={() => handleChange(optionId)} - aria-describedby={`${inputId}-description`} /> {selector.caption.render(optionId)}
diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts index d80fb91e1c..36b41d2d2e 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts @@ -6,6 +6,7 @@ import { import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; import { PreviewCaptionsProvider } from "../../Preview/PreviewCaptionsProvider"; import { PreviewOptionsProvider } from "../../Preview/PreviewOptionsProvider"; +import { getCustomCaption } from "../../utils"; export class AssociationPreviewSelector implements SingleSelector { type = "single" as const; @@ -26,7 +27,7 @@ export class AssociationPreviewSelector implements SingleSelector { this.currentId = `single-${generateUUID()}`; this.customContentType = props.optionsSourceCustomContentType; this.readOnly = props.readOnly; - this.caption = new PreviewCaptionsProvider(new Map()); + this.caption = new PreviewCaptionsProvider(new Map(), getCustomCaption(props)); this.options = new PreviewOptionsProvider(this.caption, new Map()); (this.caption as PreviewCaptionsProvider).updatePreviewProps({ customContentRenderer: props.optionsSourceAssociationCustomContent?.renderer, diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts index 73fbeeb749..c4ba54bd49 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts @@ -1,4 +1,4 @@ -import { SingleSelector, Status, CaptionsProvider, OptionsProvider } from "../../types"; +import { SingleSelector, Status, CaptionsProvider, OptionsProvider, MultiSelector } from "../../types"; import { SelectionControlsPreviewProps, OptionsSourceCustomContentTypeEnum @@ -6,6 +6,7 @@ import { import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; import { PreviewCaptionsProvider } from "../../Preview/PreviewCaptionsProvider"; import { PreviewOptionsProvider } from "../../Preview/PreviewOptionsProvider"; +import { getCustomCaption } from "../../utils"; export class DatabasePreviewSelector implements SingleSelector { type = "single" as const; @@ -25,7 +26,7 @@ export class DatabasePreviewSelector implements SingleSelector { this.currentId = `single-${generateUUID()}`; this.customContentType = props.optionsSourceCustomContentType; this.readOnly = props.readOnly; - this.caption = new PreviewCaptionsProvider(new Map()); + this.caption = new PreviewCaptionsProvider(new Map(), getCustomCaption(props)); this.options = new PreviewOptionsProvider(this.caption, new Map()); (this.caption as PreviewCaptionsProvider).updatePreviewProps({ customContentRenderer: props.optionsSourceDatabaseCustomContent?.renderer, @@ -37,3 +38,36 @@ export class DatabasePreviewSelector implements SingleSelector { updateProps() {} setValue() {} } + +export class DatabaseMultiPreviewSelector implements MultiSelector { + type = "multi" as const; + status: Status = "available"; + attributeType?: "string" | "boolean" | "big" | "date" | undefined; + selectorType?: "context" | "database" | "static" | undefined; + readOnly: boolean; + validation?: string | undefined; + clearable: boolean = false; + currentId: string[] | null; + customContentType: OptionsSourceCustomContentTypeEnum; + caption: CaptionsProvider; + options: OptionsProvider; + + constructor(props: SelectionControlsPreviewProps) { + this.currentId = [getCustomCaption(props)]; + this.customContentType = props.optionsSourceCustomContentType; + this.readOnly = props.readOnly; + this.caption = new PreviewCaptionsProvider(new Map(), getCustomCaption(props)); + this.options = new PreviewOptionsProvider(this.caption, new Map()); + (this.caption as PreviewCaptionsProvider).updatePreviewProps({ + customContentRenderer: props.optionsSourceDatabaseCustomContent?.renderer, + customContentType: props.optionsSourceCustomContentType + }); + // Show dropzones in design mode when custom content is enabled + } + getOptions(): string[] { + return this.currentId || []; + } + + updateProps() {} + setValue() {} +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx b/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx index d9c0a3c283..3ee3656f39 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx @@ -17,7 +17,10 @@ export class SimpleCaptionsProvider implements CaptionsProvider { protected customContentType: OptionsSourceCustomContentTypeEnum = "no"; emptyCaption = ""; - constructor(private optionsMap: Map) {} + constructor( + private optionsMap: Map, + private dataSourcePlaceholder: string + ) {} updateProps(props: Props): void { if (!props.emptyOptionText || props.emptyOptionText.status === "unavailable") { @@ -64,20 +67,10 @@ export class SimpleCaptionsProvider implements CaptionsProvider { } render(value: string | null): ReactNode { - if (value === null) { - return {this.emptyCaption}; - } - - const item = this.optionsMap.get(value); - if (!item) { - return ; - } - - if (this.customContentType === "yes" && this.customContent) { - return this.customContent.get(item); + if (this.customContentType === "yes") { + return this.getCustomContent(value); } - - const caption = this.formatter?.get(item).value || ""; - return {caption}; + return
{this.dataSourcePlaceholder}
; + // return this.get(value); } } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts index e38a625827..e3b794b0b2 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts @@ -6,6 +6,7 @@ import { import { PreviewCaptionsProvider } from "../../Preview/PreviewCaptionsProvider"; import { PreviewOptionsProvider } from "../../Preview/PreviewOptionsProvider"; import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; +import { getCustomCaption } from "../../utils"; export class StaticPreviewSelector implements SingleSelector { type = "single" as const; @@ -25,7 +26,7 @@ export class StaticPreviewSelector implements SingleSelector { this.currentId = `single-${generateUUID()}`; this.customContentType = props.optionsSourceCustomContentType; this.readOnly = props.readOnly; - this.caption = new PreviewCaptionsProvider(new Map()); + this.caption = new PreviewCaptionsProvider(new Map(), getCustomCaption(props)); this.options = new PreviewOptionsProvider(this.caption, new Map()); } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts index 78357f79df..0b65b31bb0 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts @@ -82,13 +82,4 @@ export interface SelectionBaseProps { selector: Selector; tabIndex: number; ariaRequired: DynamicValue; - a11yConfig: { - a11yStatusMessage: A11yStatusMessage; - }; -} - -export interface A11yStatusMessage { - a11ySelectedValue: string; - a11yOptionsAvailable: string; - a11yInstructions: string; } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts b/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts index 7a9669a541..5af8f15586 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts +++ b/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts @@ -1,4 +1,5 @@ import { Big } from "big.js"; +import { SelectionControlsPreviewProps } from "../../typings/SelectionControlsProps"; export function _valuesIsEqual( value1: string | Big | boolean | Date | undefined, @@ -18,3 +19,38 @@ export function _valuesIsEqual( return false; } + +export function getCustomCaption(values: SelectionControlsPreviewProps): string { + const { + optionsSourceType, + optionsSourceAssociationDataSource, + attributeEnumeration, + attributeBoolean, + databaseAttributeString, + source, + optionsSourceDatabaseDataSource, + staticAttribute, + optionsSourceStaticDataSource + } = values; + const emptyStringFormat = "Selection Controls"; + if (source === "context") { + switch (optionsSourceType) { + case "association": + return (optionsSourceAssociationDataSource as { caption?: string })?.caption || emptyStringFormat; + case "enumeration": + return `[${optionsSourceType}, ${attributeEnumeration}]`; + case "boolean": + return `[${optionsSourceType}, ${attributeBoolean}]`; + default: + return emptyStringFormat; + } + } else if (source === "database" && optionsSourceDatabaseDataSource) { + return ( + (optionsSourceDatabaseDataSource as { caption?: string })?.caption || + `${source}, ${databaseAttributeString}` + ); + } else if (source === "static") { + return (optionsSourceStaticDataSource as { caption?: string })?.caption || `[${source}, ${staticAttribute}]`; + } + return emptyStringFormat; +} diff --git a/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts b/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts index 486f65919d..0d72f50404 100644 --- a/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts +++ b/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts @@ -63,9 +63,6 @@ export interface SelectionControlsContainerProps { readOnlyStyle: ReadOnlyStyleEnum; onChangeEvent?: ActionValue; ariaRequired: DynamicValue; - a11ySelectedValue?: DynamicValue; - a11yOptionsAvailable?: DynamicValue; - a11yInstructions?: DynamicValue; } export interface SelectionControlsPreviewProps { @@ -99,7 +96,4 @@ export interface SelectionControlsPreviewProps { onChangeEvent: {} | null; onChangeDatabaseEvent: {} | null; ariaRequired: string; - a11ySelectedValue: string; - a11yOptionsAvailable: string; - a11yInstructions: string; } From e5edaa748273c208d67843ddaf755d9ed877a8c1 Mon Sep 17 00:00:00 2001 From: gjulivan Date: Tue, 15 Jul 2025 00:30:58 +0200 Subject: [PATCH 07/20] chore: rename selection controls to checkbox radio selection --- .../.prettierrc.js | 0 .../CHANGELOG.md | 2 +- .../README.md | 4 ++-- .../e2e/SelectionControls.spec.js | 0 .../eslint.config.mjs | 0 .../package.json | 12 +++++----- .../playwright.config.cjs | 0 .../rollup.config.js | 0 .../src/CheckboxRadioSelection.dark.png} | 0 .../CheckboxRadioSelection.editorConfig.ts} | 24 +++++++++---------- .../CheckboxRadioSelection.editorPreview.tsx} | 8 +++---- .../src/CheckboxRadioSelection.icon.png} | 0 .../src/CheckboxRadioSelection.png} | 0 .../src/CheckboxRadioSelection.tile.dark.png} | 0 .../src/CheckboxRadioSelection.tile.png} | 0 .../src/CheckboxRadioSelection.tsx} | 8 +++---- .../src/CheckboxRadioSelection.xml} | 6 ++--- .../src/__tests__/SelectionControls.spec.tsx | 24 +++++++++---------- .../src/assets/checkbox.svg | 0 .../src/assets/radiobutton.svg | 0 .../src/components/CaptionContent.tsx | 0 .../CheckboxSelection/CheckboxSelection.tsx | 14 +++++------ .../src/components/Placeholder.tsx | 9 +++++++ .../RadioSelection/RadioSelection.tsx | 14 +++++------ .../Association/AssociationMultiSelector.ts | 4 ++-- .../Association/AssociationOptionsProvider.ts | 0 .../AssociationSimpleCaptionsProvider.tsx | 2 +- .../Association/AssociationSingleSelector.ts | 4 ++-- .../Association/BaseAssociationSelector.ts | 6 ++--- .../Preview/AssociationPreviewSelector.ts | 6 ++--- .../src/helpers/Association/utils.ts | 8 +++---- .../src/helpers/BaseOptionsProvider.ts | 0 .../Database/DatabaseCaptionsProvider.tsx | 2 +- .../helpers/Database/DatabaseMultiSelector.ts | 6 ++--- .../Database/DatabaseOptionsProvider.ts | 0 .../Database/DatabaseSingleSelector.ts | 6 ++--- .../Database/DatabaseValuesProvider.ts | 0 .../Preview/DatabasePreviewSelector.ts | 8 +++---- .../src/helpers/Database/utils.ts | 8 +++---- .../EnumAndBooleanSimpleCaptionsProvider.tsx | 0 .../EnumBool/EnumBoolOptionsProvider.ts | 0 .../EnumBool/EnumBooleanSingleSelector.ts | 6 ++--- .../src/helpers/EnumBool/utils.ts | 6 ++--- .../Preview/PreviewCaptionsProvider.tsx | 4 ++-- .../helpers/Preview/PreviewOptionsProvider.ts | 0 .../Preview/SimpleCaptionsProvider.tsx | 2 +- .../Static/Preview/StaticPreviewSelector.ts | 6 ++--- .../helpers/Static/StaticCaptionsProvider.tsx | 2 +- .../helpers/Static/StaticOptionsProvider.ts | 2 +- .../helpers/Static/StaticSingleSelector.ts | 6 ++--- .../src/helpers/Static/utils.ts | 8 +++---- .../src/helpers/getSelector.ts | 4 ++-- .../src/helpers/types.ts | 6 ++--- .../src/helpers/utils.ts | 6 ++--- .../src/hooks/useGetSelector.ts | 4 ++-- .../src/package.xml | 11 +++++++++ .../src/ui/CheckboxRadioSelection.scss | 5 ++++ .../tsconfig.json | 0 .../typings/CheckboxRadioSelectionProps.d.ts} | 6 ++--- .../typings/declare-svg.ts | 0 .../src/components/Placeholder.tsx | 9 ------- .../selection-controls-web/src/package.xml | 11 --------- .../src/ui/SelectionControls.scss | 5 ---- 63 files changed, 146 insertions(+), 148 deletions(-) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/.prettierrc.js (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/CHANGELOG.md (91%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/README.md (93%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/e2e/SelectionControls.spec.js (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/eslint.config.mjs (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/package.json (87%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/playwright.config.cjs (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/rollup.config.js (100%) rename packages/pluggableWidgets/{selection-controls-web/src/SelectionControls.dark.png => checkbox-radio-selection-web/src/CheckboxRadioSelection.dark.png} (100%) rename packages/pluggableWidgets/{selection-controls-web/src/SelectionControls.editorConfig.ts => checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts} (89%) rename packages/pluggableWidgets/{selection-controls-web/src/SelectionControls.editorPreview.tsx => checkbox-radio-selection-web/src/CheckboxRadioSelection.editorPreview.tsx} (85%) rename packages/pluggableWidgets/{selection-controls-web/src/SelectionControls.icon.png => checkbox-radio-selection-web/src/CheckboxRadioSelection.icon.png} (100%) rename packages/pluggableWidgets/{selection-controls-web/src/SelectionControls.png => checkbox-radio-selection-web/src/CheckboxRadioSelection.png} (100%) rename packages/pluggableWidgets/{selection-controls-web/src/SelectionControls.tile.dark.png => checkbox-radio-selection-web/src/CheckboxRadioSelection.tile.dark.png} (100%) rename packages/pluggableWidgets/{selection-controls-web/src/SelectionControls.tile.png => checkbox-radio-selection-web/src/CheckboxRadioSelection.tile.png} (100%) rename packages/pluggableWidgets/{selection-controls-web/src/SelectionControls.tsx => checkbox-radio-selection-web/src/CheckboxRadioSelection.tsx} (77%) rename packages/pluggableWidgets/{selection-controls-web/src/SelectionControls.xml => checkbox-radio-selection-web/src/CheckboxRadioSelection.xml} (96%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/__tests__/SelectionControls.spec.tsx (69%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/assets/checkbox.svg (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/assets/radiobutton.svg (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/components/CaptionContent.tsx (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/components/CheckboxSelection/CheckboxSelection.tsx (78%) create mode 100644 packages/pluggableWidgets/checkbox-radio-selection-web/src/components/Placeholder.tsx rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/components/RadioSelection/RadioSelection.tsx (77%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Association/AssociationMultiSelector.ts (81%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Association/AssociationOptionsProvider.ts (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx (98%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Association/AssociationSingleSelector.ts (76%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Association/BaseAssociationSelector.ts (93%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Association/Preview/AssociationPreviewSelector.ts (91%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Association/utils.ts (86%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/BaseOptionsProvider.ts (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Database/DatabaseCaptionsProvider.tsx (98%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Database/DatabaseMultiSelector.ts (95%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Database/DatabaseOptionsProvider.ts (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Database/DatabaseSingleSelector.ts (96%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Database/DatabaseValuesProvider.ts (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Database/Preview/DatabasePreviewSelector.ts (93%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Database/utils.ts (88%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/EnumBool/EnumAndBooleanSimpleCaptionsProvider.tsx (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/EnumBool/EnumBoolOptionsProvider.ts (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/EnumBool/EnumBooleanSingleSelector.ts (93%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/EnumBool/utils.ts (74%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Preview/PreviewCaptionsProvider.tsx (94%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Preview/PreviewOptionsProvider.ts (100%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Preview/SimpleCaptionsProvider.tsx (98%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Static/Preview/StaticPreviewSelector.ts (89%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Static/StaticCaptionsProvider.tsx (97%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Static/StaticOptionsProvider.ts (97%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Static/StaticSingleSelector.ts (95%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/Static/utils.ts (80%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/getSelector.ts (88%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/types.ts (93%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/helpers/utils.ts (87%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/src/hooks/useGetSelector.ts (72%) create mode 100644 packages/pluggableWidgets/checkbox-radio-selection-web/src/package.xml create mode 100644 packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelection.scss rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/tsconfig.json (100%) rename packages/pluggableWidgets/{selection-controls-web/typings/SelectionControlsProps.d.ts => checkbox-radio-selection-web/typings/CheckboxRadioSelectionProps.d.ts} (96%) rename packages/pluggableWidgets/{selection-controls-web => checkbox-radio-selection-web}/typings/declare-svg.ts (100%) delete mode 100644 packages/pluggableWidgets/selection-controls-web/src/components/Placeholder.tsx delete mode 100644 packages/pluggableWidgets/selection-controls-web/src/package.xml delete mode 100644 packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss diff --git a/packages/pluggableWidgets/selection-controls-web/.prettierrc.js b/packages/pluggableWidgets/checkbox-radio-selection-web/.prettierrc.js similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/.prettierrc.js rename to packages/pluggableWidgets/checkbox-radio-selection-web/.prettierrc.js diff --git a/packages/pluggableWidgets/selection-controls-web/CHANGELOG.md b/packages/pluggableWidgets/checkbox-radio-selection-web/CHANGELOG.md similarity index 91% rename from packages/pluggableWidgets/selection-controls-web/CHANGELOG.md rename to packages/pluggableWidgets/checkbox-radio-selection-web/CHANGELOG.md index ec9eaf9f34..60630da512 100644 --- a/packages/pluggableWidgets/selection-controls-web/CHANGELOG.md +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/CHANGELOG.md @@ -10,7 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added -- Initial release of Selection Controls widget +- Initial release of Checkbox Radio Selection widget - Support for radio button lists (single selection) - Support for checkbox lists (multiple selection) - Context data source support (associations) diff --git a/packages/pluggableWidgets/selection-controls-web/README.md b/packages/pluggableWidgets/checkbox-radio-selection-web/README.md similarity index 93% rename from packages/pluggableWidgets/selection-controls-web/README.md rename to packages/pluggableWidgets/checkbox-radio-selection-web/README.md index 8e8e747a08..538552a435 100644 --- a/packages/pluggableWidgets/selection-controls-web/README.md +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/README.md @@ -1,4 +1,4 @@ -# Selection Controls +# Checkbox Radio Selection A widget for displaying radio button lists (single selection) and checkbox lists (multiple selection) based on different data sources. @@ -20,7 +20,7 @@ The widget supports various data source types: ## Usage -1. Add the Selection Controls widget to your page +1. Add the Checkbox Radio Selection widget to your page 2. Configure the data source (Context, Database, or Static) 3. Set up caption and value attributes 4. Configure selection method (single or multiple) diff --git a/packages/pluggableWidgets/selection-controls-web/e2e/SelectionControls.spec.js b/packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/e2e/SelectionControls.spec.js rename to packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js diff --git a/packages/pluggableWidgets/selection-controls-web/eslint.config.mjs b/packages/pluggableWidgets/checkbox-radio-selection-web/eslint.config.mjs similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/eslint.config.mjs rename to packages/pluggableWidgets/checkbox-radio-selection-web/eslint.config.mjs diff --git a/packages/pluggableWidgets/selection-controls-web/package.json b/packages/pluggableWidgets/checkbox-radio-selection-web/package.json similarity index 87% rename from packages/pluggableWidgets/selection-controls-web/package.json rename to packages/pluggableWidgets/checkbox-radio-selection-web/package.json index 99b9fbb054..d6accd0194 100644 --- a/packages/pluggableWidgets/selection-controls-web/package.json +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/package.json @@ -1,6 +1,6 @@ { - "name": "@mendix/selection-controls-web", - "widgetName": "SelectionControls", + "name": "@mendix/checkbox-radio-selection-web", + "widgetName": "CheckboxRadioSelection", "version": "1.0.0", "description": "Configurable radio buttons and check box widget", "copyright": "© Mendix Technology BV 2025. All rights reserved.", @@ -14,20 +14,20 @@ "mendixHost": "http://localhost:8080" }, "mxpackage": { - "name": "SelectionControls", + "name": "CheckboxRadioSelection", "type": "widget", - "mpkName": "com.mendix.widget.web.SelectionControls.mpk" + "mpkName": "com.mendix.widget.web.CheckboxRadioSelection.mpk" }, "packagePath": "com.mendix.widget.web", "marketplace": { "minimumMXVersion": "10.7.0", "appNumber": 219304, - "appName": "Selection Controls", + "appName": "Checkbox Radio Selection", "reactReady": true }, "testProject": { "githubUrl": "https://github.com/mendix/testProjects", - "branchName": "selection-controls-web" + "branchName": "checkbox-radio-selection-web" }, "scripts": { "prebuild": "rui-create-translation", diff --git a/packages/pluggableWidgets/selection-controls-web/playwright.config.cjs b/packages/pluggableWidgets/checkbox-radio-selection-web/playwright.config.cjs similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/playwright.config.cjs rename to packages/pluggableWidgets/checkbox-radio-selection-web/playwright.config.cjs diff --git a/packages/pluggableWidgets/selection-controls-web/rollup.config.js b/packages/pluggableWidgets/checkbox-radio-selection-web/rollup.config.js similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/rollup.config.js rename to packages/pluggableWidgets/checkbox-radio-selection-web/rollup.config.js diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.dark.png b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.dark.png similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/src/SelectionControls.dark.png rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.dark.png diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts similarity index 89% rename from packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts index 6b7f7edc5b..8fae07eeb3 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts @@ -9,12 +9,12 @@ import { text, svgImage } from "@mendix/widget-plugin-platform/preview/structure-preview-api"; -import { SelectionControlsPreviewProps } from "../typings/SelectionControlsProps"; +import { CheckboxRadioSelectionPreviewProps } from "../typings/CheckboxRadioSelectionProps"; import { getCustomCaption } from "./helpers/utils"; import IconRadioButtonSVG from "./assets/radiobutton.svg"; import IconCheckboxSVG from "./assets/checkbox.svg"; -const DATABASE_SOURCE_CONFIG: Array = [ +const DATABASE_SOURCE_CONFIG: Array = [ "optionsSourceDatabaseCaptionAttribute", "optionsSourceDatabaseCaptionExpression", "optionsSourceDatabaseCaptionType", @@ -26,7 +26,7 @@ const DATABASE_SOURCE_CONFIG: Array = [ "onChangeDatabaseEvent" ]; -const ASSOCIATION_SOURCE_CONFIG: Array = [ +const ASSOCIATION_SOURCE_CONFIG: Array = [ "optionsSourceAssociationCaptionAttribute", "optionsSourceAssociationCaptionExpression", "optionsSourceAssociationCaptionType", @@ -36,7 +36,7 @@ const ASSOCIATION_SOURCE_CONFIG: Array = [ ]; export function getProperties( - values: SelectionControlsPreviewProps & { Editability?: unknown }, + values: CheckboxRadioSelectionPreviewProps & { Editability?: unknown }, defaultProperties: Properties ): Properties { // Basic property hiding logic - can be expanded later @@ -144,24 +144,24 @@ function getIconPreview(isMultiSelect: boolean): ContainerProps { ); } -export function getPreview(values: SelectionControlsPreviewProps, isDarkMode: boolean): StructurePreviewProps { +export function getPreview(values: CheckboxRadioSelectionPreviewProps, isDarkMode: boolean): StructurePreviewProps { const palette = structurePreviewPalette[isDarkMode ? "dark" : "light"]; const structurePreviewChildren: StructurePreviewProps[] = []; - let readOnly = values.readOnly; + // let readOnly = values.readOnly; // Handle custom content dropzones when enabled if (values.optionsSourceCustomContentType !== "no") { if (values.source === "context" && values.optionsSourceType === "association") { structurePreviewChildren.push( dropzone( - dropzone.placeholder("Configure the selection controls: Place widgets here"), + dropzone.placeholder("Configure the checkbox radio selection: Place widgets here"), dropzone.hideDataSourceHeaderIf(false) )(values.optionsSourceAssociationCustomContent) ); } else if (values.source === "database") { structurePreviewChildren.push( dropzone( - dropzone.placeholder("Configure the selection controls: Place widgets here"), + dropzone.placeholder("Configure the checkbox radio selection: Place widgets here"), dropzone.hideDataSourceHeaderIf(false) )(values.optionsSourceDatabaseCustomContent) ); @@ -175,7 +175,7 @@ export function getPreview(values: SelectionControlsPreviewProps, isDarkMode: bo })( dropzone( dropzone.placeholder( - `Configure the selection controls: Place widgets for option ${value.staticDataSourceCaption} here` + `Configure the checkbox radio selection: Place widgets for option ${value.staticDataSourceCaption} here` ), dropzone.hideDataSourceHeaderIf(false) )(value.staticDataSourceCustomContent) @@ -186,9 +186,9 @@ export function getPreview(values: SelectionControlsPreviewProps, isDarkMode: bo } // Handle database-specific read-only logic - if (values.source === "database" && values.databaseAttributeString.length === 0) { - readOnly = values.customEditability === "never"; - } + // if (values.source === "database" && values.databaseAttributeString.length === 0) { + // readOnly = values.customEditability === "never"; + // } // If no custom content dropzones, show default preview if (structurePreviewChildren.length === 0) { diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorPreview.tsx similarity index 85% rename from packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorPreview.tsx index 7619894405..f62b1af17b 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorPreview.tsx @@ -1,6 +1,6 @@ import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; import { ReactElement, createElement, useMemo } from "react"; -import { SelectionControlsPreviewProps } from "../typings/SelectionControlsProps"; +import { CheckboxRadioSelectionPreviewProps } from "../typings/CheckboxRadioSelectionProps"; import { RadioSelection } from "./components/RadioSelection/RadioSelection"; import { dynamic } from "@mendix/widget-plugin-test-utils"; import { SingleSelector, SelectionBaseProps, MultiSelector } from "./helpers/types"; @@ -10,10 +10,10 @@ import { DatabasePreviewSelector } from "./helpers/Database/Preview/DatabasePreviewSelector"; import { AssociationPreviewSelector } from "./helpers/Association/Preview/AssociationPreviewSelector"; -import "./ui/SelectionControls.scss"; +import "./ui/CheckboxRadioSelection.scss"; import { CheckboxSelection } from "./components/CheckboxSelection/CheckboxSelection"; -export const preview = (props: SelectionControlsPreviewProps): ReactElement => { +export const preview = (props: CheckboxRadioSelectionPreviewProps): ReactElement => { const id = generateUUID().toString(); const commonProps: Omit, "selector"> = { tabIndex: 1, @@ -39,7 +39,7 @@ export const preview = (props: SelectionControlsPreviewProps): ReactElement => { }, [props]); return ( -
+
{selector.type === "single" ? ( ) : ( diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.icon.png b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.icon.png similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/src/SelectionControls.icon.png rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.icon.png diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.png b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.png similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/src/SelectionControls.png rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.png diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tile.dark.png b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tile.dark.png similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tile.dark.png rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tile.dark.png diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tile.png b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tile.png similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tile.png rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tile.png diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tsx similarity index 77% rename from packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tsx index b06e44d857..150fa650eb 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tsx @@ -1,6 +1,6 @@ import { createElement, ReactElement } from "react"; -import { SelectionControlsContainerProps } from "../typings/SelectionControlsProps"; +import { CheckboxRadioSelectionContainerProps } from "../typings/CheckboxRadioSelectionProps"; import { CheckboxSelection } from "./components/CheckboxSelection/CheckboxSelection"; import { Placeholder } from "./components/Placeholder"; @@ -8,9 +8,9 @@ import { RadioSelection } from "./components/RadioSelection/RadioSelection"; import { SelectionBaseProps } from "./helpers/types"; import { useGetSelector } from "./hooks/useGetSelector"; -import "./ui/SelectionControls.scss"; +import "./ui/CheckboxRadioSelection.scss"; -export default function SelectionControls(props: SelectionControlsContainerProps): ReactElement { +export default function CheckboxRadioSelection(props: CheckboxRadioSelectionContainerProps): ReactElement { const selector = useGetSelector(props); const commonProps: Omit, "selector"> = { tabIndex: props.tabIndex!, @@ -21,7 +21,7 @@ export default function SelectionControls(props: SelectionControlsContainerProps }; return ( -
+
{selector.status === "unavailable" ? ( ) : selector.type === "single" ? ( diff --git a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.xml similarity index 96% rename from packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.xml index 0c95135c1e..6de1f8f2d9 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/SelectionControls.xml +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.xml @@ -1,10 +1,10 @@ - - Selection Controls + + Checkbox Radio Selection Input elements Display - https://docs.mendix.com/appstore/widgets/selectioncontrols + https://docs.mendix.com/appstore/widgets/checkboxradioselection diff --git a/packages/pluggableWidgets/selection-controls-web/src/__tests__/SelectionControls.spec.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/SelectionControls.spec.tsx similarity index 69% rename from packages/pluggableWidgets/selection-controls-web/src/__tests__/SelectionControls.spec.tsx rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/SelectionControls.spec.tsx index d63c12c7bd..d0c84c174a 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/__tests__/SelectionControls.spec.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/SelectionControls.spec.tsx @@ -1,7 +1,7 @@ import { render } from "@testing-library/react"; import { createElement } from "react"; -import { SelectionControlsContainerProps } from "../../typings/SelectionControlsProps"; -import SelectionControls from "../SelectionControls"; +import { CheckboxRadioSelectionContainerProps } from "../../typings/CheckboxRadioSelectionProps"; +import CheckboxRadioSelection from "../CheckboxRadioSelection"; // Mock the selector to avoid implementation dependencies for basic tests jest.mock("../helpers/getSelector", () => ({ @@ -15,8 +15,8 @@ jest.mock("../helpers/getSelector", () => ({ })) })); -describe("SelectionControls", () => { - const defaultProps: SelectionControlsContainerProps = { +describe("CheckboxRadioSelection", () => { + const defaultProps: CheckboxRadioSelectionContainerProps = { name: "selectionControls1", id: "selectionControls1", source: "context" as const, @@ -40,9 +40,7 @@ describe("SelectionControls", () => { optionsSourceStaticDataSource: [], optionsSourceAssociationCaptionType: "attribute" as const, optionsSourceDatabaseCaptionType: "attribute" as const, - optionsSourceAssociationCustomContentType: "no" as const, - optionsSourceDatabaseCustomContentType: "no" as const, - staticDataSourceCustomContentType: "no" as const, + optionsSourceCustomContentType: "no" as const, customEditability: "default" as const, customEditabilityExpression: { status: "available", value: false } as any, readOnlyStyle: "bordered" as const, @@ -50,19 +48,19 @@ describe("SelectionControls", () => { }; it("renders without crashing", () => { - const component = render(); - expect(component.container.querySelector(".widget-selection-controls")).toBeTruthy(); + const component = render(); + expect(component.container.querySelector(".widget-checkbox-radio-selection")).toBeTruthy(); }); it("renders placeholder when selector status is unavailable", () => { // This test would need more setup to properly mock the unavailable state - const component = render(); + const component = render(); expect(component.container).toBeDefined(); }); it("applies correct CSS class", () => { - const component = render(); - const widget = component.container.querySelector(".widget-selection-controls"); - expect(widget?.className).toContain("widget-selection-controls"); + const component = render(); + const widget = component.container.querySelector(".widget-checkbox-radio-selection"); + expect(widget?.className).toContain("widget-checkbox-radio-selection"); }); }); diff --git a/packages/pluggableWidgets/selection-controls-web/src/assets/checkbox.svg b/packages/pluggableWidgets/checkbox-radio-selection-web/src/assets/checkbox.svg similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/src/assets/checkbox.svg rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/assets/checkbox.svg diff --git a/packages/pluggableWidgets/selection-controls-web/src/assets/radiobutton.svg b/packages/pluggableWidgets/checkbox-radio-selection-web/src/assets/radiobutton.svg similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/src/assets/radiobutton.svg rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/assets/radiobutton.svg diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/CaptionContent.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CaptionContent.tsx similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/src/components/CaptionContent.tsx rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CaptionContent.tsx diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CheckboxSelection/CheckboxSelection.tsx similarity index 78% rename from packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CheckboxSelection/CheckboxSelection.tsx index a3949548bb..93a966b83f 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/components/CheckboxSelection/CheckboxSelection.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CheckboxSelection/CheckboxSelection.tsx @@ -23,13 +23,13 @@ export function CheckboxSelection({ return (
No options available
+
No options available
)}
diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/Placeholder.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/Placeholder.tsx new file mode 100644 index 0000000000..c7b9a88f89 --- /dev/null +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/Placeholder.tsx @@ -0,0 +1,9 @@ +import { ReactElement, createElement } from "react"; + +export function Placeholder(): ReactElement { + return ( +
+
Loading...
+
+ ); +} diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx similarity index 77% rename from packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx index fe70b5224f..fc6f6a4b52 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/components/RadioSelection/RadioSelection.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx @@ -22,13 +22,13 @@ export function RadioSelection({ return (
No options available
+
No options available
)}
diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationMultiSelector.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/AssociationMultiSelector.ts similarity index 81% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationMultiSelector.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/AssociationMultiSelector.ts index 8f5ab4e3ea..720b42b39a 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationMultiSelector.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/AssociationMultiSelector.ts @@ -1,5 +1,5 @@ import { ReferenceSetValue } from "mendix"; -import { SelectionControlsContainerProps } from "../../../typings/SelectionControlsProps"; +import { CheckboxRadioSelectionContainerProps } from "../../../typings/CheckboxRadioSelectionProps"; import { MultiSelector } from "../types"; import { BaseAssociationSelector } from "./BaseAssociationSelector"; @@ -9,7 +9,7 @@ export class AssociationMultiSelector { type = "multi" as const; - updateProps(props: SelectionControlsContainerProps): void { + updateProps(props: CheckboxRadioSelectionContainerProps): void { super.updateProps(props); // Convert reference set value to array of IDs diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationOptionsProvider.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/AssociationOptionsProvider.ts similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationOptionsProvider.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/AssociationOptionsProvider.ts diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx similarity index 98% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx index 1806fc9529..a7cff1208f 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/AssociationSimpleCaptionsProvider.tsx @@ -1,6 +1,6 @@ import { DynamicValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, ObjectItem } from "mendix"; import { ReactNode } from "react"; -import { OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; +import { OptionsSourceCustomContentTypeEnum } from "../../../typings/CheckboxRadioSelectionProps"; import { CaptionsProvider } from "../types"; interface AssociationSimpleCaptionsProviderProps { diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSingleSelector.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/AssociationSingleSelector.ts similarity index 76% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSingleSelector.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/AssociationSingleSelector.ts index 939aa43ff4..6e3e8afbcb 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/AssociationSingleSelector.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/AssociationSingleSelector.ts @@ -1,5 +1,5 @@ import { ReferenceValue } from "mendix"; -import { SelectionControlsContainerProps } from "../../../typings/SelectionControlsProps"; +import { CheckboxRadioSelectionContainerProps } from "../../../typings/CheckboxRadioSelectionProps"; import { SingleSelector } from "../types"; import { BaseAssociationSelector } from "./BaseAssociationSelector"; @@ -9,7 +9,7 @@ export class AssociationSingleSelector { type = "single" as const; - updateProps(props: SelectionControlsContainerProps): void { + updateProps(props: CheckboxRadioSelectionContainerProps): void { super.updateProps(props); this.currentId = this._attr?.value?.id ?? null; } diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/BaseAssociationSelector.ts similarity index 93% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/BaseAssociationSelector.ts index 6733f4f3b0..ab4729ed75 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Association/BaseAssociationSelector.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/BaseAssociationSelector.ts @@ -1,9 +1,9 @@ import { ActionValue, ObjectItem, ReferenceSetValue, ReferenceValue } from "mendix"; import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action"; import { - SelectionControlsContainerProps, + CheckboxRadioSelectionContainerProps, OptionsSourceCustomContentTypeEnum -} from "../../../typings/SelectionControlsProps"; +} from "../../../typings/CheckboxRadioSelectionProps"; import { Status } from "../types"; import { AssociationOptionsProvider } from "./AssociationOptionsProvider"; import { AssociationSimpleCaptionsProvider } from "./AssociationSimpleCaptionsProvider"; @@ -27,7 +27,7 @@ export class BaseAssociationSelector | undefined; captionProvider: ListAttributeValue | ListExpressionValue; clearable: boolean; @@ -39,7 +39,7 @@ export function extractDatabaseProps(props: SelectionControlsContainerProps): { value: "" }; - // Selection controls don't need clearable like combobox does + // Checkbox Radio Selection controls don't need clearable like combobox does const clearable = false; const customContent = props.optionsSourceDatabaseCustomContent; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumAndBooleanSimpleCaptionsProvider.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/EnumBool/EnumAndBooleanSimpleCaptionsProvider.tsx similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumAndBooleanSimpleCaptionsProvider.tsx rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/EnumBool/EnumAndBooleanSimpleCaptionsProvider.tsx diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBoolOptionsProvider.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/EnumBool/EnumBoolOptionsProvider.ts similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBoolOptionsProvider.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/EnumBool/EnumBoolOptionsProvider.ts diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts similarity index 93% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts index 4761f10554..26de1946a0 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/EnumBool/EnumBooleanSingleSelector.ts @@ -1,9 +1,9 @@ import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action"; import { ActionValue, EditableValue } from "mendix"; import { - SelectionControlsContainerProps, + CheckboxRadioSelectionContainerProps, OptionsSourceCustomContentTypeEnum -} from "../../../typings/SelectionControlsProps"; +} from "../../../typings/CheckboxRadioSelectionProps"; import { SingleSelector, Status } from "../types"; import { EnumAndBooleanSimpleCaptionsProvider } from "./EnumAndBooleanSimpleCaptionsProvider"; import { EnumBoolOptionsProvider } from "./EnumBoolOptionsProvider"; @@ -29,7 +29,7 @@ export class EnumBooleanSingleSelector implements SingleSelector { this.options = new EnumBoolOptionsProvider(this.caption); } - updateProps(props: SelectionControlsContainerProps): void { + updateProps(props: CheckboxRadioSelectionContainerProps): void { const [attr, emptyOption, clearable, filterType] = extractEnumerationProps(props); this._attr = attr; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/utils.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/EnumBool/utils.ts similarity index 74% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/utils.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/EnumBool/utils.ts index cf181720a6..8bc0d6e73f 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/EnumBool/utils.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/EnumBool/utils.ts @@ -1,8 +1,8 @@ import { DynamicValue, EditableValue, ValueStatus } from "mendix"; -import { SelectionControlsContainerProps } from "../../../typings/SelectionControlsProps"; +import { CheckboxRadioSelectionContainerProps } from "../../../typings/CheckboxRadioSelectionProps"; export function extractEnumerationProps( - props: SelectionControlsContainerProps + props: CheckboxRadioSelectionContainerProps ): [EditableValue, DynamicValue, boolean, "none"] { const attribute = props.optionsSourceType === "enumeration" ? props.attributeEnumeration : props.attributeBoolean; @@ -12,7 +12,7 @@ export function extractEnumerationProps( value: "" }; - // Selection controls don't need clearable like combobox does + // Checkbox Radio Selection controls don't need clearable like combobox does const clearable = false; // No filtering needed for radio buttons/checkboxes diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewCaptionsProvider.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Preview/PreviewCaptionsProvider.tsx similarity index 94% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewCaptionsProvider.tsx rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Preview/PreviewCaptionsProvider.tsx index c825c838bd..276f2ea586 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewCaptionsProvider.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Preview/PreviewCaptionsProvider.tsx @@ -1,4 +1,4 @@ -import { OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; +import { OptionsSourceCustomContentTypeEnum } from "../../../typings/CheckboxRadioSelectionProps"; import { SimpleCaptionsProvider } from "./SimpleCaptionsProvider"; import { createElement, ReactNode, ComponentType } from "react"; interface PreviewProps { @@ -9,7 +9,7 @@ interface PreviewProps { } export class PreviewCaptionsProvider extends SimpleCaptionsProvider { - emptyCaption = "Selection controls"; + emptyCaption = "Checkbox Radio Selection"; private customContentRenderer: ComponentType<{ children: ReactNode; caption?: string }> = () =>
Dropzone
; get(value: string | null): string { return value || this.emptyCaption; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewOptionsProvider.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Preview/PreviewOptionsProvider.ts similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/PreviewOptionsProvider.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Preview/PreviewOptionsProvider.ts diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Preview/SimpleCaptionsProvider.tsx similarity index 98% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Preview/SimpleCaptionsProvider.tsx index 3ee3656f39..a8652bb8c3 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Preview/SimpleCaptionsProvider.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Preview/SimpleCaptionsProvider.tsx @@ -1,6 +1,6 @@ import { DynamicValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, ObjectItem } from "mendix"; import { ReactNode, createElement } from "react"; -import { OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps"; +import { OptionsSourceCustomContentTypeEnum } from "../../../typings/CheckboxRadioSelectionProps"; import { CaptionsProvider } from "../types"; interface Props { diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/Preview/StaticPreviewSelector.ts similarity index 89% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/Preview/StaticPreviewSelector.ts index e3b794b0b2..b77e567ef9 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/Preview/StaticPreviewSelector.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/Preview/StaticPreviewSelector.ts @@ -1,8 +1,8 @@ import { SingleSelector, Status, CaptionsProvider, OptionsProvider } from "../../types"; import { - SelectionControlsPreviewProps, + CheckboxRadioSelectionPreviewProps, OptionsSourceCustomContentTypeEnum -} from "../../../../typings/SelectionControlsProps"; +} from "../../../../typings/CheckboxRadioSelectionProps"; import { PreviewCaptionsProvider } from "../../Preview/PreviewCaptionsProvider"; import { PreviewOptionsProvider } from "../../Preview/PreviewOptionsProvider"; import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; @@ -22,7 +22,7 @@ export class StaticPreviewSelector implements SingleSelector { caption: CaptionsProvider; options: OptionsProvider; - constructor(props: SelectionControlsPreviewProps) { + constructor(props: CheckboxRadioSelectionPreviewProps) { this.currentId = `single-${generateUUID()}`; this.customContentType = props.optionsSourceCustomContentType; this.readOnly = props.readOnly; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/StaticCaptionsProvider.tsx similarity index 97% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/StaticCaptionsProvider.tsx index 895d90a49b..4961bb9f4d 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticCaptionsProvider.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/StaticCaptionsProvider.tsx @@ -3,7 +3,7 @@ import { ReactNode } from "react"; import { OptionsSourceCustomContentTypeEnum, OptionsSourceStaticDataSourceType -} from "../../../typings/SelectionControlsProps"; +} from "../../../typings/CheckboxRadioSelectionProps"; import { CaptionsProvider } from "../types"; interface StaticCaptionsProviderProps { diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticOptionsProvider.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/StaticOptionsProvider.ts similarity index 97% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticOptionsProvider.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/StaticOptionsProvider.ts index 173b1dd515..5aac408e10 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticOptionsProvider.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/StaticOptionsProvider.ts @@ -1,4 +1,4 @@ -import { OptionsSourceStaticDataSourceType } from "../../../typings/SelectionControlsProps"; +import { OptionsSourceStaticDataSourceType } from "../../../typings/CheckboxRadioSelectionProps"; import { BaseOptionsProvider } from "../BaseOptionsProvider"; import { CaptionsProvider } from "../types"; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/StaticSingleSelector.ts similarity index 95% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/StaticSingleSelector.ts index a214856ab6..a4ee92a4da 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/StaticSingleSelector.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/StaticSingleSelector.ts @@ -1,10 +1,10 @@ import { ActionValue, EditableValue } from "mendix"; import { Big } from "big.js"; import { - SelectionControlsContainerProps, + CheckboxRadioSelectionContainerProps, OptionsSourceCustomContentTypeEnum, OptionsSourceStaticDataSourceType -} from "../../../typings/SelectionControlsProps"; +} from "../../../typings/CheckboxRadioSelectionProps"; import { SingleSelector, Status } from "../types"; import { StaticOptionsProvider } from "./StaticOptionsProvider"; import { StaticCaptionsProvider } from "./StaticCaptionsProvider"; @@ -33,7 +33,7 @@ export class StaticSingleSelector implements SingleSelector { this.options = new StaticOptionsProvider(this.caption, this._objectsMap); } - updateProps(props: SelectionControlsContainerProps): void { + updateProps(props: CheckboxRadioSelectionContainerProps): void { const [attr, ds, emptyOption, clearable, onChangeEvent, customContentType] = extractStaticProps(props); this._attr = attr; this.caption.updateProps({ diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/utils.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/utils.ts similarity index 80% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/Static/utils.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/utils.ts index bfd7dedf07..a2eaa09fef 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/Static/utils.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/utils.ts @@ -1,13 +1,13 @@ import { ActionValue, DynamicValue, EditableValue, ValueStatus } from "mendix"; import { Big } from "big.js"; import { - SelectionControlsContainerProps, + CheckboxRadioSelectionContainerProps, OptionsSourceCustomContentTypeEnum, OptionsSourceStaticDataSourceType -} from "../../../typings/SelectionControlsProps"; +} from "../../../typings/CheckboxRadioSelectionProps"; export function extractStaticProps( - props: SelectionControlsContainerProps + props: CheckboxRadioSelectionContainerProps ): [ EditableValue, OptionsSourceStaticDataSourceType[], @@ -25,7 +25,7 @@ export function extractStaticProps( value: "" }; - // Selection controls don't need clearable like combobox does + // Checkbox Radio Selection controls don't need clearable like combobox does const clearable = false; const onChangeEvent = props.onChangeEvent; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/getSelector.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/getSelector.ts similarity index 88% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/getSelector.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/getSelector.ts index 62d8ed807c..090f08de97 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/getSelector.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/getSelector.ts @@ -1,4 +1,4 @@ -import { SelectionControlsContainerProps } from "../../typings/SelectionControlsProps"; +import { CheckboxRadioSelectionContainerProps } from "../../typings/CheckboxRadioSelectionProps"; import { EnumBooleanSingleSelector } from "./EnumBool/EnumBooleanSingleSelector"; import { StaticSingleSelector } from "./Static/StaticSingleSelector"; import { AssociationSingleSelector } from "./Association/AssociationSingleSelector"; @@ -7,7 +7,7 @@ import { DatabaseSingleSelector } from "./Database/DatabaseSingleSelector"; import { DatabaseMultiSelector } from "./Database/DatabaseMultiSelector"; import { Selector } from "./types"; -export function getSelector(props: SelectionControlsContainerProps): Selector { +export function getSelector(props: CheckboxRadioSelectionContainerProps): Selector { if (props.source === "context") { if (["enumeration", "boolean"].includes(props.optionsSourceType)) { return new EnumBooleanSingleSelector(); diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts similarity index 93% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts index 0b65b31bb0..ffb84529d9 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/types.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts @@ -1,10 +1,10 @@ import { DynamicValue, ListAttributeValue, ListExpressionValue, ListValue } from "mendix"; import { ReactNode } from "react"; import { - SelectionControlsContainerProps, + CheckboxRadioSelectionContainerProps, OptionsSourceCustomContentTypeEnum, ReadOnlyStyleEnum -} from "../../typings/SelectionControlsProps"; +} from "../../typings/CheckboxRadioSelectionProps"; export type Status = "unavailable" | "loading" | "available"; export type SelectionType = "single" | "multi"; @@ -46,7 +46,7 @@ export interface OptionsProvider { } interface SelectorBase { - updateProps(props: SelectionControlsContainerProps): void; + updateProps(props: CheckboxRadioSelectionContainerProps): void; status: Status; attributeType?: "string" | "big" | "boolean" | "date"; selectorType?: "context" | "database" | "static"; diff --git a/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/utils.ts similarity index 87% rename from packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/utils.ts index 5af8f15586..5d762b020f 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/helpers/utils.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/utils.ts @@ -1,5 +1,5 @@ import { Big } from "big.js"; -import { SelectionControlsPreviewProps } from "../../typings/SelectionControlsProps"; +import { CheckboxRadioSelectionPreviewProps } from "../../typings/CheckboxRadioSelectionProps"; export function _valuesIsEqual( value1: string | Big | boolean | Date | undefined, @@ -20,7 +20,7 @@ export function _valuesIsEqual( return false; } -export function getCustomCaption(values: SelectionControlsPreviewProps): string { +export function getCustomCaption(values: CheckboxRadioSelectionPreviewProps): string { const { optionsSourceType, optionsSourceAssociationDataSource, @@ -32,7 +32,7 @@ export function getCustomCaption(values: SelectionControlsPreviewProps): string staticAttribute, optionsSourceStaticDataSource } = values; - const emptyStringFormat = "Selection Controls"; + const emptyStringFormat = "Checkbox Radio Selection"; if (source === "context") { switch (optionsSourceType) { case "association": diff --git a/packages/pluggableWidgets/selection-controls-web/src/hooks/useGetSelector.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/hooks/useGetSelector.ts similarity index 72% rename from packages/pluggableWidgets/selection-controls-web/src/hooks/useGetSelector.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/hooks/useGetSelector.ts index 92b491c3ba..63fa4bdc51 100644 --- a/packages/pluggableWidgets/selection-controls-web/src/hooks/useGetSelector.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/hooks/useGetSelector.ts @@ -1,9 +1,9 @@ import { useRef, useState } from "react"; -import { SelectionControlsContainerProps } from "../../typings/SelectionControlsProps"; +import { CheckboxRadioSelectionContainerProps } from "../../typings/CheckboxRadioSelectionProps"; import { getSelector } from "../helpers/getSelector"; import { Selector } from "../helpers/types"; -export function useGetSelector(props: SelectionControlsContainerProps): Selector { +export function useGetSelector(props: CheckboxRadioSelectionContainerProps): Selector { const selectorRef = useRef(undefined); const [, setInput] = useState({}); if (!selectorRef.current) { diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/package.xml b/packages/pluggableWidgets/checkbox-radio-selection-web/src/package.xml new file mode 100644 index 0000000000..a326e380df --- /dev/null +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/package.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelection.scss b/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelection.scss new file mode 100644 index 0000000000..7b6b8cc933 --- /dev/null +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelection.scss @@ -0,0 +1,5 @@ +// Checkbox Radio Selection Widget Styles +.widget-checkbox-radio-selection { + display: block; + position: relative; +} diff --git a/packages/pluggableWidgets/selection-controls-web/tsconfig.json b/packages/pluggableWidgets/checkbox-radio-selection-web/tsconfig.json similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/tsconfig.json rename to packages/pluggableWidgets/checkbox-radio-selection-web/tsconfig.json diff --git a/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/typings/CheckboxRadioSelectionProps.d.ts similarity index 96% rename from packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/typings/CheckboxRadioSelectionProps.d.ts index 0d72f50404..c1a27affef 100644 --- a/packages/pluggableWidgets/selection-controls-web/typings/SelectionControlsProps.d.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/typings/CheckboxRadioSelectionProps.d.ts @@ -1,5 +1,5 @@ /** - * This file was generated from SelectionControls.xml + * This file was generated from CheckboxRadioSelection.xml * WARNING: All changes made to this file will be overwritten * @author Mendix Widgets Framework Team */ @@ -33,7 +33,7 @@ export interface OptionsSourceStaticDataSourcePreviewType { staticDataSourceCaption: string; } -export interface SelectionControlsContainerProps { +export interface CheckboxRadioSelectionContainerProps { name: string; tabIndex?: number; id: string; @@ -65,7 +65,7 @@ export interface SelectionControlsContainerProps { ariaRequired: DynamicValue; } -export interface SelectionControlsPreviewProps { +export interface CheckboxRadioSelectionPreviewProps { readOnly: boolean; renderMode: "design" | "xray" | "structure"; translate: (text: string) => string; diff --git a/packages/pluggableWidgets/selection-controls-web/typings/declare-svg.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/typings/declare-svg.ts similarity index 100% rename from packages/pluggableWidgets/selection-controls-web/typings/declare-svg.ts rename to packages/pluggableWidgets/checkbox-radio-selection-web/typings/declare-svg.ts diff --git a/packages/pluggableWidgets/selection-controls-web/src/components/Placeholder.tsx b/packages/pluggableWidgets/selection-controls-web/src/components/Placeholder.tsx deleted file mode 100644 index fef47d463c..0000000000 --- a/packages/pluggableWidgets/selection-controls-web/src/components/Placeholder.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { ReactElement, createElement } from "react"; - -export function Placeholder(): ReactElement { - return ( -
-
Loading...
-
- ); -} diff --git a/packages/pluggableWidgets/selection-controls-web/src/package.xml b/packages/pluggableWidgets/selection-controls-web/src/package.xml deleted file mode 100644 index a11d2e0592..0000000000 --- a/packages/pluggableWidgets/selection-controls-web/src/package.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss b/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss deleted file mode 100644 index e41e4dcad1..0000000000 --- a/packages/pluggableWidgets/selection-controls-web/src/ui/SelectionControls.scss +++ /dev/null @@ -1,5 +0,0 @@ -// Selection Controls Widget Styles -.widget-selection-controls { - display: block; - position: relative; -} From 7b556e1914a7f93b5594c7f97ec52b0388d62734 Mon Sep 17 00:00:00 2001 From: gjulivan Date: Tue, 15 Jul 2025 00:34:18 +0200 Subject: [PATCH 08/20] chore: update lock file --- .../checkbox-radio-selection-web/CHANGELOG.md | 4 - pnpm-lock.yaml | 147 ++++++++---------- 2 files changed, 64 insertions(+), 87 deletions(-) diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/CHANGELOG.md b/packages/pluggableWidgets/checkbox-radio-selection-web/CHANGELOG.md index 60630da512..c596ccd65c 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/CHANGELOG.md +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/CHANGELOG.md @@ -6,8 +6,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] -## [1.0.0] - 2025-01-20 - ### Added - Initial release of Checkbox Radio Selection widget @@ -17,5 +15,3 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Database data source support - Static data source support - Custom content options -- Accessibility features with ARIA labels -- Keyboard navigation support diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d5e5ef71c..53e070614c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -843,6 +843,46 @@ importers: specifier: ^7.0.3 version: 7.0.3 + packages/pluggableWidgets/checkbox-radio-selection-web: + dependencies: + classnames: + specifier: ^2.3.2 + version: 2.5.1 + devDependencies: + '@mendix/automation-utils': + specifier: workspace:* + version: link:../../../automation/utils + '@mendix/eslint-config-web-widgets': + specifier: workspace:* + version: link:../../shared/eslint-config-web-widgets + '@mendix/pluggable-widgets-tools': + specifier: 10.21.2 + version: 10.21.2(@jest/transform@29.7.0)(@jest/types@29.6.3)(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/babel__core@7.20.3)(@types/node@22.14.1)(picomatch@4.0.2)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.3(@babel/core@7.27.4)(@babel/preset-env@7.26.9(@babel/core@7.27.4))(@types/react@18.2.36)(react@18.2.0))(react@18.2.0)(tslib@2.8.1) + '@mendix/prettier-config-web-widgets': + specifier: workspace:* + version: link:../../shared/prettier-config-web-widgets + '@mendix/run-e2e': + specifier: workspace:^* + version: link:../../../automation/run-e2e + '@mendix/widget-plugin-component-kit': + specifier: workspace:* + version: link:../../shared/widget-plugin-component-kit + '@mendix/widget-plugin-grid': + specifier: workspace:* + version: link:../../shared/widget-plugin-grid + '@mendix/widget-plugin-hooks': + specifier: workspace:* + version: link:../../shared/widget-plugin-hooks + '@mendix/widget-plugin-platform': + specifier: workspace:* + version: link:../../shared/widget-plugin-platform + '@mendix/widget-plugin-test-utils': + specifier: workspace:* + version: link:../../shared/widget-plugin-test-utils + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + packages/pluggableWidgets/color-picker-web: dependencies: classnames: @@ -2136,46 +2176,6 @@ importers: specifier: ^1.1.3 version: 1.1.3(rollup@3.29.5) - packages/pluggableWidgets/selection-controls-web: - dependencies: - classnames: - specifier: ^2.3.2 - version: 2.5.1 - devDependencies: - '@mendix/automation-utils': - specifier: workspace:* - version: link:../../../automation/utils - '@mendix/eslint-config-web-widgets': - specifier: workspace:* - version: link:../../shared/eslint-config-web-widgets - '@mendix/pluggable-widgets-tools': - specifier: 10.21.2 - version: 10.21.2(@jest/transform@29.7.0)(@jest/types@29.6.3)(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/babel__core@7.20.3)(@types/node@22.14.1)(picomatch@4.0.2)(react-dom@18.2.0(react@18.2.0))(react-native@0.75.3(@babel/core@7.27.4)(@babel/preset-env@7.26.9(@babel/core@7.27.4))(@types/react@18.2.36)(react@18.2.0))(react@18.2.0)(tslib@2.8.1) - '@mendix/prettier-config-web-widgets': - specifier: workspace:* - version: link:../../shared/prettier-config-web-widgets - '@mendix/run-e2e': - specifier: workspace:^* - version: link:../../../automation/run-e2e - '@mendix/widget-plugin-component-kit': - specifier: workspace:* - version: link:../../shared/widget-plugin-component-kit - '@mendix/widget-plugin-grid': - specifier: workspace:* - version: link:../../shared/widget-plugin-grid - '@mendix/widget-plugin-hooks': - specifier: workspace:* - version: link:../../shared/widget-plugin-hooks - '@mendix/widget-plugin-platform': - specifier: workspace:* - version: link:../../shared/widget-plugin-platform - '@mendix/widget-plugin-test-utils': - specifier: workspace:* - version: link:../../shared/widget-plugin-test-utils - cross-env: - specifier: ^7.0.3 - version: 7.0.3 - packages/pluggableWidgets/selection-helper-web: devDependencies: '@mendix/automation-utils': @@ -6778,10 +6778,6 @@ packages: es-array-method-boxes-properly@1.0.0: resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} - es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} - engines: {node: '>= 0.4'} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -10055,10 +10051,6 @@ packages: resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} engines: {node: '>= 0.4'} - regexp.prototype.flags@1.5.2: - resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} - engines: {node: '>= 0.4'} - regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -14724,7 +14716,7 @@ snapshots: '@typescript-eslint/types': 6.13.2 '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.8.2) eslint: 9.32.0(jiti@2.4.2) - semver: 7.7.1 + semver: 7.7.2 transitivePeerDependencies: - supports-color - typescript @@ -15153,11 +15145,11 @@ snapshots: array-includes@3.1.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 is-string: 1.1.1 array-normalize@1.1.4: @@ -15471,10 +15463,10 @@ snapshots: call-bind@1.0.7: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 set-function-length: 1.2.2 call-bind@1.0.8: @@ -16475,7 +16467,7 @@ snapshots: '@one-ini/wasm': 0.1.1 commander: 10.0.1 minimatch: 9.0.1 - semver: 7.7.1 + semver: 7.7.2 ee-first@1.1.1: {} @@ -16592,13 +16584,13 @@ snapshots: data-view-buffer: 1.0.1 data-view-byte-length: 1.0.1 data-view-byte-offset: 1.0.0 - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 es-set-tostringtag: 2.0.3 es-to-primitive: 1.2.1 function.prototype.name: 1.1.8 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 get-symbol-description: 1.0.2 globalthis: 1.0.3 gopd: 1.2.0 @@ -16606,7 +16598,7 @@ snapshots: has-proto: 1.0.3 has-symbols: 1.1.0 hasown: 2.0.2 - internal-slot: 1.0.7 + internal-slot: 1.1.0 is-array-buffer: 3.0.4 is-callable: 1.2.7 is-data-view: 1.0.1 @@ -16619,7 +16611,7 @@ snapshots: object-inspect: 1.13.4 object-keys: 1.1.1 object.assign: 4.1.7 - regexp.prototype.flags: 1.5.2 + regexp.prototype.flags: 1.5.4 safe-array-concat: 1.1.2 safe-regex-test: 1.0.3 string.prototype.trim: 1.2.10 @@ -16688,10 +16680,6 @@ snapshots: es-array-method-boxes-properly@1.0.0: {} - es-define-property@1.0.0: - dependencies: - get-intrinsic: 1.3.0 - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -17404,7 +17392,7 @@ snapshots: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 - has-proto: 1.0.3 + has-proto: 1.2.0 has-symbols: 1.1.0 hasown: 2.0.2 @@ -17904,7 +17892,7 @@ snapshots: dependencies: es-errors: 1.3.0 hasown: 2.0.2 - side-channel: 1.0.4 + side-channel: 1.1.0 internal-slot@1.1.0: dependencies: @@ -17964,7 +17952,7 @@ snapshots: is-boolean-object@1.1.2: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 has-tostringtag: 1.0.2 is-boolean-object@1.2.2: @@ -18076,7 +18064,7 @@ snapshots: is-reference@1.2.1: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 is-regex@1.1.4: dependencies: @@ -18125,7 +18113,7 @@ snapshots: is-symbol@1.0.4: dependencies: - has-symbols: 1.0.3 + has-symbols: 1.1.0 is-symbol@1.1.1: dependencies: @@ -19616,10 +19604,10 @@ snapshots: object.fromentries@2.0.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 object.hasown@1.1.3: dependencies: @@ -20873,13 +20861,6 @@ snapshots: define-properties: 1.2.1 set-function-name: 2.0.1 - regexp.prototype.flags@1.5.2: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-errors: 1.3.0 - set-function-name: 2.0.1 - regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -21003,7 +20984,7 @@ snapshots: resolve@2.0.0-next.5: dependencies: - is-core-module: 2.13.1 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -21409,7 +21390,7 @@ snapshots: get-stdin: 9.0.0 git-hooks-list: 3.2.0 is-plain-obj: 4.1.0 - semver: 7.7.1 + semver: 7.7.2 sort-object-keys: 1.1.3 tinyglobby: 0.2.12 @@ -21420,7 +21401,7 @@ snapshots: get-stdin: 9.0.0 git-hooks-list: 3.2.0 is-plain-obj: 4.1.0 - semver: 7.7.1 + semver: 7.7.2 sort-object-keys: 1.1.3 tinyglobby: 0.2.12 @@ -21539,7 +21520,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 gopd: 1.2.0 has-symbols: 1.1.0 From 36241911a9e244d916b00b5954f8bfd91225effe Mon Sep 17 00:00:00 2001 From: gjulivan Date: Tue, 15 Jul 2025 10:29:53 +0200 Subject: [PATCH 09/20] fix: lint and build --- .../checkbox-radio-selection-web/eslint.config.mjs | 4 +++- .../src/components/CaptionContent.tsx | 3 +-- .../Association/Preview/AssociationPreviewSelector.ts | 4 ++-- .../src/helpers/BaseOptionsProvider.ts | 8 +++----- .../helpers/Database/Preview/DatabasePreviewSelector.ts | 8 ++++---- .../src/helpers/Static/Preview/StaticPreviewSelector.ts | 4 ++-- .../checkbox-radio-selection-web/src/helpers/types.ts | 1 + 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/eslint.config.mjs b/packages/pluggableWidgets/checkbox-radio-selection-web/eslint.config.mjs index 59cd869516..ed68ae9e78 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/eslint.config.mjs +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/eslint.config.mjs @@ -1 +1,3 @@ -export { default } from "@mendix/eslint-config-web-widgets"; +import config from "@mendix/eslint-config-web-widgets/widget-ts.mjs"; + +export default config; diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CaptionContent.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CaptionContent.tsx index b429fa481f..705c7f857a 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CaptionContent.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CaptionContent.tsx @@ -1,5 +1,4 @@ -import { createElement, PropsWithChildren, ReactElement } from "react"; -import { MouseEvent } from "react"; +import { createElement, PropsWithChildren, ReactElement, MouseEvent } from "react"; export interface CaptionContentProps extends PropsWithChildren { htmlFor?: string; diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts index 63d1b32b28..144866f154 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Association/Preview/AssociationPreviewSelector.ts @@ -35,6 +35,6 @@ export class AssociationPreviewSelector implements SingleSelector { }); } - updateProps() {} - setValue() {} + updateProps(): void {} + setValue(): void {} } diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/BaseOptionsProvider.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/BaseOptionsProvider.ts index 86ba5d8137..6a50f87ab7 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/BaseOptionsProvider.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/BaseOptionsProvider.ts @@ -6,9 +6,7 @@ export class BaseOptionsProvider implements OptionsProv searchTerm = ""; - constructor(_caption: CaptionsProvider) { - // Caption provider stored for potential future use - } + constructor(protected caption: CaptionsProvider) {} get hasMore(): boolean { return false; @@ -22,11 +20,11 @@ export class BaseOptionsProvider implements OptionsProv return "available"; } - get sortOrder() { + get sortOrder(): undefined { return undefined; } - get datasourceFilter() { + get datasourceFilter(): undefined { return undefined; } diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts index d8820cb45c..fe9fbb8502 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Database/Preview/DatabasePreviewSelector.ts @@ -35,8 +35,8 @@ export class DatabasePreviewSelector implements SingleSelector { // Show dropzones in design mode when custom content is enabled } - updateProps() {} - setValue() {} + updateProps(): void {} + setValue(): void {} } export class DatabaseMultiPreviewSelector implements MultiSelector { @@ -68,6 +68,6 @@ export class DatabaseMultiPreviewSelector implements MultiSelector { return this.currentId || []; } - updateProps() {} - setValue() {} + updateProps(): void {} + setValue(): void {} } diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/Preview/StaticPreviewSelector.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/Preview/StaticPreviewSelector.ts index b77e567ef9..0033c1f8c0 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/Preview/StaticPreviewSelector.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/Preview/StaticPreviewSelector.ts @@ -30,6 +30,6 @@ export class StaticPreviewSelector implements SingleSelector { this.options = new PreviewOptionsProvider(this.caption, new Map()); } - updateProps() {} - setValue() {} + updateProps(): void {} + setValue(): void {} } diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts index ffb84529d9..91fa9ff3d5 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts @@ -69,6 +69,7 @@ interface SelectorBase { customContentType: OptionsSourceCustomContentTypeEnum; } +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface SingleSelector extends SelectorBase<"single", string> {} export interface MultiSelector extends SelectorBase<"multi", string[]> { From 34ddaffb1782ee22ad954ee353cc033e4124af8f Mon Sep 17 00:00:00 2001 From: gjulivan Date: Tue, 15 Jul 2025 10:48:07 +0200 Subject: [PATCH 10/20] fix: update test --- .../checkbox-radio-selection-web/package.json | 2 +- .../src/__tests__/SelectionControls.spec.tsx | 25 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/package.json b/packages/pluggableWidgets/checkbox-radio-selection-web/package.json index d6accd0194..925d807edf 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/package.json +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/package.json @@ -42,7 +42,7 @@ "publish-marketplace": "rui-publish-marketplace", "release": "pluggable-widgets-tools release:web", "start": "pluggable-widgets-tools start:server", - "test": "jest --projects jest.config.js", + "test": "pluggable-widgets-tools test:unit:web:enzyme-free", "update-changelog": "rui-update-changelog-widget", "verify": "rui-verify-package-format" }, diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/SelectionControls.spec.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/SelectionControls.spec.tsx index d0c84c174a..485fbf2d2a 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/SelectionControls.spec.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/SelectionControls.spec.tsx @@ -8,9 +8,32 @@ jest.mock("../helpers/getSelector", () => ({ getSelector: jest.fn(() => ({ type: "single", status: "available", + readOnly: false, + currentId: "option1", + clearable: false, + customContentType: "no", updateProps: jest.fn(), + setValue: jest.fn(), options: { - onAfterSearchTermChange: jest.fn() + status: "available", + searchTerm: "", + sortOrder: undefined, + datasourceFilter: undefined, + hasMore: false, + isLoading: false, + getAll: jest.fn(() => ["option1", "option2", "option3"]), + setSearchTerm: jest.fn(), + onAfterSearchTermChange: jest.fn(), + loadMore: jest.fn(), + _updateProps: jest.fn(), + _optionToValue: jest.fn(), + _valueToOption: jest.fn() + }, + caption: { + get: jest.fn(value => `Caption ${value}`), + render: jest.fn(value => `Caption ${value}`), + emptyCaption: "Select an option", + formatter: undefined } })) })); From 7096c76c5942f702a760175822cbcfbbf365e394 Mon Sep 17 00:00:00 2001 From: gjulivan Date: Tue, 15 Jul 2025 13:58:13 +0200 Subject: [PATCH 11/20] fix: allow group name and fix onclick label --- .../checkbox-radio-selection-web/CHANGELOG.md | 6 ---- .../CheckboxRadioSelection.editorConfig.ts | 12 +++++-- .../CheckboxRadioSelection.editorPreview.tsx | 4 ++- .../src/CheckboxRadioSelection.icon.png | Bin 133 -> 4575 bytes .../src/CheckboxRadioSelection.tsx | 3 +- .../src/CheckboxRadioSelection.xml | 7 +++- .../CheckboxSelection/CheckboxSelection.tsx | 21 +++++++++--- .../RadioSelection/RadioSelection.tsx | 31 +++++++++++++----- .../src/helpers/types.ts | 1 + .../src/ui/CheckboxRadioSelection.scss | 28 ++++++++++++++++ .../src/ui/CheckboxRadioSelectionPreview.scss | 16 +++++++++ .../typings/CheckboxRadioSelectionProps.d.ts | 2 ++ 12 files changed, 105 insertions(+), 26 deletions(-) create mode 100644 packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelectionPreview.scss diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/CHANGELOG.md b/packages/pluggableWidgets/checkbox-radio-selection-web/CHANGELOG.md index c596ccd65c..9b2f61be93 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/CHANGELOG.md +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/CHANGELOG.md @@ -9,9 +9,3 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - Initial release of Checkbox Radio Selection widget -- Support for radio button lists (single selection) -- Support for checkbox lists (multiple selection) -- Context data source support (associations) -- Database data source support -- Static data source support -- Custom content options diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts index 8fae07eeb3..15043ebbd5 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts @@ -147,7 +147,7 @@ function getIconPreview(isMultiSelect: boolean): ContainerProps { export function getPreview(values: CheckboxRadioSelectionPreviewProps, isDarkMode: boolean): StructurePreviewProps { const palette = structurePreviewPalette[isDarkMode ? "dark" : "light"]; const structurePreviewChildren: StructurePreviewProps[] = []; - // let readOnly = values.readOnly; + let readOnly = values.readOnly; // Handle custom content dropzones when enabled if (values.optionsSourceCustomContentType !== "no") { @@ -165,6 +165,10 @@ export function getPreview(values: CheckboxRadioSelectionPreviewProps, isDarkMod dropzone.hideDataSourceHeaderIf(false) )(values.optionsSourceDatabaseCustomContent) ); + + if (values.databaseAttributeString.length === 0) { + readOnly = values.customEditability === "never"; + } } else if (values.source === "static") { values.optionsSourceStaticDataSource.forEach(value => { structurePreviewChildren.push( @@ -196,7 +200,8 @@ export function getPreview(values: CheckboxRadioSelectionPreviewProps, isDarkMod return container()( rowLayout({ columnSize: "grow", - backgroundColor: palette.background.container + borderRadius: 2, + backgroundColor: readOnly ? palette.background.containerDisabled : palette.background.container })( getIconPreview(isMultiSelect), container()(container({ padding: 3 })(), text()(getCustomCaption(values))) @@ -210,7 +215,8 @@ export function getPreview(values: CheckboxRadioSelectionPreviewProps, isDarkMod columnSize: "grow", borders: true, borderWidth: 1, - borderRadius: 2 + borderRadius: 2, + backgroundColor: readOnly ? palette.background.containerDisabled : palette.background.container })(container({ grow: 1, padding: 4 })(...structurePreviewChildren)) ); } diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorPreview.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorPreview.tsx index f62b1af17b..c277bdfe64 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorPreview.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorPreview.tsx @@ -11,6 +11,7 @@ import { } from "./helpers/Database/Preview/DatabasePreviewSelector"; import { AssociationPreviewSelector } from "./helpers/Association/Preview/AssociationPreviewSelector"; import "./ui/CheckboxRadioSelection.scss"; +import "./ui/CheckboxRadioSelectionPreview.scss"; import { CheckboxSelection } from "./components/CheckboxSelection/CheckboxSelection"; export const preview = (props: CheckboxRadioSelectionPreviewProps): ReactElement => { @@ -20,7 +21,8 @@ export const preview = (props: CheckboxRadioSelectionPreviewProps): ReactElement inputId: id, labelId: `${id}-label`, readOnlyStyle: props.readOnlyStyle, - ariaRequired: dynamic(false) + ariaRequired: dynamic(false), + groupName: dynamic(`${id}-group`) }; // eslint-disable-next-line react-hooks/rules-of-hooks diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.icon.png b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.icon.png index 1d794f2faa95b6ce49e993cd12590e5de20a3961..21f35fc0227627404ddd3a3d7b1056ae39192366 100644 GIT binary patch literal 4575 zcmeHLdsGzX6(4-m2)-2sMH!YLqRj5>?0dor$S$zR;tD8=dSrHXh8^9Pvje+mTdBnp zjUF``1T96eqE@THR#DVQXz+Dn6HRN5^-09V7!fQQNo5WFW`E<`Y8qjo$=fGPN~$~A&~V9k zqwd1utYKO4W4zF+No5n3?XT~QSU*ubEu_8i?kAV-z87a+xNF7BdT7C4Hgf`DQ`-?^ zL!e*LnEH^55%hOgt{NwnH$Is*`}{p^!Or%Y*h_`!XmG>$jby4K`oYKtyYzD(otqaz zQ@XI(k>%*O>*}{IEm`*K#-;3{hFwJX@5&=bjo(x$+A`3f%Pk)8~m_5Gq^}C;3 zzb;6MGfz2vy2ac!%~3OWg!=LJFxB=w2Mp1JT616M3v!-K{C2r>&C1rs7k-!1vp!dR zS{H^c+}E79)zzNY{<3vf{@U2#UbeBm=1LVvyL<+u*CKr?PTH+P!elp6Lbufc@(zMx zV%-jcTuE`Tk;-LkYC-GSS^>SCT4|Aa=HYj2j04 zR*EBFx7A{E;%>FThl_(YKP(c!J`rxETChlOfD`O21xtleA&O|-Og<)iG`@hY8Ca>aB|vw0MhNyPiij+Tq!R;sgCU&1NkkadMjh)Qn-P38*0^UAuvXhJn_cbw6 zu8#iSgO5(I@9 zNr@Q2Fe$CVXv!#28hubE5>K?VRst+1V(Le#z z0An-RUC!RAOvXxOa0Jh%SRun?N~u(WDls{#QuYchq*y1&MIKd*3MF#i48Jfq2nSG0 z@R;kN=Dbh^Xaw)KXbSLbaapotI0aM9M5?U@pBq}ipR!}NIR5VQ>3I!;7g#=T|yQAts zciL&rMX*$SF5nSx1rpT96+EMRsiOLO<1$nHIsnQLRDtwRCP4+F?qEgyj`1ei7}0<6 z5#tl+v&8_tt}(E6ft^s)vlVvp1$_NEukLmDIZeRufRVT2cYv+|y55R`w-O%6t^vB< zih;Kh9>}i$8(sdrhbhVizJOfds5C;@cmf==XhUkI)_357-qk$}PxMTm>xoS9M1rU8 zVWiG89sE)}5h)KNQko)C`1bUaCSD+=3ACe9JyCj(D*KTx_i+~cWT~s!QuLJD)Uv7k zUmqQ4tN!cD=imS6^>%t+cY^DAQPe$P$&X7<%YfpVZi2rMAV1^WBn`A-=+tD;8SY44 z?1Z3z@%-%v?Jf%fgTpwTUOVjZu+bx-X`X;}Wk6@FP7|N0{QT&SCtHf(5$i7)kE6`y z<}a?EQQYs0Dw#QB!}RCm%6;bAYYFqLyVq{0c6~JH_KtIv>JtkBuRR&Dx8ewtq`fmU z=gYgHZ0#>v?mzDcd{*65>mB;+hu1gT4t^@_{N({}!A7P$qkP*dM}<(E7BzavC^ZhV z7oy5Z3pMkb(kk18N0STx)+W^+3C_>UxwGQys>NDMamMcONgc<|T-D#WSbW;w@G6Z6 zD$`kJE$2qv(cC|6o&NnFZw}H(n>{;Eg~Zhzhe{8RTaz{9;Q5I)GSgVq&g$VGKPae~ zO_S!QRn4u`m6yu%4!7(5>uAmRx-TmxPg{E8==KGFKD*&sWXrks%kvYb-kKAns%kt^ a`!Y1hF)p~$sf`1CK{{=k=GdH^b^igeQk6IW literal 133 zcmWlSK@P$&3 Read-only style - How the combo box will appear in read-only mode. + How the checkbox radio selection will appear in read-only mode. Control Content only @@ -255,6 +255,11 @@ + + Group name + + + diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CheckboxSelection/CheckboxSelection.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CheckboxSelection/CheckboxSelection.tsx index 93a966b83f..60caf0b7d2 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CheckboxSelection/CheckboxSelection.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CheckboxSelection/CheckboxSelection.tsx @@ -1,5 +1,5 @@ import classNames from "classnames"; -import { ReactElement, createElement } from "react"; +import { ReactElement, createElement, MouseEvent } from "react"; import { SelectionBaseProps, MultiSelector } from "../../helpers/types"; import { CaptionContent } from "../CaptionContent"; @@ -8,11 +8,13 @@ export function CheckboxSelection({ tabIndex = 0, inputId, ariaRequired, - readOnlyStyle + readOnlyStyle, + groupName }: SelectionBaseProps): ReactElement { const options = selector.getOptions(); const currentIds = selector.currentId || []; const isReadOnly = selector.readOnly; + const name = groupName?.value ?? inputId; const handleChange = (optionId: string, checked: boolean): void => { if (!isReadOnly) { @@ -37,7 +39,6 @@ export function CheckboxSelection({ {options.map((optionId, index) => { const isSelected = currentIds.includes(optionId); const checkboxId = `${inputId}-checkbox-${index}`; - const name = selector.caption.get(optionId); return (
0 ? name : inputId} + name={name} value={optionId} checked={isSelected} disabled={isReadOnly} tabIndex={tabIndex} onChange={e => handleChange(optionId, e.target.checked)} /> - {selector.caption.render(optionId)} + ) => { + e.preventDefault(); + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + handleChange(optionId, !isSelected); + }} + htmlFor={checkboxId} + > + {selector.caption.render(optionId)} +
); })} diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx index fc6f6a4b52..2356000002 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx @@ -1,5 +1,5 @@ import classNames from "classnames"; -import { ReactElement, createElement } from "react"; +import { ChangeEvent, ReactElement, createElement, MouseEvent } from "react"; import { SelectionBaseProps, SingleSelector } from "../../helpers/types"; import { CaptionContent } from "../CaptionContent"; @@ -8,15 +8,18 @@ export function RadioSelection({ tabIndex = 0, inputId, ariaRequired, - readOnlyStyle + readOnlyStyle, + groupName }: SelectionBaseProps): ReactElement { const options = selector.options.getAll(); const currentId = selector.currentId; const isReadOnly = selector.readOnly; + const name = groupName?.value ?? inputId; - const handleChange = (optionId: string): void => { + const handleChange = (e: ChangeEvent): void => { + const selectedItem = e.target.value; if (!isReadOnly) { - selector.setValue(optionId); + selector.setValue(selectedItem); } }; @@ -36,8 +39,6 @@ export function RadioSelection({ {options.map((optionId, index) => { const isSelected = currentId === optionId; const radioId = `${inputId}-radio-${index}`; - const name = selector.caption.get(optionId); - return (
0 ? name : inputId} + name={name} value={optionId} checked={isSelected} disabled={isReadOnly} tabIndex={tabIndex} - onChange={() => handleChange(optionId)} + onChange={handleChange} /> - {selector.caption.render(optionId)} + ) => { + e.preventDefault(); + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + if (!isReadOnly) { + selector.setValue(optionId); + } + }} + htmlFor={radioId} + > + {selector.caption.render(optionId)} +
); })} diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts index 91fa9ff3d5..02e915aff2 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts @@ -83,4 +83,5 @@ export interface SelectionBaseProps { selector: Selector; tabIndex: number; ariaRequired: DynamicValue; + groupName: DynamicValue | undefined; } diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelection.scss b/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelection.scss index 7b6b8cc933..aa9e23f1f7 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelection.scss +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelection.scss @@ -2,4 +2,32 @@ .widget-checkbox-radio-selection { display: block; position: relative; + + &-readonly { + &.widget-checkbox-radio-selection-readonly-text { + .widget-checkbox-radio-selection-radio-item { + display: none; + + &.widget-checkbox-radio-selection-radio-item-selected { + display: block; + + input { + display: none; + } + } + } + } + } + + label { + font-weight: inherit; + } + + input[type="radio"], + input[type="checkbox"] { + & + label { + cursor: pointer; + margin-left: var(--spacing-small, 8px); + } + } } diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelectionPreview.scss b/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelectionPreview.scss new file mode 100644 index 0000000000..083123580e --- /dev/null +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelectionPreview.scss @@ -0,0 +1,16 @@ +// Checkbox Radio Selection Widget Styles +.widget-checkbox-radio-selection.widget-checkbox-radio-selection-editor-preview { + .widget-checkbox-radio-selection-readonly { + &.widget-checkbox-radio-selection-readonly-text { + .widget-checkbox-radio-selection-radio-item { + display: block; + + &.widget-checkbox-radio-selection-radio-item { + input { + display: none; + } + } + } + } + } +} diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/typings/CheckboxRadioSelectionProps.d.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/typings/CheckboxRadioSelectionProps.d.ts index c1a27affef..616410d95f 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/typings/CheckboxRadioSelectionProps.d.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/typings/CheckboxRadioSelectionProps.d.ts @@ -63,6 +63,7 @@ export interface CheckboxRadioSelectionContainerProps { readOnlyStyle: ReadOnlyStyleEnum; onChangeEvent?: ActionValue; ariaRequired: DynamicValue; + groupName?: DynamicValue; } export interface CheckboxRadioSelectionPreviewProps { @@ -96,4 +97,5 @@ export interface CheckboxRadioSelectionPreviewProps { onChangeEvent: {} | null; onChangeDatabaseEvent: {} | null; ariaRequired: string; + groupName: string; } From eb90d2337c2265a65bd95c7b2a546a341a7682a5 Mon Sep 17 00:00:00 2001 From: Rahman Unver Date: Fri, 18 Jul 2025 16:43:30 +0200 Subject: [PATCH 12/20] test(checkbox-radio-selection-web): update e2e test with new naming --- .../e2e/SelectionControls.spec.js | 38 +++++++++---------- .../CheckboxRadioSelection.editorConfig.ts | 24 +++++------- ...ec.tsx => CheckboxRadioSelection.spec.tsx} | 0 3 files changed, 28 insertions(+), 34 deletions(-) rename packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/{SelectionControls.spec.tsx => CheckboxRadioSelection.spec.tsx} (100%) diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js b/packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js index bdda75f78c..09ce0eeda4 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js @@ -5,46 +5,46 @@ test.afterEach("Cleanup session", async ({ page }) => { await page.evaluate(() => window.mx.session.logout()); }); -test.describe("selection-controls-web", () => { +test.describe("checkbox-radio-selection-web", () => { test.beforeEach(async ({ page }) => { - await page.goto("/p/selectioncontrols"); + await page.goto("p/CheckboxRadioSelection"); await page.waitForLoadState("networkidle"); }); test.describe("data source types", () => { - test("renders selection controls using association", async ({ page }) => { - const selectionControls = page.locator(".mx-name-selectionControls1"); + test("renders checkbox radio selection using association", async ({ page }) => { + const selectionControls = page.locator(".mx-name-checkboxRadioSelection1"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); - await expect(selectionControls).toHaveScreenshot(`selectionControlsAssociation.png`); + await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionAssociation.png`); }); - test("renders selection controls using enum", async ({ page }) => { - const selectionControls = page.locator(".mx-name-selectionControls2"); + test("renders checkbox radio selection using enum", async ({ page }) => { + const selectionControls = page.locator(".mx-name-checkboxRadioSelection2"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); - await expect(selectionControls).toHaveScreenshot(`selectionControlsEnum.png`); + await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionEnum.png`); }); - test("renders selection controls using boolean", async ({ page }) => { - const selectionControls = page.locator(".mx-name-selectionControls3"); + test("renders checkbox radio selection using boolean", async ({ page }) => { + const selectionControls = page.locator(".mx-name-checkboxRadioSelection3"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); - await expect(selectionControls).toHaveScreenshot(`selectionControlsBoolean.png`); + await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionBoolean.png`); }); - test("renders selection controls using static values", async ({ page }) => { - const selectionControls = page.locator(".mx-name-selectionControls4"); + test("renders checkbox radio selection using static values", async ({ page }) => { + const selectionControls = page.locator(".mx-name-checkboxRadioSelection4"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); - await expect(selectionControls).toHaveScreenshot(`selectionControlsStatic.png`); + await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionStatic.png`); }); - test("renders selection controls using database", async ({ page }) => { - const selectionControls = page.locator(".mx-name-selectionControls5"); + test("renders checkbox radio selection using database", async ({ page }) => { + const selectionControls = page.locator(".mx-name-checkboxRadioSelection5"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); - await expect(selectionControls).toHaveScreenshot(`selectionControlsDatabase.png`); + await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionDatabase.png`); }); test.describe("selection behavior", () => { test("handles radio button selection", async ({ page }) => { - const selectionControls = page.locator(".mx-name-selectionControls1"); + const selectionControls = page.locator(".mx-name-checkboxRadioSelection1"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); const radioOption = selectionControls.locator('input[type="radio"]').first(); @@ -53,7 +53,7 @@ test.describe("selection-controls-web", () => { }); test("handles checkbox selection", async ({ page }) => { - const selectionControls = page.locator(".mx-name-selectionControls6"); // multi selection + const selectionControls = page.locator(".mx-name-checkboxRadioSelection6"); // multi selection await expect(selectionControls).toBeVisible({ timeout: 10000 }); const checkboxOption = selectionControls.locator('input[type="checkbox"]').first(); diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts index 15043ebbd5..c3b9e91b8c 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts @@ -6,8 +6,7 @@ import { dropzone, container, rowLayout, - text, - svgImage + text } from "@mendix/widget-plugin-platform/preview/structure-preview-api"; import { CheckboxRadioSelectionPreviewProps } from "../typings/CheckboxRadioSelectionProps"; import { getCustomCaption } from "./helpers/utils"; @@ -134,14 +133,14 @@ export function getProperties( } function getIconPreview(isMultiSelect: boolean): ContainerProps { - return container({ grow: 0 })( - container({ padding: 3 })(), - svgImage({ width: 16, height: 16, grow: 0 })( - decodeURIComponent( - (isMultiSelect ? IconCheckboxSVG : IconRadioButtonSVG).replace("data:image/svg+xml,", "") - ) - ) - ); + return container({ grow: 0 })(container({ padding: 3 })(), { + type: "Image", + document: decodeURIComponent( + (isMultiSelect ? IconCheckboxSVG : IconRadioButtonSVG).replace("data:image/svg+xml,", "") + ), + width: 16, + height: 16 + }); } export function getPreview(values: CheckboxRadioSelectionPreviewProps, isDarkMode: boolean): StructurePreviewProps { @@ -189,11 +188,6 @@ export function getPreview(values: CheckboxRadioSelectionPreviewProps, isDarkMod } } - // Handle database-specific read-only logic - // if (values.source === "database" && values.databaseAttributeString.length === 0) { - // readOnly = values.customEditability === "never"; - // } - // If no custom content dropzones, show default preview if (structurePreviewChildren.length === 0) { const isMultiSelect = values.optionsSourceDatabaseItemSelection === "Multi"; diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/SelectionControls.spec.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/CheckboxRadioSelection.spec.tsx similarity index 100% rename from packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/SelectionControls.spec.tsx rename to packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/CheckboxRadioSelection.spec.tsx From 0989223afdb33131a9a37ee1db072e949b4d53c8 Mon Sep 17 00:00:00 2001 From: gjulivan Date: Thu, 24 Jul 2025 01:34:44 +0200 Subject: [PATCH 13/20] chore: update checkbox radio icon --- .../src/CheckboxRadioSelection.icon.png | Bin 4575 -> 615 bytes .../src/CheckboxRadioSelection.tile.png | Bin 132 -> 1871 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.icon.png b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.icon.png index 21f35fc0227627404ddd3a3d7b1056ae39192366..c36690a473694c544b1ae11a0938ba490dbff04b 100644 GIT binary patch delta 601 zcmV-f0;c`nBj*H=8Gi-<0047(dh`GQ00DDSM?wIu&K&6g000DMK}|sb0I`n?{9y$E z000SaNLh0L01m?d01m?e$8V@)0005}NklH3rS^#X2W+IQkl$;0ak0J$CKi6gf= zXO_<~XIub?K)gpDZyb4gq9PYdO?(J9414gabrdO{ZV%*E ze|eqUe#-~;EdQ|Ivms9@5oIL0x)Vf97Y+wjm(hvyMSodA$*7bs*kvI-MHIw}r`mxI zQ@7ovTw{zcu9KG{WIlkOqtM--lsb%zhZe%#D1hd9Nw)=z4k{wxhARK~(k20Sd>4ur z(_sYk9T+_b^^*cPGF$Eo$d>G?F0KE(=BakzH(tDNjsjrIY^YuV=N^E+e6j}>v=t}{ zx&kihB2wxo$fHFew!{7bpxV*FWu-%yb+rTWD0rl&S5->=Vmd`##82i0FB>D|bs)D? nWY$EeTRXbpy3?HhF<90&JE0|7v8}2#00000NkvXXu0mjfT>c8x literal 4575 zcmeHLdsGzX6(4-m2)-2sMH!YLqRj5>?0dor$S$zR;tD8=dSrHXh8^9Pvje+mTdBnp zjUF``1T96eqE@THR#DVQXz+Dn6HRN5^-09V7!fQQNo5WFW`E<`Y8qjo$=fGPN~$~A&~V9k zqwd1utYKO4W4zF+No5n3?XT~QSU*ubEu_8i?kAV-z87a+xNF7BdT7C4Hgf`DQ`-?^ zL!e*LnEH^55%hOgt{NwnH$Is*`}{p^!Or%Y*h_`!XmG>$jby4K`oYKtyYzD(otqaz zQ@XI(k>%*O>*}{IEm`*K#-;3{hFwJX@5&=bjo(x$+A`3f%Pk)8~m_5Gq^}C;3 zzb;6MGfz2vy2ac!%~3OWg!=LJFxB=w2Mp1JT616M3v!-K{C2r>&C1rs7k-!1vp!dR zS{H^c+}E79)zzNY{<3vf{@U2#UbeBm=1LVvyL<+u*CKr?PTH+P!elp6Lbufc@(zMx zV%-jcTuE`Tk;-LkYC-GSS^>SCT4|Aa=HYj2j04 zR*EBFx7A{E;%>FThl_(YKP(c!J`rxETChlOfD`O21xtleA&O|-Og<)iG`@hY8Ca>aB|vw0MhNyPiij+Tq!R;sgCU&1NkkadMjh)Qn-P38*0^UAuvXhJn_cbw6 zu8#iSgO5(I@9 zNr@Q2Fe$CVXv!#28hubE5>K?VRst+1V(Le#z z0An-RUC!RAOvXxOa0Jh%SRun?N~u(WDls{#QuYchq*y1&MIKd*3MF#i48Jfq2nSG0 z@R;kN=Dbh^Xaw)KXbSLbaapotI0aM9M5?U@pBq}ipR!}NIR5VQ>3I!;7g#=T|yQAts zciL&rMX*$SF5nSx1rpT96+EMRsiOLO<1$nHIsnQLRDtwRCP4+F?qEgyj`1ei7}0<6 z5#tl+v&8_tt}(E6ft^s)vlVvp1$_NEukLmDIZeRufRVT2cYv+|y55R`w-O%6t^vB< zih;Kh9>}i$8(sdrhbhVizJOfds5C;@cmf==XhUkI)_357-qk$}PxMTm>xoS9M1rU8 zVWiG89sE)}5h)KNQko)C`1bUaCSD+=3ACe9JyCj(D*KTx_i+~cWT~s!QuLJD)Uv7k zUmqQ4tN!cD=imS6^>%t+cY^DAQPe$P$&X7<%YfpVZi2rMAV1^WBn`A-=+tD;8SY44 z?1Z3z@%-%v?Jf%fgTpwTUOVjZu+bx-X`X;}Wk6@FP7|N0{QT&SCtHf(5$i7)kE6`y z<}a?EQQYs0Dw#QB!}RCm%6;bAYYFqLyVq{0c6~JH_KtIv>JtkBuRR&Dx8ewtq`fmU z=gYgHZ0#>v?mzDcd{*65>mB;+hu1gT4t^@_{N({}!A7P$qkP*dM}<(E7BzavC^ZhV z7oy5Z3pMkb(kk18N0STx)+W^+3C_>UxwGQys>NDMamMcONgc<|T-D#WSbW;w@G6Z6 zD$`kJE$2qv(cC|6o&NnFZw}H(n>{;Eg~Zhzhe{8RTaz{9;Q5I)GSgVq&g$VGKPae~ zO_S!QRn4u`m6yu%4!7(5>uAmRx-TmxPg{E8==KGFKD*&sWXrks%kvYb-kKAns%kt^ a`!Y1hF)p~$sf`1CK{{=k=GdH^b^igeQk6IW diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tile.png b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tile.png index cc15910ddc1347b786dad0cb3797dee8d1c3893c..39ad275a21e39ef8f8275dabe4d7843454ee113f 100644 GIT binary patch literal 1871 zcmV-V2e9~wP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2INUZK~#8N?VZta z+c*%0-GM!kGWY5dTtYffM8y`R7v*d!dAJej4xA3+4(twm9mE~@IcC7x-o%2DohCLuyfc@xbB&-{?W+!{7*AzMTgKu;;)4_8b_%o&y8eb6@~_ z4h&$=fdT9}Fn~P=2C(O}J-~-qe%wu8GCuqT7(mUmIl!d+PC9?=W{cPEY<|hzE5HD1 zrmX=kD!-GyJp7jZd(0oL00XF*HU@ZI`G?u-5r6axFo2qAdVt?&`N>b^&+j)WpMU|> zOp^ml%I~K0T*mzD`Ifsk0t2X-rUtn4$NYn>@(CC~%``E<@6vxJO7X(&&z`Tj+Xxsy z&6o@@DUcF3KD1&*`Puv%cN+r(sF`g8EL}G#Pb6-(MnKQ@Rg_P_0BXi$fGK9%8bLQ* zTvb&*0RyNR(*d?I0$<7}nlymk@au6jJpbH3ORm*Yd0H`@bJiK~eedvc+facE(0N7ka(Pkx=MWmF4lYcYzFICyhb>5q!9nO-A5b`Gl=} zru2pvALvfj*XNe^hskX}PA;R-`A6~yytoVhw#)c!Ar(HFw7lw7`65 zOwkC6%733Nyl&%im9GrCYYQ=*{{6OohgV@p-a%ZGuP;LbDA4nGdC>(fTD&1k*X7s91MZTi)g$H;%9XG$rn8Sih6oWK^ zDg)dERpcuwzfPCGjddg7?l7n_z)j#PpN{!$D_^r>1R>=Ft^?c@F7i?4&ELyIP`-vj z5XnTc_0O8iMK;0%^}jIy`JB&Hy(SYrQ#dhv9mHgu0n`a>2e@f$<%@>u@vA?Y-f&lN zh_Iw8ka2RyUF~5zz)fQ#pSt_i`Bhy1VU{1svUa-o(WOGyveq%rW#8=Y%WV^4o8Wvv z6{IRuAibamj1PmuMm~M_i$Qgofh46$1#5+GX`OU_<7x=PL&=IE%|~!oJJ<|x8<@&h z_3oF8SC(rQEn2$B-`do&iQZ&#BxIFdpw-%d9yC4-4paH$gpN4RCH6~qU#47}Ca%Gk zGiE8XqsQ`sNJxTxN1$%>EYIS;$#Op5GNu-ntyON8o`7Uke_yio@2d@dHdTWcV57>} zwi2#(|0ae~M8$juxBpfX;lMU05OS+95Kz5h=g@^8TA;yQMs#L{*-T$eB$0f1BXr^# z{Iqe%EMuufbxq#=WeW7r#I=o+I<-bY$S(c{?gF+f3o91!-)rn&Yn_?-LReaQro2~R zlSI43AWoiW5N+Zpx#}|Id1)45fMk{i&}JgI>&UxbG(I$f61Gz1d1$muj(=t=Tn~_Q z1Kb5{aWa4s#rbUfGe)?`4+_8l_8c*I)+N<$`utR;I4@0v7ycO|U;q^fEeRAkz&-!W zR*(dGK$o+7;*pBGwnC3;Wy{pah)=T2BXO zK1;%O)4|L5Z#jzzO3+KJTw*D`#G+iG=FR0r7|R8$i2v0omQ-DqSV>Rd5)R~d?3aZ3 zTEGel=vhJ6B~~&gn9YsX2+;Gs>!#Rf{N+l@8u0S_5n68Ir)$K?z3r4VXc005nvNC9TZRVfrq&{OeeW=60qGS)o-Fv7_LM?~`0dGrJ*Rm6&KZ$<(k}! Date: Thu, 24 Jul 2025 01:59:26 +0200 Subject: [PATCH 14/20] feat: allow boolean to choose for checkbox type --- .../src/CheckboxRadioSelection.editorConfig.ts | 8 +++++++- .../src/CheckboxRadioSelection.xml | 10 +++++++++- .../src/components/RadioSelection/RadioSelection.tsx | 2 +- .../helpers/Association/AssociationSingleSelector.ts | 1 + .../Association/Preview/AssociationPreviewSelector.ts | 3 ++- .../src/helpers/Database/DatabaseSingleSelector.ts | 1 + .../Database/Preview/DatabasePreviewSelector.ts | 2 +- .../src/helpers/EnumBool/EnumBooleanSingleSelector.ts | 2 ++ .../helpers/Static/Preview/StaticPreviewSelector.ts | 2 +- .../src/helpers/Static/StaticSingleSelector.ts | 1 + .../checkbox-radio-selection-web/src/helpers/types.ts | 4 +++- .../checkbox-radio-selection-web/src/helpers/utils.ts | 5 +++-- .../typings/CheckboxRadioSelectionProps.d.ts | 4 ++++ 13 files changed, 36 insertions(+), 9 deletions(-) diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts index c3b9e91b8c..3241bc6cec 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.editorConfig.ts @@ -43,6 +43,10 @@ export function getProperties( hidePropertiesIn(defaultProperties, values, ["customEditability", "customEditabilityExpression"]); } + if (values.optionsSourceType !== "boolean") { + hidePropertiesIn(defaultProperties, values, ["controlType"]); + } + if (values.source === "context") { hidePropertiesIn(defaultProperties, values, [ "staticAttribute", @@ -197,7 +201,9 @@ export function getPreview(values: CheckboxRadioSelectionPreviewProps, isDarkMod borderRadius: 2, backgroundColor: readOnly ? palette.background.containerDisabled : palette.background.container })( - getIconPreview(isMultiSelect), + getIconPreview( + isMultiSelect || (values.optionsSourceType === "boolean" && values.controlType === "checkbox") + ), container()(container({ padding: 3 })(), text()(getCustomCaption(values))) ) ); diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.xml b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.xml index 4d0ec5f3d3..f9bd8903f0 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.xml +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.xml @@ -1,6 +1,6 @@ - Checkbox Radio Selection + Check box / Radio button Input elements Display @@ -28,6 +28,14 @@ Boolean + + Component + + + Checkbox + Radio button + + diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx index 2356000002..39738cd33f 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx @@ -47,7 +47,7 @@ export function RadioSelection({ })} > | undefined; @@ -58,6 +59,7 @@ export class EnumBooleanSingleSelector implements SingleSelector { this.currentId = attr.value?.toString() ?? null; this.readOnly = attr.readOnly; this.validation = attr.validation; + this.controlType = props.controlType; } setValue(value: string | null): void { diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/Preview/StaticPreviewSelector.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/Preview/StaticPreviewSelector.ts index 0033c1f8c0..0f1d8871b9 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/Preview/StaticPreviewSelector.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/Preview/StaticPreviewSelector.ts @@ -13,7 +13,7 @@ export class StaticPreviewSelector implements SingleSelector { status: Status = "available"; attributeType?: "string" | "boolean" | "big" | "date" | undefined; selectorType?: "context" | "database" | "static" | undefined; - // type: "single"; + controlType: "checkbox" | "radio" = "radio"; readOnly: boolean; validation?: string | undefined; clearable: boolean = false; diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/StaticSingleSelector.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/StaticSingleSelector.ts index a4ee92a4da..366ba90922 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/StaticSingleSelector.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/Static/StaticSingleSelector.ts @@ -17,6 +17,7 @@ export class StaticSingleSelector implements SingleSelector { attributeType: "string" | "big" | "boolean" | "date" = "string"; selectorType: "context" | "database" | "static" = "static"; status: Status = "unavailable"; + controlType: "checkbox" | "radio" = "radio"; options: StaticOptionsProvider; caption: StaticCaptionsProvider; clearable = false; diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts index 02e915aff2..eb8a95bb36 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/types.ts @@ -70,7 +70,9 @@ interface SelectorBase { } // eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface SingleSelector extends SelectorBase<"single", string> {} +export interface SingleSelector extends SelectorBase<"single", string> { + controlType: "checkbox" | "radio"; +} export interface MultiSelector extends SelectorBase<"multi", string[]> { getOptions(): string[]; diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/utils.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/utils.ts index 5d762b020f..1d9bb631a2 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/utils.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/helpers/utils.ts @@ -30,7 +30,8 @@ export function getCustomCaption(values: CheckboxRadioSelectionPreviewProps): st source, optionsSourceDatabaseDataSource, staticAttribute, - optionsSourceStaticDataSource + optionsSourceStaticDataSource, + controlType } = values; const emptyStringFormat = "Checkbox Radio Selection"; if (source === "context") { @@ -40,7 +41,7 @@ export function getCustomCaption(values: CheckboxRadioSelectionPreviewProps): st case "enumeration": return `[${optionsSourceType}, ${attributeEnumeration}]`; case "boolean": - return `[${optionsSourceType}, ${attributeBoolean}]`; + return `[${controlType} ${optionsSourceType}, ${attributeBoolean}]`; default: return emptyStringFormat; } diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/typings/CheckboxRadioSelectionProps.d.ts b/packages/pluggableWidgets/checkbox-radio-selection-web/typings/CheckboxRadioSelectionProps.d.ts index 616410d95f..879116d131 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/typings/CheckboxRadioSelectionProps.d.ts +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/typings/CheckboxRadioSelectionProps.d.ts @@ -11,6 +11,8 @@ export type SourceEnum = "context" | "database" | "static"; export type OptionsSourceTypeEnum = "association" | "enumeration" | "boolean"; +export type ControlTypeEnum = "checkbox" | "radio"; + export type OptionsSourceAssociationCaptionTypeEnum = "attribute" | "expression"; export type OptionsSourceDatabaseCaptionTypeEnum = "attribute" | "expression"; @@ -39,6 +41,7 @@ export interface CheckboxRadioSelectionContainerProps { id: string; source: SourceEnum; optionsSourceType: OptionsSourceTypeEnum; + controlType: ControlTypeEnum; attributeEnumeration: EditableValue; attributeBoolean: EditableValue; optionsSourceDatabaseDataSource?: ListValue; @@ -72,6 +75,7 @@ export interface CheckboxRadioSelectionPreviewProps { translate: (text: string) => string; source: SourceEnum; optionsSourceType: OptionsSourceTypeEnum; + controlType: ControlTypeEnum; attributeEnumeration: string; attributeBoolean: string; optionsSourceDatabaseDataSource: {} | { caption: string } | { type: string } | null; From 32110e29293feed5a2d257b1230f70a781d013a8 Mon Sep 17 00:00:00 2001 From: gjulivan Date: Fri, 25 Jul 2025 00:46:17 +0200 Subject: [PATCH 15/20] chore: update styling --- .../src/CheckboxRadioSelection.tsx | 2 +- .../CheckboxSelection/CheckboxSelection.tsx | 82 ++++++++---------- .../RadioSelection/RadioSelection.tsx | 86 +++++++++---------- .../src/ui/CheckboxRadioSelection.scss | 13 ++- 4 files changed, 91 insertions(+), 92 deletions(-) diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tsx index 2f15c500ae..7ec2bc338d 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tsx @@ -22,7 +22,7 @@ export default function CheckboxRadioSelection(props: CheckboxRadioSelectionCont }; return ( -
+
{selector.status === "unavailable" ? ( ) : selector.type === "single" ? ( diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CheckboxSelection/CheckboxSelection.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CheckboxSelection/CheckboxSelection.tsx index 60caf0b7d2..1b3ebf3709 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CheckboxSelection/CheckboxSelection.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/CheckboxSelection/CheckboxSelection.tsx @@ -25,56 +25,50 @@ export function CheckboxSelection({ return (
-
- {options.map((optionId, index) => { - const isSelected = currentIds.includes(optionId); - const checkboxId = `${inputId}-checkbox-${index}`; + {options.map((optionId, index) => { + const isSelected = currentIds.includes(optionId); + const checkboxId = `${inputId}-checkbox-${index}`; - return ( -
+ handleChange(optionId, e.target.checked)} + /> + ) => { + e.preventDefault(); + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + handleChange(optionId, !isSelected); + }} + htmlFor={checkboxId} > - handleChange(optionId, e.target.checked)} - /> - ) => { - e.preventDefault(); - e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); - handleChange(optionId, !isSelected); - }} - htmlFor={checkboxId} - > - {selector.caption.render(optionId)} - -
- ); - })} - {options.length === 0 && ( -
No options available
- )} -
+ {selector.caption.render(optionId)} + +
+ ); + })} + {options.length === 0 &&
{`<...>`}
}
); } diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx index 39738cd33f..55327b78ff 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx @@ -25,57 +25,51 @@ export function RadioSelection({ return (
-
- {options.map((optionId, index) => { - const isSelected = currentId === optionId; - const radioId = `${inputId}-radio-${index}`; - return ( -
{ + const isSelected = currentId === optionId; + const radioId = `${inputId}-radio-${index}`; + return ( +
+ + ) => { + e.preventDefault(); + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + if (!isReadOnly) { + selector.setValue(optionId); + } + }} + htmlFor={radioId} > - - ) => { - e.preventDefault(); - e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); - if (!isReadOnly) { - selector.setValue(optionId); - } - }} - htmlFor={radioId} - > - {selector.caption.render(optionId)} - -
- ); - })} - {options.length === 0 && ( -
No options available
- )} -
+ {selector.caption.render(optionId)} + +
+ ); + })} + {options.length === 0 &&
{`<...>`}
}
); } diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelection.scss b/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelection.scss index aa9e23f1f7..539baa77d2 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelection.scss +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/ui/CheckboxRadioSelection.scss @@ -5,7 +5,7 @@ &-readonly { &.widget-checkbox-radio-selection-readonly-text { - .widget-checkbox-radio-selection-radio-item { + .radio-item { display: none; &.widget-checkbox-radio-selection-radio-item-selected { @@ -19,6 +19,17 @@ } } + &-list { + display: flex; + flex-direction: column; + } + + &-item { + input { + vertical-align: middle; + } + } + label { font-weight: inherit; } From 65f881686a278414788f9d6a0b605f5153b97878 Mon Sep 17 00:00:00 2001 From: Rahman Unver Date: Tue, 5 Aug 2025 14:59:21 +0200 Subject: [PATCH 16/20] feat(checkbox-radio-selection-web): add new icons --- .../src/CheckboxRadioSelection.dark.png | 2 -- .../src/CheckboxRadioSelection.icon.dark.png | Bin 0 -> 1756 bytes .../src/CheckboxRadioSelection.icon.png | Bin 615 -> 1890 bytes .../src/CheckboxRadioSelection.png | 2 -- .../src/CheckboxRadioSelection.tile.dark.png | Bin 151 -> 6343 bytes .../src/CheckboxRadioSelection.tile.png | Bin 1871 -> 6688 bytes 6 files changed, 4 deletions(-) delete mode 100644 packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.dark.png create mode 100644 packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.icon.dark.png delete mode 100644 packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.png diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.dark.png b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.dark.png deleted file mode 100644 index 91d519192d..0000000000 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.dark.png +++ /dev/null @@ -1,2 +0,0 @@ -// Placeholder for SelectionControls.dark.png - widget icon for dark mode -// In a real implementation, this would be a proper PNG image file \ No newline at end of file diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.icon.dark.png b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.icon.dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca3d273b50fb9834aa828fb784763e59d789f81 GIT binary patch literal 1756 zcmYL}c{~*8AIF~=Gmd7E#5&fR@QV!Vi26Bp7{)OTCL)F`%8}&yQOMDl96PDxm>TB} z+or@c&ctAnH92x@j@2xUG$_|N5>j^je*4l4jr!7egzp(R7VHj=^DWTjVO> z1?k^6cwh&M6}K{(l{9@C(U2a_(LcJR`?Mv}Dt2I~)w4PxsyIN!*f5aq5X>=6wZdRF zRBqZIk)*KaoAd5t266)W>zoePHLzHGtB}&WN~)S<{`|-7kuWfJkQrPzF58HIasGta z%iuMFI%lmhuip5}*Dp-2Bzb&b!f_SM2&G zT#bSuYB#UgmSn)#Wtrph`JQ#;-&zyrAxwGuAJfT1zxhYf$D6j-9TsD>PyDg4_7=9E zt!mN(6PUkTtj`mc!yJVl9_FT*wAd11dP@_XJ3JVsp28q)#w6b>m&E%_nnFz8XMBF8 z06`n#`NUCfPoME07E)ugRMIfV@ByFt>on>`zwwaT z%%>%rArvn0VT;};C!nny$iJS(q%c(0@2skqlIaNlQ}w!W=?y=ypR5aQ^^6c;GbN>I z=UmYc9nNQ!4`yGJ^Y;G!F3(AWzrG&3Ix}-vwj`x1;Wv}6_#SgjLNz~)D!cHX=;#%_ zQ0C?EF8#<-nH4hZctQAamgiHqV%!r81q~sO$CI98Jp^%4R_e*UsKE9K_I^dKon z3qU5lGj4e?d!ga=L1JZm4RRdWwJ^OJ-JsP>GzrN%^Y~r|)QMQiDO*_T?8HS!MBEZ| z#zsY1KcOz#SOrOqN^wqki+N#^3zA^n1rZcw35Tm=QMbyu&5k~-!tw`Jiq!`O?WamP zXk7SA)vfGoVu4=w1tj@rrcE^Q$N>AUwgLCmg?~G|v#F%cQ@xS`e(^w%m;G!dvacIr z-+4=JnHKKRL* zKYO)(%|7gtr>ttNEZ)UPlQ#Y-D_sKPdg3Z=p*b2jr)EK}G)t6%z z0xkBVH<6W!pY9~q{%ndkdE#|{wH(9le1*GiLbP#Ts?nrD{@l!W zOk_iwM|H#iln-rl4~~sBF__nz_K17f`+8)8Gcm&3?`i3*sM&oVUQfPe=M>gszALc5 z?zOVnYo4rh}F3)p*Ivd-nEPjQ?wR1&0pGqZ<#?H1uaLi1_I3bxaAM@ z;FT9?fH@Y>V{xKDXVxlg9Y+Dn-DU~0&&zcd&W^zIbEDEV@^9!T9^!Q^Fr!{x36aF4-)zw1$;9bP)@Nf1EU{#JF~1G z)7UXuUTZ{vjE2ni@6RY0W)qVz;s2z5gV5^9j96s1ZFO_P;C2tE-3>4LZ+p*)&` zbU}dyEU+LpSV3x7N)Q(aXjH(mVc(v8bIzULojZ5t&zUqQ2b+CDvO)lWeK=dJ3y0xW(j}w-@{ugoVRts1Mssd0Msi0Z1W=OG63;90Ic`{fGz?+ zn)u5DXAEzH6zG8q!sEdSo`(Z|m@L@60mhRlOzvNA4O0Vrf9DVYsiA=XpN<2MyTgyC z-826q9~1UZo0l=6f7|9vzW;k(8x*G$oAZDm+Iq$UAo6%uumW`vIRFF*IIIPM1Y7lb z;V5S#kr)-BR7@m}S`S7WxnVyqvHlqiC-wQowq2cX3#Zy zp4s0s`3%(dR2}O^o)5VPT|Wx9uU`4ZF?Gw-|5;Rcf-l*}q!F|iaC9el+fsPy@~ zX3GnsSGiUzCbWYbDm-GQ`h*EIQ#}_Bv!BGUuEtkWlGFQayy2wJa~t`h4LdWB%59u8 zUV5_o5PqC%7waJ*tI82vyAyhLELA9543ncEdZhE2h{Rb_^eQ-cL3|!G>*@tvQ+pm`bJIy{MZ5 z=?d;Q#T^B`4)!Vr8r=QVwAwU99*#1B^&Y1Z0kjev-;L@V zfv-N;WD3IDJ2TU%!4G7FNmR{q`_Em)ODpfw4oS~h2#wP{r}y5N-X^w@JcetaJMOtA zF-^F|H5QgwsPVw|C>=(vhu3SfZ*wJ2S7J7oCQD5pdB$Po*5Gi%+`=bm%MIR&0+DAZ zdZ{dr_#S__>~^mCv$N3|tqqBCZgRP~RO^k!Q}dsL*&)USMtsCybFlmx;U=EHZ3z>$ zitK7m8R%~loGQXan!RrxUPeUFM z(X?Fee0Egrm#jdEbNQWtnQ-;3VM`m2sVd*~7vi4VuJH>L7P*f2*ky}ruFrUZMBqo* z;wTv}KB($tU(cU!vW7nRXSRy#Tui!HEM+xo>z~#wf6!#;+*5F8R?l#9L9%kM2Y4P; zryU$fR}WKReo_>W*;^SV`Y=|VHT_BP=%MlcV<}q+S}dH`7~mUp_2GP^SPCnz)7Zf! z-RQ=e{nhn{Bb}8Jxz`ti7OGC~XX;#LWayqv5sjXEm$#U4`x2v4MJ)CC9V4MDE*0}; zeKrTw`&`Cv5E@n0y3B72W;iD1s2);HsZrIa>6vZ0F{~Wja*Bl_XuH*&YCc323H9eb zotiTpi{`F1Px(F&}(4#-_On=LF z{6PGsQSmzTCV<`$p*o#+QVcw5QVDsCc^V@_H^dw8t+%n!siOC%66W7Y$Rcs@&lvLH zvNGtMzp;VL|N1m1-PPleE@CgW*~<6Th!?*JP9D}h9W-D(`%|u(o4`<|4CUMHvXv)=1J#1RVaX>(k!NfqMPFHm*wt%;#Jmq0Van9DCNe|U z_1(uf%QuGu?#Vya(VxiR*P2JiJba*`;8v&(Np+Gl`JF1Bmfpo2E7;hceS67HyHG$t zy|77bIQ9x;l`1^OnShUN@X6gSB4g3brTw`pQ`zv*Lc+$jRf2`jWZ>G+fKC9TG(xB$ zx|T%mR+tqbD-Vz?14Gj_fA4jf(v8V2)4HHSosVyMVmV!0>(^#SIB9$14_ZoHvQ8_ zcH+2q-{>(T_aNr;P)16%A%;4pEC8iR{&h%Z)oDFITg^mq{Aqwy&I#@~m`M zKYTCgCuK(3>&kMtU&?16jCOa@&U~;g@jKs1dlxHZTC(s)9R;J)!TtCUYE92aUl6L+ zs6RrNIgX4gs_)E=8+28vK)!bRBfGfV?tz(_b%&YLH*3eSzu)$uo*yw~Ce5K?r{i`% NADoo~w%*c@_7~ZqH^Tq` delta 609 zcmV-n0-pWi4(9|eiBL{Q4GJ0x0000DNk~Le0000W0000W2nGNE0CReJ^Z)<=0drDE zLIAGL9O;oE6MqSCNLh0L01m?d01m?e$8V@)0005}NklPSj z4x(^85$Sjm?anc8A`_w}(EIhkqw=>H3rS^#X2W+IQkl$;0ak z0J$CKi6gf=XO_<~XIub?K)gpDZyb4gq9PYdO?(J9414ga zbrdO{ZV%*Ee|eqUe#-~;EdQ|Ivms9@5oIL0x)Vf97Y+wjm(hvyMOi_~sFW|*Wg$I9 z6vT?B+JO#Jx80>&V~j7Zlb0f7K7gO2(A}SuI)99ehZe%#D1hd9Nw)=z4k{wxhARK~ z(k20Sd>4ur(_sYk9T+_b^^*cPGF$Eo$d>G?F0KE(=BakzH(tDNjsjrIY^YuV=N^E+ ze6j}>v=t}{x&kihBI+o}qeUUM!~OxF+R?#fr9+r?wFB`ec%-LSRZ9J0Iz?T?Pv!+L v8!98@bs)D?WY$EeTRXbpy3?HhF<90&JE0|7v8}2#00000NkvXXu0mjfAIu6P diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.png b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.png deleted file mode 100644 index 3879a20612..0000000000 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.png +++ /dev/null @@ -1,2 +0,0 @@ -// Placeholder for SelectionControls.png - widget icon -// In a real implementation, this would be a proper PNG image file \ No newline at end of file diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tile.dark.png b/packages/pluggableWidgets/checkbox-radio-selection-web/src/CheckboxRadioSelection.tile.dark.png index a3d5da808d9dc83c010753fc35bf90fa01f9f949..1b901bbf2af54ca4463a5d7706b0ff15d1605142 100644 GIT binary patch literal 6343 zcmd5ghg(z6vL_^l-UWduMMR}Xq=^VYF!UnQn;;-niqdNU>AeJ`34$Ooc4l^GzKztAd*lk0N@S>fHiXfka`CI z*Icq1o=Ovccv%}Lzf@NT_=q?e00FT9|48^}4jB;ae{dw|E&%=)odf{F?EuKXc{GUd zpU0fo{~7b&5S$D8H#d=%OY$FX5ReQ04-R07+(5eP>31R^cTqBY0{}GN{^=kzKMjni zV7;<}oQ@Z0Gu!y}j3jfI@&d!109>j0JEq#|;6#18WuCc5%KD|B-9G>6S!McWCYSZ8 zcufPnxn?50|J&-ycqw*`>8+vMdRun1VDvsmx*NhE1Tl|` zQzcntd=NM>updo_P|@dt;E<#jN?1h_ zQ9ve@&HeWOsyKwY%4SY0yQ}q5MoOn>u^d<{WccD@BL7P={1N7Orb-}jAQqBERLUjH z$Ln#33P)k)YjG;Vx+qD)aaaXh+wjXFMuFCuFm<}z4J-X<S7^`G1<`ncz5GqQ-Eh)**(_2=y-A54Z~UAY1-XNGDx zS7e8htnm{l_*xjy2sla7YM)+o4(ZW-?Cj-$k{btvhhjfcKvqCkT_z(-9SerOBrnT)2E~JtGWhzqn(q!V@$^bOUm~e$TM@jIw#rzxFIPxUzI_`zv#DyDL%&%LVcP z3A7LJ2X^;5CyG3I9tu1{l?=jmXE-j-c{SqWkM1?z5i;pYZAFD7+7lgQfRimctGU0- z7s&QDJAdjFUsqP?z-1V9D@c{m{2Fe!8+mDNh0n3XbEwDLm@*RmsMJ)t7j7{r^8zoS zl#!=KZ+;C+*E8J9ap38}Az03)5=M0M-Jr-lAYhVk6;^P+urlz%XHseTCCvdywrR4a z=I?#}%Xci^hF2iBy8tRa3+F#Lx3Ad`lajWSCF7URXl=JA?_ zr|SWh&sulM)cP$E_ei|1)@7~T-_dv!gp-YDHd^H3ZT)P;72n1u(-?oPt}n3XzCKT7 zUEMW*!a?!W_VHqRdV1?8JGMTKKL!et%spo6;N=EPd9?q{@AO-^2HFVE)YMcd&+U1< zqnb-P3z#$1afGaLkJ7?(W_(;vT2{6-_-d;>-s$3S;<@X4DZbb2-(chK%x{F*>`HS& zLCfFnetS*TYS(L49$JdS_ob1y-?;7*-a2sp_n1f8eMW&5;C%!`<`QFYXt^~ywe^v^ zXKVr0<;CCaA5&9P4~sDTC(-*>ACrtHq3n>QsKg%X$FQ&$%b#)r?E9+hCT|Y_N*~fi zNle=NoW$rRE*8JXJ7}*aYRkWx+bs0cXkdA(>vg3h-Lo>tN&fG5jzF~m(+6^-w}%0x z(|2%^kW&bTw|4Ij!JjR`IkLs)@N-b_(az3}7N4nGB9+d7P`Rp0HfAl5a~8cbH1t#= zDzAM0)3uJ_O^^Kg`g)d$9u_%)z=Zu9ztzuTrYnpiAPMmoyg{C$Rco(~+VOB>#+3FwY&+S!~)pl90o{KHuNrlEo?cCMx%$1B6bP zB>gNEjI5|y9(DfANr~*{e{}Top7z4H@nQ9X=^O1>hZY zjX}k$dYzgBIAp~K=hnDhMzl^jgvpr{9WbI>AUwoPNBiVG3xeaGo@{hUq!4Fk%(%!(3U4o%W7{N5Kl#)PkezFD=Q8~;KbI-}-O*bSRL{wGZl@Dr zQ-sTG=!>@$x$i8`r<={+^1gGH4tFA)M7r7J=J38b;GB@|F&n)yN{l*mbPU4mwBBnO zzh&WidT+2U{yDV+{fyTwPZ(3+`D^X6n%<&9{xDs4PDb$6c{0Y(dMNe{{2K1GZf=%%5c7tlj^khwuTKi)G}l*%B% zTK=qF`5@@|cL<+Gw7I zVfdVi@BMA-sbh|>GM#BB^@*xO)V6lP8M-ZEO5j{HH2)JM?8`;9XsW7AXOyhV?hl@L z+Lj2e1krx-W&?&Mlz4fPth=!;w@`;DAY|v8q&HU9S~^eVd5){?lrY)-B9b8561a7RC`kiGA(MW3>Y|x zfxIgtGPlHbynB*Ae1nufkczDD4o76fe(4LtRoUI5Tk)@Dg8_q88^&|2_2Sg=?r2VP z&3TrQ$u=BKf8pZ@=HAImJ3AgS`G6KL&ThJc`r8Cf|6w9DS;09wuqbqd_61g)jY2j^ z%(I6F=X#xos6{f?wq2e%mAH;ruEc6IdkQy$!{DouRuy3uX}PJB1i$%b+UBSz$l;LVEwGR>y$> zeTQE5?8tg9RqFM2{uxE=!r9qbRM|mTPnjPTr#_!U^Z35xF`91BbkOr_EsI}qJv1cX z8eW(>Sgap8TYFN)Vlk_8aH2tOOslsAGWf~G$dU;(RR)uyygsx~(DKRV`w z-%!>$dV{>qj_**(rQAn&ZaJ6h8Rd~CJj>a8)bdN;_B<&66?Kne8~#gbj9lB-W|YDh z(>g?S4D$m{jZ$x5??F=Pz=4QM3)OSCmmWV-9gSb__M8MxCQ;X48TS%pH&!d*%z);i zEPc3Fw!hnN(QA%kiYqafLE_`)Dql?>*gZ2-|C)kSL{l!pN?bE7{wcK|CBabP^{<>d15%)ob&NHm4Ezt2Y$)D`p+z0Zzl=(R*A>8Ky7idd>O}#m zMBZ3ST(Qf@lcygSQjp!af}F{ST)wycR~wZJbxzJsYX%y$mGh@yw-02&iV;>FgQ{;$};w zB>y{}T3%!2*tegQ^8wVYtai%I7(^j>Uz&tp0>6B^?%ez&R+wsL&@?AB((BhI0%rF| z#144P@T`U7H^?Bp+P9&ot6tZ|FL$Bp(A)JK&+&~>A7KlaB$j(i$va8$n*wd9PVr0q zH`kX(DMxlr>qEk5uqVthg1vc`N(Z4YYylw!(Hqyg8ODq>ECrne2;NQ>jO`6wkOn%d zp7WnN&YukpjEwR?T0wBf9K=@z_q>l+rwC5XZs2y#%JDotZ#W}W$1CHl%Y>gnJYVNK zSfC;7SUBG^AF6VjHZjE}JIYW^GlZmXwM8^|_A>1D%oLFOQ5Bp!l(Sc~O!6 zSRkLR93$8`QpQ-&%}>9OQ4^y6mkdV_553k?wy%W{%HMN26=jx6S`t!+AL5H!8)bzb zD?T(9zk(@N_UsLTBJyQ_JUykTJd%W0H!HYLKCGwwGspINJFMp1I@8C3_o(o;Y<=LR z3^fWG`ZOE@=UU0mxc=M0e%8(F_<1zB9yy`2cDAHfwIkPh>X3HlqElX1lV#y6n{VJFq+#;SGIMJsy?iW6=$$V2b_Kk6}Z<4n4DV>aXd83J;V zo5~m6NgNj+)twt$Ar|J5n*vJD8)=Z{@D-b?g`77E*3N%^Ip$bxeChBY2Cv7d5AO}Z zXCGj|L@n;~bZ-g^2s5u6OYSzrWH)vF;8}gp?lUo(F0>ay6tL%oOLL+OgEBF4jTCWS z-EW!jwe`7}d5em`hf13QE%(%(`5l!o4jekG>RU~Ek9>!>*)xe)>I!v%r|oBTiJO?o zE`GR2uUPOvap*CVkwE5DVo=+qbc;uyTpDnnVQAsf``6b^kSuy4VLZ_IJ+^29Looti zZf+V%*(QD!LuZyZp`_>9Fo+7_uw3()y?y;jTD}&)y%}_0&brYsLI3wfw1!1F%kiyT zMz7O7yiA-Jda);jl-7yY)uydY@yV`fTU%Oa>+WJ+$alxpo=cgZrVG;kf>Y@P8}gu4 zUxnXifZKFe&7~f@XY(hJi~4N-!O~Nv7W%sq8&l9@cX0mLx(z8GnV%Tcf}3jTnI$iK zv45G4RxvE>q+=u|AR!<@H^Pc5=`){y99h{V+sUgxu2Ysr__EKlfyy~h#9bGD9-uCe zu`ET3wJ19C$OA3$s<>KAhO6spNC~MqE8bpjju6d$9o(%N^7jM8Wu)9e6LQn*im|-# zpEhk0udPd+vNH_4O|lQUN_#zp%QHB@TU-)@r6}8o`cm-)L50J---FD95(^m2-zBLStC%&TRd3WQ=u6Gbb{wv6*^6s>U2k>FHUy3zk@~ZdW%{zF1t=mbRPb6 z&m$fmE`eMiOqs}&lX@Sn-~20!GP?2iu0z@tCW{2LPID;b4-LjF*ifHUu{8(yVnAoB z$bJ#|{1g3&%B_;KcI7#%s`zQ@tYM(&$+w+Q2(h3eRPJ?|<(a8*yQt)s?u8f%LeED< zl3j{8%(Ea}Tv{Rxg8sAawQdB1Hp!dKtuGDm;T9V&Rx748vhqlA=dbkDp;lUSjE1Rr zsJ_@cJ23W!CF&8dmL1&Bjs&v{dxzKa8s&M6BX4SUT}1Zb6@`S#nu0jG z^JQ2!!+#k+?ToZY>D~58)YJ`kZ5IU=wSJ3nlc%tP&h93*IAQVKmj8OEL zs(@-EHAx&ep2bWd(!LX8ce0Lehj} z+(j>>*GPdrMyTy;)7kGvi3I%9+p=Oj&!@)|@7$8XCqfbYa>xn|^KSlc^;)d(*`gV% zcP91GMC|5P*TyrahS%$x>E zZCmq-uJD%yl&Wx0_7k4 z0BR~7t(kXsZEZfdc5Ti{!pOiNVcdkTaic1Dhx2;8J%0b&4lMmgT?j^CZmuU@bND^f zJV|AwqbY(55$cpz9m~e;l3hb{;9XZ+TU!ico`{;%h=*duEqXaJ5#nntQ52 zuXe~7ZXMBdt(2{9h1IC}$v+ka7JyjUDoq4oMd`@})ZcBx@{D!nV|zZ@Ssusn{rh+4 zssXtdY8b^}TR@tx)J{*Aa1b6IF7jA7@E%{q!7`XUe=M#tP3B@MY=1a#yO({LA?pNH usY8I2H`m;uvZ1(#Ai4E_+u(S}1?}ayIYAHp>i$1B{K`)>6v`i)2mcq2|A&JB literal 151 zcmW-aK@I{T5CreOq935}2oJ`K*~Ax&Fv}7LhOlGy`wL#XtE;`oA{2O2iQKVj$D4(N zWvzFu)>U?AS=dGDU)O5F4M&cN4Kqd#TK}`zqiE!d_jpziI+GxmCS150P30A~1yUu_VOgZIBY(1Fti(!9?5afHxQ-P8vFNPGTuP%=L$ z0|3xbYA8Q2@(1l@e)V8fL-v<7YX}~T_C22eG2ft@u~^RTJp)RZAoUp3$iKtJ3v+y( z8u1A~v2C5}Kp4Xl8O};X4i!-6eChxEn?|h!OU(-HtZmT58BNc9*(i;7_lsK&u*ceo zQ*vaaQWjUK=cI@ij}(xPj!P=7u6AY>m+`@H;QyOu{RSn*c_!(tRk$AFyD^nuRH{8H z)gM4}`X(a5`)anY^-(o|UZore1Al($;}y*&AGk2EdQFaQZfQ}ls;HDua##VOL6CCL z3}w^v^}ZHA9yrzYcc)R^OsRU=W?J=B4sZ!Sz-t$ffXdJ=GuUa+IFFY@LW)G=In?4p zTLMpxo$JB(j3dYUE%;JkCw>!4jwwvTB-{jy8gO42oL-6Qn&;j1^^Wu$_G){1=q^OG z7>I}x+XsQjUq=E|2w6r}D8yR>AcCjNMyQ7(WFa}guN4Abc)S)^A87h^Uzr~Aixq&9 zlc}kL(B_mV@PJ(d>o}a848()-msBJoSWCdc2-T9jyM^{AK!AF>DGU(s6da6qn%iqJ zf*vItHuKwfFt~{WfX07yRVsJ@XU1Xw^$epTAK=8nnA~~z>Hjv7efDJI;QkBM5p_##1F;-HG=AO#iE-B8{dC-iFY`r38VQf5#R2x{pOU_Y zwI*2hw)0I9B_TX?C0!x_DimF%lEyJihWcV`cuma*t_)!gh16gyfY8COm0MZi*?DCJ zkIzcIE3+0;+3i=AafXL4{I)oIl-QOa7T1-n7@_SMuMZG9Tr+6Rvpke*XC!T~IBQoP zX6XZGdx5v`?!G;{i;_rbV2KwtI@><{MpAkH@Qh5VGnoOkO{wmQeT_O@lYW%Z!I`cAZ~ZS1E`Rpo~+U|xEcFo z{hQ_>f88ST+qK~2<)W)6mke&2iPxZakS0y8Co;|cqS#%B^;xO9b6-Bs;(v>3f1yTo zeXgo<{~0OeFT`>WPG2{s65ELkw^DzYpUx8A&OkR(pbg{$B6f)OF5$vc%-128^(q@8 zd>aH@7yQf){HAQ^JUM1vJP!|S0qIi&jk!yc z9nnh?a$>s4UU&p*2IWQWP89yLYgev2Du$942EEI<_f@vu*um9+&~qha++wEtaFOTI zLfw;X{vQIShu+dCF^qD#{*vc%7Fj~kvO*X$d>76SqKBr&jdbij2LJlnvPl!sKaL&w z$q-QI_xHTn8`*klD9M12HgIaP&PF~;dcR39dLgo2rnI)LM-^rSozvu$?REBl@B+6Oizz#3EbXmK@9$OIt9s8t{c^pz z)?SH$XOZ<@uC^CXtuI2gcZYYhQtsuE*Ry3qsdmZtM5sd!QqGP;!hs(Gk%zR{)9`=~ z-~6to*>rLe8j}}<{OXqiqmuG8P7q3jWkSi=G;X_zqN(Ht2Z_@Y`}FVk<@n#HrL`(* z)iH`5!Y1*Tc`n%L?(phGw}y<3e6qt>TQ&dMspvg+8nt9HQR%pET&ghMkb7%!Vz~bC z>ZIS>HuBw2>05!+NF=DXAw0zi>DA82={!F_9)$3Y#^uS45_(6LR^2rENX-pA^CadNUpOmt2Y8PNY!*m zyS%Ai`9xbu@Wm99=y3{G{s-@oroZ3f;$gjG#n00w3gJ36+BQM6@r^-?LiDdbZ3^u$ z@G>!zD&d+JtV)Y12d$Yr(dvGX;;=x6uz&n8WS@HQ(}2Ustq`@8bDGE@%5qd`cP z&f3`eWBC~$pO-dNz~%s)6H#=^$TIGvfyZhKN0 z=N(IWgv$5AownaP^jw#sb`{x z2Yl(D>a$p!2Wt9|2H!3QB;(#1fFG>%UkCLE<_9I-9F0URq2Bdke+%mpZx~%hja>41Iocj#|f9eys^+qXrz*6*xOu&a7|RR@-YbPp)AvMJFS> z#JPCG$YvKteV$0c4GN7UOa?j7`RzUt&os(3Nc!a2$c7xAo* z%`FZaeYw+r@FOZfr}#^wguG?%0*SIHg%5HdT$UiuzE6}LLh|oS{0ThTyY!SC%Jd|r zg^J~h6qG4d+X|c8e!gn<`Xd_aT&^0nu850T%HAYDR|q^a+D(Q>!*2%rT9a1_Vn!_% znr8I~ZJYD}BI{Q_J9`Yg$5!r$V7cot_{&&cw16M6A_&qHxjF`M6d^l3k-5N2v^>M3 z2}%O|h>-?-$*|5KQz=D|Eho1(C0TF+8PQG0!cIQKzM{5xpZL|?@MwL27cuG*FWcJa zk`PBVrQv$Wk{nqM$;-!+HRW-ch{y%r4r!R55_+~|+jmric@0EhcYZ6LF}?zUPA5-$ zxMcNER9l$>zC{1*2wz2jueq3C7PAavGK{A$If#!*AorHp$S9h7UOOl`H&TC8SVnK? zO%X!l`L16IuF4Lo>Fx5kX9;jDR!V_;%f+BMfa>5Pq5pfG;JI}E zg#eY7U$S)F+pR@4iNz#|zte@XGr*>_)F|+n4|HD$+_3Jw;zue}y{(sF%Y3#%`vz6^ zJKWI_4$B8WYvIS}H-%)}qQuBtCq0?=7z)|`-MjaBV_xg=lWO zNw*%eSeFDMBe85f|LGP~)u|h~EZZ*lZPuo_ zn@E=5Kix~FX)<M{b6PYk?jBt#we!BCmWW8G^!RG;pqhDHr zRi*zb(HUDSB~;i_dAh+(5PKg}Ew53k(ZQnleL~eDE#-Nz!TvX1RGoHi0ji&l)T5KB zSoZyT@V%(IP@{E$Zjz6)v~ha zTLEIc`#dGO@#fg0BZ<0J@8w=EI68A+D&CZ?>HY^3(}Ncd<`q{c%2Ay-!}j_whIt+b z6~uSCA^U8c2%UF*GdItrjkNmdM5}J+oPN<2jYgYahy_F~)OTd}i1@-#(#kOpbivl| zyK*#y^J{;{WUv%y4ISe(g7E#?0)_&u5l8B5CA zH#7nEEFR5l%qg+wXH@6gs_SOE-IbXO2}DkwwM?vJ`Dtu8);A-6=rW0;1`ZddH<-@` zw!iuFy4QLL1h?4mDPHay`tTc_*BA5;qk530TX>k;yMJ=ndNgf#)esgCS5D#Yy6tlT z=jMk!JGO`E5kHM>sGSv46SPJSB$8Fvec9i{Pm9Q!DAI&}oG?R2qh+F=?a&}7qm*g8 zDRR0L7@8*<6zdJQ=3XyYg|&Ce+8fu2U-^yP0baFnAzV&2{h2 zYxfI=%-)G%arQrI#V5ffk;z_uhaVQ&t%rzmT4~x@yUWP$ditGONz5J*b*-=q0GWM| zmg18l#d?Lt8=aM$xpqg*2G%C~3c#$nynkLksPrRNr#TnU9v@D0rP~JzvQVSiD%r+s zf@ukE+La0IqMR8gVg#SK)^}5h`1$S-!8L6auO=zhw=`0^zP;9VaFw%l+gh>zVNo#B zfp<@C#9wn|$~Qnv@W=Nnu0?%-YkNcM#mCb$0aQfP*5=ryLPs@wkrsXyEmKy~aJ(cG z2JgYkS|e1SCu&a;QOR{FVC8BWd>s{&&_ngHl_d*?kn>S^t8w(s#u~&JWLjSER}PTCmU-;SR{yrd%aI=BS@Sl}>T&>l*Wm6IZ|4dkHtaTO z-d-qpO9i@#$dN$z5ZV7(UR8iW$rTLMA?O8M9yOq29IK<8@hmYxM4f=DgVxqYKx3@2 zm*Krv(LC44K@lF*Qy!fTIWZLpqk9uX;_SlfZJuQTy@M>& z;uL;n{_9)bPHkukEhRE`PbCn9gX>a#!%a!e+S#SlKG3Bio!OXq!S?-(PLQQ+Npfnd zq!Q>TPZYiR(WjC^w?Ts8q<<@M=Tt;fOO-0fc)=Qxo1EIUZ0!173>TTBv_CE_<(cWV zbn>k@JN zkZo)!=d=N=JYt1d`ZWVfN# z)8H@i_p6-f49DtPGgRw}}vUg|nZnr!i zTN#g3z?@ywx^zahyw0KLxlgvci{Fiq4i>zYLt&pLrd+RyW9v;GGyaV{%98$tTO2HmDGL@zuYq*+p3 z;>@FBj!8N)*txB*W47NCue{>$${X5%wLJ>js|Mu-eIUwu#0qY# zRjy`Zp*=)I&}HZIp&Sh}c*$xue&7JMb2>!{!@r@l_?yzd;D-yOsFqGYb9oiB+;erTpyY#=LB;xnV0>_h4J2;c=t6=p#y*6|%P=r-e>bX-cT5Wxojt* z(~rhS3eU%1sEFcozp`_#ZwPCfpsc^jb}tt!pbHWMjTdBxRy$>*s7tw?8#Yx7CwdUA z0aG13b2PqrJK}N%<0{61LS0N->v#-{_4SZA?Jr`hKSJbqqAR+iWN-) zaT(3hpEQoqe8PB+J;um&zKp=-d>!;1t`yiNZ3@dVE`3L2eiT+LQV{uI^PcYf0mznv zy@r{@l22fIq>dWodhf4}q zv-3Vx6E2q~bFslE6Yc(Lm)i>0n83Y-9+|2I|5MVv(;FnDXR$cr)P!x*;8+n<@J1mR zeY2Jsoo$vi%oL(PWFuVS0cgT=Si`K&3eBZtyM<=`o10D=SA4dyV-R%(GH+@;OYY^D zU!wKS3~g-ZD$SdYe`cC<_91Ms)YndS!wdxOw(qI|RaMCVu>8$xyLj-yVf!n0(^Bn{ zY(fI;ze5>bO=arq*KVbLzsDQIE7N|~36 zZR*nejK$+Dx>PZxW(^G(HIRKUL^wF4v9c+dfBSBd z59UJ{FylQbB6(+zmcx{^g5k%%G;=+1VEEvkZ0pQMuiW>prL0wjl;d;-YZwd;N3RY* z=EU>V#PhtH!@dtmSY9Uhx#Sq`9zwEhOz54?MUGFbMhxk5fo2~H2UU)-XM|qxcQad6 z^7CIz2zA%C2f9DhwRdaAG~c`rGq=>*QtqmuWu2cH ziu9%zds%(q`piv^ENZo4OuB_t%HnU~iM-yQpqc=M6ipJM9DX$~HX(56u$@AZHh+k2 z=jNbJuch`!%%uNQy)5l(N9i0OPkA{mvm==y+HNi{n#dro4gJEgjZJcWw!RK_n{3zA z!BX&Hg=Nu4sl6?g>&-9IGlqE3oC(xCExJK`%q;g^$b2Z?yGPB}V~cWzTBUM(aqGC6 z69=?j`RlPgw(f7J<7Q0-i?aO7Fz{BKhH}KbbvG0HUD^lgkma)2(Nlj74wzj6(Pns>8 z1=-tIEt?s4Qe$i18$k-Q1#K3VT<$L|;95z@49!xjqL6xr){93)edD;)q4SOK=FmhuP zS0iK%cjme9@f+p7-{o1Z{L7vk0R2C0Xb~>VP3eA}53#*n%D-PH8Y;TVm5SCO{{<-N B;t2o% literal 1871 zcmV-V2e9~wP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2INUZK~#8N?VZta z+c*%0-GM!kGWY5dTtYffM8y`R7v*d!dAJej4xA3+4(twm9mE~@IcC7x-o%2DohCLuyfc@xbB&-{?W+!{7*AzMTgKu;;)4_8b_%o&y8eb6@~_ z4h&$=fdT9}Fn~P=2C(O}J-~-qe%wu8GCuqT7(mUmIl!d+PC9?=W{cPEY<|hzE5HD1 zrmX=kD!-GyJp7jZd(0oL00XF*HU@ZI`G?u-5r6axFo2qAdVt?&`N>b^&+j)WpMU|> zOp^ml%I~K0T*mzD`Ifsk0t2X-rUtn4$NYn>@(CC~%``E<@6vxJO7X(&&z`Tj+Xxsy z&6o@@DUcF3KD1&*`Puv%cN+r(sF`g8EL}G#Pb6-(MnKQ@Rg_P_0BXi$fGK9%8bLQ* zTvb&*0RyNR(*d?I0$<7}nlymk@au6jJpbH3ORm*Yd0H`@bJiK~eedvc+facE(0N7ka(Pkx=MWmF4lYcYzFICyhb>5q!9nO-A5b`Gl=} zru2pvALvfj*XNe^hskX}PA;R-`A6~yytoVhw#)c!Ar(HFw7lw7`65 zOwkC6%733Nyl&%im9GrCYYQ=*{{6OohgV@p-a%ZGuP;LbDA4nGdC>(fTD&1k*X7s91MZTi)g$H;%9XG$rn8Sih6oWK^ zDg)dERpcuwzfPCGjddg7?l7n_z)j#PpN{!$D_^r>1R>=Ft^?c@F7i?4&ELyIP`-vj z5XnTc_0O8iMK;0%^}jIy`JB&Hy(SYrQ#dhv9mHgu0n`a>2e@f$<%@>u@vA?Y-f&lN zh_Iw8ka2RyUF~5zz)fQ#pSt_i`Bhy1VU{1svUa-o(WOGyveq%rW#8=Y%WV^4o8Wvv z6{IRuAibamj1PmuMm~M_i$Qgofh46$1#5+GX`OU_<7x=PL&=IE%|~!oJJ<|x8<@&h z_3oF8SC(rQEn2$B-`do&iQZ&#BxIFdpw-%d9yC4-4paH$gpN4RCH6~qU#47}Ca%Gk zGiE8XqsQ`sNJxTxN1$%>EYIS;$#Op5GNu-ntyON8o`7Uke_yio@2d@dHdTWcV57>} zwi2#(|0ae~M8$juxBpfX;lMU05OS+95Kz5h=g@^8TA;yQMs#L{*-T$eB$0f1BXr^# z{Iqe%EMuufbxq#=WeW7r#I=o+I<-bY$S(c{?gF+f3o91!-)rn&Yn_?-LReaQro2~R zlSI43AWoiW5N+Zpx#}|Id1)45fMk{i&}JgI>&UxbG(I$f61Gz1d1$muj(=t=Tn~_Q z1Kb5{aWa4s#rbUfGe)?`4+_8l_8c*I)+N<$`utR;I4@0v7ycO|U;q^fEeRAkz&-!W zR*(dGK$o+7;*pBGwnC3;Wy{pah)=T2BXO zK1;%O)4|L5Z#jzzO3+KJTw*D`#G+iG=FR0r7|R8$i2v0omQ-DqSV>Rd5)R~d?3aZ3 zTEGel=vhJ6B~~&gn9YsX2+;Gs>!#Rf{N+l@8u0S_5n68Ir)$K?z3r4VXc005nvNC9TZRVfrq&{OeeW=60qGS)o Date: Tue, 5 Aug 2025 21:11:06 +0200 Subject: [PATCH 17/20] feat(checkbox-radio-selection-web): add checkbox for booleans --- .../RadioSelection/RadioSelection.tsx | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx index 55327b78ff..013d39f925 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/components/RadioSelection/RadioSelection.tsx @@ -11,15 +11,25 @@ export function RadioSelection({ readOnlyStyle, groupName }: SelectionBaseProps): ReactElement { - const options = selector.options.getAll(); + const asSingleCheckbox = selector.controlType === "checkbox"; + + const allOptions = selector.options.getAll(); + const checkboxOption = asSingleCheckbox ? (allOptions.includes("true") ? "true" : allOptions[0]) : undefined; + const options: string[] = asSingleCheckbox ? (checkboxOption ? [checkboxOption] : []) : allOptions; + const currentId = selector.currentId; const isReadOnly = selector.readOnly; const name = groupName?.value ?? inputId; const handleChange = (e: ChangeEvent): void => { - const selectedItem = e.target.value; - if (!isReadOnly) { - selector.setValue(selectedItem); + if (isReadOnly) { + return; + } + + if (asSingleCheckbox) { + selector.setValue(e.target.checked ? "true" : "false"); + } else { + selector.setValue(e.target.value); } }; @@ -29,23 +39,24 @@ export function RadioSelection({ "widget-checkbox-radio-selection-readonly": isReadOnly, [`widget-checkbox-radio-selection-readonly-${readOnlyStyle}`]: isReadOnly })} - role="radiogroup" + role={asSingleCheckbox ? "group" : "radiogroup"} aria-labelledby={`${inputId}-label`} aria-required={ariaRequired?.value} > {options.map((optionId, index) => { const isSelected = currentId === optionId; - const radioId = `${inputId}-radio-${index}`; + const controlId = `${inputId}-${selector.controlType}-${index}`; + return (
{selector.caption.render(optionId)} From b2576163a312cf3d817f54916695cd01a8948d3e Mon Sep 17 00:00:00 2001 From: Rahman Unver Date: Tue, 5 Aug 2025 23:39:07 +0200 Subject: [PATCH 18/20] test(checkbox-radio-selection-web): update test --- .../src/__tests__/CheckboxRadioSelection.spec.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/CheckboxRadioSelection.spec.tsx b/packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/CheckboxRadioSelection.spec.tsx index 485fbf2d2a..fa4082c77d 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/CheckboxRadioSelection.spec.tsx +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/src/__tests__/CheckboxRadioSelection.spec.tsx @@ -29,9 +29,10 @@ jest.mock("../helpers/getSelector", () => ({ _optionToValue: jest.fn(), _valueToOption: jest.fn() }, + controlType: "radio", caption: { - get: jest.fn(value => `Caption ${value}`), - render: jest.fn(value => `Caption ${value}`), + get: jest.fn((value: string) => `Caption ${value}`), + render: jest.fn((value: string) => `Caption ${value}`), emptyCaption: "Select an option", formatter: undefined } @@ -67,7 +68,8 @@ describe("CheckboxRadioSelection", () => { customEditability: "default" as const, customEditabilityExpression: { status: "available", value: false } as any, readOnlyStyle: "bordered" as const, - ariaRequired: { status: "available", value: false } as any + ariaRequired: { status: "available", value: false } as any, + controlType: "checkbox" as const }; it("renders without crashing", () => { From 58a20c2330b82cd8877d18c9b6e581d3daf540ae Mon Sep 17 00:00:00 2001 From: Rahman Unver Date: Wed, 6 Aug 2025 14:50:29 +0200 Subject: [PATCH 19/20] test(checkbox-radio-selection-web): fix e2e tests --- .../e2e/SelectionControls.spec.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js b/packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js index 09ce0eeda4..53b64fc6f6 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js @@ -13,38 +13,38 @@ test.describe("checkbox-radio-selection-web", () => { test.describe("data source types", () => { test("renders checkbox radio selection using association", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioSelection1"); + const selectionControls = page.locator(".mx-name-checkboxRadioButton1"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionAssociation.png`); }); test("renders checkbox radio selection using enum", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioSelection2"); + const selectionControls = page.locator(".mx-name-checkboxRadioButton2"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionEnum.png`); }); test("renders checkbox radio selection using boolean", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioSelection3"); + const selectionControls = page.locator(".mx-name-checkboxRadioButton3"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionBoolean.png`); }); test("renders checkbox radio selection using static values", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioSelection4"); + const selectionControls = page.locator(".mx-name-checkboxRadioButton4"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionStatic.png`); }); test("renders checkbox radio selection using database", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioSelection5"); + const selectionControls = page.locator(".mx-name-checkboxRadioButton5"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionDatabase.png`); }); test.describe("selection behavior", () => { test("handles radio button selection", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioSelection1"); + const selectionControls = page.locator(".mx-name-checkboxRadioButton1"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); const radioOption = selectionControls.locator('input[type="radio"]').first(); @@ -53,7 +53,7 @@ test.describe("checkbox-radio-selection-web", () => { }); test("handles checkbox selection", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioSelection6"); // multi selection + const selectionControls = page.locator(".mx-name-checkboxRadioButton6"); // multi selection await expect(selectionControls).toBeVisible({ timeout: 10000 }); const checkboxOption = selectionControls.locator('input[type="checkbox"]').first(); From 8b2579b6be1447f8c6fc35a15b44f48b10bd6fb8 Mon Sep 17 00:00:00 2001 From: gjulivan Date: Thu, 7 Aug 2025 23:01:36 +0200 Subject: [PATCH 20/20] chore: update rollup config and e2e --- .../e2e/SelectionControls.spec.js | 14 ++++++------- .../checkbox-radio-selection-web/package.json | 1 + .../rollup.config.js | 20 ------------------- .../rollup.config.mjs | 5 +++++ pnpm-lock.yaml | 3 +++ 5 files changed, 16 insertions(+), 27 deletions(-) delete mode 100644 packages/pluggableWidgets/checkbox-radio-selection-web/rollup.config.js create mode 100644 packages/pluggableWidgets/checkbox-radio-selection-web/rollup.config.mjs diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js b/packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js index 53b64fc6f6..09ce0eeda4 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/e2e/SelectionControls.spec.js @@ -13,38 +13,38 @@ test.describe("checkbox-radio-selection-web", () => { test.describe("data source types", () => { test("renders checkbox radio selection using association", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioButton1"); + const selectionControls = page.locator(".mx-name-checkboxRadioSelection1"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionAssociation.png`); }); test("renders checkbox radio selection using enum", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioButton2"); + const selectionControls = page.locator(".mx-name-checkboxRadioSelection2"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionEnum.png`); }); test("renders checkbox radio selection using boolean", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioButton3"); + const selectionControls = page.locator(".mx-name-checkboxRadioSelection3"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionBoolean.png`); }); test("renders checkbox radio selection using static values", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioButton4"); + const selectionControls = page.locator(".mx-name-checkboxRadioSelection4"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionStatic.png`); }); test("renders checkbox radio selection using database", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioButton5"); + const selectionControls = page.locator(".mx-name-checkboxRadioSelection5"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); await expect(selectionControls).toHaveScreenshot(`checkboxRadioSelectionDatabase.png`); }); test.describe("selection behavior", () => { test("handles radio button selection", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioButton1"); + const selectionControls = page.locator(".mx-name-checkboxRadioSelection1"); await expect(selectionControls).toBeVisible({ timeout: 10000 }); const radioOption = selectionControls.locator('input[type="radio"]').first(); @@ -53,7 +53,7 @@ test.describe("checkbox-radio-selection-web", () => { }); test("handles checkbox selection", async ({ page }) => { - const selectionControls = page.locator(".mx-name-checkboxRadioButton6"); // multi selection + const selectionControls = page.locator(".mx-name-checkboxRadioSelection6"); // multi selection await expect(selectionControls).toBeVisible({ timeout: 10000 }); const checkboxOption = selectionControls.locator('input[type="checkbox"]').first(); diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/package.json b/packages/pluggableWidgets/checkbox-radio-selection-web/package.json index 925d807edf..d783ca8768 100644 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/package.json +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/package.json @@ -54,6 +54,7 @@ "@mendix/eslint-config-web-widgets": "workspace:*", "@mendix/pluggable-widgets-tools": "*", "@mendix/prettier-config-web-widgets": "workspace:*", + "@mendix/rollup-web-widgets": "workspace:*", "@mendix/run-e2e": "workspace:^*", "@mendix/widget-plugin-component-kit": "workspace:*", "@mendix/widget-plugin-grid": "workspace:*", diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/rollup.config.js b/packages/pluggableWidgets/checkbox-radio-selection-web/rollup.config.js deleted file mode 100644 index 48e21a9f79..0000000000 --- a/packages/pluggableWidgets/checkbox-radio-selection-web/rollup.config.js +++ /dev/null @@ -1,20 +0,0 @@ -const { join } = require("path"); -const { cp, mkdir, rm } = require("shelljs"); - -const sourcePath = process.cwd(); -const outDir = join(sourcePath, "/dist/tmp/widgets/"); - -module.exports = args => { - const result = args.configDefaultConfig; - - const localesDir = join(outDir, "locales/"); - mkdir("-p", localesDir); - - const translationFiles = join(sourcePath, "dist/locales/**/*"); - // copy everything under dist/locales to dist/tmp/widgets/locales for the widget mpk - cp("-r", translationFiles, localesDir); - // remove root level *.json locales files (duplicate with language specific files (e.g. en-US/*.json)) - rm("-f", join(outDir, "locales/*.json"), localesDir); - - return result; -}; diff --git a/packages/pluggableWidgets/checkbox-radio-selection-web/rollup.config.mjs b/packages/pluggableWidgets/checkbox-radio-selection-web/rollup.config.mjs new file mode 100644 index 0000000000..688a1a7197 --- /dev/null +++ b/packages/pluggableWidgets/checkbox-radio-selection-web/rollup.config.mjs @@ -0,0 +1,5 @@ +import copyFiles from "@mendix/rollup-web-widgets/copyFiles.mjs"; + +export default args => { + return copyFiles(args); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 53e070614c..cf449decbe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -861,6 +861,9 @@ importers: '@mendix/prettier-config-web-widgets': specifier: workspace:* version: link:../../shared/prettier-config-web-widgets + '@mendix/rollup-web-widgets': + specifier: workspace:* + version: link:../../shared/rollup-web-widgets '@mendix/run-e2e': specifier: workspace:^* version: link:../../../automation/run-e2e